// $Id: ClientGUI.java,v 3.4 1998/12/14 15:57:06 jpg3u Exp $
/////////////////////////////////////////////////////////////////////
//
// File: ClientGUI.java
//
// Purpose: The ClientGUI is responsible for providing a graphical
// interface for the ClientEngine. The GUI uses the
// ClientApplet to display components and detect user
// actions in the browser.
//
// Authors: txe Travis Emmitt emmitt@virginia.edu
//
// Modifications:
// 23-OCT-1998 txe (v1) Initial creation
// 24-OCT-1998 txe Added Drawable, Node, Module, Depend
// 28-OCT-1998 txe Overhauled version of screen
// 29-OCT-1998 txe Moved most of logic out (to ClientEngine)
// 03-NOV-1998 txe Added comments, we now use ClientApplet
// 04-NOV-1998 txe Debugging for integration
// 06-NOV-1998 rgb Added version control header info
// 06-NOV-1998 txe Print verbose map in DisplayMap(), Debugging
//
// 12-NOV-1998 txe (v2) Added GridBagLayout, Exceptions
// 13-NOV-1998 txe Added modular ring
// 14-NOV-1998 txe Adding zoom, depends, left/right click
// 15-NOV-1998 txe Improved Exception, comments
// 16-NOV-1998 txe Improved appearance, added constants
// 17-NOV-1998 txe Misc debugging edits, improved details
// 18-NOV-1998 txe Now catch ClientEngine exceptions
// 19-NOV-1998 txe Stats scroll, dis/enable buttons during stab.
// 22-NOV-1998 txe Submit enabled only via Unfreeze()
//
// 30-NOV-1998 txe (v3) Improved format, contrast, width, speed
// 02-DEC-1998 txe Added Quit()
// 08-DEC-1998 txe Added port value to constructor
// 14-DEC-1998 jpg Handled exceptions from invalid engine constr.
//
/////////////////////////////////////////////////////////////////////
import java.applet.*;
import java.awt.*;
import java.util.*;
public class ClientGUI {
// Adjoining Modules //
private ClientApplet applet; // provides access to browser
private ClientEngine engine; // handles high-level logic
// Screen components //
private DrawableMap draw_map; // screen representation of map
private TextArea details_area; // details area (at top right)
private TextArea stats_area; // stats area (at right)
private TextArea message_area; // message area (at bottom left)
private TextField module_field; // used for selecting a module id
private Button submit_button; // used for submitting changes
private Button matrix_button; // used for displaying matrix
private GridBagLayout layout; // used for positioning components
private GridBagConstraints constraints; // used by GridBagLayout
// Other private data //
private int edit_id = -1; // which module can we edit?
private boolean frozen; // if true, disable user input
private boolean show_all_nodes; // show zoomed & unzoomed nodes
private boolean draw_matrix; // if true, draw matrix not map
// Component resources (constants) //
private final static int
ANY = -1, // specifies any node in module
MAP_SIZE = 440, // width and height of map area
FONT_SIZE = 10, // base font size
DETAILS_COLS = 40, // number of columns in details area
DETAILS_ROWS = 14, // number of rows in details area
MESSAGE_COLS = 70, // number of columns in message area
MESSAGE_ROWS = 4, // number of rows in message area
STATS_COLS = 40, // number of columns in stats area
STATS_ROWS = 14, // number of rows in stats area
MODULE_COLS = 3; // number of columns in module field
private final static Color
MAIN_BCOLOR = new Color (0.1f, 0.1f, 0.3f), // main background
DETAILS_FCOLOR = new Color (0.0f, 0.0f, 0.0f), // details foreground
DETAILS_BCOLOR = new Color (1.0f, 1.0f, 0.9f), // details background
STATS_FCOLOR = new Color (0.3f, 0.3f, 0.1f), // stats foreground
STATS_BCOLOR = new Color (0.9f, 0.9f, 1.0f), // stats background
MESSAGE_FCOLOR = new Color (1.0f, 1.0f, 0.0f), // message foreground
MESSAGE_BCOLOR = new Color (0.0f, 0.0f, 0.0f), // message background
MODULE_FCOLOR = new Color (0.0f, 0.0f, 0.1f), // module foreground
MODULE_BCOLOR = new Color (1.0f, 1.0f, 1.0f); // module background
///////////////////////////////////////////////////////////////////
//
// Method: ClientGUI [constructor]
//
// Invoker: ClientApplet
//
// Purpose: By passing a reference to itself when calling the
// ClientGUI construtor, ClientApplet gives ClientGUI
// a way to invoke the ClientApplet methods, enabling
// two-way communication between the applet and the GUI.
//
// This constructor then creates a ClientEngine,
// establishing a similar two-way interface. It also
// creates and adds various graphical components and
// performs other needed initialization.
//
///////////////////////////////////////////////////////////////////
public ClientGUI (ClientApplet parent_applet, String address, String port) {
if (parent_applet == null) {
Error (new NullException ("parent_applet"));
return;
}
applet = parent_applet;
frozen = false;
show_all_nodes = false;
draw_matrix = false;
try {
SetupLayout ();
engine = new ClientEngine (this, address, port);
message_area.appendText ("Please select a module\n");
details_area.appendText ("DETAILS\n\n");
stats_area.appendText ("STATISTICS\n\n");
}
catch (Exception e) {
Warn("Startup Failure: Is the server running? \nClose this applet and restart.");
return;
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Add (private)
//
// Purpose: Adds specified component to layout with specified
// cell bounds.
//
///////////////////////////////////////////////////////////////////
private void Add (Component component, int x, int y, int w, int h)
throws GraphicsException, NullException {
if (component == null) {
throw new NullException ("component");
}
try {
constraints.gridx = x;
constraints.gridy = y;
constraints.gridwidth = w;
constraints.gridheight = h;
layout.setConstraints (component, constraints);
applet.add (component);
}
catch (Exception e) {
throw new GraphicsException (e);
}
}
/////////////////////////////////////////////////////////////////////
//
// Method: DisplayDetails
//
// Invoker: ClientEngine
//
// Purpose: Displays details about specified Module, its contents,
// and its related dependencies.
//
////////////////////////////////////////////////////////////////////
public void DisplayDetails (int module_id)
throws BoundsException, GraphicsException, MapException {
DisplayDetails (module_id, ANY);
}
/////////////////////////////////////////////////////////////////////
//
// Method: DisplayDetails
//
// Invoker: ClientEngine
//
// Purpose: Displays details about specified Node and its related
// dependencies.
//
////////////////////////////////////////////////////////////////////
public void DisplayDetails (int module_id, int node_id)
throws BoundsException, GraphicsException, MapException {
try {
if (draw_map != null) {
details_area.setText (draw_map.Verbose (module_id, node_id));
}
}
catch (Exception e) {
throw new GraphicsException (e);
}
}
///////////////////////////////////////////////////////////////////
//
// Method: DisplayMap
//
// Invoker: ClientEngine
//
// Purpose: This displays the specified Map on the screen.
//
///////////////////////////////////////////////////////////////////
public void DisplayMap (Map new_map)
throws MapException, GUIException {
if (new_map == null) {
throw new MapException ("Map passed to ClientGUI is null");
}
try {
new_map.Validate();
}
catch (Exception e) {
Notify (" See Details Window...");
details_area.setText (new_map.Verbose ());
Error (e);
throw new MapException (e);
}
try {
int map_bounds [] = { 0, 0, MAP_SIZE, MAP_SIZE};
draw_map = new DrawableMap (new_map, map_bounds, edit_id);
}
catch (Exception e) {
Error (e);
throw new GUIException ("Error creating drawable map");
}
matrix_button.enable ();
applet.repaint ();
}
///////////////////////////////////////////////////////////////////
//
// Method: DisplayMap
//
// Invoker: ClientEngine
//
// Purpose: This displays the specified Map on the screen.
//
// Note: You should probably use the method without the
// edit_id parameter, since it is no longer used.
//
///////////////////////////////////////////////////////////////////
public void DisplayMap (Map new_map, int edit_id)
throws BoundsException, GUIException, MapException {
DisplayMap (new_map);
}
/////////////////////////////////////////////////////////////////////
//
// Method: DisplayStats
//
// Invoker: ClientEngine
//
// Purpose: Displays the specified Stats data structure.
//
////////////////////////////////////////////////////////////////////
public void DisplayStats (Stats new_stats)
throws GraphicsException, NullException, StatsException {
if (new_stats == null) {
throw new NullException ("new_stats");
}
String string = null;
try {
string = new_stats.Verbose (edit_id);
}
catch (Exception e) {
Error (e);
throw new StatsException (e);
}
try {
stats_area.appendText (string);
}
catch (Exception e) {
throw new GraphicsException (e);
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Draw
//
// Invoker: ClientApplet
//
// Purpose: The main purpose of this method is to enable the
// Drawable objects to be displayed in the browser
// window. These objects need access to the applet's
// Graphics object. ClientGUI passes a reference to
// the Graphics object to the DrawableMap class, which
// in turn passes it to the other Drawable classes.
//
///////////////////////////////////////////////////////////////////
public void Draw (Graphics graphics) {
if (draw_map == null) {
return;
}
try {
if (draw_matrix) {
draw_map.DrawMatrix (graphics);
}
else {
draw_map.Draw (graphics, show_all_nodes);
}
}
catch (Exception e) {
Error (e);
return;
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Error
//
// Invoker: ClientEngine
//
// Purpose: Displays the specified error in the message box, and
// then tells the applet to dump a stack trace to the
// console. Use this for debugging programming errors.
//
///////////////////////////////////////////////////////////////////
public void Error (Exception e) {
Notify ("ERROR: " + e.getMessage());
applet.DumpStack (e);
}
///////////////////////////////////////////////////////////////////
//
// Method: Freeze
//
// Invoker: ClientEngine
//
// Purpose: This "freezes" the ClientGUI; it disables user input.
//
///////////////////////////////////////////////////////////////////
public void Freeze () {
frozen = true;
submit_button.disable ();
}
/////////////////////////////////////////////////////////////////////
//
// Method: HandleAction
//
// Invoker: ClientApplet
//
// Purpose: When the user activates a component, the browser
// notifies ClientApplet, which then invokes this
// method (HandleAction).
//
// HandleAction's job is to find out which screen
// component was activated, and then inform the
// ClientEngine.
//
// Notes: Currently we don't use the object parameter.
//
/////////////////////////////////////////////////////////////////////
public void HandleAction (Event event, Object object) {
// User activated module field //
if (engine == null) {
return ;
}
if (event.target.equals (module_field)) {
String string = null;
try {
string = module_field.getText().trim();
edit_id = new Integer(string).intValue();
}
catch (Exception e) {
Warn (new ParsingException ("module field", string).getMessage());
edit_id = -1;
return;
}
try {
if (!engine.SetClientID (edit_id)) {
Notify ("Server refused to let you use module " + edit_id);
edit_id = -1;
return;
}
}
catch (Exception e) {
Error (e);
edit_id = -1;
return;
}
Notify ("Congratulations... you now control module " + edit_id);
module_field.disable ();
}
// user activated the submit button //
else if (event.target.equals (submit_button)) {
try {
engine.DoneClicked ();
}
catch (Exception e) {
Error (e);
return;
}
}
// user activated the matrix button //
else if (event.target.equals (matrix_button)) {
if (draw_map == null) {
Warn ("There's no Map/Matrix display");
}
else {
if (draw_matrix) {
matrix_button.setLabel ("Display Matrix");
draw_matrix = false;
}
else {
matrix_button.setLabel ("Display Map");
draw_matrix = true;
}
applet.repaint ();
}
}
}
///////////////////////////////////////////////////////////////////
//
// Method: HandleClick
//
// Invoker: ClientApplet
//
// Purpose: When the user presses a mouse button, the applet
// viewer notifies ClientApplet, which in turn invokes
// this method (HandleClick) to handle it.
//
///////////////////////////////////////////////////////////////////
public void HandleClick (Event event, int x, int y) {
if (draw_map == null || draw_matrix || engine == null) {
return;
}
try {
boolean left_click = !event.metaDown ();
int zoom_id = draw_map.GetZoomID ();
int module_id = draw_map.GetPointedModuleID (x, y);
int node_id = draw_map.GetPointedNodeID (x, y);
// if a node was selected... //
if (node_id >= 0) {
if (left_click) {
if (frozen) {
Warn ("You can't toggle nodes at this point.");
}
else {
engine.NodeClicked (zoom_id, node_id);
}
}
else {
draw_map.SelectNode (node_id);
}
DisplayDetails (zoom_id, node_id);
applet.repaint();
}
// if a module was selected... //
else if (module_id >= 0) {
if (left_click) {
if (edit_id < 0) {
try {
edit_id = module_id;
if (!engine.SetClientID (edit_id)) {
Notify ("Server refused to let you use module " + edit_id);
edit_id = -1;
return;
}
Notify ("Congratulations... you now control module " + edit_id);
}
catch (Exception e) {
Error (e);
edit_id = -1;
return;
}
}
else {
draw_map.Zoom (module_id);
}
}
else {
draw_map.SelectModule (module_id);
}
DisplayDetails (module_id);
applet.repaint();
}
// neither a node nor a module was clicked... //
else {
if (left_click) {
if (draw_map.Inside (x, y)) {
if (zoom_id < 0) {
draw_map.Zoom (edit_id);
}
else {
draw_map.Zoom (-1); // unzoom
}
applet.repaint();
}
}
else {
show_all_nodes = true;
applet.repaint();
}
}
}
catch (Exception e) {
Error (e);
return;
}
}
///////////////////////////////////////////////////////////////////
//
// Method: HandleRelease
//
// Invoker: ClientApplet
//
// Purpose: When the user release a mouse button, the applet
// viewer notifies ClientApplet, which in turn invokes
// this method (HandleRelease) to handle it.
//
///////////////////////////////////////////////////////////////////
public void HandleRelease (Event event, int x, int y) {
if (draw_map == null) {
return;
}
try {
boolean left_click = !event.metaDown ();
if (left_click) {
}
else {
show_all_nodes = false;
draw_map.SelectNode (-1); // selects nothing
applet.repaint();
}
}
catch (Exception e) {
Error (e);
return;
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Notify
//
// Invoker: ClientEngine
//
// Purpose: This displays the specified string in the message box.
//
///////////////////////////////////////////////////////////////////
public void Notify (String string) {
if (message_area == null) {
System.out.println ("ClientGUI: " + string);
return;
}
message_area.appendText ((string == null ? "" : string) + "\n");
}
///////////////////////////////////////////////////////////////////
//
// Method: Quit
//
// Invoker: ClientApplet
//
// Purpose: This tells the engine that the applet has been exited
// by the user.
//
///////////////////////////////////////////////////////////////////
public void Quit () {
Notify ("Shutting down client...");
if (engine != null) {
try {
engine.Quit ();
}
catch (Exception e) {
Error (e);
}
}
}
//////////////////////////////////////////////////////////////////////////
//
// Method: SetupLayout (private)
//
// Purpose: Sets up screen layout by positioning buttons, text areas, etc.
//
//////////////////////////////////////////////////////////////////////////
private void SetupLayout ()
throws GraphicsException {
try {
applet.setFont (new Font ("Helvetica", Font.PLAIN, FONT_SIZE));
applet.setLayout (layout = new GridBagLayout());
constraints = new GridBagConstraints ();
int END = GridBagConstraints.REMAINDER;
int REL = GridBagConstraints.RELATIVE;
Add (details_area = new TextArea (DETAILS_ROWS, DETAILS_COLS), 1, 0, END, 1 );
Add (stats_area = new TextArea (STATS_ROWS, STATS_COLS ), 1, 1, END, REL );
Add (message_area = new TextArea (MESSAGE_ROWS, MESSAGE_COLS), 0, 2, 1, END );
// lower right panel //
Panel lower_right = new Panel ();
GridBagLayout layout2 = new GridBagLayout ();
GridBagConstraints constraints2 = new GridBagConstraints ();
lower_right.setLayout (layout2);
constraints2.gridx = 0;
constraints2.gridy = 0;
constraints2.gridwidth = 1;
constraints2.gridheight = 1;
Label label2 = new Label ("Module:");
layout2.setConstraints (label2, constraints2);
lower_right.add (label2);
constraints2.gridx = 1;
constraints2.gridy = 0;
constraints2.gridwidth = END;
constraints2.gridheight = 1;
module_field = new TextField ("0", MODULE_COLS);
layout2.setConstraints (module_field, constraints2);
lower_right.add (module_field);
constraints2.gridx = 0;
constraints2.gridy = 1;
constraints2.gridwidth = END;
constraints2.gridheight = REL;
Panel panel2 = new Panel ();
layout2.setConstraints (panel2, constraints2);
lower_right.add (panel2);
constraints2.gridx = 0;
constraints2.gridy = 2;
constraints2.gridwidth = REL;
constraints2.gridheight = END;
submit_button = new Button ("Submit Changes");
layout2.setConstraints (submit_button, constraints2);
lower_right.add (submit_button);
constraints2.gridx = 1;
constraints2.gridy = 2;
constraints2.gridwidth = END;
constraints2.gridheight = END;
matrix_button = new Button ("Display Matrix");
layout2.setConstraints (matrix_button, constraints2);
lower_right.add (matrix_button);
// add lower right panel to main screen //
Add (lower_right, 1, 2, END, END);
details_area.setEditable (false);
details_area.setForeground (DETAILS_FCOLOR);
details_area.setBackground (DETAILS_BCOLOR);
stats_area.setEditable (false);
stats_area.setForeground (STATS_FCOLOR);
stats_area.setBackground (STATS_BCOLOR);
message_area.setEditable (false);
message_area.setForeground (MESSAGE_FCOLOR);
message_area.setBackground (MESSAGE_BCOLOR);
module_field.setEditable (true);
module_field.setForeground (MODULE_FCOLOR);
module_field.setBackground (MODULE_BCOLOR);
submit_button.disable (); // wait until unfrozen
matrix_button.disable (); // wait until we have a map
}
catch (Exception e) {
throw new GraphicsException (e);
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Unfreeze
//
// Invoker: ClientEngine
//
// Purpose: This "unfreezes" the ClientGUI; it enables user input.
//
///////////////////////////////////////////////////////////////////
public void Unfreeze () {
frozen = false;
if (edit_id >= 0) {
submit_button.enable ();
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Warn
//
// Invoker: ClientApplet, ClientEngine
//
// Purpose: Displays a warning in the message box. Warning
// messages must be of interest to the user; use the
// Error() method for reporting programming errors
// and facilitating debugging.
//
///////////////////////////////////////////////////////////////////
public void Warn (String message) {
Notify ("WARNING: " + message);
}
}
////////////////////////////////////////////////////////////////////////