diff java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java @ 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 9e67171477bc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java	Fri Dec 16 17:35:51 2011 +0000
@@ -0,0 +1,757 @@
+/*  
+ 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.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+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.AbstractAction;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.KeyStroke;
+
+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.DiagramModelTreeNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
+import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
+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());
+
+		getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0),"delete");
+		getActionMap().put("delete", new AbstractAction(){
+			@Override
+			public void actionPerformed(ActionEvent evt) {
+				/* nothing selected DELETE key has no effect */
+				if(selectedElements.isEmpty())
+					return;
+				/* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems  */
+				HashSet<DiagramElement> iterationSet = new HashSet<DiagramElement>(selectedElements); 
+				HashSet<DiagramElement>alreadyLockedElements = new HashSet<DiagramElement>();
+				/* check which, of the selected elements, can be deleted and which ones are currently held by     *
+				 * other clients. If an element is locked it's removed from the list and put into a separated set */
+				for(Iterator<DiagramElement> itr=iterationSet.iterator(); itr.hasNext();){
+					DiagramElement  selected = itr.next();
+					if(!modelUpdater.getLock(selected, Lock.DELETE)){
+						itr.remove();
+						alreadyLockedElements.add(selected);
+					}
+				}
+				ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
+				/* all the elements are locked by other clients */
+				if(iterationSet.isEmpty()){
+					iLog("Could not get lock on any selected element for deletion","");
+					JOptionPane.showMessageDialog(
+							JOptionPane.getFrameForComponent(GraphPanel.this), 
+							alreadyLockedElements.size() == 1 ? // singular vs plural 
+									resources.getString("dialog.lock_failure.delete") :
+										resources.getString("dialog.lock_failure.deletes"));
+					return;
+				}
+
+				String warning = "";
+				if(!alreadyLockedElements.isEmpty()){
+					StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning"));
+					for(DiagramElement alreadyLocked : alreadyLockedElements)
+						builder.append(alreadyLocked.getName()).append(' ');
+					warning = builder.append('\n').toString();
+					iLog("Could not get lock on some selected element for deletion",warning);
+				}
+
+				iLog("open delete dialog",warning);
+				int answer = JOptionPane.showConfirmDialog(
+						JOptionPane.getFrameForComponent(GraphPanel.this),
+						warning+resources.getString("dialog.confirm.deletions"), 
+						resources.getString("dialog.confirm.title"),
+						SpeechOptionPane.YES_NO_OPTION);
+				if(answer == JOptionPane.YES_OPTION){
+					/* the user chose to delete the elements, proceed (locks       *
+					 * will be automatically removed upon deletion by the server ) */
+					for(DiagramElement selected : iterationSet)
+						modelUpdater.takeOutFromCollection(selected);
+				}else{
+					/* the user chose not to delete the elements, release the acquired locks */
+					for(DiagramElement selected : iterationSet){
+						/* if it's a node all its attached edges were locked as well */
+						/*if(selected instanceof Node){ DONE IN THE SERVER
+						Node n = (Node)selected;
+						for(int i=0; i<n.getEdgesNum();i++){
+							modelUpdater.yieldLock(n.getEdgeAt(i), Lock.DELETE);
+						}
+					}*/
+						modelUpdater.yieldLock(selected, Lock.DELETE);
+					}
+					iLog("cancel delete node dialog","");
+				}
+			}});
+
+		/* 	---- 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(model) ){ //FIXME change model into this model source changes 
+					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.nodeRef.equals(element))
+						nodePopup.setVisible(false);
+					nodes.remove(element);
+				}
+				else{
+					if(edgePopup != null && edgePopup.edgeRef.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
+								EdgePopupMenu pop = new EdgePopupMenu(e,GraphPanel.this,modelUpdater);
+								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
+								EdgePopupMenu pop = new EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater);
+								edgePopup = pop;
+								pop.show(GraphPanel.this, event.getX(), event.getY());
+							}
+						}
+					}else if(n != null){
+						NodePopupMenu pop = new NodePopupMenu(n,GraphPanel.this,modelUpdater);
+						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);
+							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());
+						/* 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);
+					}	
+				}
+
+				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){
+						if(wasMoving){
+							iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
+							for(Object element : moveLockedElements){
+								modelUpdater.stopMove((GraphElement)element);
+								modelUpdater.yieldLock((DiagramModelTreeNode)element, Lock.MOVE);
+							}
+							moveLockedElements.clear();
+						}
+					}else{ // instanceof Edge 
+						if(wasMoving){
+							iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
+							if(moveLockedEdge != null){
+								modelUpdater.stopMove(moveLockedEdge);
+								modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE);
+								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(); 
+							if(modelUpdater.getLock(element, Lock.MOVE)){
+								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);
+						else
+							modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy);
+					}
+				} else if(dragMode == DRAG_EDGE){
+					if(!wasMoving){
+						wasMoving = true;
+						if(modelUpdater.getLock(lastSelected, Lock.MOVE))
+							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()));
+				} 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);
+				modelUpdater.insertInCollection(e);
+			} 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 NodePopupMenu nodePopup;
+	private 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);
+}
\ No newline at end of file