fiore@0: /* fiore@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool fiore@0: fiore@0: Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com) fiore@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@0: fiore@0: This program is free software: you can redistribute it and/or modify fiore@0: it under the terms of the GNU General Public License as published by fiore@0: the Free Software Foundation, either version 3 of the License, or fiore@0: (at your option) any later version. fiore@0: fiore@0: This program is distributed in the hope that it will be useful, fiore@0: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@0: GNU General Public License for more details. fiore@0: fiore@0: You should have received a copy of the GNU General Public License fiore@0: along with this program. If not, see . fiore@0: */ fiore@0: fiore@0: package uk.ac.qmul.eecs.ccmi.gui; fiore@0: fiore@0: import java.awt.Color; fiore@0: import java.awt.Dimension; fiore@0: import java.awt.Graphics; fiore@0: import java.awt.Graphics2D; fiore@0: import java.awt.event.ActionEvent; fiore@0: import java.awt.event.InputEvent; fiore@0: import java.awt.event.KeyEvent; fiore@0: import java.awt.event.MouseAdapter; fiore@0: import java.awt.event.MouseEvent; fiore@0: import java.awt.event.MouseMotionAdapter; fiore@0: import java.awt.geom.Point2D; fiore@0: import java.awt.geom.Rectangle2D; fiore@0: import java.util.ArrayList; fiore@0: import java.util.HashSet; fiore@0: import java.util.Iterator; fiore@0: import java.util.LinkedList; fiore@0: import java.util.List; fiore@0: import java.util.ResourceBundle; fiore@0: import java.util.Set; fiore@0: fiore@0: import javax.swing.AbstractAction; fiore@0: import javax.swing.JOptionPane; fiore@0: import javax.swing.JPanel; fiore@0: import javax.swing.KeyStroke; fiore@0: fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; fiore@0: import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; fiore@0: fiore@0: /** fiore@0: * A panel to draw a graph fiore@0: */ fiore@0: @SuppressWarnings("serial") fiore@0: public class GraphPanel extends JPanel{ fiore@0: /** fiore@0: * Constructs a graph. fiore@0: * @param aDiagram a diagram to paint in the graph fiore@0: * @param a aToolbar a toolbar containing the node and edges prototypes for creating fiore@0: * elements in the graph. fiore@0: */ fiore@0: fiore@0: public GraphPanel(Diagram aDiagram, GraphToolbar aToolbar) { fiore@0: grid = new Grid(); fiore@0: gridSize = GRID; fiore@0: grid.setGrid((int) gridSize, (int) gridSize); fiore@0: zoom = 1; fiore@0: toolbar = aToolbar; fiore@0: setBackground(Color.WHITE); fiore@0: wasMoving = false; fiore@0: minBounds = null; fiore@0: fiore@0: this.model = aDiagram.getCollectionModel(); fiore@0: synchronized(model.getMonitor()){ fiore@0: edges = new LinkedList(model.getEdges()); fiore@0: nodes = new LinkedList(model.getNodes()); fiore@0: } fiore@0: setModelUpdater(aDiagram.getModelUpdater()); fiore@0: fiore@0: selectedElements = new HashSet(); fiore@0: moveLockedElements = new HashSet(); fiore@0: fiore@0: toolbar.addEdgeCreatedListener(new innerEdgeListener()); fiore@0: fiore@0: getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0),"delete"); fiore@0: getActionMap().put("delete", new AbstractAction(){ fiore@0: @Override fiore@0: public void actionPerformed(ActionEvent evt) { fiore@0: /* nothing selected DELETE key has no effect */ fiore@0: if(selectedElements.isEmpty()) fiore@0: return; fiore@0: /* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems */ fiore@0: HashSet iterationSet = new HashSet(selectedElements); fiore@0: HashSetalreadyLockedElements = new HashSet(); fiore@0: /* check which, of the selected elements, can be deleted and which ones are currently held by * fiore@0: * other clients. If an element is locked it's removed from the list and put into a separated set */ fiore@0: for(Iterator itr=iterationSet.iterator(); itr.hasNext();){ fiore@0: DiagramElement selected = itr.next(); fiore@0: if(!modelUpdater.getLock(selected, Lock.DELETE)){ fiore@0: itr.remove(); fiore@0: alreadyLockedElements.add(selected); fiore@0: } fiore@0: } fiore@0: ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName()); fiore@0: /* all the elements are locked by other clients */ fiore@0: if(iterationSet.isEmpty()){ fiore@0: iLog("Could not get lock on any selected element for deletion",""); fiore@0: JOptionPane.showMessageDialog( fiore@0: JOptionPane.getFrameForComponent(GraphPanel.this), fiore@0: alreadyLockedElements.size() == 1 ? // singular vs plural fiore@0: resources.getString("dialog.lock_failure.delete") : fiore@0: resources.getString("dialog.lock_failure.deletes")); fiore@0: return; fiore@0: } fiore@0: fiore@0: String warning = ""; fiore@0: if(!alreadyLockedElements.isEmpty()){ fiore@0: StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning")); fiore@0: for(DiagramElement alreadyLocked : alreadyLockedElements) fiore@0: builder.append(alreadyLocked.getName()).append(' '); fiore@0: warning = builder.append('\n').toString(); fiore@0: iLog("Could not get lock on some selected element for deletion",warning); fiore@0: } fiore@0: fiore@0: iLog("open delete dialog",warning); fiore@0: int answer = JOptionPane.showConfirmDialog( fiore@0: JOptionPane.getFrameForComponent(GraphPanel.this), fiore@0: warning+resources.getString("dialog.confirm.deletions"), fiore@0: resources.getString("dialog.confirm.title"), fiore@0: SpeechOptionPane.YES_NO_OPTION); fiore@0: if(answer == JOptionPane.YES_OPTION){ fiore@0: /* the user chose to delete the elements, proceed (locks * fiore@0: * will be automatically removed upon deletion by the server ) */ fiore@0: for(DiagramElement selected : iterationSet) fiore@0: modelUpdater.takeOutFromCollection(selected); fiore@0: }else{ fiore@0: /* the user chose not to delete the elements, release the acquired locks */ fiore@0: for(DiagramElement selected : iterationSet){ fiore@0: /* if it's a node all its attached edges were locked as well */ fiore@0: /*if(selected instanceof Node){ DONE IN THE SERVER fiore@0: Node n = (Node)selected; fiore@0: for(int i=0; i iterator = selectedElements.iterator(); fiore@0: while(iterator.hasNext()){ fiore@0: DiagramElement element = iterator.next(); fiore@0: if(modelUpdater.getLock(element, Lock.MOVE)){ fiore@0: moveLockedElements.add(element); fiore@0: }else{ fiore@0: iLog("Could not get move lock for element",DiagramElement.toLogString(element)); fiore@0: iterator.remove(); fiore@0: } fiore@0: } fiore@0: iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY()); fiore@0: } fiore@0: fiore@0: for (DiagramElement selected : selectedElements){ fiore@0: if(selected instanceof Node) fiore@0: modelUpdater.translate((Node)selected, lastMousePoint, dx, dy); fiore@0: else fiore@0: modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy); fiore@0: } fiore@0: } else if(dragMode == DRAG_EDGE){ fiore@0: if(!wasMoving){ fiore@0: wasMoving = true; fiore@0: if(modelUpdater.getLock(lastSelected, Lock.MOVE)) fiore@0: moveLockedEdge = (Edge)lastSelected; fiore@0: else fiore@0: iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected)); fiore@0: iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY()); fiore@0: } fiore@0: if(moveLockedEdge != null) fiore@0: modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY())); fiore@0: } else if (dragMode == DRAG_LASSO){ fiore@0: double x1 = mouseDownPoint.getX(); fiore@0: double y1 = mouseDownPoint.getY(); fiore@0: double x2 = mousePoint.getX(); fiore@0: double y2 = mousePoint.getY(); fiore@0: Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), fiore@0: Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2)); fiore@0: for (Node n : GraphPanel.this.nodes){ fiore@0: Rectangle2D bounds = n.getBounds(); fiore@0: if(!isCtrl && !lasso.contains(bounds)){ fiore@0: removeElementFromSelection(n); fiore@0: } fiore@0: else if (lasso.contains(bounds)){ fiore@0: addElementToSelection(n,true); fiore@0: } fiore@0: } fiore@0: if(selectedElements.size() != oldLazoSelectedNum){ fiore@0: StringBuilder builder = new StringBuilder(); fiore@0: for(DiagramElement de : selectedElements) fiore@0: builder.append(DiagramElement.toLogString(de)).append(' '); fiore@0: iLog("added by lazo",builder.toString()); fiore@0: } fiore@0: oldLazoSelectedNum = selectedElements.size(); fiore@0: } fiore@0: lastMousePoint = mousePoint; fiore@0: } fiore@0: }); fiore@0: } fiore@0: /* --------------------------------------------------------------------------- */ fiore@0: fiore@0: @Override fiore@0: public void paintComponent(Graphics g){ fiore@0: super.paintComponent(g); fiore@0: paintGraph(g); fiore@0: } fiore@0: fiore@0: public void paintGraph(Graphics g){ fiore@0: Graphics2D g2 = (Graphics2D) g; fiore@0: g2.translate(-minX, -minY); fiore@0: g2.scale(zoom, zoom); fiore@0: Rectangle2D bounds = getBounds(); fiore@0: Rectangle2D graphBounds = getGraphBounds(); fiore@0: if (!hideGrid) grid.draw(g2, new Rectangle2D.Double(minX, minY, fiore@0: Math.max(bounds.getMaxX() / zoom, graphBounds.getMaxX()), fiore@0: Math.max(bounds.getMaxY() / zoom, graphBounds.getMaxY()))); fiore@0: fiore@0: /* draw nodes and edges */ fiore@0: for (Edge e : edges) fiore@0: e.draw(g2); fiore@0: for (Node n : nodes) fiore@0: n.draw(g2); fiore@0: fiore@0: for(DiagramElement selected : selectedElements){ fiore@0: if (selected instanceof Node){ fiore@0: Rectangle2D grabberBounds = ((Node) selected).getBounds(); fiore@0: drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMinY()); fiore@0: drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMaxY()); fiore@0: drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMinY()); fiore@0: drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMaxY()); fiore@0: } fiore@0: else if (selected instanceof Edge){ fiore@0: for(Point2D p : ((Edge)selected).getConnectionPoints()) fiore@0: drawGrabber(g2, p.getX(), p.getY()); fiore@0: } fiore@0: } fiore@0: fiore@0: if (dragMode == DRAG_LASSO){ fiore@0: Color oldColor = g2.getColor(); fiore@0: g2.setColor(GRABBER_COLOR); fiore@0: double x1 = mouseDownPoint.getX(); fiore@0: double y1 = mouseDownPoint.getY(); fiore@0: double x2 = lastMousePoint.getX(); fiore@0: double y2 = lastMousePoint.getY(); fiore@0: Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), fiore@0: Math.min(y1, y2), Math.abs(x1 - x2) , Math.abs(y1 - y2)); fiore@0: g2.draw(lasso); fiore@0: g2.setColor(oldColor); fiore@0: repaint(); fiore@0: } fiore@0: } fiore@0: fiore@0: /** fiore@0: * Draws a single "grabber", a filled square fiore@0: * @param g2 the graphics context fiore@0: * @param x the x coordinate of the center of the grabber fiore@0: * @param y the y coordinate of the center of the grabber fiore@0: */ fiore@0: static void drawGrabber(Graphics2D g2, double x, double y){ fiore@0: final int SIZE = 5; fiore@0: Color oldColor = g2.getColor(); fiore@0: g2.setColor(GRABBER_COLOR); fiore@0: g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE)); fiore@0: g2.setColor(oldColor); fiore@0: } fiore@0: fiore@0: @Override fiore@0: public Dimension getPreferredSize(){ fiore@0: Rectangle2D graphBounds = getGraphBounds(); fiore@0: return new Dimension((int) (zoom * graphBounds.getMaxX()), fiore@0: (int) (zoom * graphBounds.getMaxY())); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Changes the zoom of this panel. The zoom is 1 by default and is multiplied fiore@0: * by sqrt(2) for each positive stem or divided by sqrt(2) for each negative fiore@0: * step. fiore@0: * @param steps the number of steps by which to change the zoom. A positive fiore@0: * value zooms in, a negative value zooms out. fiore@0: */ fiore@0: public void changeZoom(int steps){ fiore@0: final double FACTOR = Math.sqrt(2); fiore@0: for (int i = 1; i <= steps; i++) fiore@0: zoom *= FACTOR; fiore@0: for (int i = 1; i <= -steps; i++) fiore@0: zoom /= FACTOR; fiore@0: revalidate(); fiore@0: repaint(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Changes the grid size of this panel. The zoom is 10 by default and is fiore@0: * multiplied by sqrt(2) for each positive stem or divided by sqrt(2) for fiore@0: * each negative step. fiore@0: * @param steps the number of steps by which to change the zoom. A positive fiore@0: * value zooms in, a negative value zooms out. fiore@0: */ fiore@0: public void changeGridSize(int steps){ fiore@0: final double FACTOR = Math.sqrt(2); fiore@0: for (int i = 1; i <= steps; i++) fiore@0: gridSize *= FACTOR; fiore@0: for (int i = 1; i <= -steps; i++) fiore@0: gridSize /= FACTOR; fiore@0: grid.setGrid((int) gridSize, (int) gridSize); fiore@0: repaint(); fiore@0: } fiore@0: fiore@0: private void addElementToSelection(DiagramElement element, boolean byLasso){ fiore@0: /* if not added to selected elements by including it in the lasso, the element is moved * fiore@0: * to the back of the collection so that it will be painted on the top on the next refresh */ fiore@0: if(!byLasso) fiore@0: if(element instanceof Node){ fiore@0: /* put the node in the last position so that it will be drawn on the top */ fiore@0: nodes.remove(element); fiore@0: nodes.add((Node)element); fiore@0: iLog("addeded node to selected",DiagramElement.toLogString(element)); fiore@0: }else{ fiore@0: /* put the edge in the last position so that it will be drawn on the top */ fiore@0: edges.remove(element); fiore@0: edges.add((Edge)element); fiore@0: iLog("addeded edge to selected",DiagramElement.toLogString(element)); fiore@0: } fiore@0: if(selectedElements.contains(element)){ fiore@0: lastSelected = element; fiore@0: return; fiore@0: } fiore@0: lastSelected = element; fiore@0: selectedElements.add(element); fiore@0: return; fiore@0: } fiore@0: fiore@0: private void removeElementFromSelection(DiagramElement element){ fiore@0: if (element == lastSelected){ fiore@0: lastSelected = null; fiore@0: } fiore@0: if(selectedElements.contains(element)){ fiore@0: selectedElements.remove(element); fiore@0: } fiore@0: } fiore@0: fiore@0: private void setElementSelected(DiagramElement element){ fiore@0: /* clear the selection */ fiore@0: selectedElements.clear(); fiore@0: lastSelected = element; fiore@0: selectedElements.add(element); fiore@0: if(element instanceof Node){ fiore@0: nodes.remove(element); fiore@0: nodes.add((Node)element); fiore@0: iLog("node selected",DiagramElement.toLogString(element)); fiore@0: }else{ fiore@0: edges.remove(element); fiore@0: edges.add((Edge)element); fiore@0: iLog("edge selected",DiagramElement.toLogString(element)); fiore@0: } fiore@0: } fiore@0: fiore@0: private void clearSelection(){ fiore@0: iLog("selection cleared",""); fiore@0: selectedElements.clear(); fiore@0: lastSelected = null; fiore@0: } fiore@0: fiore@0: /** fiore@0: * Sets the value of the hideGrid property fiore@0: * @param newValue true if the grid is being hidden fiore@0: */ fiore@0: public void setHideGrid(boolean newValue){ fiore@0: hideGrid = newValue; fiore@0: repaint(); fiore@0: } fiore@0: fiore@0: /** fiore@0: * Gets the value of the hideGrid property fiore@0: * @return true if the grid is being hidden fiore@0: */ fiore@0: public boolean getHideGrid(){ fiore@0: return hideGrid; fiore@0: } fiore@0: fiore@0: /** fiore@0: Gets the smallest rectangle enclosing the graph fiore@0: @return the bounding rectangle fiore@0: */ fiore@0: public Rectangle2D getMinBounds() { return minBounds; } fiore@0: public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; } fiore@0: fiore@0: public Rectangle2D getGraphBounds(){ fiore@0: Rectangle2D r = minBounds; fiore@0: for (Node n : nodes){ fiore@0: Rectangle2D b = n.getBounds(); fiore@0: if (r == null) r = b; fiore@0: else r.add(b); fiore@0: } fiore@0: for (Edge e : edges){ fiore@0: r.add(e.getBounds()); fiore@0: } fiore@0: return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(), fiore@0: r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY)); fiore@0: } fiore@0: fiore@0: public void setModelUpdater(DiagramModelUpdater modelUpdater){ fiore@0: this.modelUpdater = modelUpdater; fiore@0: } fiore@0: fiore@0: private void iLog(String action,String args){ fiore@0: InteractionLog.log("GRAPH",action,args); fiore@0: } fiore@0: fiore@0: private void checkBounds(DiagramElement de, boolean wasRemoved){ fiore@0: GraphElement ge; fiore@0: if(de instanceof Node) fiore@0: ge = (Node)de; fiore@0: else fiore@0: ge = (Edge)de; fiore@0: if(wasRemoved){ fiore@0: if(ge == top){ fiore@0: top = null; fiore@0: minY = 0; fiore@0: Rectangle2D bounds; fiore@0: for(Edge e : edges){ fiore@0: bounds = e.getBounds(); fiore@0: if(bounds.getY() < minY){ fiore@0: top = e; fiore@0: minY = bounds.getY(); fiore@0: } fiore@0: } fiore@0: for(Node n : nodes){ fiore@0: bounds = n.getBounds(); fiore@0: if(bounds.getY() < minY){ fiore@0: top = n; fiore@0: minY = bounds.getY(); fiore@0: } fiore@0: } fiore@0: } fiore@0: if(ge == left){ fiore@0: minX = 0; fiore@0: left = null; fiore@0: synchronized(model.getMonitor()){ fiore@0: Rectangle2D bounds; fiore@0: for(Edge e : model.getEdges()){ fiore@0: bounds = e.getBounds(); fiore@0: if(bounds.getX() < minX){ fiore@0: left = e; fiore@0: minX = bounds.getX(); fiore@0: } fiore@0: } fiore@0: for(Node n : model.getNodes()){ fiore@0: bounds = n.getBounds(); fiore@0: if(bounds.getX() < minX){ fiore@0: left = n; fiore@0: minX = bounds.getX(); fiore@0: } fiore@0: } fiore@0: } fiore@0: } fiore@0: }else{ // was added or translated fiore@0: Rectangle2D bounds = ge.getBounds(); fiore@0: if(top == null){ fiore@0: if(bounds.getY() < 0){ fiore@0: top = ge; fiore@0: minY = bounds.getY(); fiore@0: } fiore@0: }else if(ge == top){ //the top-most has been translated recalculate the new top-most, as itf it were deleted fiore@0: checkBounds(de, true); fiore@0: }else if(bounds.getY() < top.getBounds().getY()){ fiore@0: top = ge; fiore@0: minY = bounds.getY(); fiore@0: } fiore@0: fiore@0: if(left == null){ fiore@0: if(bounds.getX() < 0){ fiore@0: left = ge; fiore@0: minX = bounds.getX(); fiore@0: } fiore@0: }else if(ge == left){ fiore@0: checkBounds(de,true);//the left-most has been translated recalculate the new left-most, as if it were deleted fiore@0: } fiore@0: else if(bounds.getX() < left.getBounds().getX()){ fiore@0: left = ge; fiore@0: minX = bounds.getX(); fiore@0: } fiore@0: } fiore@0: } fiore@0: fiore@0: private class innerEdgeListener implements GraphToolbar.EdgeCreatedListener { fiore@0: @Override fiore@0: public void edgeCreated(Edge e) { fiore@0: ArrayList nodesToConnect = new ArrayList(selectedElements.size()); fiore@0: for(DiagramElement element : selectedElements){ fiore@0: if(element instanceof Node) fiore@0: nodesToConnect.add((Node)element); fiore@0: } fiore@0: try { fiore@0: e.connect(nodesToConnect); fiore@0: modelUpdater.insertInCollection(e); fiore@0: } catch (ConnectNodesException cnEx) { fiore@0: JOptionPane.showMessageDialog(GraphPanel.this, fiore@0: cnEx.getLocalizedMessage(), fiore@0: ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"), fiore@0: JOptionPane.ERROR_MESSAGE); fiore@0: iLog("insert edge error",cnEx.getMessage()); fiore@0: } fiore@0: } fiore@0: } fiore@0: fiore@0: private List edges; fiore@0: private List nodes; fiore@0: private DiagramModelUpdater modelUpdater; fiore@0: private CollectionModel model; fiore@0: fiore@0: private Grid grid; fiore@0: private GraphToolbar toolbar; fiore@0: private NodePopupMenu nodePopup; fiore@0: private EdgePopupMenu edgePopup; fiore@0: fiore@0: private double zoom; fiore@0: private double gridSize; fiore@0: private boolean hideGrid; fiore@0: private boolean wasMoving; fiore@0: fiore@0: private GraphElement top; fiore@0: private GraphElement left; fiore@0: private double minX; fiore@0: private double minY; fiore@0: fiore@0: private DiagramElement lastSelected; fiore@0: private Edge moveLockedEdge; fiore@0: private Set selectedElements; fiore@0: private Set moveLockedElements; fiore@0: fiore@0: private Point2D lastMousePoint; fiore@0: private Point2D mouseDownPoint; fiore@0: private Rectangle2D minBounds; fiore@0: private int dragMode; fiore@0: fiore@0: private int oldLazoSelectedNum; fiore@0: fiore@0: /* button is not down, mouse motion will habe no effects */ fiore@0: private static final int DRAG_NONE = 0; fiore@0: /* one or more nodes (and eventually some edges) have been selected, mouse motion will result in a translation */ fiore@0: private static final int DRAG_NODE = 1; fiore@0: /* one edge has been selected, mouse motion will result in an edge bending */ fiore@0: private static final int DRAG_EDGE = 2; fiore@0: /* mouse button down but nothing selected, mouse motion will result in a lasso */ fiore@0: private static final int DRAG_LASSO = 3; // multiple selection fiore@0: fiore@0: private static final int GRID = 10; fiore@0: private static final double EDGE_END_MIN_CLICK_DIST = 10; fiore@0: fiore@0: public static final Color GRABBER_COLOR = new Color(0,128,255); fiore@0: }