package causation.lab; // Sun packages import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.*; import java.beans.*; import java.util.*; import java.net.URL; // our packages import tetrad.graph.*; import tetrad.model.*; // custom classes import causation.lab.WorkbenchModel; import causation.lab.plaf.WorkbenchUI; import causation.lab.WBVariable; import causation.lab.Randomizer; import causation.lab.Latent; import causation.lab.WorkbenchEdge; import causation.lab.ScreenShot; import causation.lab.WorkbenchObject; import stats.DataTableModel; import stats.TextVariable; /** *

The Workbench provides a "workspace" for manipulating WBVariables, which * are based on an underlying BayesNet. The Workbench maintains the * WBVariables, and provides a JComponent on which to move and draw them. It * also allows the user to manipulate them in various ways, depending on * the mode of the Workbench. Note: It's important to keep the underlying * "true" BayesNet distinct in one's coding from the DiGraph being * constructed by the user by drawing arrows. *

* The following PropertyChangeEvents are sent by this class: *

*

* Copyright 1999 by David Danks. All rights reserved. *

* * @see WBVariable * @see Randomizer * @see Latent * @version 0.9 Aug 18, 1999 * @author David Danks */ public class Workbench extends JComponent implements LayoutManager, PropertyChangeListener, MouseListener, MouseMotionListener, ActionListener { /////////////////// Static Class Variables /////////////////// // mode and tool constants (since we want the model to be invisible // to classes using the Workbench. public static final int SETUP_GATHER_MODE = WorkbenchModel.SETUP_GATHER_MODE; public static final int HYPOTHESIZE_PREDICT_MODE = WorkbenchModel.HYPOTHESIZE_PREDICT_MODE; public static final int MOVE = WorkbenchModel.MOVE; public static final int LOCK = WorkbenchModel.LOCK; public static final int RANDOMIZER = WorkbenchModel.RANDOMIZER; public static final int ARROW = WorkbenchModel.ARROW; public static final int LATENT = WorkbenchModel.LATENT; public static final int GATHER_DATA = -10000; // these two are used only public static final int INDEPENDENCE = -10001; // to describe functions // GUI settings private static final int DEFAULT_WIDTH = 400; private static final int DEFAULT_HEIGHT = 400; private static final int MINIMUM_WIDTH = 50; private static final int VARIABLE_SEPARATION = 3; private static final int BIN_DIVIDER_WIDTH = 1; private static final Insets WB_MARGIN = new Insets(3, 3, 3, 3); private static final Insets BIN_MARGIN = new Insets(3, 3, 3, 3); private static final Font FONT = new Font("Dialog", Font.BOLD, 14); private static final String BIN_LABEL = "Variable bin"; private static final String WORKBENCH_LABEL = "Workbench"; /////////////////// Instance Variables /////////////////// private static final String UIClassID = "causation.lab.plaf.WorkbenchUI"; private WorkbenchModel model; private WBVariable[] variables; private Vector onWorkbench; private Vector randomizers; private Vector latents; private Vector screenShots = new Vector(); private int numCreatedLatents = 0; // GUI variables private int BIN_HEIGHT; private Rectangle bin_rect = new Rectangle(); private int var_width; private Point clickPoint = new Point(); private boolean drawingEdge = false; private boolean movingVariable = false; private Vector edges; private Object selected; private URL codebase; /////////////////// Constructors /////////////////// /** * Creates a workbench based on a trivial BayesNetIM, with all images * loaded from the given codebase, and in the Setup & Gather Data mode. * * @param codebase The codebase to load the images from */ public Workbench(URL codebase) { init(new WorkbenchModel(), codebase); } /** * Creates a workbench based on the given BayesNetIM, with all images * loaded from the given codebase, and in the Setup & Gather Data mode. * * @param bn The underlying BayesNetIM for the Workbench * @param codebase The codebase to load the images from */ public Workbench(BayesNetIM bn, URL codebase) { init(new WorkbenchModel(bn), codebase); } /** * Creates a workbench based on a trivial BayesNetIM, with all images * loaded from the given codebase, and in the given mode. * * @param mode The mode in which to start the Workbench * @param codebase The codebase to load the images from */ public Workbench(int mode, URL codebase) { init(new WorkbenchModel(mode), codebase); } /** * Creates a workbench based on the given BayesNetIM, with all images * loaded from the given codebase, and in the given mode. * * @param bn The underlying BayesNetIM for the Workbench * @param mode The mode in which to start the Workbench * @param codebase The codebase to load the images from */ public Workbench(BayesNetIM bn, int mode, URL codebase) { init(new WorkbenchModel(bn, mode), codebase); } /** * Helper method for the constructors that does most of the work. * @param wModel The WorkbenchModel underlying the Workbench * @param codebase The codebase to load all images from */ private void init(WorkbenchModel wModel, URL codebase) { // initialize the workbench model = wModel; setLayout(this); setFont(FONT); this.codebase = codebase; // listen for clicks to create latents, rands, and select edges addMouseListener(this); variables = createVariables(); for (int i=0; i=0; i--) { if ((WorkbenchEdge)edges.elementAt(i) == selected) { edges.removeElement(selected); break; } } // remove from TetradGraph removeEdgeFromTetradGraph((WorkbenchEdge)selected); selected = null; repaint(); return; } else if (selected instanceof Randomizer) { // remove from the randomizer Vector for (int i=randomizers.size()-1; i>=0; i--) { if ((Randomizer)randomizers.elementAt(i) == selected) { randomizers.removeElement(selected); break; } } // remove from the associated variable WBVariable variable = ((Randomizer)selected).getTarget(); variable.setRandomizer(null); firePropertyChange("randomized:"+variable.getName(), true, false); // remove from the layout remove((Component)selected); selected = null; repaint(); return; } } /** * Called when the user clicks somewhere on the Workbench, or one of its * Components (WBVariables, Randomizers, or Latents). * @param m The MouseEvent to respond to */ public void mouseClicked(MouseEvent m) { // ignore, if the frame isn't selected if (!acceptEvent()) return; // if they clicked on the workbench itself, then select edges or // create a new component if (m.getSource() instanceof Workbench) { // de-select selected, if there is one if (selected != null) { if (selected instanceof WorkbenchEdge) ((WorkbenchEdge)selected).setSelected(false); else if (selected instanceof Randomizer) ((Randomizer)selected).setSelected(false); selected = null; repaint(); } // if it's a click in the bin, just ignore it if (bin_rect.contains(m.getPoint())) return; switch(model.getTool()) { case MOVE: // only pay attention if the edges could be selected if (model.getMode() == SETUP_GATHER_MODE) return; case ARROW: // check for selecting an edge selectEdge(m.getPoint()); break; case LATENT: // add a latent addLatent(m.getPoint()); break; default: break; } firePropertyChange("Performed operation", null, null); return; } // if they clicked on a Rand in SG mode, then select it if (m.getSource() instanceof Randomizer) { switch(model.getTool()) { case MOVE: if (model.getMode() == HYPOTHESIZE_PREDICT_MODE) return; case RANDOMIZER: Randomizer src = (Randomizer)m.getSource(); if (src.isSelected()) return; src.setSelected(true); selected = src; repaint(); break; default: break; } firePropertyChange("Performed operation", null, null); return; } // don't do anything right now if (m.getSource() instanceof Latent) { return; } // if on a Var, then lock or randomize if (m.getSource() instanceof WBVariable) { WBVariable src = (WBVariable)m.getSource(); switch(model.getTool()) { case LOCK: // if the variable is on the workbench, set the lock if (onWorkbench.contains(src)) getAndSetLock(src); break; case RANDOMIZER: // if it already has a randomizer, ignore if (src.isRandomized()) return; addRandomizer(src); break; default: break; // the rest of the tools don't respond } firePropertyChange("Performed operation", null, null); return; } } /** * Called when the user is dragging the mouse (so is either drawing an * arrow, or moving a Component). * @param m The MouseEvent to respond to */ public void mouseDragged(MouseEvent m) { // disregard if on Workbench if (m.getSource() instanceof Workbench) return; Component comp = (Component)m.getSource(); Point origin = comp.getLocation(); Point current = m.getPoint(); switch(model.getTool()) { case MOVE: if (drawingEdge) { // could be doing this if they shift-dragged ((WorkbenchEdge)selected).setEndPoint(origin.x+current.x, origin.y+current.y); repaint(); return; } comp.setLocation(origin.x+(current.x-clickPoint.x), origin.y+(current.y-clickPoint.y)); // we need an additional repaint() for moving edges repaint(); return; case ARROW: if (!(drawingEdge)) return; // ignore if not drawing edge ((WorkbenchEdge)selected).setEndPoint(origin.x+current.x, origin.y+current.y); repaint(); return; default: return; } } /** * Called when the user releases the mouse. * @param m The MouseEvent to respond to */ public void mouseReleased(MouseEvent m) { // ignore mouseReleasedEvents from the Workbench if (m.getSource() instanceof Workbench) return; // if it's a Rand, make sure it's on the bench else if (m.getSource() instanceof Randomizer) { Randomizer rand = (Randomizer)m.getSource(); switch(model.getTool()) { case MOVE: rand.setLocation(moveOnScreen(rand)); Rectangle oldBounds = rand.getBounds(); oldBounds.y = (oldBounds.y+oldBounds.height > getInsets().top+getBounds().height-BIN_HEIGHT) ? getInsets().top+getBounds().height- BIN_HEIGHT-oldBounds.height : oldBounds.y; rand.setBounds(oldBounds); repaint(); break; default: break; } return; } // check if it's a Latent else if (m.getSource() instanceof Latent) { Latent latent = (Latent)m.getSource(); switch(model.getTool()) { case ARROW: // finish the WorkbenchEdge if (!(drawingEdge)) return; // if we weren't drawing, ignore drawingEdge = false; boolean onVariable = finishEdge(new Point(m.getPoint().x+latent.getLocation().x, m.getPoint().y+latent.getLocation().y)); if (!(onVariable)) { // released off of a variable edges.removeElement(selected); selected = null; repaint(); } firePropertyChange("Performed operation", null, null); break; case MOVE: // if it's off the bench, then remove it if (drawingEdge) { // could be doing this if they shift-dragged drawingEdge = false; onVariable = finishEdge(new Point(m.getPoint().x+latent.getLocation().x, m.getPoint().y+latent.getLocation().y)); if (!(onVariable)) { // released off of a variable edges.removeElement(selected); selected = null; repaint(); } firePropertyChange("Performed operation", null, null); return; } // if it gets here, then it must be an actual move movingVariable = false; Rectangle bounds = latent.getBounds(); Insets insets = getInsets(); Dimension size = getSize(); if ((bounds.x < insets.left+WB_MARGIN.left) || (bounds.y < insets.top+WB_MARGIN.top+ getFontMetrics(getFont()).getHeight()) || (bounds.x+bounds.width > size.width-insets.right- WB_MARGIN.right) || (bounds.y+bounds.height > insets.top+size.height-BIN_HEIGHT)) removeLatent(latent); break; default: break; } return; } // if it's a Var, put it in the right place by shifting, then checking else if (m.getSource() instanceof WBVariable) { WBVariable src = (WBVariable)m.getSource(); switch(model.getTool()) { case MOVE: if (drawingEdge) { // could be doing this if they shift-dragged drawingEdge = false; boolean onVariable = finishEdge(new Point(m.getPoint().x+src.getLocation().x, m.getPoint().y+src.getLocation().y)); if (!(onVariable)) { // released off of a variable edges.removeElement(selected); selected = null; repaint(); } firePropertyChange("Performed operation", null, null); return; } movingVariable = false; // first, we need to shift the bounds src.setLocation(moveOnScreen(src)); // then check where it is if (src.getBounds().intersects(bin_rect)) setOnWorkbench(src, false); else setOnWorkbench(src, true); repaint(); break; case ARROW: if (!(drawingEdge)) return; // if we weren't drawing, ignore drawingEdge = false; boolean onVariable = finishEdge(new Point(m.getPoint().x+src.getLocation().x, m.getPoint().y+src.getLocation().y)); if (!(onVariable)) { // released off of a variable edges.removeElement(selected); selected = null; repaint(); } firePropertyChange("Performed operation", null, null); break; default: break; } return; } } /** * Called when the user presses down the mouse * @param m The MouseEvent to respond to */ public void mousePressed(MouseEvent m) { // ignore mouseEvents from the Workbench if (m.getSource() instanceof Workbench) return; // otherwise, our response depends on the current tool switch(model.getTool()) { case MOVE: clickPoint = m.getPoint(); // if SHIFT is down in HP mode with a press on a variable or a latent, // then draw an arrow instead of moving it. if ((m.isShiftDown()) && (getMode() == HYPOTHESIZE_PREDICT_MODE)) { if (selected != null) { ((WorkbenchEdge)selected).setSelected(false); selected = null; repaint(); } if (onWorkbench.contains(m.getSource())) { drawingEdge = true; selected = new WorkbenchEdge((WBVariable)m.getSource(), m.getPoint()); edges.addElement(selected); } else if (latents.contains(m.getSource())) { drawingEdge = true; selected = new WorkbenchEdge((Latent)m.getSource(), m.getPoint()); edges.addElement(selected); } } // track the moving of variables so that we don't get flicker from // the mouse exiting the variable by out-racing it. else if (m.getSource() instanceof WBVariable) movingVariable = true; else if (m.getSource() instanceof Latent) movingVariable = true; break; case ARROW: // only respond if it's on a Workbench variable or Latent if (selected != null) { ((WorkbenchEdge)selected).setSelected(false); selected = null; repaint(); } if (onWorkbench.contains(m.getSource())) { drawingEdge = true; selected = new WorkbenchEdge((WBVariable)m.getSource(), m.getPoint()); edges.addElement(selected); } else if (latents.contains(m.getSource())) { drawingEdge = true; selected = new WorkbenchEdge((Latent)m.getSource(), m.getPoint()); edges.addElement(selected); } break; default: break; } return; } /** * Currently does nothing * @param m The MouseEvent to respond to */ public void mouseMoved(MouseEvent m) { m.consume(); } /** * Tells the WBVariable entered to highlight itself * @param m The MouseEvent to respond to */ public void mouseEntered(MouseEvent m) { // ignore, if we're moving a variable if (movingVariable) return; // Highlight the WBVariable if (m.getSource() instanceof WBVariable) { ((WBVariable)m.getSource()).setHighlight(true); ((WBVariable)m.getSource()).repaint(); return; } // Highlight the Latent if (m.getSource() instanceof Latent) { ((Latent)m.getSource()).setHighlight(true); ((Latent)m.getSource()).repaint(); return; } } /** * Tells the WBVariable exited to "un-highlight" itself * @param m The MouseEvent to respond to */ public void mouseExited(MouseEvent m) { // ignore, if we're moving a variable if (movingVariable) return; // Un-highlight the WBVariable if (m.getSource() instanceof WBVariable) { ((WBVariable)m.getSource()).setHighlight(false); ((WBVariable)m.getSource()).repaint(); return; } // Un-highlight the Latent if (m.getSource() instanceof Latent) { ((Latent)m.getSource()).setHighlight(false); ((Latent)m.getSource()).repaint(); return; } } // MouseEvent helper methods /** * @return Whether the JInternalFrame is in a state to accept the * event */ private boolean acceptEvent() { // find the JInternalFrame containing the Workbench Container p = getParent(); while (true) { if (p == null) return true; if (p instanceof JInternalFrame) break; else p = p.getParent(); } return ((JInternalFrame)p).isSelected(); } /** * Selects the edge passing through p, if there is one. * @param p The Point to test on */ private void selectEdge(Point p) { boolean needsRepaint = false; if (selected != null) { ((WorkbenchEdge)selected).setSelected(false); selected = null; needsRepaint = true; } for (int i=edges.size()-1; i>=0; i--) { WorkbenchEdge edge = (WorkbenchEdge)edges.elementAt(i); if (edge.getSleeve().contains(p)) { edge.setSelected(true); selected = edge; repaint(); return; } } if (needsRepaint) repaint(); return; } /** * Adds a latent variable at the indicated point. * @param p The Point at which to add the Latent */ private void addLatent(Point p) { Latent latent = new Latent("L"+String.valueOf(numCreatedLatents)); numCreatedLatents++; latents.addElement(latent); add(latent); model.addLatent(latent.getName()); latent.setBounds(p.x, p.y, latent.getPreferredSize().width, latent.getPreferredSize().height); latent.addMouseListener(this); latent.addMouseMotionListener(this); latent.addPropertyChangeListener(this); repaint(); return; } /** * Removes the given latent variable. * @param l The Latent to remove */ private void removeLatent(Latent l) { latents.removeElement(l); remove(l); // remove all of the WorkbenchEdges that l is involved in for (int i=edges.size()-1; i>=0; i--) { WorkbenchEdge e = (WorkbenchEdge)edges.elementAt(i); if ((e.getFromObject() == l) || (e.getToObject() == l)) edges.removeElement(e); } // and tell the model to fix the TetradGraph model.removeLatent(l.getName()); l = null; repaint(); return; } /** * Adds a randomizer to the given WBVariable * @param src The WBVariable we're adding a Randomizer to */ private void addRandomizer(WBVariable src) { // first remove the lock on the WBVariable (if there is one) if (src.isLocked()) { String oldLockValue = src.getActiveValue(); src.setLocked(false); src.setActiveValue(null); firePropertyChange("lock:"+src.getName(), oldLockValue, null); src.setSize(src.getPreferredSize()); src.repaint(); } // then add the randomizer Randomizer random = new Randomizer(src); random.setCodebase(codebase); randomizers.addElement(random); src.setRandomizer(random); add(random); Rectangle srcBounds = src.getBounds(); random.setBounds(srcBounds.x+srcBounds.width+10, srcBounds.y, random.getPreferredSize().width, random.getPreferredSize().height); random.addMouseListener(this); random.addMouseMotionListener(this); random.addPropertyChangeListener(this); repaint(); firePropertyChange("randomized:"+src.getName(), false, true); return; } /** * Figures out the new value for the lock, and then calls the set method. * @param src The WBVariable to lock */ private void getAndSetLock(WBVariable src) { // figure out the old state String oldLockValue = src.getActiveValue(); String preset = (oldLockValue == null) ? "Unlocked" : oldLockValue; // get the list of values String[] shortValues = src.getValues(); String[] values = new String[shortValues.length+1]; System.arraycopy(shortValues, 0, values, 0, shortValues.length); values[shortValues.length] = "Unlocked"; // get the new value String newValue = (String)JOptionPane.showInternalInputDialog(this, "Set the lock value:", "Lock value", JOptionPane.QUESTION_MESSAGE, null, values, preset); // if they chose "Cancel", return if (newValue == null) return; // if they choose the same thing, then return else if (newValue.equals(preset)) return; // otherwise, set it to the appropriate value else if (newValue.equals("Unlocked")) setLock(src, null); else setLock(src, newValue); if (getParent() instanceof JInternalFrame) getParent().requestFocus(); } /** * This method does the locking of a WBVariable * @param src The WBVariable to lock * @param newValue The locking value of the WBVariable */ private void setLock(WBVariable src, String newValue) { String oldLockValue = src.getActiveValue(); // remove the randomizer (if any), and set the lock if (src.isRandomized()) { Randomizer rand = src.getRandomizer(); randomizers.removeElement(rand); remove((Component)rand); src.setRandomizer(null); firePropertyChange("randomized:"+src.getName(), true, false); } if (newValue == null) { src.setLocked(false); src.setActiveValue(null); firePropertyChange("lock:"+src.getName(), oldLockValue, null); } else { src.setLocked(true); src.setActiveValue(newValue); firePropertyChange("lock:"+src.getName(), oldLockValue, newValue); } src.setSize(src.getPreferredSize()); src.repaint(); repaint(); } /** * Returns a Point indicating the closest Workbench spot to the Component's * current location (so we can move it back onto the Workbench). * @param src The Component to check * @return The nearest Point on the Workbench */ private Point moveOnScreen(Component src) { Rectangle bounds = src.getBounds(); Dimension size = getSize(); Insets insets = getInsets(); FontMetrics fm = getFontMetrics(getFont()); if (bounds.x < insets.left+WB_MARGIN.left) bounds.x = insets.left+WB_MARGIN.left; if (bounds.y < insets.top+WB_MARGIN.top+fm.getHeight()) bounds.y = insets.top+WB_MARGIN.top+fm.getHeight(); if (bounds.x+bounds.width > size.width-insets.right-WB_MARGIN.right) bounds.x = size.width-insets.right-WB_MARGIN.right-bounds.width; if (bounds.y+bounds.height > size.height-insets.bottom-BIN_MARGIN.bottom) bounds.y = size.height-bounds.height-insets.bottom-BIN_MARGIN.bottom; return new Point(bounds.x, bounds.y); } /** * Sets the given WBVariable either on or off the workbench * @param src The WBVariable to set * @param on true, if the WBVariable is now on the workbench; * false, if not. */ private void setOnWorkbench(WBVariable src, boolean on) { boolean previouslyOn = false; if (!(on)) { // if it's on the Workbench, take it off if (onWorkbench.contains(src)) { previouslyOn = true; onWorkbench.removeElement(src); // unlock if (src.isLocked()) { String oldVal = src.getActiveValue(); src.setLocked(false); src.setActiveValue(null); firePropertyChange("lock:"+src.getName(), oldVal, null); } // pass through the WorkbenchEdges to find the ones to remove for (int i=edges.size()-1; i>=0; i--) { WorkbenchEdge check = (WorkbenchEdge)edges.elementAt(i); if ((src == check.getFromObject()) || (src == check.getToObject())) { edges.removeElement(check); removeEdgeFromTetradGraph(check); } } if (src.isRandomized()) { Randomizer rand = src.getRandomizer(); randomizers.removeElement(rand); remove(rand); src.setRandomizer(null); } } // set the bin location by figuring out which var it is, and // moving it there (using it's "unique" location) int i; for (i=0; itrue, if the edge was completed; * false, if not */ private boolean finishEdge(Point p) { // check to see if any of the Workbench vars is the target WBVariable target; for (int i=0; i=0; i--) { removeLatent((Latent)latents.elementAt(i)); } // First, deal with the WBVariables for (int i=0; i=0; i--) { edges.addElement(edgesToLoad.elementAt(i)); addEdgeToTetradGraph((WorkbenchEdge)edgesToLoad.elementAt(i)); } // Finally, add the var-to-latent and latent-to-var edges Vector lEdgesToLoad = ss.getLatentEdgesVector(); for (int i=lEdgesToLoad.size()-1; i>=0; i--) { StringTokenizer st = new StringTokenizer((String)lEdgesToLoad.elementAt(i), "->"); String from = st.nextToken(); String to = st.nextToken(); // find the fromObject WorkbenchObject fromObj = null; if (from.startsWith(ScreenShot.LATENT)) { String newFrom = from.substring(ScreenShot.LATENT.length()); for (int j=latents.size()-1; j>=0; j--) { if (newFrom.equals(((Latent)latents.elementAt(j)).getName())) { fromObj = (WorkbenchObject)latents.elementAt(j); break; } } } else { for (int j=0; j=0; j--) { if (newTo.equals(((Latent)latents.elementAt(j)).getName())) { toObj = (WorkbenchObject)latents.elementAt(j); break; } } } else { for (int j=0; j=0; i--) { ((WorkbenchEdge)edges.elementAt(i)).setEnabled(b); } for (int i=0; i=0; i--) { ((Latent)latents.elementAt(i)).setExperimenting(!(b)); } for (int i=randomizers.size()-1; i>=0; i--) { ((Randomizer)randomizers.elementAt(i)).setExperimenting(!(b)); } repaint(); } /** * This method is an accessor method, since we're storing the ScreenShots in * the workbench. * @param index The index of the ScreenShot to load */ public void loadScreenShot(int index) { loadScreenShot((ScreenShot)screenShots.elementAt(index)); } /** * @param The Cursor for both the Workbench and its WBVariables */ public void changeCursor(Cursor cursor) { setCursor(cursor); for (int i=0; i=0; i--) { if (((WBVariable)onWorkbench.elementAt(i)).isLocked()) locks.addElement(onWorkbench.elementAt(i)); } String[] locked = new String[locks.size()]; for (int i=0; i=0; i--) { rands[i] = ((Randomizer)randomizers.elementAt(i)).getTarget().getName(); } return rands; } /** * @return An Image of the area of the Workbench with either variables or * randomizers on it. If we're in the hypothesize_predict mode, * then the "broken" arrows (because of locks or randomizers) * have X's through them. */ public Image getWorkbenchImage() { return getWorkbenchImage(false); } /** * @return An Image of the area of the Workbench with either variables or * randomizers on it, but without any edges. */ public Image getExperimentalSetupImage() { return getWorkbenchImage(true); } /** * This is a private method so that we can use the same code to draw the * experimental setups and the workbench images. * * @param expSetup Whether we're drawing the experimental setup * @return An Image of the relevant Workbench area */ private Image getWorkbenchImage(boolean expSetup) { if (onWorkbench.size() == 0) return null; // we need to figure out the bounding rectangle for the Image int x1 = getWidth(); int y1 = getHeight(); // the defaults are backwards because we want int x2 = 0; // to be able to use Math.min and Math.max int y2 = 0; Vector rects = new Vector(); // pass through the variables for (int i=onWorkbench.size()-1; i>=0; i--) { Rectangle bounds = ((WBVariable)onWorkbench.elementAt(i)).getBounds(); x1 = Math.min(x1, bounds.x); y1 = Math.min(y1, bounds.y); x2 = Math.max(x2, bounds.x+bounds.width); y2 = Math.max(y2, bounds.y+bounds.height); rects.add(bounds.clone()); } // pass through the randomizers for (int i=randomizers.size()-1; i>=0; i--) { Rectangle bounds = ((Randomizer)randomizers.elementAt(i)).getBounds(); x1 = Math.min(x1, bounds.x); y1 = Math.min(y1, bounds.y); x2 = Math.max(x2, bounds.x+bounds.width); y2 = Math.max(y2, bounds.y+bounds.height); rects.add(bounds.clone()); } // pass through the latents, if we're not in experimental setup if (!expSetup) { for (int i=latents.size()-1; i>=0; i--) { Rectangle bounds = ((Latent)latents.elementAt(i)).getBounds(); x1 = Math.min(x1, bounds.x); y1 = Math.min(y1, bounds.y); x2 = Math.max(x2, bounds.x+bounds.width); y2 = Math.max(y2, bounds.y+bounds.height); rects.add(bounds.clone()); } } Point origin = new Point(x1-1, y1-1); // start the scaling...first, translate all rects so origin=(0,0) int[][] shift_len = new int[rects.size()][2]; for (int i=rects.size()-1; i>=0; i--) { Rectangle r = (Rectangle)rects.elementAt(i); r.translate(-origin.x, -origin.y); shift_len[i][0] = -r.x; shift_len[i][1] = -r.y; } // now, determine how much we can bring in the variables int width = x2-x1+2; int height = y2-y1+2; int n = 1; double mult = 1.0; int MIN_SHIFT = 5; int MIN_DIST = 40; while ((width/(1< MIN_SHIFT) && (height/(1< MIN_SHIFT)) { // first, proportionally translate all of the rects in for (int i=rects.size()-1; i>=0; i--) { Rectangle r = (Rectangle)rects.elementAt(i); r.translate(shift_len[i][0]/(1<0; i--) { for (int j=i-1; j>=0; j--) { Rectangle r1 = (Rectangle)rects.elementAt(i); Rectangle r2 = (Rectangle)rects.elementAt(j); Rectangle exR1 = new Rectangle(r1.x-MIN_DIST/2, r1.y-MIN_DIST/2, r1.width+MIN_DIST, r1.height+MIN_DIST); Rectangle exR2 = new Rectangle(r2.x-MIN_DIST/2, r2.y-MIN_DIST/2, r2.width+MIN_DIST, r2.height+MIN_DIST); if (exR1.intersects(exR2)) { tooClose = true; break; } } if (tooClose) break; } // if any were too close, then translate back if (tooClose) { for (int i=rects.size()-1; i>=0; i--) { Rectangle r = (Rectangle)rects.elementAt(i); r.translate(-shift_len[i][0]/(1<=0; i--) { Rectangle r = (Rectangle)rects.elementAt(i); xMax = Math.max(xMax, r.x+r.width); yMax = Math.max(yMax, r.y+r.height); } width = xMax+1; height = yMax+1; // now, create the new Image, and pass the Graphics context to the ui Image benchIm = createImage(width, height); Graphics g = benchIm.getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, width, height); ((WorkbenchUI)ui).drawWorkbenchImage(g, origin, mult, expSetup, this); return benchIm; } /** * @return A deep clone of the edges vector */ public Vector getClonedEdgesVector() { Vector cl = new Vector(edges.size()); for (int i=0; itrue, since the Workbench is opaque */ public boolean isOpaque() { return true; } /** * @return The margin around the inside edge of the workbench */ public Insets getWorkbenchMargin() { return WB_MARGIN; } /** * @return The margin around the inside edge of the variable bin */ public Insets getBinMargin() { return BIN_MARGIN; } /** * @return The width of the divider between the workbench and variable bin */ public int getBinDividerWidth() { return BIN_DIVIDER_WIDTH; } /** * @return The text label for the variable bin */ public String getBinLabel() { return BIN_LABEL; } /** * @return The text label for the workbench */ public String getWorkbenchLabel() { return WORKBENCH_LABEL; } /** * @param index The index of the WBVariable * @param icon The icon for the WBVariable */ public void setVariableIcon(int index, Icon icon) { variables[index].setNameIcon(icon); } /** * @param name The name of the WBVariable * @param icon The icon for the WBVariable */ public void setVariableIcon(String name, Icon icon) { for (int i=0; i insets.left+size.width) ? insets.left+size.width-oldBounds.width : oldBounds.x; oldBounds.y = (oldBounds.y+oldBounds.height > insets.top+size.height-BIN_HEIGHT) ? insets.top+size.height-BIN_HEIGHT-oldBounds.height : oldBounds.y; variables[i].setBounds(oldBounds); } // otherwise, we need to place it properly in the bin else { variables[i].setBounds(bin_rect.x+BIN_MARGIN.left+var_width*i, bin_rect.y+BIN_MARGIN.top, var_width-VARIABLE_SEPARATION, variables[0].getPreferredSize().height); } } // lay out the randomizers for (int i=randomizers.size()-1; i>=0; i--) { Rectangle oldBounds = ((Randomizer)randomizers.elementAt(i)).getBounds(); oldBounds.x = (oldBounds.x < insets.left+WB_MARGIN.left) ? insets.left+WB_MARGIN.left : oldBounds.x; oldBounds.y = (oldBounds.y < insets.top+WB_MARGIN.top) ? insets.top+WB_MARGIN.top : oldBounds.y; oldBounds.x = (oldBounds.x+oldBounds.width > insets.left+size.width) ? insets.left+size.width-oldBounds.width : oldBounds.x; oldBounds.y = (oldBounds.y+oldBounds.height > insets.top+size.height-BIN_HEIGHT) ? insets.top+size.height-BIN_HEIGHT-oldBounds.height : oldBounds.y; ((Randomizer)randomizers.elementAt(i)).setBounds(oldBounds); } // lay out the latents for (int i=latents.size()-1; i>=0; i--) { Rectangle oldBounds = ((Latent)latents.elementAt(i)).getBounds(); oldBounds.x = (oldBounds.x < insets.left+WB_MARGIN.left) ? insets.left+WB_MARGIN.left : oldBounds.x; oldBounds.y = (oldBounds.y < insets.top+WB_MARGIN.top) ? insets.top+WB_MARGIN.top : oldBounds.y; oldBounds.x = (oldBounds.x+oldBounds.width > insets.left+size.width) ? insets.left+size.width-oldBounds.width : oldBounds.x; oldBounds.y = (oldBounds.y+oldBounds.height > insets.top+size.height-BIN_HEIGHT) ? insets.top+size.height-BIN_HEIGHT-oldBounds.height : oldBounds.y; ((Latent)latents.elementAt(i)).setBounds(oldBounds); } } /** * @param parent The parent of the Workbench * @return The preferred size of the Workbench */ public Dimension preferredLayoutSize(Container parent) { return getPreferredSize(); } /** * @param parent The parent of the Workbench * @return The minimum size of the Workbench */ public Dimension minimumLayoutSize(Container parent) { return getMinimumSize(); } /** * Currently does nothing. * @param name The name of the Component * @param comp The Component to be added */ public void addLayoutComponent(String name, Component comp) { ; } /** * Currently does nothing * @param comp The Component to remove */ public void removeLayoutComponent(Component comp) { ; } // Model and WBVariable accessor methods /** * @return A Vector of the WorkbenchEdges */ public Vector getEdges() { return edges; } /** * @return A Vector of the Latents */ public Vector getLatents() { return latents; } /** * @return A Vector of the Randomizers */ public Vector getRandomizers() { return randomizers; } /** * @return The user-constructed TetradGraph, with edges into locked and * randomized variables removed */ public TetradGraph getUserManipulatedTetradGraph() { return applySetupToTetradGraph( (TetradGraph)model.getTetradGraph().clone()); } /** * @return The current manipulated DiGraph for the underlying BayesNet. */ public TetradGraph getTrueManipulatedTetradGraph() { TetradGraph trueGraph = (TetradGraph)model.getBayesNet().getGraph(); return applySetupToTetradGraph((TetradGraph)trueGraph.clone()); } /** * Applies the current experimental setup to the given TetradGraph. * @param tg The TetradGraph to be manipulated * @return The adjusted TetradGraph */ private TetradGraph applySetupToTetradGraph(TetradGraph tg) { for (int i=0; iindex-th WBVariable */ public WBVariable getVariable(int index) { return variables[index]; } /** * @param newVars The new array of WBVariables */ public void setVariables(WBVariable[] newVars) { if (newVars != variables) { if (newVars == null) newVars = createVariables(); variables = newVars; firePropertyChange("allvariables", null, null); } } /** * @param variable The new WBVariable * @param index The index of the new WBVariable */ public void setVariable(WBVariable variable, int index) { if (variable != variables[index]) { if (variable == null) variable = new WBVariable(); String oldName = variables[index].getName(); variables[index] = variable; firePropertyChange("variable"+index, oldName, variable.getName()); } } /** * @return The current Workbench mode */ public int getMode() { return model.getMode(); } /** * @param newMode The new Workbench mode */ public void setMode(int newMode) { if (newMode != model.getMode()) { int oldMode = model.getMode(); model.setMode(newMode); // deal with the mode switching changes if (selected != null) { if (selected instanceof WorkbenchEdge) ((WorkbenchEdge)selected).setSelected(false); else if (selected instanceof Randomizer) ((Randomizer)selected).setSelected(false); selected = null; } boolean b = (newMode == HYPOTHESIZE_PREDICT_MODE); for (int i=edges.size()-1; i>=0; i--) { ((WorkbenchEdge)edges.elementAt(i)).setEnabled(b); } for (int i=0; i=0; i--) { ((Latent)latents.elementAt(i)).setExperimenting(!(b)); } for (int i=randomizers.size()-1; i>=0; i--) { ((Randomizer)randomizers.elementAt(i)).setExperimenting(!(b)); } repaint(); firePropertyChange("mode", oldMode, newMode); } } /** * @return The current Workbench tool */ public int getTool() { return model.getTool(); } /** * @param newTool The new Workbench tool */ public void setTool(int newTool) { if (newTool != model.getTool()) { int oldTool = model.getTool(); // there might be something selected if (selected != null) { if (selected instanceof WorkbenchEdge) ((WorkbenchEdge)selected).setSelected(false); else if (selected instanceof Randomizer) ((Randomizer)selected).setSelected(false); selected = null; repaint(); } model.setTool(newTool); firePropertyChange("tool", oldTool, newTool); } } // UI methods /** * @param ui The WorkbenchUI to attach to this Workbench */ public void setUI(WorkbenchUI ui) { super.setUI(ui); } /** * Sets up the UI appropriately */ public void updateUI() { setUI((WorkbenchUI)UIManager.getUI(this)); invalidate(); } /** * @return The String identifying the UI for this Class */ public String getUIClassID() { return UIClassID; } // Helper methods /** * Creates and initializes the WBVariables using the underlying BayesNet * @return The array of WBVariables to use */ private WBVariable[] createVariables() { BayesNetIM bn = model.getBayesNet(); WBVariable[] retVars = new WBVariable[bn.getGraph().getNumNodes()]; Iterator it = model.getBayesNet().nodeIterator(); int i=0; while (it.hasNext()) { Node node = (Node)it.next(); Vector vals = model.getBayesNet().getValue(node); String[] valArray = new String[vals.size()]; for (int j=0; j