// $Id: DemonGUI.java,v 3.11 1998/12/11 18:40:04 jpg3u Exp $
/////////////////////////////////////////////////////////////////////
//
// File: DemonGUI.java
//
// Purpose: The DemonGUI is responsible for providing a graphical
// interface for the DemonEngine. The GUI uses the
// DemonApplet to display components and detect user
// actions in the browser.
//
// Authors: txr Travis Emmitt emmitt@virginia.edu
// jpg James P. Gunderson gunders@Virginia.EDU
//
// Modifications:
// 11-DEC-1998 jpg fixed null pointer problems, editable problem in
// delay field, cosmetic changes to buttons.
// 09-DEC-1998 jpg added state constants, set initial state to SHUTDOWN
// 09-DEC-1998 jpg added Quit method, initial state
// 09-DEC-1998 jpg fixed start of run messaging
// 09-DEC-1998 jpg fixed race condition in event handler/ submit button
// 09-DEC-1998 jpg converted to ReceiveState signature
// 08-DEC-1998 jpg added new constructor for port and server strings
// 07-DEC-1998 jpg modified to support changes to ClientConnectStatus
// 03-DEC-1998 jpg modified SetState functionality
// 02-DEC-1998 jpg added auto/manual and abort buttond
// 02-DEC-1998 jpg modified ClientConnect Status to int
// 16-NOV-1998 jpg renamed existing ClientGUI.java to DemonGUI.java
// 17-NOV-1998 jpg shimmed display
// 17-NOV-1998 jpg conformed DisplayStats
// 18-NOV-1998 jpg added Exception Exit code
// 20-NOV-1998 jpg input is disabled during simulation
// 20-NOV-1998 jpg updated stats display
// 20-NOV-1998 jpg added graphic connection status display
//
/////////////////////////////////////////////////////////////////////
import java.applet.*;
import java.awt.*;
import java.util.*;
public class DemonGUI {
// Adjoining Modules //
private DemonApplet applet; // provides access to browser
private DemonEngine engine; // handles high-level logic
// Screen components //
private DrawableMap draw_map; // screen representation of map
private Panel main_panel; // area where drawable map goes
private TextArea details_area; // details area (at top right)
private TextArea stats_area; // stats area (at right)
private TextArea ccs_area; // Client Connection Display
private TextArea status_area; // status area (at bottom left)
private TextField run_field; // used for selecting delay
private TextField delay_field; // used for selecting number of runs
private Button all_button; // used for selecting all prob mode
private Button sum_button; // used for selecting sum prob mode
private Button any_button; // used for selecting any prob mode
private Button manual_button; // used for selecting manual mode
private Button automatic_button; // used for selecting automatic mode
private Button abort_button; // used to kill a running simulation
private ClientConnectStatus ccs ;
private Button submit_button; // used for submitting changes
private Button quit_button;
// Component resources //
private int font_size = 10; // size of default font
private int stats_cols = 47; // number of columns in stats area
private int stats_rows = 12; // number of rows in stats area
private int ccs_cols = 70; // number of columns in stats area
private int ccs_rows = 1; // number of rows in stats area
private int status_cols = 47; // number of columns in status area
private int status_rows = 20; // number of rows in status area
private int delay_cols = 5; // number of columns in delay field
private int run_cols = 5; // number of columns in run field
private int prob_mode = -1; // mode of probability propagation
private int proc_mode = -1; // 0 = manual, 1 = automatic
private long delay = 1000 ;
private int runs = 0;
private int map_bounds[] = { 0, 0, 400, 400 };
// Other private data //
private int edit_id; // which module can we edit?
private static final int SIM_CHOOSEMODE = 0;
private static final int SIM_WAITSUBMIT = 1;
private static final int SIM_RUNNING = 2;
private static final int SIM_SHUTDOWN = 3;
private int current_mode = SIM_SHUTDOWN;
///////////////////////////////////////////////////////////////////
//
// Method: DemonGUI [constructor]
//
// Invoker: DemonApplet
//
// Purpose: By passing a reference to itself when calling the
// DemonGUI construtor, DemonApplet gives DemonGUI
// a way to invoke the DemonApplet methods, enabling
// two-way communication between the applet and the GUI.
//
// This constructor then creates a DemonEngine, passing server and // port ID,
// establishing a similar two-way interface. It also
// creates and adds various graphical components and
// performs other needed initialization.
//
///////////////////////////////////////////////////////////////////
public DemonGUI (DemonApplet parent_applet, String server, String port)
throws NullException, Exception {
current_mode = 0;
if (parent_applet == null) {
throw new NullException ("parent_applet");
}
if (server == null) {
throw new NullException ("server string");
}
if (port == null) {
throw new NullException ("port string");
}
try {
applet = parent_applet;
SetupLayout ();
ccs = new ClientConnectStatus();
engine = new DemonEngine (this, server, port);
}
catch (Exception e) {
Warn("Startup Failure: Is the server running? \nClose this applet and restart.");
ReceiveState(SIM_SHUTDOWN) ;
}
}
///////////////////////////////////////////////////////////////////
//
// 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 NullException, Exception {
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 e;
}
}
///////////////////////////////////////////////////////////////////
//
// Method: DisplayMap
//
// Invoker: DemonEngine
//
// Purpose: This displays the specified Map on the screen.
// The second argument tells the GUI which Module
// "belongs" to this particular Demon, since each
// GUI treats its own Module a little differently
// from how it treats other Demons' Modules.
//
///////////////////////////////////////////////////////////////////
public void DisplayMap (Map new_map)
throws NullException, Exception {
if (new_map == null) {
throw new NullException ("new_map");
}
try {
draw_map = new DrawableMap (new_map, map_bounds, edit_id);
if (!draw_map.Valid ()) {
Warn ("Map is invalid... you might get errors!");
}
}
catch (Exception e) {
Error (e);
throw e;
}
applet.repaint ();
}
/////////////////////////////////////////////////////////////////////
//
// Method: DisplayModuleDetails
//
// Invoker: (private)
//
// Purpose: Displays details about specified Module, its contents,
// and its related dependencies.
//
////////////////////////////////////////////////////////////////////
private void DisplayModuleDetails (int module_id)
throws Exception {
try {
int num_modules = draw_map.GetNumModules ();
String details = draw_map.GetModule (module_id).Verbose();
int mids[] = draw_map.GetDependeeModuleIDs (module_id);
details += "\n" + " Depends upon:";
for (int i = 0; i < num_modules && mids[i] >= 0; i++) {
if (mids[i] == module_id) {
continue; // don't display internal dependencies
}
Module dest = draw_map.GetModule (mids[i]);
int nids[] = draw_map.GetDependeeNodeIDs (module_id, mids[i]);
details += "\n " + dest.GetName();
for (int j = 0; j < dest.GetNumNodes() && nids[j] >= 0; j++) {
details += (j == 0 ? ": " : ", ") + dest.GetNode(nids[j]).GetName();
}
}
mids = draw_map.GetDependerModuleIDs (module_id);
details += "\n\n Is depended upon by:";
for (int i = 0; i < num_modules && mids[i] >= 0; i++) {
if (mids[i] == module_id) {
continue; // don't display internal dependencies
}
Module source = draw_map.GetModule (mids[i]);
int nids[] = draw_map.GetDependerNodeIDs (module_id, mids[i]);
details += "\n " + source.GetName();
for (int j = 0; j < source.GetNumNodes() && nids[j] >= 0; j++) {
details += (j == 0 ? ": " : ", ") + source.GetNode(nids[j]).GetName();
}
}
details_area.setText (details);
}
catch (Exception e) {
Error (e);
throw e;
}
}
/////////////////////////////////////////////////////////////////////
//
// Method: DisplayNodeDetails
//
// Invoker: (private)
//
// Purpose: Displays details about specified Node and its related
// dependencies.
//
////////////////////////////////////////////////////////////////////
private void DisplayNodeDetails (int module_id, int node_id)
throws Exception {
try {
int num_modules = draw_map.GetNumModules ();
String details = draw_map.GetNode (module_id, node_id).Verbose();
int mids[] = draw_map.GetDependeeModuleIDs (module_id, node_id);
details += "\n" + " Depends upon:";
for (int i = 0; i < num_modules && mids[i] >= 0; i++) {
Module dest = draw_map.GetModule (mids[i]);
int nids[] = draw_map.GetDependeeNodeIDs (module_id, mids[i]);
details += "\n " + dest.GetName();
for (int j = 0; j < dest.GetNumNodes() && nids[j] >= 0; j++) {
details += (j == 0 ? ": " : ", ") + dest.GetNode(nids[j]).GetName();
}
}
mids = draw_map.GetDependerModuleIDs (module_id, node_id);
details += "\n\n Is depended upon by:";
for (int i = 0; i < num_modules && mids[i] >= 0; i++) {
Module source = draw_map.GetModule (mids[i]);
int nids[] = draw_map.GetDependerNodeIDs (module_id, mids[i]);
details += "\n " + source.GetName();
for (int j = 0; j < source.GetNumNodes() && nids[j] >= 0; j++) {
details += (j == 0 ? ": " : ", ") + source.GetNode(nids[j]).GetName();
}
}
details_area.setText (details);
}
catch (Exception e) {
Error (e);
throw e;
}
}
/////////////////////////////////////////////////////////////////////
//
// Method: DisplayStats
//
// Invoker: DemonEngine
//
// Purpose: Displays the specified Stats data structure.
//
////////////////////////////////////////////////////////////////////
public void DisplayStats (Stats new_stats)
throws NullException, Exception {
if (new_stats.GetTurn() == 1) {
stats_area.appendText ("\n\nSTART *****************\n\n" );
}
String next_line = new String("Tick: " + new_stats.GetTurn() + " ");
try {
if (new_stats == null) {
throw new NullException("new_stats");
}
next_line += " Recov: " + new_stats.GetTurnRecoveries() + "/"
+ new_stats.GetTotalRecoveries() ;
next_line += " Destab: " + new_stats.GetTurnDestabilizations() + "/"
+ new_stats.GetTotalDestabilizations() ;
next_line += " Unstable: " + new_stats.GetTurnUnstableNodes() + "/"
+ new_stats.GetTotalUnstableNodes() ;
next_line += "\n" ;
stats_area.appendText (next_line );
}
catch (Exception e) {
Error (e);
throw e;
}
}
/////////////////////////////////////////////////////////////////////
// // Method: DisplayClientConnectStatus // // Invoker: DemonEngine //
// Purpose: Displays the specified ClientConnectStatus data structure.
//
////////////////////////////////////////////////////////////////////
public void DisplayClientConnectStatus (ClientConnectStatus new_ccs)
throws NullException, Exception {
ccs = new ClientConnectStatus(new_ccs);
String ConStat = "\nConnected: " ;
try {
if (new_ccs == null) {
throw new NullException ("new_ccs");
}
for(int i = 0; i < new_ccs.GetNumClients(); i++) {
if (new_ccs.GetClientConnStatus(i)){
ConStat += i + " " ;
}
}
ccs_area.appendText (ConStat);
}
catch (Exception e) {
Error (e);
throw e;
}
applet.repaint();
}
//////////////////////////////////////////////////////////////////////////
//
// Method: SetupLayout (private)
//
// Purpose: Sets up screen layout by positioning buttons, text areas, etc.
//
//////////////////////////////////////////////////////////////////////////
private GridBagLayout layout;
private GridBagConstraints constraints;
private void SetupLayout ()
throws Exception {
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 (ccs_area = new TextArea (ccs_rows, ccs_cols), 0, 3, 1, END );
Add (status_area = new TextArea(status_rows, status_cols), 2, 0, END, 1);
Add(stats_area = new TextArea(stats_rows, stats_cols), 2, 1, END, 1 );
Panel upper_right = new Panel() ;
upper_right.add (manual_button = new Button ("Manual"));
upper_right.add (automatic_button = new Button ("Automatic"));
upper_right.add (run_field = new TextField("0", run_cols));
automatic_button.setBackground(Color.gray);
manual_button.setBackground(Color.gray);
automatic_button.setForeground(Color.black);
manual_button.setForeground(Color.black);
run_field.setEnabled(false);
upper_right.add (new Label ("# of Runs"));
Add(upper_right, 2,3,END,1);
Panel mid_right = new Panel ();
mid_right.add (new Label ("Delay"));
mid_right.add (delay_field = new TextField ("1000", delay_cols));
mid_right.add (all_button = new Button ("All"));
mid_right.add (sum_button = new Button ("Sum"));
mid_right.add (any_button = new Button ("Any"));
all_button.setBackground(Color.gray);
sum_button.setBackground(Color.gray);
any_button.setBackground(Color.gray);
all_button.setForeground(Color.black);
sum_button.setForeground(Color.black);
any_button.setForeground(Color.black);
Add (mid_right, 2, 4, END, 1);
Panel lower_right = new Panel() ;
lower_right.add (submit_button = new Button ("Submit"));
lower_right.add (quit_button = new Button("Quit"));
lower_right.add (abort_button = new Button("Abort"));
Add(lower_right, 2,5,END,1);
stats_area.setEditable (false);
stats_area.setBackground (Color.black);
stats_area.setForeground (Color.white);
status_area.setEditable (false);
status_area.setBackground (Color.black);
status_area.setForeground (Color.yellow);
delay_field.setEnabled (false);
delay_field.setBackground (Color.white);
delay_field.setForeground (Color.blue);
submit_button.disable ();
all_button.disable() ;
sum_button.disable() ;
any_button.disable() ;
}
///////////////////////////////////////////////////////////////////
//
// Method: Draw
//
// Invoker: DemonApplet
//
// 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. DemonGUI 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)
throws Exception {
int x_base = 30 ;
int y_base = 430 ;
int size = 15 ;
if (draw_map != null) {
try {
draw_map.DrawMatrix (graphics);
}
catch (Exception e) {
Notify("Map is Bad");
Error (e) ;
throw e ;
}
}
graphics.setColor(Color.black);
graphics.fillRect(x_base - 5, y_base - 5, 21 * size, 25);
if (ccs.GetNumClients() != 0) {
for ( int i = 0; i < ccs.GetNumClients(); i++) {
if (!ccs.GetClientConnStatus(i)) {
graphics.setColor(Color.gray);
}
else {
if (!ccs.GetClientSubStatus(i)) {
graphics.setColor(Color.yellow);
}
else {
graphics.setColor(Color.blue);
}
}
graphics.fillOval(x_base + (2 * i * size), y_base, size, size);
}
}
}
///////////////////////////////////////////////////////////////////
//
// Method: HandleClick
//
// Invoker: DemonApplet
//
// Purpose: When the user clicks the mouse inside the applet
// window, the browser notifies DemonApplet, which
// then invokes this method (HandleClick).
//
// HandleClick's job is to find out which screen object
// was clicked, and then inform the DemonEngine.
//
///////////////////////////////////////////////////////////////////
public void HandleClick (Event event, int x, int y) {
if (draw_map == null) {
return; // No need for exception; just ignore click
}
try {
applet.repaint();
}
catch (Exception e) {
Error (e);
}
}
/////////////////////////////////////////////////////////////////////
//
// Method: HandleAction
//
// Invoker: DemonApplet
//
// Purpose: When the user activates a component, the browser
// notifies DemonApplet, which then invokes this
// method (HandleAction).
//
// HandleAction's job is to find out which screen
// component was activated, and then inform the
// DemonEngine.
//
// Notes: Currently we don't use the object parameter.
//
/////////////////////////////////////////////////////////////////////
public void HandleAction (Event event, Object object)
throws Exception {
if (event.target.equals(all_button)) {
prob_mode = 3;
all_button.setBackground(Color.blue);
sum_button.setBackground(Color.gray);
any_button.setBackground(Color.gray);
all_button.setForeground(Color.white);
sum_button.setForeground(Color.black);
any_button.setForeground(Color.black);
submit_button.enable();
}
if (event.target.equals(sum_button)) {
prob_mode = 2 ;
all_button.setBackground(Color.gray);
sum_button.setBackground(Color.blue);
any_button.setBackground(Color.gray);
all_button.setForeground(Color.black);
sum_button.setForeground(Color.white);
any_button.setForeground(Color.black);
submit_button.enable();
}
if (event.target.equals(any_button)) {
prob_mode = 1 ;
all_button.setBackground(Color.gray);
sum_button.setBackground(Color.gray);
any_button.setBackground(Color.blue);
all_button.setForeground(Color.black);
sum_button.setForeground(Color.black);
any_button.setForeground(Color.white);
submit_button.enable();
}
if (event.target.equals(manual_button)) {
automatic_button.setBackground(Color.gray);
manual_button.setBackground(Color.blue);
automatic_button.setForeground(Color.black);
manual_button.setForeground(Color.white);
run_field.setEnabled(false);
submit_button.setEnabled(true);
proc_mode = 1 ;
runs = 1 ;
}
if (event.target.equals(automatic_button)) {
automatic_button.setBackground(Color.blue);
manual_button.setBackground(Color.gray);
automatic_button.setForeground(Color.white);
manual_button.setForeground(Color.black);
run_field.setEnabled(true);
submit_button.setEnabled(true);
proc_mode = 0 ;
}
if (event.target.equals (submit_button)) {
if (current_mode == 0) {
if (proc_mode == 1) {
Notify("Submitting Manual Mode");
}
if (proc_mode == 0) {
Notify("Submitting Automatic Mode, Runs = " + runs );
}
engine.SubmitMode (proc_mode, runs);
}
else if (current_mode == 1) {
Notify("Submitting delay= " + delay + " prob_mode = " + prob_mode );
engine.SetParameters (prob_mode, delay);
}
}
if (event.target.equals (run_field)) {
Notify("Run entered");
String string = null;
try {
string = run_field.getText().trim();
runs = new Integer(string).intValue();
}
catch (Exception e) {
Warn (new ParsingException ("runs field", string).getMessage());
runs = -1;
return;
}
}
if (event.target.equals (abort_button)) {
Notify("Aborting current Run");
engine.AbortClicked ();
}
if (event.target.equals (quit_button)) {
engine.QuitClicked ();
}
if (event.target.equals (delay_field)) {
Notify("Delay entered");
String d_string = null;
try {
d_string = delay_field.getText().trim();
delay = new Integer(d_string).intValue();
}
catch (Exception e) {
Warn (new ParsingException ("delay field", d_string).getMessage());
delay = -1;
return;
}
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Notify
//
// Invoker: DemonEngine
//
// Purpose: This displays the specified string in the message box.
//
///////////////////////////////////////////////////////////////////
public void Notify (String string) {
status_area.appendText ((string == null ? "ooops" : string) + "\n");
}
///////////////////////////////////////////////////////////////////
//
// Method: Freeze
//
// Invoker: DemonEngine
//
// Purpose: This "freezes" the DemonGUI; it disables user input.
//
///////////////////////////////////////////////////////////////////
public void ReceiveState (int mode) {
Notify("SetState invoked: mode = " + mode);
current_mode = mode ;
if (current_mode == SIM_CHOOSEMODE) {
// the user needs to select auto or manual first
automatic_button.setBackground(Color.gray);
manual_button.setBackground(Color.gray);
automatic_button.setForeground(Color.black);
manual_button.setForeground(Color.black);
all_button.setBackground(Color.gray);
sum_button.setBackground(Color.gray);
any_button.setBackground(Color.gray);
all_button.setForeground(Color.black);
sum_button.setForeground(Color.black);
any_button.setForeground(Color.black);
submit_button.setBackground(Color.gray);
quit_button.setBackground(Color.gray);
abort_button.setBackground(Color.gray);
submit_button.setForeground(Color.black);
quit_button.setForeground(Color.black);
abort_button.setForeground(Color.black);
manual_button.setEnabled(true);
automatic_button.setEnabled(true) ;
run_field.setEnabled(false); // only activate after Auto is selected
all_button.setEnabled(false);
sum_button.setEnabled(false);
any_button.setEnabled(false);
submit_button.setEnabled(false);
quit_button.setEnabled(true);
delay_field.setEnabled(false);
abort_button.setEnabled(false) ;
prob_mode = -1 ;
proc_mode = -1 ;
}
if (current_mode == SIM_WAITSUBMIT) {
// now we're waiting for the user to select the propagation and delay
all_button.setBackground(Color.gray);
sum_button.setBackground(Color.gray);
any_button.setBackground(Color.gray);
all_button.setForeground(Color.black);
sum_button.setForeground(Color.black);
any_button.setForeground(Color.black);
all_button.setEnabled(true);
submit_button.setBackground(Color.gray);
quit_button.setBackground(Color.gray);
abort_button.setBackground(Color.gray);
submit_button.setForeground(Color.black);
quit_button.setForeground(Color.black);
abort_button.setForeground(Color.black);
all_button.setEnabled(true);
sum_button.setEnabled(true);
any_button.setEnabled(true);
manual_button.setEnabled(false);
automatic_button.setEnabled(false) ;
run_field.setEnabled(false);
submit_button.setEnabled(false);
quit_button.setEnabled(true);
delay_field.setEnabled(true);
}
if (current_mode == SIM_RUNNING) {
// while we are running, these buttons are off
all_button.setEnabled(false);
sum_button.setEnabled(false);
any_button.setEnabled(false);
manual_button.setEnabled(false);
automatic_button.setEnabled(false) ;
run_field.setEnabled(false);
submit_button.setEnabled(false);
quit_button.setEnabled(false);
delay_field.setEnabled(false);
// this guy is only active while we are running
abort_button.setEnabled(true) ;
}
if (current_mode == SIM_SHUTDOWN) {
// while we are running, these buttons are off
all_button.setEnabled(false);
sum_button.setEnabled(false);
any_button.setEnabled(false);
manual_button.setEnabled(false);
automatic_button.setEnabled(false) ;
run_field.setEnabled(false);
submit_button.setEnabled(false);
quit_button.setEnabled(false);
delay_field.setEnabled(false);
abort_button.setEnabled(false) ;
}
}
///////////////////////////////////////////////////////////////////
//
// Method: Warn
//
// Invoker: (private)
//
// Purpose: Displays specified warning in message box.
//
///////////////////////////////////////////////////////////////////
private void Warn (String message) {
Notify ("WARNING: " + (message == null ? "null" : message));
}
///////////////////////////////////////////////////////////////////
//
// Method: DEBUG
//
// Invoker: (private)
//
// Purpose: Displays specified debug message (remove this later).
//
///////////////////////////////////////////////////////////////////
private void DEBUG (String message) {
Notify ("DEBUG: " + (message == null ? "null" : message));
}
///////////////////////////////////////////////////////////////////
//
// Method: Error
//
// Invoker: DemonEngine
//
// 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.Exit (e);
}
///////////////////////////////////////////////////////////////////
//
// Method: Quit
//
// Invoker: ClientApplet
//
// Purpose: This tells the engine that the applet has been exited
// by the user.
//
///////////////////////////////////////////////////////////////////
public void Quit () {
Notify ("Shutting down Demon...");
if (engine != null) {
try {
engine.Quit ();
}
catch (Exception e) {
Error (e);
}
}
}
} // end of Class
////////////////////////////////////////////////////////////////////////