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:
 * 
 *	- ("bayesNet", null, null)
 *	
- ("tetradGraph", null, null)
 *	
- ("allvariables", null, null)
 *	
- ("variable"+index#, String oldName, String newName)
 *	- Values different, unless the new WBVariable has the same name
 *
- ("mode", Integer oldMode, Integer newMode)
 *	
 *	
- ("tool", Integer oldTool, Integer newTool)
 *	
 *	
- ("lock:"+varname, String oldValue, String newValue)
 *	
 *	
- ("randomized:"+varname, Boolean oldState, Boolean newState)
 *	
 *	
- ("onWorkbench:"+varname, Boolean oldState, Boolean newState)
 *	
 * 
- ("edgeadded", String fromName, String toName)
 * 
 * 
- ("edgeremoved", String fromName, String toName)
 * 
 * 
- ("Performed operation", null, null)
 * 
*
 * 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--) {
			// figure out which variable it is
			String colName = tm.getColumnName(i);
			int j; for (j=0; j=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