f@0: /* f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool f@0: f@0: Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com) f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: f@0: package uk.ac.qmul.eecs.ccmi.gui; f@0: f@0: import java.awt.Color; f@0: import java.awt.Dimension; f@0: import java.awt.Graphics; f@0: import java.awt.Graphics2D; f@0: import java.awt.event.InputEvent; f@0: import java.awt.event.MouseAdapter; f@0: import java.awt.event.MouseEvent; f@0: import java.awt.event.MouseMotionAdapter; f@0: import java.awt.geom.Point2D; f@0: import java.awt.geom.Rectangle2D; f@0: import java.util.ArrayList; f@0: import java.util.HashSet; f@0: import java.util.Iterator; f@0: import java.util.LinkedList; f@0: import java.util.List; f@0: import java.util.ResourceBundle; f@0: import java.util.Set; f@0: f@0: import javax.swing.JOptionPane; f@0: import javax.swing.JPanel; f@0: f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; f@0: import uk.ac.qmul.eecs.ccmi.network.Command; f@0: import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource; f@0: import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; f@0: f@0: /** f@0: * A panel to draw a graph f@0: */ f@0: @SuppressWarnings("serial") f@0: public class GraphPanel extends JPanel{ f@0: /** f@0: * Constructs a graph. f@0: * @param aDiagram a diagram to paint in the graph f@0: * @param aToolbar a toolbar containing the node and edges prototypes for creating f@0: * elements in the graph. f@0: */ f@0: f@0: public GraphPanel(Diagram aDiagram, GraphToolbar aToolbar) { f@0: grid = new Grid(); f@0: gridSize = GRID; f@0: grid.setGrid((int) gridSize, (int) gridSize); f@0: zoom = 1; f@0: toolbar = aToolbar; f@0: setBackground(Color.WHITE); f@0: wasMoving = false; f@0: minBounds = null; f@0: f@0: this.model = aDiagram.getCollectionModel(); f@0: synchronized(model.getMonitor()){ f@0: edges = new LinkedList(model.getEdges()); f@0: nodes = new LinkedList(model.getNodes()); f@0: } f@0: setModelUpdater(aDiagram.getModelUpdater()); f@0: f@0: selectedElements = new HashSet(); f@0: moveLockedElements = new HashSet(); f@0: f@0: toolbar.setEdgeCreatedListener(new innerEdgeListener()); f@0: f@0: /* ---- COLLECTION LISTENER ---- f@0: * Adding a collection listener. This listener reacts at changes in the model f@0: * by any source, and thus the graph itself. Basically it refreshes the graph f@0: * and paints again all the nodes and edges. f@0: */ f@0: model.addCollectionListener(new CollectionListener(){ f@0: @Override f@0: public void elementInserted(final CollectionEvent e) { f@0: DiagramElement element = e.getDiagramElement(); f@0: if(element instanceof Node) f@0: nodes.add((Node)element); f@0: else f@0: edges.add((Edge)element); f@0: checkBounds(element,false); f@0: if(e.getDiagramElement() instanceof Node && e.getSource().equals(DiagramEventSource.GRPH)){ f@0: setElementSelected(e.getDiagramElement()); f@0: dragMode = DRAG_NODE; f@0: } f@0: revalidate(); f@0: repaint(); f@0: } f@0: @Override f@0: public void elementTakenOut(final CollectionEvent e) { f@0: DiagramElement element = e.getDiagramElement(); f@0: if(element instanceof Node){ f@0: if(nodePopup != null && nodePopup.getElement().equals(element)) f@0: nodePopup.setVisible(false); f@0: nodes.remove(element); f@0: } f@0: else{ f@0: if(edgePopup != null && edgePopup.getElement().equals(element)) f@0: edgePopup.setVisible(false); f@0: edges.remove(element); f@0: } f@0: checkBounds(e.getDiagramElement(),true); f@0: removeElementFromSelection(e.getDiagramElement()); f@0: revalidate(); f@0: repaint(); f@0: } f@0: @Override f@0: public void elementChanged(final ElementChangedEvent e) { f@0: /* we changed the position of an element and might need to update the boundaries */ f@0: if(e.getChangeType().equals("stop_move")){ f@0: checkBounds(e.getDiagramElement(),false); f@0: } f@0: revalidate(); f@0: repaint(); f@0: } f@0: }); f@0: /* --------------------------------------------------------------------------- */ f@0: f@0: /* ------------- MOUSE LISTENERS -------------------------------------------- f@0: * For pressed and released mouse click and moved mouse f@0: */ f@0: addMouseListener(new MouseAdapter(){ f@0: @Override f@0: public void mousePressed(MouseEvent event){ f@0: requestFocusInWindow(); f@0: final Point2D mousePoint = new Point2D.Double( f@0: (event.getX()+minX)/zoom, f@0: (event.getY()+minY)/zoom f@0: ); f@0: boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; f@0: Node n = Finder.findNode(mousePoint,nodes); f@0: Edge e = Finder.findEdge(mousePoint,edges); f@0: f@0: Object tool = toolbar.getSelectedTool(); f@0: /* - right click - */ f@0: if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) { f@0: if(n != null){ f@0: CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements); f@0: nodePopup = pop; f@0: pop.show(GraphPanel.this, event.getX(), event.getY()); f@0: }else if(e != null){ f@0: if( e.contains(mousePoint)){ f@0: Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST); f@0: if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item f@0: CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,GraphPanel.this,modelUpdater,selectedElements); f@0: edgePopup = pop; f@0: pop.show(GraphPanel.this, event.getX(), event.getY()); f@0: }else{ // click near an attached nodes, prompt for name change, set end label and select arrow head f@0: CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater,selectedElements); f@0: edgePopup = pop; f@0: pop.show(GraphPanel.this, event.getX(), event.getY()); f@0: } f@0: } f@0: }else { f@0: return; f@0: } f@0: } f@0: f@0: /* - one click && palette == select - */ f@0: else if (tool == null){ f@0: if(n != null){ // node selected f@0: if (isCtrl){ f@0: addElementToSelection(n,false); f@0: }else{ f@0: setElementSelected(n); f@0: } f@0: dragMode = DRAG_NODE; f@0: }else if (e != null){ // edge selected f@0: if (isCtrl){ f@0: addElementToSelection(e,false); f@0: dragMode = DRAG_NODE; f@0: }else{ f@0: setElementSelected(e); f@0: modelUpdater.startMove(e, mousePoint,DiagramEventSource.GRPH); f@0: dragMode = DRAG_EDGE; f@0: } f@0: }else{ // nothing selected : make selection lasso f@0: if (!isCtrl) f@0: clearSelection(); f@0: dragMode = DRAG_LASSO; f@0: } f@0: } f@0: /* - one click && palette == node - */ f@0: else { f@0: /* click on an already existing node = select it*/ f@0: if (n != null){ f@0: if (isCtrl) f@0: addElementToSelection(n,false); f@0: else f@0: setElementSelected(n); f@0: dragMode = DRAG_NODE; f@0: }else{ f@0: Node prototype = (Node) tool; f@0: Node newNode = (Node) prototype.clone(); f@0: Rectangle2D bounds = newNode.getBounds(); f@0: /* perform the translation from the origin */ f@0: newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(), f@0: mousePoint.getY() - bounds.getY(),DiagramEventSource.NONE); f@0: /* log stuff */ f@0: iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId())); f@0: /* insert the node into the model (no lock needed) */ f@0: modelUpdater.insertInCollection(newNode,DiagramEventSource.GRPH); f@0: } f@0: } f@0: f@0: lastMousePoint = mousePoint; f@0: mouseDownPoint = mousePoint; f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void mouseReleased(MouseEvent event){ f@0: final Point2D mousePoint = new Point2D.Double( f@0: (event.getX()+minX)/zoom, f@0: (event.getY()+minY)/zoom f@0: ); f@0: if(lastSelected != null){ f@0: if(lastSelected instanceof Node || selectedElements.size() > 1){ // differentiate between translate and edge bending f@0: if(wasMoving){ f@0: iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY()); f@0: for(Object element : moveLockedElements){ f@0: modelUpdater.stopMove((GraphElement)element,DiagramEventSource.GRPH); f@0: boolean isNode = element instanceof Node; f@0: modelUpdater.yieldLock((DiagramTreeNode)element, f@0: Lock.MOVE, f@0: new DiagramEventActionSource( f@0: DiagramEventSource.GRPH, f@0: isNode ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE, f@0: ((DiagramElement)element).getId(),((DiagramElement)element).getName())); f@0: } f@0: moveLockedElements.clear(); f@0: } f@0: }else{ // instanceof Edge && selectedelements.size() = 1. Bending f@0: if(wasMoving){ f@0: iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY()); f@0: if(moveLockedEdge != null){ f@0: modelUpdater.stopMove(moveLockedEdge,DiagramEventSource.GRPH); f@0: modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE, new DiagramEventActionSource( f@0: DiagramEventSource.GRPH, f@0: Command.Name.BEND, f@0: moveLockedEdge.getId(), f@0: moveLockedEdge.getName())); f@0: moveLockedEdge = null; f@0: } f@0: } f@0: } f@0: } f@0: dragMode = DRAG_NONE; f@0: wasMoving = false; f@0: repaint(); f@0: } f@0: }); f@0: f@0: addMouseMotionListener(new MouseMotionAdapter(){ f@0: public void mouseDragged(MouseEvent event){ f@0: Point2D mousePoint = new Point2D.Double( f@0: (event.getX()+minX)/zoom, f@0: (event.getY()+minY)/zoom f@0: ); f@0: boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; f@0: f@0: if (dragMode == DRAG_NODE){ f@0: /* translate selected nodes (edges as well) */ f@0: double dx = mousePoint.getX() - lastMousePoint.getX(); f@0: double dy = mousePoint.getY() - lastMousePoint.getY(); f@0: if(!wasMoving){ f@0: wasMoving = true; f@0: /* when the motion starts, we need to get the move-lock from the server */ f@0: Iterator iterator = selectedElements.iterator(); f@0: while(iterator.hasNext()){ f@0: DiagramElement element = iterator.next(); f@0: boolean isNode = element instanceof Node; f@0: if(modelUpdater.getLock(element, f@0: Lock.MOVE, f@0: new DiagramEventActionSource( f@0: DiagramEventSource.GRPH, f@0: isNode ? Command.Name.TRANSLATE_NODE : Command.Name.TRANSLATE_EDGE, f@0: element.getId(), f@0: element.getName()))){ f@0: moveLockedElements.add(element); f@0: }else{ f@0: iLog("Could not get move lock for element",DiagramElement.toLogString(element)); f@0: iterator.remove(); f@0: } f@0: } f@0: iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY()); f@0: } f@0: f@0: for (DiagramElement selected : selectedElements){ f@0: if(selected instanceof Node) f@0: modelUpdater.translate((Node)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH); f@0: else f@0: modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH); f@0: } f@0: } else if(dragMode == DRAG_EDGE){ f@0: if(!wasMoving){ f@0: wasMoving = true; f@0: if(modelUpdater.getLock(lastSelected, f@0: Lock.MOVE, f@0: new DiagramEventActionSource( f@0: DiagramEventSource.GRPH, f@0: Command.Name.BEND, f@0: lastSelected.getId(), f@0: lastSelected.getName())) f@0: ){ f@0: moveLockedEdge = (Edge)lastSelected; f@0: }else{ f@0: iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected)); f@0: } f@0: iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY()); f@0: } f@0: if(moveLockedEdge != null) f@0: modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()),DiagramEventSource.GRPH); f@0: } else if (dragMode == DRAG_LASSO){ f@0: double x1 = mouseDownPoint.getX(); f@0: double y1 = mouseDownPoint.getY(); f@0: double x2 = mousePoint.getX(); f@0: double y2 = mousePoint.getY(); f@0: Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), f@0: Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2)); f@0: for (Node n : GraphPanel.this.nodes){ f@0: Rectangle2D bounds = n.getBounds(); f@0: if(!isCtrl && !lasso.contains(bounds)){ f@0: removeElementFromSelection(n); f@0: } f@0: else if (lasso.contains(bounds)){ f@0: addElementToSelection(n,true); f@0: } f@0: } f@0: if(selectedElements.size() != oldLazoSelectedNum){ f@0: StringBuilder builder = new StringBuilder(); f@0: for(DiagramElement de : selectedElements) f@0: builder.append(DiagramElement.toLogString(de)).append(' '); f@0: iLog("added by lazo",builder.toString()); f@0: } f@0: oldLazoSelectedNum = selectedElements.size(); f@0: } f@0: lastMousePoint = mousePoint; f@0: } f@0: }); f@0: } f@0: /* --------------------------------------------------------------------------- */ f@0: f@0: @Override f@0: public void paintComponent(Graphics g){ f@0: super.paintComponent(g); f@0: paintGraph(g); f@0: } f@0: f@0: /** f@0: * Paints the graph on the graphics passed as argument. This function is called f@0: * each time the component is painted. f@0: * f@0: * @see #paintComponent(Graphics) f@0: * @param g the graphics object used to paint this graph f@0: */ f@0: public void paintGraph(Graphics g){ f@0: Graphics2D g2 = (Graphics2D) g; f@0: g2.translate(-minX, -minY); f@0: g2.scale(zoom, zoom); f@0: Rectangle2D bounds = getBounds(); f@0: Rectangle2D graphBounds = getGraphBounds(); f@0: if (!hideGrid) grid.draw(g2, new Rectangle2D.Double(minX, minY, f@0: Math.max(bounds.getMaxX() / zoom, graphBounds.getMaxX()), f@0: Math.max(bounds.getMaxY() / zoom, graphBounds.getMaxY()))); f@0: f@0: /* draw nodes and edges */ f@0: for (Edge e : edges) f@0: e.draw(g2); f@0: for (Node n : nodes) f@0: n.draw(g2); f@0: f@0: for(DiagramElement selected : selectedElements){ f@0: if (selected instanceof Node){ f@0: Rectangle2D grabberBounds = ((Node) selected).getBounds(); f@0: drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMinY()); f@0: drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMaxY()); f@0: drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMinY()); f@0: drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMaxY()); f@0: } f@0: else if (selected instanceof Edge){ f@0: for(Point2D p : ((Edge)selected).getConnectionPoints()) f@0: drawGrabber(g2, p.getX(), p.getY()); f@0: } f@0: } f@0: f@0: if (dragMode == DRAG_LASSO){ f@0: Color oldColor = g2.getColor(); f@0: g2.setColor(GRABBER_COLOR); f@0: double x1 = mouseDownPoint.getX(); f@0: double y1 = mouseDownPoint.getY(); f@0: double x2 = lastMousePoint.getX(); f@0: double y2 = lastMousePoint.getY(); f@0: Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2), f@0: Math.min(y1, y2), Math.abs(x1 - x2) , Math.abs(y1 - y2)); f@0: g2.draw(lasso); f@0: g2.setColor(oldColor); f@0: repaint(); f@0: } f@0: } f@0: f@0: /** f@0: * Draws a single "grabber", a filled square f@0: * @param g2 the graphics context f@0: * @param x the x coordinate of the center of the grabber f@0: * @param y the y coordinate of the center of the grabber f@0: */ f@0: static void drawGrabber(Graphics2D g2, double x, double y){ f@0: final int SIZE = 5; f@0: Color oldColor = g2.getColor(); f@0: g2.setColor(GRABBER_COLOR); f@0: g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE)); f@0: g2.setColor(oldColor); f@0: } f@0: f@0: @Override f@0: public Dimension getPreferredSize(){ f@0: Rectangle2D graphBounds = getGraphBounds(); f@0: return new Dimension((int) (zoom * graphBounds.getMaxX()), f@0: (int) (zoom * graphBounds.getMaxY())); f@0: } f@0: f@0: /** f@0: * Changes the zoom of this panel. The zoom is 1 by default and is multiplied f@0: * by sqrt(2) for each positive stem or divided by sqrt(2) for each negative f@0: * step. f@0: * @param steps the number of steps by which to change the zoom. A positive f@0: * value zooms in, a negative value zooms out. f@0: */ f@0: public void changeZoom(int steps){ f@0: final double FACTOR = Math.sqrt(2); f@0: for (int i = 1; i <= steps; i++) f@0: zoom *= FACTOR; f@0: for (int i = 1; i <= -steps; i++) f@0: zoom /= FACTOR; f@0: revalidate(); f@0: repaint(); f@0: } f@0: f@0: /** f@0: * Changes the grid size of this panel. The zoom is 10 by default and is f@0: * multiplied by sqrt(2) for each positive stem or divided by sqrt(2) for f@0: * each negative step. f@0: * @param steps the number of steps by which to change the zoom. A positive f@0: * value zooms in, a negative value zooms out. f@0: */ f@0: public void changeGridSize(int steps){ f@0: final double FACTOR = Math.sqrt(2); f@0: for (int i = 1; i <= steps; i++) f@0: gridSize *= FACTOR; f@0: for (int i = 1; i <= -steps; i++) f@0: gridSize /= FACTOR; f@0: grid.setGrid((int) gridSize, (int) gridSize); f@0: repaint(); f@0: } f@0: f@0: private void addElementToSelection(DiagramElement element, boolean byLasso){ f@0: /* if not added to selected elements by including it in the lasso, the element is moved * f@0: * to the back of the collection so that it will be painted on the top on the next refresh */ f@0: if(!byLasso) f@0: if(element instanceof Node){ f@0: /* put the node in the last position so that it will be drawn on the top */ f@0: nodes.remove(element); f@0: nodes.add((Node)element); f@0: iLog("addeded node to selected",DiagramElement.toLogString(element)); f@0: }else{ f@0: /* put the edge in the last position so that it will be drawn on the top */ f@0: edges.remove(element); f@0: edges.add((Edge)element); f@0: iLog("addeded edge to selected",DiagramElement.toLogString(element)); f@0: } f@0: if(selectedElements.contains(element)){ f@0: lastSelected = element; f@0: return; f@0: } f@0: lastSelected = element; f@0: selectedElements.add(element); f@0: return; f@0: } f@0: f@0: private void removeElementFromSelection(DiagramElement element){ f@0: if (element == lastSelected){ f@0: lastSelected = null; f@0: } f@0: if(selectedElements.contains(element)){ f@0: selectedElements.remove(element); f@0: } f@0: } f@0: f@0: private void setElementSelected(DiagramElement element){ f@0: /* clear the selection */ f@0: selectedElements.clear(); f@0: lastSelected = element; f@0: selectedElements.add(element); f@0: if(element instanceof Node){ f@0: nodes.remove(element); f@0: nodes.add((Node)element); f@0: iLog("node selected",DiagramElement.toLogString(element)); f@0: }else{ f@0: edges.remove(element); f@0: edges.add((Edge)element); f@0: iLog("edge selected",DiagramElement.toLogString(element)); f@0: } f@0: } f@0: f@0: private void clearSelection(){ f@0: iLog("selection cleared",""); f@0: selectedElements.clear(); f@0: lastSelected = null; f@0: } f@0: f@0: /** f@0: * Sets the value of the hideGrid property f@0: * @param newValue true if the grid is being hidden f@0: */ f@0: public void setHideGrid(boolean newValue){ f@0: hideGrid = newValue; f@0: repaint(); f@0: } f@0: f@0: /** f@0: * Gets the value of the hideGrid property f@0: * @return true if the grid is being hidden f@0: */ f@0: public boolean getHideGrid(){ f@0: return hideGrid; f@0: } f@0: f@0: /** f@0: * Gets the smallest rectangle enclosing the graph f@0: * @return the bounding rectangle f@0: */ f@0: public Rectangle2D getMinBounds() { return minBounds; } f@0: f@0: /** f@0: * Sets the smallest rectangle enclosing the graph f@0: * @param newValue the bounding rectangle f@0: */ f@0: public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; } f@0: f@0: /** f@0: * Returns the smallest rectangle enclosing the graph, that is all the nodes f@0: * and all the edges in the diagram. f@0: * f@0: * @return the bounding rectangle f@0: */ f@0: public Rectangle2D getGraphBounds(){ f@0: Rectangle2D r = minBounds; f@0: for (Node n : nodes){ f@0: Rectangle2D b = n.getBounds(); f@0: if (r == null) r = b; f@0: else r.add(b); f@0: } f@0: for (Edge e : edges){ f@0: r.add(e.getBounds()); f@0: } f@0: return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(), f@0: r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY)); f@0: } f@0: f@0: /** f@0: * Sets the model updater for this graph. The model updater is used f@0: * to make changes to the diagram (e.g. adding, removing, renaming nodes and edges) f@0: * f@0: * @param modelUpdater the model updater for this graph panel f@0: */ f@0: public void setModelUpdater(DiagramModelUpdater modelUpdater){ f@0: this.modelUpdater = modelUpdater; f@0: } f@0: f@0: private void iLog(String action,String args){ f@0: InteractionLog.log("GRAPH",action,args); f@0: } f@0: f@0: private void checkBounds(DiagramElement de, boolean wasRemoved){ f@0: GraphElement ge; f@0: if(de instanceof Node) f@0: ge = (Node)de; f@0: else f@0: ge = (Edge)de; f@0: if(wasRemoved){ f@0: if(ge == top){ f@0: top = null; f@0: minY = 0; f@0: Rectangle2D bounds; f@0: for(Edge e : edges){ f@0: bounds = e.getBounds(); f@0: if(bounds.getY() < minY){ f@0: top = e; f@0: minY = bounds.getY(); f@0: } f@0: } f@0: for(Node n : nodes){ f@0: bounds = n.getBounds(); f@0: if(bounds.getY() < minY){ f@0: top = n; f@0: minY = bounds.getY(); f@0: } f@0: } f@0: } f@0: if(ge == left){ f@0: minX = 0; f@0: left = null; f@0: synchronized(model.getMonitor()){ f@0: Rectangle2D bounds; f@0: for(Edge e : model.getEdges()){ f@0: bounds = e.getBounds(); f@0: if(bounds.getX() < minX){ f@0: left = e; f@0: minX = bounds.getX(); f@0: } f@0: } f@0: for(Node n : model.getNodes()){ f@0: bounds = n.getBounds(); f@0: if(bounds.getX() < minX){ f@0: left = n; f@0: minX = bounds.getX(); f@0: } f@0: } f@0: } f@0: } f@0: }else{ // was added or translated f@0: Rectangle2D bounds = ge.getBounds(); f@0: if(top == null){ f@0: if(bounds.getY() < 0){ f@0: top = ge; f@0: minY = bounds.getY(); f@0: } f@0: }else if(ge == top){ //the top-most has been translated recalculate the new top-most, as itf it were deleted f@0: checkBounds(de, true); f@0: }else if(bounds.getY() < top.getBounds().getY()){ f@0: top = ge; f@0: minY = bounds.getY(); f@0: } f@0: f@0: if(left == null){ f@0: if(bounds.getX() < 0){ f@0: left = ge; f@0: minX = bounds.getX(); f@0: } f@0: }else if(ge == left){ f@0: checkBounds(de,true);//the left-most has been translated recalculate the new left-most, as if it were deleted f@0: } f@0: else if(bounds.getX() < left.getBounds().getX()){ f@0: left = ge; f@0: minX = bounds.getX(); f@0: } f@0: } f@0: } f@0: f@0: private class innerEdgeListener implements GraphToolbar.EdgeCreatedListener { f@0: @Override f@0: public void edgeCreated(Edge e) { f@0: ArrayList nodesToConnect = new ArrayList(selectedElements.size()); f@0: f@0: for(DiagramElement element : selectedElements){ f@0: if(element instanceof Node){ f@0: if(!modelUpdater.getLock(element, f@0: Lock.MUST_EXIST, f@0: new DiagramEventActionSource(DiagramEventSource.GRPH, f@0: Command.Name.SELECT_NODE_FOR_EDGE_CREATION, f@0: element.getId(), f@0: element.getName()))){ f@0: /* unlock the nodes locked so far */ f@0: yieldLocks(nodesToConnect); f@0: /* notify user */ f@0: JOptionPane.showMessageDialog(GraphPanel.this, f@0: ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.lock_failure.no_edge_creation")); f@0: return; f@0: } f@0: nodesToConnect.add((Node)element); f@0: } f@0: } f@0: try { f@0: e.connect(nodesToConnect); f@0: modelUpdater.insertInCollection(e,DiagramEventSource.GRPH); f@0: /* release the must-exist lock on nodes now that the edge is created */ f@0: yieldLocks(nodesToConnect); f@0: } catch (ConnectNodesException cnEx) { f@0: JOptionPane.showMessageDialog(GraphPanel.this, f@0: cnEx.getLocalizedMessage(), f@0: ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"), f@0: JOptionPane.ERROR_MESSAGE); f@0: iLog("insert edge error",cnEx.getMessage()); f@0: } f@0: } f@0: f@0: /* release all locks */ f@0: private void yieldLocks(ArrayList nodesToConnect){ f@0: for(DiagramNode node : nodesToConnect){ f@0: modelUpdater.yieldLock(node, f@0: Lock.MUST_EXIST, f@0: new DiagramEventActionSource( f@0: DiagramEventSource.GRPH, f@0: Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION, f@0: node.getId(), f@0: node.getName()) f@0: ); f@0: } f@0: } f@0: } f@0: f@0: private List edges; f@0: private List nodes; f@0: private DiagramModelUpdater modelUpdater; f@0: private CollectionModel model; f@0: f@0: private Grid grid; f@0: private GraphToolbar toolbar; f@0: private CCmIPopupMenu.NodePopupMenu nodePopup; f@0: private CCmIPopupMenu.EdgePopupMenu edgePopup; f@0: f@0: private double zoom; f@0: private double gridSize; f@0: private boolean hideGrid; f@0: private boolean wasMoving; f@0: f@0: private GraphElement top; f@0: private GraphElement left; f@0: private double minX; f@0: private double minY; f@0: f@0: private DiagramElement lastSelected; f@0: private Edge moveLockedEdge; f@0: private Set selectedElements; f@0: private Set moveLockedElements; f@0: f@0: private Point2D lastMousePoint; f@0: private Point2D mouseDownPoint; f@0: private Rectangle2D minBounds; f@0: private int dragMode; f@0: f@0: private int oldLazoSelectedNum; f@0: f@0: /* button is not down, mouse motion will habe no effects */ f@0: private static final int DRAG_NONE = 0; f@0: /* one or more nodes (and eventually some edges) have been selected, mouse motion will result in a translation */ f@0: private static final int DRAG_NODE = 1; f@0: /* one edge has been selected, mouse motion will result in an edge bending */ f@0: private static final int DRAG_EDGE = 2; f@0: /* mouse button down but nothing selected, mouse motion will result in a lasso */ f@0: private static final int DRAG_LASSO = 3; // multiple selection f@0: f@0: private static final int GRID = 10; f@0: private static final double EDGE_END_MIN_CLICK_DIST = 10; f@0: f@0: public static final Color GRABBER_COLOR = new Color(0,128,255); f@0: }