//     $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);
   }
 }
 ////////////////////////////////////////////////////////////////////////