view java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java @ 3:9e67171477bc

PHANTOM Omni Heptic device release
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Wed, 25 Apr 2012 17:09:09 +0100
parents 9418ab7b7f3f
children d66dd5880081
line wrap: on
line source
/*  
 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool

 Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
 Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.ac.qmul.eecs.ccmi.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;

import javax.swing.JOptionPane;
import javax.swing.JPanel;

import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
import uk.ac.qmul.eecs.ccmi.network.Command;
import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;

/**
 * A panel to draw a graph
 */
@SuppressWarnings("serial")
public class GraphPanel extends JPanel{
	/**
	 * Constructs a graph.
	 * @param aDiagram a diagram to paint in the graph 
	 * @param a aToolbar a toolbar containing the node and edges prototypes for creating 
	 * elements in the graph. 
	 */

	public GraphPanel(Diagram aDiagram, GraphToolbar aToolbar) {
		grid = new Grid();
		gridSize = GRID;
		grid.setGrid((int) gridSize, (int) gridSize);
		zoom = 1;
		toolbar = aToolbar;
		setBackground(Color.WHITE);
		wasMoving = false;
		minBounds = null;

		this.model = aDiagram.getCollectionModel();
		synchronized(model.getMonitor()){
			edges = new LinkedList<Edge>(model.getEdges());
			nodes = new LinkedList<Node>(model.getNodes());
		}
		setModelUpdater(aDiagram.getModelUpdater());

		selectedElements = new HashSet<DiagramElement>();
		moveLockedElements = new HashSet<Object>();

		toolbar.addEdgeCreatedListener(new innerEdgeListener());

		/* 	---- COLLECTION LISTENER ----
		 * Adding a collection listener. This listener reacts at changes in the model 
		 * by any source, and thus the graph itself. Basically it refreshes the graph 
		 * and paints again all the nodes and edges.
		 */
		model.addCollectionListener(new CollectionListener(){
			@Override
			public void elementInserted(final CollectionEvent e) {
				DiagramElement element = e.getDiagramElement(); 
				if(element instanceof Node)
					nodes.add((Node)element);
				else
					edges.add((Edge)element);
				checkBounds(element,false);
				if(e.getDiagramElement() instanceof Node && e.getSource().equals(DiagramEventSource.GRPH)){
					setElementSelected(e.getDiagramElement());
					dragMode = DRAG_NODE;
				}
				revalidate();
				repaint();
			}
			@Override
			public void elementTakenOut(final CollectionEvent e) {
				DiagramElement element = e.getDiagramElement(); 
				if(element instanceof Node){
					if(nodePopup != null && nodePopup.getElement().equals(element))
						nodePopup.setVisible(false);
					nodes.remove(element);
				}
				else{
					if(edgePopup != null && edgePopup.getElement().equals(element))
						edgePopup.setVisible(false);
					edges.remove(element);
				}
				checkBounds(e.getDiagramElement(),true);
				removeElementFromSelection(e.getDiagramElement());
				revalidate();
				repaint();
			}
			@Override
			public void elementChanged(final ElementChangedEvent e) {
				/* we changed the position of an element and might need to update the boundaries */
				if(e.getChangeType().equals("stop_move")){
					checkBounds(e.getDiagramElement(),false);
				}
				revalidate();
				repaint();
			}
		});
		/*  ---------------------------------------------------------------------------   */

		/*  -------------   MOUSE LISTENERS --------------------------------------------        
		 *  For pressed and released mouse click and moved mouse  
		 */    
		addMouseListener(new MouseAdapter(){
			@Override
			public void mousePressed(MouseEvent event){
				requestFocusInWindow();
				final Point2D mousePoint = new Point2D.Double(
						(event.getX()+minX)/zoom,
						(event.getY()+minY)/zoom 
				);
				boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; 
				Node n = Finder.findNode(mousePoint,nodes);
				Edge e = Finder.findEdge(mousePoint,edges);

				Object tool = toolbar.getSelectedTool();
				/* - right click - */
				if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) {
					if(e != null){
						if( e.contains(mousePoint)){
							Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST);
							if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item
								CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,GraphPanel.this,modelUpdater,selectedElements);
								edgePopup = pop;
								pop.show(GraphPanel.this, event.getX(), event.getY());
							}else{ // click near an attached nodes, prompt for name change, set end label and select arrow head
								CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater,selectedElements);
								edgePopup = pop;
								pop.show(GraphPanel.this, event.getX(), event.getY());
							}
						}
					}else if(n != null){
						CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements);
						nodePopup = pop;
						pop.show(GraphPanel.this, event.getX(), event.getY());
					}else
						return;
				}

				/* - one click && palette == select - */
				else if (tool == null){
					if(n != null){ // node selected
						if (isCtrl){
							addElementToSelection(n,false);
						}else{
							setElementSelected(n);
						}
						dragMode = DRAG_NODE;
					}else if (e != null){ // edge selected
						if (isCtrl){
							addElementToSelection(e,false);
							dragMode = DRAG_NODE;
						}else{
							setElementSelected(e);
							modelUpdater.startMove(e, mousePoint,DiagramEventSource.GRPH);
							dragMode = DRAG_EDGE;
						}
					}else{  // nothing selected : make selection lasso
						if (!isCtrl)
							clearSelection();
						dragMode = DRAG_LASSO;
					}
				}
				/* - one click && palette == node - */
				else {
					/* click on an already existing node = select it*/
					if (n != null){
						if (isCtrl)
							addElementToSelection(n,false);
						else 
							setElementSelected(n);
						dragMode = DRAG_NODE;
					}else{
						Node prototype = (Node) tool;
						Node newNode = (Node) prototype.clone();
						Rectangle2D bounds = newNode.getBounds();
						/* perform the translation from the origin */
						newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(), 
								mousePoint.getY() - bounds.getY(),DiagramEventSource.NONE);
						/* log stuff */
						iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId()));
						/* insert the node into the model (no lock needed) */
						modelUpdater.insertInCollection(newNode,DiagramEventSource.GRPH);
					}	
				}

				lastMousePoint = mousePoint;
				mouseDownPoint = mousePoint;
				repaint();
			}

			@Override
			public void mouseReleased(MouseEvent event){
				final Point2D mousePoint = new Point2D.Double(
						(event.getX()+minX)/zoom,
						(event.getY()+minY)/zoom 
				);
				if(lastSelected != null){
					if(lastSelected instanceof Node || selectedElements.size() > 1){ // differentiate between translate and edge bending
						if(wasMoving){
							iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
							for(Object element : moveLockedElements){
								modelUpdater.stopMove((GraphElement)element,DiagramEventSource.GRPH);
								boolean isNode = element instanceof Node;
								modelUpdater.yieldLock((DiagramTreeNode)element, 
										Lock.MOVE,
										new DiagramEventActionSource(
												DiagramEventSource.GRPH, 
												isNode ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
												((DiagramElement)element).getId(),((DiagramElement)element).getName()));
							}
							moveLockedElements.clear();
						}
					}else{ // instanceof Edge  && selectedelements.size() = 1. Bending
						if(wasMoving){
							iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
							if(moveLockedEdge != null){
								modelUpdater.stopMove(moveLockedEdge,DiagramEventSource.GRPH);
								modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE, new DiagramEventActionSource(
										DiagramEventSource.GRPH,
										Command.Name.BEND,
										moveLockedEdge.getId(),
										moveLockedEdge.getName()));
								moveLockedEdge = null;
							}
						}
					}
				}
				dragMode = DRAG_NONE;
				wasMoving = false;
				repaint();
			}
		});

		addMouseMotionListener(new MouseMotionAdapter(){
			public void mouseDragged(MouseEvent event){
				Point2D mousePoint = new Point2D.Double(
						(event.getX()+minX)/zoom,
						(event.getY()+minY)/zoom 
				);
				boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; 

				if (dragMode == DRAG_NODE){               
					/* translate selected nodes (edges as well) */
					double dx = mousePoint.getX() - lastMousePoint.getX();
					double dy = mousePoint.getY() - lastMousePoint.getY();
					if(!wasMoving){
						wasMoving = true;
						/* when the motion starts, we need to get the move-lock from the server */
						Iterator<DiagramElement> iterator = selectedElements.iterator();
						while(iterator.hasNext()){
							DiagramElement element = iterator.next(); 
							boolean isNode = element instanceof Node;
							if(modelUpdater.getLock(element, 
									Lock.MOVE,
									new DiagramEventActionSource(
											DiagramEventSource.GRPH, 
											isNode ? Command.Name.TRANSLATE_NODE : Command.Name.TRANSLATE_EDGE,
											element.getId(),
											element.getName()))){
								moveLockedElements.add(element);
							}else{
								iLog("Could not get move lock for element",DiagramElement.toLogString(element));
								iterator.remove();
							}
						}
						iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY());
					}

					for (DiagramElement selected : selectedElements){
						if(selected instanceof Node)
							modelUpdater.translate((Node)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
						else
							modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
					}
				} else if(dragMode == DRAG_EDGE){
					if(!wasMoving){
						wasMoving = true;
						if(modelUpdater.getLock(lastSelected, 
								Lock.MOVE, 
								new DiagramEventActionSource(
										DiagramEventSource.GRPH, 
										Command.Name.BEND,
										lastSelected.getId(),
										lastSelected.getName()))
						){
							moveLockedEdge = (Edge)lastSelected;
						}else{
							iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected));
						}
						iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY());
					}
					if(moveLockedEdge != null)
						modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()),DiagramEventSource.GRPH);
				} else if (dragMode == DRAG_LASSO){
					double x1 = mouseDownPoint.getX();
					double y1 = mouseDownPoint.getY();
					double x2 = mousePoint.getX();
					double y2 = mousePoint.getY();
					Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), 
							Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
					for (Node n : GraphPanel.this.nodes){
						Rectangle2D bounds = n.getBounds();
						if(!isCtrl && !lasso.contains(bounds)){
							removeElementFromSelection(n);
						}
						else if (lasso.contains(bounds)){
							addElementToSelection(n,true);
						}
					}
					if(selectedElements.size() != oldLazoSelectedNum){
						StringBuilder builder = new StringBuilder();
						for(DiagramElement de : selectedElements)
							builder.append(DiagramElement.toLogString(de)).append(' ');
						iLog("added by lazo",builder.toString());
					}
					oldLazoSelectedNum = selectedElements.size();
				}
				lastMousePoint = mousePoint;
			}
		});
	}
	/*  ---------------------------------------------------------------------------   */

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		paintGraph(g);
	}

	public void paintGraph(Graphics g){
		Graphics2D g2 = (Graphics2D) g;
		g2.translate(-minX, -minY);
		g2.scale(zoom, zoom);
		Rectangle2D bounds = getBounds();
		Rectangle2D graphBounds = getGraphBounds();
		if (!hideGrid) grid.draw(g2, new Rectangle2D.Double(minX, minY, 
				Math.max(bounds.getMaxX() / zoom, graphBounds.getMaxX()), 
				Math.max(bounds.getMaxY() / zoom, graphBounds.getMaxY())));

		/* draw nodes and edges */  
		for (Edge e : edges)
			e.draw(g2);	
		for (Node n : nodes)
			n.draw(g2);

		for(DiagramElement selected : selectedElements){
			if (selected instanceof Node){
				Rectangle2D grabberBounds = ((Node) selected).getBounds();
				drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMinY());
				drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMaxY());
				drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMinY());
				drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMaxY());
			}
			else if (selected instanceof Edge){
				for(Point2D p : ((Edge)selected).getConnectionPoints())
					drawGrabber(g2, p.getX(), p.getY()); 
			}
		}                 

		if (dragMode == DRAG_LASSO){
			Color oldColor = g2.getColor();
			g2.setColor(GRABBER_COLOR);
			double x1 = mouseDownPoint.getX();
			double y1 = mouseDownPoint.getY();
			double x2 = lastMousePoint.getX();
			double y2 = lastMousePoint.getY();
			Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), 
					Math.min(y1, y2), Math.abs(x1 - x2) , Math.abs(y1 - y2));
			g2.draw(lasso);
			g2.setColor(oldColor);
			repaint();
		}    
	}

	/**
	 * Draws a single "grabber", a filled square
	 * @param g2 the graphics context
	 * @param x the x coordinate of the center of the grabber
	 * @param y the y coordinate of the center of the grabber
	 */
	static void drawGrabber(Graphics2D g2, double x, double y){
		final int SIZE = 5;
		Color oldColor = g2.getColor();
		g2.setColor(GRABBER_COLOR);
		g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE));
		g2.setColor(oldColor);
	}

	@Override
	public Dimension getPreferredSize(){
		Rectangle2D graphBounds = getGraphBounds();
		return new Dimension((int) (zoom * graphBounds.getMaxX()),
				(int) (zoom * graphBounds.getMaxY()));
	}

	/**
	 * Changes the zoom of this panel. The zoom is 1 by default and is multiplied
	 * by sqrt(2) for each positive stem or divided by sqrt(2) for each negative
	 * step.
	 * @param steps the number of steps by which to change the zoom. A positive
	 * value zooms in, a negative value zooms out.
	 */
	public void changeZoom(int steps){
		final double FACTOR = Math.sqrt(2);
		for (int i = 1; i <= steps; i++)
			zoom *= FACTOR;
		for (int i = 1; i <= -steps; i++)
			zoom /= FACTOR;
		revalidate();
		repaint();
	}

	/**
	 * Changes the grid size of this panel. The zoom is 10 by default and is
	 * multiplied by sqrt(2) for each positive stem or divided by sqrt(2) for
	 * each negative step.
	 * @param steps the number of steps by which to change the zoom. A positive
	 * value zooms in, a negative value zooms out.
	 */
	public void changeGridSize(int steps){
		final double FACTOR = Math.sqrt(2);
		for (int i = 1; i <= steps; i++)
			gridSize *= FACTOR;
		for (int i = 1; i <= -steps; i++)
			gridSize /= FACTOR;
		grid.setGrid((int) gridSize, (int) gridSize);
		repaint();
	}

	private void addElementToSelection(DiagramElement element, boolean byLasso){
		/* if not added to selected elements by including it in the lasso, the element is moved    *
		 * to the back of the collection so that it will be painted on the top on the next refresh */
		if(!byLasso)
			if(element instanceof Node){
				/* put the node in the last position so that it will be drawn on the top */
				nodes.remove(element);
				nodes.add((Node)element);
				iLog("addeded node to selected",DiagramElement.toLogString(element));
			}else{
				/* put the edge in the last position so that it will be drawn on the top */
				edges.remove(element);
				edges.add((Edge)element);
				iLog("addeded edge to selected",DiagramElement.toLogString(element));
			}
		if(selectedElements.contains(element)){
			lastSelected = element;
			return;
		}
		lastSelected = element;
		selectedElements.add(element);
		return;
	}

	private void removeElementFromSelection(DiagramElement element){
		if (element == lastSelected){
			lastSelected = null;
		}
		if(selectedElements.contains(element)){
			selectedElements.remove(element);
		}
	}

	private void setElementSelected(DiagramElement element){ 
		/* clear the selection */
		selectedElements.clear();
		lastSelected = element;
		selectedElements.add(element);
		if(element instanceof Node){
			nodes.remove(element);
			nodes.add((Node)element);
			iLog("node selected",DiagramElement.toLogString(element));
		}else{
			edges.remove(element);
			edges.add((Edge)element);
			iLog("edge selected",DiagramElement.toLogString(element));
		}
	}

	private void clearSelection(){
		iLog("selection cleared","");
		selectedElements.clear();
		lastSelected = null;
	}

	/**
	 * Sets the value of the hideGrid property
	 * @param newValue true if the grid is being hidden
	 */
	public void setHideGrid(boolean newValue){
		hideGrid = newValue;
		repaint();
	}

	/**
	 * Gets the value of the hideGrid property
	 * @return true if the grid is being hidden
	 */
	public boolean getHideGrid(){
		return hideGrid;
	}

	/**
   Gets the smallest rectangle enclosing the graph
   @return the bounding rectangle
	 */
	public Rectangle2D getMinBounds() { return minBounds; }
	public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; }

	public Rectangle2D getGraphBounds(){
		Rectangle2D r = minBounds;
		for (Node n : nodes){
			Rectangle2D b = n.getBounds();
			if (r == null) r = b;
			else r.add(b);
		}
		for (Edge e : edges){
			r.add(e.getBounds());
		}
		return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(), 
				r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY));
	}

	public void setModelUpdater(DiagramModelUpdater modelUpdater){
		this.modelUpdater = modelUpdater;
	}

	private void iLog(String action,String args){
		InteractionLog.log("GRAPH",action,args);
	}

	private void checkBounds(DiagramElement de, boolean wasRemoved){
		GraphElement ge; 
		if(de instanceof Node)
			ge = (Node)de;
		else 
			ge = (Edge)de;
		if(wasRemoved){
			if(ge == top){
				top = null;
				minY = 0;
				Rectangle2D bounds;
				for(Edge e : edges){
					bounds = e.getBounds(); 
					if(bounds.getY() < minY){
						top = e;
						minY = bounds.getY();
					}
				}
				for(Node n : nodes){
					bounds = n.getBounds();
					if(bounds.getY() < minY){
						top = n;
						minY = bounds.getY();
					}
				}
			}
			if(ge == left){
				minX = 0;
				left = null;
				synchronized(model.getMonitor()){
					Rectangle2D bounds;
					for(Edge e : model.getEdges()){
						bounds = e.getBounds(); 
						if(bounds.getX() < minX){
							left = e;
							minX = bounds.getX();
						}
					}
					for(Node n : model.getNodes()){
						bounds = n.getBounds(); 
						if(bounds.getX() < minX){
							left = n;
							minX = bounds.getX();
						}
					}
				}
			}
		}else{ // was added or translated
			Rectangle2D bounds = ge.getBounds();
		if(top == null){
			if(bounds.getY() < 0){
				top = ge;
				minY = bounds.getY(); 
			}
		}else if(ge == top){ //the top-most has been translated recalculate the new top-most, as itf it were deleted
			checkBounds(de, true);
		}else if(bounds.getY() < top.getBounds().getY()){
			top = ge;
			minY = bounds.getY();
		}

		if(left == null){
			if(bounds.getX() < 0){
				left = ge;
				minX = bounds.getX(); 
			}
		}else if(ge == left){
			checkBounds(de,true);//the left-most has been translated recalculate the new left-most, as if it were deleted
		}
		else if(bounds.getX() < left.getBounds().getX()){
			left = ge;
			minX = bounds.getX();
		}
		}
	}

	private class innerEdgeListener implements GraphToolbar.EdgeCreatedListener {
		@Override
		public void edgeCreated(Edge e) {
			ArrayList<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(selectedElements.size());
			for(DiagramElement element : selectedElements){
				if(element instanceof Node)
					nodesToConnect.add((Node)element);
			}
			try {
				e.connect(nodesToConnect);
				/* perform the command, no lock is needed for inserting */
				modelUpdater.insertInCollection(e,DiagramEventSource.GRPH);
			} catch (ConnectNodesException cnEx) {
				JOptionPane.showMessageDialog(GraphPanel.this,
						cnEx.getLocalizedMessage(),
						ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"),
						JOptionPane.ERROR_MESSAGE);
				iLog("insert edge error",cnEx.getMessage());
			}
		}
	}

	private List<Edge> edges;
	private List<Node> nodes;
	private DiagramModelUpdater modelUpdater; 
	private CollectionModel<Node,Edge> model;

	private Grid grid;
	private GraphToolbar toolbar;
	private CCmIPopupMenu.NodePopupMenu nodePopup;
	private CCmIPopupMenu.EdgePopupMenu edgePopup;

	private double zoom;
	private double gridSize;
	private boolean hideGrid;
	private boolean wasMoving;

	private GraphElement top;
	private GraphElement left;
	private double minX;
	private double minY;

	private DiagramElement lastSelected;
	private Edge moveLockedEdge;
	private Set<DiagramElement> selectedElements;
	private Set<Object> moveLockedElements;

	private Point2D lastMousePoint;
	private Point2D mouseDownPoint;
	private Rectangle2D minBounds;
	private int dragMode;

	private int oldLazoSelectedNum;

	/* button is not down, mouse motion will habe no effects */
	private static final int DRAG_NONE = 0;
	/* one or more nodes (and eventually some edges) have been selected, mouse motion will result in a translation */ 
	private static final int DRAG_NODE = 1;
	/* one edge has been selected, mouse motion will result in an edge bending */ 
	private static final int DRAG_EDGE = 2;  
	/* mouse button down but nothing selected, mouse motion will result in a lasso */
	private static final int DRAG_LASSO = 3; // multiple selection 

	private static final int GRID = 10;
	private static final double EDGE_END_MIN_CLICK_DIST = 10;

	public static final Color GRABBER_COLOR =  new Color(0,128,255);
}