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 PropertyChangeEvents. * It also sends off PropertyChangeEvents 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 PropertyChangeEvents, even * though some of messages are more intuitively thought of as * ActionEvents. However, it seems better (to me) to * keep only one line of communication. *

* The following PropertyChangeEvents 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= 0) sOffset += diff; // helpButton is bigger else hOffset -= diff; // submitButton is bigger helpButton.setBounds(BORDER.left+lMarg, BORDER.top+2*barHeight+ switchButton.getPreferredSize().height+DIVIDER_WIDTH+tMarg+hOffset, helpButton.getPreferredSize().width, helpButton.getPreferredSize().height); submitButton.setBounds(BORDER.left+lMarg+BUTTON_SEPARATION+ helpButton.getPreferredSize().width, BORDER.top+2*barHeight+switchButton.getPreferredSize().height+ DIVIDER_WIDTH+tMarg+sOffset, submitButton.getPreferredSize().width, submitButton.getPreferredSize().height); workbench.setBounds(BORDER.left+barWidth+DIVIDER_WIDTH, BORDER.top, size.width-barWidth-DIVIDER_WIDTH, size.height); } /** * @param parent This CausalityLab * @return The minimum size of the CausalityLab */ public Dimension minimumLayoutSize(Container parent) { return getPreferredSize(); } /** * @param parent This CausalityLab * @return The preferred size of the CausalityLab */ public Dimension preferredLayoutSize(Container parent) { return getPreferredSize(); } /** * Currently does not do anything */ public void addLayoutComponent(String name, Component comp) { ; } /** * Currently does not do anything */ public void removeLayoutComponent(Component comp) { ; } /** * A helper method that creates appropriately set up JButtons * @param action The action command * @param tooltip The rollover String * @param enabledIcon The filename of the Icon for an enabled button * @param selectedIcon The filename of the Icon for a selected button * @param rolloverIcon The filename of the Icon for a rollover button * @param disabledIcon The filename of the Icon for a disabled button */ private JButton createButton(String action, String tooltip, String enabledIcon, String selectedIcon, String rolloverIcon, String disabledIcon) { JButton button = new JButton(); button.setContentAreaFilled(false); button.setMargin(new Insets(0, 0, 0, 0)); button.setBorder(null); try { if (enabledIcon != null) button.setIcon(new ImageIcon(new URL(codebase+DIR+enabledIcon))); if (selectedIcon != null) { button.setSelectedIcon(new ImageIcon(new URL(codebase+DIR+selectedIcon))); button.setPressedIcon(new ImageIcon(new URL(codebase+DIR+selectedIcon))); } if (rolloverIcon != null) button.setRolloverIcon(new ImageIcon(new URL(codebase+DIR+rolloverIcon))); if (disabledIcon != null) button.setDisabledIcon(new ImageIcon(new URL(codebase+DIR+disabledIcon))); } catch (MalformedURLException m) { System.out.println("Bad Icon URL: "+m.toString()); return null; } button.addActionListener(this); button.setActionCommand(action); button.setToolTipText(tooltip); return button; } }