package causation.lab; // Sun packages import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.beans.*; import java.awt.event.*; import java.net.*; // our packages import tetrad.graph.TetradGraph; import tetrad.model.BayesNetIM; import stats.DataTableModel; // custom classes import causation.lab.Workbench; import causation.lab.ButtonBar; import causation.lab.ExperimentInfoPanel; /** *
* This basically just groups the various parts of the CausalityLab
* together, and acts as a router for PropertyChangeEvent
s.
* It also sends off PropertyChangeEvent
s of its own, so that
* it's easy for the containing JApplet to know what's happening. It
* currently doesn't do any sophisticated initialization; it just puts
* together all of the pieces.
*
* Note: All messages are sent as PropertyChangeEvent
s, even
* though some of messages are more intuitively thought of as
* ActionEvent
s. However, it seems better (to me) to
* keep only one line of communication.
*
* The following PropertyChangeEvent
s are sent by this class:
*
* Copyright 1999 by David Danks. All rights reserved. *
* * @see Workbench * @see ButtonBar * @version 1.0 May 20, 1999 * @author David Danks */ public class LabPanel extends JPanel implements ActionListener, PropertyChangeListener, LayoutManager { /////////////////// Class Variables /////////////////// // Layout/color info private static final Color BACKGROUND = new Color(153, 204, 153); private static final Color LINE = new Color(102, 102, 102); private static final Insets BORDER = new Insets(5, 5, 5, 5); private static final int BORDER_WIDTH = 1; private static final int DIVIDER_WIDTH = 1; private static final int BUTTON_SEPARATION = 5; // Button info private static final String DIR = "icons/"; private static final String HELP_ENABLED = "helpup.gif"; private static final String HELP_ROLLOVER = "helproll.gif"; private static final String HELP_SELECTED = "helpdown.gif"; private static final String HELP_DISABLED = "helpoff.gif"; private static final String SUBMIT_ENABLED = "submitup.gif"; private static final String SUBMIT_ROLLOVER = "submitroll.gif"; private static final String SUBMIT_SELECTED = "submitdown.gif"; private static final String SUBMIT_DISABLED = "submitoff.gif"; private static final String SWITCH_SG_ENABLED = "downarrowup.gif"; private static final String SWITCH_SG_ROLLOVER = "downarrowroll.gif"; private static final String SWITCH_SG_SELECTED = "downarrowdown.gif"; private static final String SWITCH_SG_DISABLED = "downarrowoff.gif"; private static final String SWITCH_HP_ENABLED = "uparrowup.gif"; private static final String SWITCH_HP_ROLLOVER = "uparrowroll.gif"; private static final String SWITCH_HP_SELECTED = "uparrowdown.gif"; private static final String SWITCH_HP_DISABLED = "uparrowoff.gif"; // Tooltip info private static final String SETUP_GATHER_HELP_TOOLTIP = "Setup/Gather Data Help"; private static final String HYPOTHESIZE_PREDICT_HELP_TOOLTIP = "Hypothesize/Predict Help"; private static final String SETUP_GATHER_SWITCH_TOOLTIP = "Switch to Hypothesize & Make Predictions mode"; private static final String HYPOTHESIZE_PREDICT_SWITCH_TOOLTIP = "Switch to Setup Experiment and Gather Data mode"; private static final String SUBMIT_TOOLTIP = "Submit Graph"; /////////////////// Instance Variables /////////////////// // Components private Workbench workbench; private ButtonBar hypPredBar; private ButtonBar setupGatherBar; private JButton helpButton; private JButton switchButton; private JButton submitButton; // Variables private URL codebase; private int barWidth; private int barHeight; private int experimentNumber = 1; // Cursors private Cursor lockCursor = null; private Cursor randCursor = null; private Cursor latentCursor = null; private Cursor arrowCursor = null; /////////////////// Constructors /////////////////// /* public LabPanel() { codebase = new URL(); init(new Workbench()); } public LabPanel(BayesNet bn) { codebase = new URL(); init(new Workbench(bn)); } */ /** * This constructor creates a LabPanel based on the given BayesNetIM, and * getting icons from the given codebase. * @param bn The underlying BayesNetIM * @param codebase The URL from which to get icons */ public LabPanel(BayesNetIM bn, URL codebase) { this.codebase = codebase; init(new Workbench(bn, codebase)); } /** * This is a helper method that creates all of the pieces, and adds them * @param wb The Workbench to be contained in the LabPanel */ private void init(Workbench wb) { setLayout(this); workbench = wb; workbench.addPropertyChangeListener(this); hypPredBar = new ButtonBar(ButtonBar.HYPOTHESIZE_PREDICT_BAR, codebase); hypPredBar.setBarEnabled(false); hypPredBar.addPropertyChangeListener(this); setupGatherBar = new ButtonBar(ButtonBar.SETUP_GATHER_DATA_BAR, codebase); setupGatherBar.addPropertyChangeListener(this); // set up the buttons switchButton = createButton("switch", SETUP_GATHER_SWITCH_TOOLTIP, SWITCH_SG_ENABLED, SWITCH_SG_SELECTED, SWITCH_SG_ROLLOVER, SWITCH_SG_DISABLED); helpButton = createButton("help", SETUP_GATHER_HELP_TOOLTIP, HELP_ENABLED, HELP_SELECTED, HELP_ROLLOVER, HELP_DISABLED); submitButton = createButton("submit", SUBMIT_TOOLTIP, SUBMIT_ENABLED, SUBMIT_SELECTED, SUBMIT_ROLLOVER, SUBMIT_DISABLED); // and add the components add(hypPredBar); add(switchButton); add(setupGatherBar); add(helpButton); add(submitButton); add(workbench); } /////////////////// Instance Methods /////////////////// /** * Called whenever a contained Component sends a PropertyChangeEvent. * Note: not all of the PropertyChangeEvents are responded to. * @param p The PropertyChangeEvent sent to this object */ public void propertyChange(PropertyChangeEvent p) { // NOTE: in general, the ButtonBars send ALLCAPS PCEs, and the Workbench // sends alllower Events. if (p.getPropertyName().equals("DATA")) { String newExp = "exp"+String.valueOf(experimentNumber); ExperimentInfoPanel eip = new ExperimentInfoPanel(newExp); JOptionPane jop = new JOptionPane(eip, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); JInternalFrame jif = jop.createInternalFrame(this, "Get experiment info..."); eip.setCursor(); // this code is lifted straight from JInternalFrame.startModal(); // I need to make jif "modal", but the method to do that in // JInternalFrame is protected, so I just pasted the code in here try { if (SwingUtilities.isEventDispatchThread()) { EventQueue theQueue = getToolkit().getSystemEventQueue(); while (jif.isVisible()) { // This is essentially the body of EventDispatchThread AWTEvent event = theQueue.getNextEvent(); Object src = event.getSource(); // can't call theQueue.dispatchEvent, so I pasted it's body here /*if (event instanceof ActiveEvent) { ((ActiveEvent) event).dispatch(); } else */ if (src instanceof Component) ((Component) src).dispatchEvent(event); else if (src instanceof MenuComponent) ((MenuComponent) src).dispatchEvent(event); else System.err.println("unable to dispatch event: " + event); } } else while (jif.isVisible()) wait(); } catch(InterruptedException e){} Object value = jop.getValue(); jif.dispose(); if (value == null) return; else if (value instanceof Integer) { if (((Integer)value).intValue() != JOptionPane.OK_OPTION) return; } else return; if (eip.isSampleInfinite()) { // do code for an infinite sample // make the size negative, so that we know it's infinite int size = 0-eip.getSampleSize(); // then, fire off the property change firePropertyChange("Data", eip.getSampleName(), new Integer(size)); experimentNumber++; return; } else { // it's a finite sample int size = eip.getSampleSize(); // if size is 0, then the user entered a bad number if (size == 0) { JOptionPane.showInternalMessageDialog(this, "You must enter an integer for the sample size.", "Error!", JOptionPane.ERROR_MESSAGE); return; } // if the size is negative, don't go further if (size < 0) { JOptionPane.showInternalMessageDialog(this, "The sample size must be greater than zero.", "Error!", JOptionPane.ERROR_MESSAGE); return; } // if the size is greater than 5000, then don't let through if (size > 5000) { JOptionPane.showInternalMessageDialog(this, "The sample size must be smaller than 5000.", "Error!", JOptionPane.ERROR_MESSAGE); return; } // everything is in order, so fire the property change firePropertyChange("Data", eip.getSampleName(), new Integer(size)); experimentNumber++; return; } } else if (p.getPropertyName().equals("INDEPENDENCE")) { if (workbench.getOnWorkbenchVariableNames().length == 0) return; DSeparationFacts dsf = new DSeparationFacts(workbench.getUserManipulatedTetradGraph()); dsf.setFilterAndLockedVars(workbench.getOnWorkbenchVariableNames(), workbench.getLockedVariableNames()); HypIndepDisplay hid = new HypIndepDisplay(workbench.getWorkbenchImage(), dsf); firePropertyChange("independence", new ExperimentalSetup(workbench), hid); return; } // deal with the tool buttons else if (p.getPropertyName().equals("LOCK")) { boolean newVal = ((Boolean)p.getNewValue()).booleanValue(); if (newVal) { // activating tool int oldTool = workbench.getTool(); workbench.setTool(Workbench.LOCK); changeCursor(Workbench.LOCK); firePropertyChange("tool", oldTool, Workbench.LOCK); } else { // deactivating tool (=activating move tool) workbench.setTool(Workbench.MOVE); changeCursor(Workbench.MOVE); firePropertyChange("tool", Workbench.LOCK, Workbench.MOVE); } return; } else if (p.getPropertyName().equals("RANDOMIZER")) { boolean newVal = ((Boolean)p.getNewValue()).booleanValue(); if (newVal) { // activating tool int oldTool = workbench.getTool(); workbench.setTool(Workbench.RANDOMIZER); changeCursor(Workbench.RANDOMIZER); firePropertyChange("tool", oldTool, Workbench.RANDOMIZER); } else { // deactivating tool (=activating move tool) workbench.setTool(Workbench.MOVE); changeCursor(Workbench.MOVE); firePropertyChange("tool", Workbench.RANDOMIZER, Workbench.MOVE); } return; } else if (p.getPropertyName().equals("ARROW")) { boolean newVal = ((Boolean)p.getNewValue()).booleanValue(); if (newVal) { // activating tool int oldTool = workbench.getTool(); workbench.setTool(Workbench.ARROW); changeCursor(Workbench.ARROW); firePropertyChange("tool", oldTool, Workbench.ARROW); } else { // deactivating tool (=activating move tool) workbench.setTool(Workbench.MOVE); changeCursor(Workbench.MOVE); firePropertyChange("tool", Workbench.ARROW, Workbench.MOVE); } return; } else if (p.getPropertyName().equals("LATENT")) { boolean newVal = ((Boolean)p.getNewValue()).booleanValue(); if (newVal) { // activating tool int oldTool = workbench.getTool(); workbench.setTool(Workbench.LATENT); changeCursor(Workbench.LATENT); firePropertyChange("tool", oldTool, Workbench.LATENT); } else { // deactivating tool (=activating move tool) workbench.setTool(Workbench.MOVE); changeCursor(Workbench.MOVE); firePropertyChange("tool", Workbench.LATENT, Workbench.MOVE); } return; } // deal with changes on the workbench else if (p.getPropertyName().startsWith("onWorkbench:")) { boolean oldVal = ((Boolean)p.getOldValue()).booleanValue(); boolean newVal = ((Boolean)p.getNewValue()).booleanValue(); // fire a property change only if they're different if (oldVal != newVal) firePropertyChange(p.getPropertyName(), oldVal, newVal); return; } else if (p.getPropertyName().startsWith("lock:")) { // only fired if there is actually a change, so just pass on firePropertyChange(p.getPropertyName(), p.getOldValue(), p.getNewValue()); return; } else if (p.getPropertyName().equals("edgeadded")) { // just pass on firePropertyChange(p.getPropertyName(), p.getOldValue(), p.getNewValue()); return; } else if (p.getPropertyName().equals("edgeremoved")) { // just pass on firePropertyChange(p.getPropertyName(), p.getOldValue(), p.getNewValue()); return; } else if (p.getPropertyName().equals("Performed operation")) { if (workbench.getMode() == Workbench.SETUP_GATHER_MODE) setupGatherBar.deselectAll(); else if (workbench.getMode() == Workbench.HYPOTHESIZE_PREDICT_MODE) hypPredBar.deselectAll(); } } /** * Sets the mode of the whole CausalityLab to the new value * @param newMode The new mode for the CausalityLab */ public void setMode(int newMode) { if (newMode == Workbench.SETUP_GATHER_MODE) { // set the tool state to move if (workbench.getTool() != Workbench.MOVE) hypPredBar.deselectAll(); // set the mode/enabled states workbench.setMode(Workbench.SETUP_GATHER_MODE); hypPredBar.setBarEnabled(false); hypPredBar.repaint(); setupGatherBar.setBarEnabled(true); setupGatherBar.repaint(); try { switchButton.setIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_SG_ENABLED))); switchButton.setSelectedIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_SG_SELECTED))); switchButton.setPressedIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_SG_SELECTED))); switchButton.setRolloverIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_SG_ROLLOVER))); switchButton.setDisabledIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_SG_DISABLED))); } catch (MalformedURLException m) { System.out.println("Bad Icon URL: "+m.toString()); return; } switchButton.setToolTipText(SETUP_GATHER_SWITCH_TOOLTIP); helpButton.setToolTipText(SETUP_GATHER_HELP_TOOLTIP); firePropertyChange("mode", Workbench.HYPOTHESIZE_PREDICT_MODE, Workbench.SETUP_GATHER_MODE); } else if (newMode == Workbench.HYPOTHESIZE_PREDICT_MODE) { // set the tool state to move if (workbench.getTool() != Workbench.MOVE) setupGatherBar.deselectAll(); // set the mode/enabled states workbench.setMode(Workbench.HYPOTHESIZE_PREDICT_MODE); hypPredBar.setBarEnabled(true); hypPredBar.repaint(); setupGatherBar.setBarEnabled(false); setupGatherBar.repaint(); try { switchButton.setIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_HP_ENABLED))); switchButton.setSelectedIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_HP_SELECTED))); switchButton.setPressedIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_HP_SELECTED))); switchButton.setRolloverIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_HP_ROLLOVER))); switchButton.setDisabledIcon(new ImageIcon(new URL(codebase+DIR+SWITCH_HP_DISABLED))); } catch (MalformedURLException m) { System.out.println("Bad Icon URL: "+m.toString()); return; } switchButton.setToolTipText(HYPOTHESIZE_PREDICT_SWITCH_TOOLTIP); helpButton.setToolTipText(HYPOTHESIZE_PREDICT_HELP_TOOLTIP); firePropertyChange("mode", Workbench.SETUP_GATHER_MODE, Workbench.HYPOTHESIZE_PREDICT_MODE); } } /** * Called when a top-level button (i.e., not in a ButtonBar) is pressed * @param a The ActionEvent to respond to */ public void actionPerformed(ActionEvent a) { if (a.getActionCommand().equals("switch")) { // switch between the two modes if (workbench.getMode() == Workbench.SETUP_GATHER_MODE) setMode(Workbench.HYPOTHESIZE_PREDICT_MODE); else if (workbench.getMode() == Workbench.HYPOTHESIZE_PREDICT_MODE) setMode(Workbench.SETUP_GATHER_MODE); } else if (a.getActionCommand().equals("help")) { // actually need to throw up the help window here if (workbench.getMode() == Workbench.SETUP_GATHER_MODE) firePropertyChange("setup_gather_help", null, null); else firePropertyChange("hypothesize_predict_help", null, null); } else if (a.getActionCommand().equals("submit")) { firePropertyChange("Submit", null, null); } } /** * @param size The size of the dataset to get from the Workbench * @return The dataset */ public DataTableModel performExperiment(int size) { return workbench.performExperiment(size); } /** * @param index The index of the previous experimental setup to load */ public void loadScreenShot(int index) { workbench.loadScreenShot(index); } /** * Allows us to enable/disable an entire mode of the CausalityLab * @param modeType The mode to enable/disable (from Workbench constants) * @param value Whether the mode should be enabled */ public void setModeEnabled(int modeType, boolean value) { // if we're activating it, just enable the button if (value) { if (workbench.getMode() == modeType) return; switchButton.setEnabled(true); return; } // if we're deactivating it, first set the mode to the other, then // disable the switch button else { if (modeType == Workbench.SETUP_GATHER_MODE) { setMode(Workbench.HYPOTHESIZE_PREDICT_MODE); switchButton.setEnabled(false); } else if (modeType == Workbench.HYPOTHESIZE_PREDICT_MODE) { setMode(Workbench.SETUP_GATHER_MODE); switchButton.setEnabled(false); } return; } } /** * Allows us to enable/disable a tool of the CausalityLab * @param tool The tool to enable/disable (from Workbench constants) * @param value Whether the tool should be enabled */ public void setToolEnabled(int tool, boolean value) { // don't let the user touch the move function if (tool == Workbench.MOVE) return; // figure out the ButtonBar and index ButtonBar bar = null; int index = -1; switch (tool) { case Workbench.LOCK: bar = setupGatherBar; index = 0; break; case Workbench.RANDOMIZER: bar = setupGatherBar; index = 1; break; case Workbench.GATHER_DATA: bar = setupGatherBar; index = 2; break; case Workbench.ARROW: bar = hypPredBar; index = 0; break; case Workbench.LATENT: bar = hypPredBar; index = 1; break; case Workbench.INDEPENDENCE: bar = hypPredBar; index = 2; break; } // if we're deactivating it, set the function to move first if (!value) workbench.setTool(Workbench.MOVE); // now, just set the button appropriately bar.setButtonActivated(index, value); } /** * @param value Whether the help button should be enabled */ public void setHelpEnabled(boolean value) { helpButton.setEnabled(value); } /** * @param value Whether the submit button should be enabled */ public void setSubmitEnabled(boolean value) { submitButton.setEnabled(value); } /** * @param newCursor The Cursor to display when setting a lock; *null
, to not change the cursor
*/
public void setLockCursor(Cursor newCursor) {
lockCursor = newCursor;
}
/**
* @param newCursor The Cursor to display when creating a randomizer;
* null
, to not change the cursor
*/
public void setRandCursor(Cursor newCursor) {
randCursor = newCursor;
}
/**
* @param newCursor The Cursor to display when creating a latent;
* null
, to not change the cursor
*/
public void setLatentCursor(Cursor newCursor) {
latentCursor = newCursor;
}
/**
* @param newCursor The Cursor to display when starting an arrow;
* null
, to not change the cursor
*/
public void setArrowCursor(Cursor newCursor) {
arrowCursor = newCursor;
}
/**
* @param index The index of the WBVariable
* @param icon The icon for the WBVariable
*/
public void setVariableIcon(int index, Icon icon) {
workbench.setVariableIcon(index, icon);
}
/**
* @param name The name of the WBVariable
* @param icon The icon for the WBVariable
*/
public void setVariableIcon(String name, Icon icon) {
workbench.setVariableIcon(name, icon);
}
/**
* @return An array of all of the variables
*/
public WBVariable[] getVariables() { return workbench.getVariables(); }
/**
* A helper method to set the Cursor appropriately when we change modes
* @param type The mode we're changing to
*/
private void changeCursor(int type) {
Cursor newCursor;
switch(type) {
case Workbench.LOCK: newCursor = lockCursor; break;
case Workbench.RANDOMIZER: newCursor = randCursor; break;
case Workbench.ARROW: newCursor = arrowCursor; break;
case Workbench.LATENT: newCursor = latentCursor; break;
default: newCursor = Cursor.getDefaultCursor();
}
if (newCursor != null) workbench.changeCursor(newCursor);
}
/**
* @return The underlying TetradGraph (with experimental manipulations)
*/
public TetradGraph getTrueManipulatedTetradGraph() {
return workbench.getTrueManipulatedTetradGraph();
}
/**
* @return The names of the Variables on the Workbench
*/
public String[] getOnWorkbenchVariableNames() {
return workbench.getOnWorkbenchVariableNames();
}
/**
* @return The names of the locked Variables on the Workbench
*/
public String[] getLockedVariableNames() {
return workbench.getLockedVariableNames();
}
/**
* @return The DiGraph that the user has constructed
*/
public TetradGraph getUserTetradGraph() {
return workbench.getTetradGraph();
}
/**
* @return The Workbench in this CausalityLab
*/
public Workbench getWorkbench() { return workbench; }
/**
* @return The HypothesizePredict ButtonBar in this CausalityLab
*/
public ButtonBar getHypothesizePredictBar() { return hypPredBar; }
/**
* @return The SetupGatherData ButtonBar in this CausalityLab
*/
public ButtonBar getSetupGatherBar() { return setupGatherBar; }
/**
* @return true
always, since we draw the background
*/
public boolean isOpaque() { return true; }
/**
* @return An Image of the part of the Workbench with Variables on it
*/
public Image getWorkbenchImage() {
return workbench.getWorkbenchImage();
}
/**
* @return An Image of the experimenting part of the Workbench
*/
public Image getExperimentalSetupImage() {
return workbench.getExperimentalSetupImage();
}
/**
* Paints the background of the CausalityLab
* @param g The Graphics context in which to paint the background
*/
public void paintComponent(Graphics g) {
int height = getHeight();
int width = getWidth();
int binHeight = workbench.getBinHeight();
// first, paint everything in the background color
g.setColor(BACKGROUND);
g.fillRect(0, 0, getWidth(), height);
// then the outer border
g.setColor(LINE);
for (int i=0; i