annotate java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
rev   line source
f@0 1 /*
f@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0 3
f@0 4 Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
f@0 5 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0 6
f@0 7 This program is free software: you can redistribute it and/or modify
f@0 8 it under the terms of the GNU General Public License as published by
f@0 9 the Free Software Foundation, either version 3 of the License, or
f@0 10 (at your option) any later version.
f@0 11
f@0 12 This program is distributed in the hope that it will be useful,
f@0 13 but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0 15 GNU General Public License for more details.
f@0 16
f@0 17 You should have received a copy of the GNU General Public License
f@0 18 along with this program. If not, see <http://www.gnu.org/licenses/>.
f@0 19 */
f@0 20
f@0 21 package uk.ac.qmul.eecs.ccmi.gui;
f@0 22
f@0 23 import java.awt.Color;
f@0 24 import java.awt.Dimension;
f@0 25 import java.awt.Graphics;
f@0 26 import java.awt.Graphics2D;
f@0 27 import java.awt.event.InputEvent;
f@0 28 import java.awt.event.MouseAdapter;
f@0 29 import java.awt.event.MouseEvent;
f@0 30 import java.awt.event.MouseMotionAdapter;
f@0 31 import java.awt.geom.Point2D;
f@0 32 import java.awt.geom.Rectangle2D;
f@0 33 import java.util.ArrayList;
f@0 34 import java.util.HashSet;
f@0 35 import java.util.Iterator;
f@0 36 import java.util.LinkedList;
f@0 37 import java.util.List;
f@0 38 import java.util.ResourceBundle;
f@0 39 import java.util.Set;
f@0 40
f@0 41 import javax.swing.JOptionPane;
f@0 42 import javax.swing.JPanel;
f@0 43
f@0 44 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
f@0 45 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
f@0 46 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
f@0 47 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
f@0 48 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
f@0 49 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
f@0 50 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
f@0 51 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
f@0 52 import uk.ac.qmul.eecs.ccmi.network.Command;
f@0 53 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
f@0 54 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
f@0 55
f@0 56 /**
f@0 57 * A panel to draw a graph
f@0 58 */
f@0 59 @SuppressWarnings("serial")
f@0 60 public class GraphPanel extends JPanel{
f@0 61 /**
f@0 62 * Constructs a graph.
f@0 63 * @param aDiagram a diagram to paint in the graph
f@0 64 * @param aToolbar a toolbar containing the node and edges prototypes for creating
f@0 65 * elements in the graph.
f@0 66 */
f@0 67
f@0 68 public GraphPanel(Diagram aDiagram, GraphToolbar aToolbar) {
f@0 69 grid = new Grid();
f@0 70 gridSize = GRID;
f@0 71 grid.setGrid((int) gridSize, (int) gridSize);
f@0 72 zoom = 1;
f@0 73 toolbar = aToolbar;
f@0 74 setBackground(Color.WHITE);
f@0 75 wasMoving = false;
f@0 76 minBounds = null;
f@0 77
f@0 78 this.model = aDiagram.getCollectionModel();
f@0 79 synchronized(model.getMonitor()){
f@0 80 edges = new LinkedList<Edge>(model.getEdges());
f@0 81 nodes = new LinkedList<Node>(model.getNodes());
f@0 82 }
f@0 83 setModelUpdater(aDiagram.getModelUpdater());
f@0 84
f@0 85 selectedElements = new HashSet<DiagramElement>();
f@0 86 moveLockedElements = new HashSet<Object>();
f@0 87
f@0 88 toolbar.setEdgeCreatedListener(new innerEdgeListener());
f@0 89
f@0 90 /* ---- COLLECTION LISTENER ----
f@0 91 * Adding a collection listener. This listener reacts at changes in the model
f@0 92 * by any source, and thus the graph itself. Basically it refreshes the graph
f@0 93 * and paints again all the nodes and edges.
f@0 94 */
f@0 95 model.addCollectionListener(new CollectionListener(){
f@0 96 @Override
f@0 97 public void elementInserted(final CollectionEvent e) {
f@0 98 DiagramElement element = e.getDiagramElement();
f@0 99 if(element instanceof Node)
f@0 100 nodes.add((Node)element);
f@0 101 else
f@0 102 edges.add((Edge)element);
f@0 103 checkBounds(element,false);
f@0 104 if(e.getDiagramElement() instanceof Node && e.getSource().equals(DiagramEventSource.GRPH)){
f@0 105 setElementSelected(e.getDiagramElement());
f@0 106 dragMode = DRAG_NODE;
f@0 107 }
f@0 108 revalidate();
f@0 109 repaint();
f@0 110 }
f@0 111 @Override
f@0 112 public void elementTakenOut(final CollectionEvent e) {
f@0 113 DiagramElement element = e.getDiagramElement();
f@0 114 if(element instanceof Node){
f@0 115 if(nodePopup != null && nodePopup.getElement().equals(element))
f@0 116 nodePopup.setVisible(false);
f@0 117 nodes.remove(element);
f@0 118 }
f@0 119 else{
f@0 120 if(edgePopup != null && edgePopup.getElement().equals(element))
f@0 121 edgePopup.setVisible(false);
f@0 122 edges.remove(element);
f@0 123 }
f@0 124 checkBounds(e.getDiagramElement(),true);
f@0 125 removeElementFromSelection(e.getDiagramElement());
f@0 126 revalidate();
f@0 127 repaint();
f@0 128 }
f@0 129 @Override
f@0 130 public void elementChanged(final ElementChangedEvent e) {
f@0 131 /* we changed the position of an element and might need to update the boundaries */
f@0 132 if(e.getChangeType().equals("stop_move")){
f@0 133 checkBounds(e.getDiagramElement(),false);
f@0 134 }
f@0 135 revalidate();
f@0 136 repaint();
f@0 137 }
f@0 138 });
f@0 139 /* --------------------------------------------------------------------------- */
f@0 140
f@0 141 /* ------------- MOUSE LISTENERS --------------------------------------------
f@0 142 * For pressed and released mouse click and moved mouse
f@0 143 */
f@0 144 addMouseListener(new MouseAdapter(){
f@0 145 @Override
f@0 146 public void mousePressed(MouseEvent event){
f@0 147 requestFocusInWindow();
f@0 148 final Point2D mousePoint = new Point2D.Double(
f@0 149 (event.getX()+minX)/zoom,
f@0 150 (event.getY()+minY)/zoom
f@0 151 );
f@0 152 boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
f@0 153 Node n = Finder.findNode(mousePoint,nodes);
f@0 154 Edge e = Finder.findEdge(mousePoint,edges);
f@0 155
f@0 156 Object tool = toolbar.getSelectedTool();
f@0 157 /* - right click - */
f@0 158 if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) {
f@0 159 if(n != null){
f@0 160 CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements);
f@0 161 nodePopup = pop;
f@0 162 pop.show(GraphPanel.this, event.getX(), event.getY());
f@0 163 }else if(e != null){
f@0 164 if( e.contains(mousePoint)){
f@0 165 Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST);
f@0 166 if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item
f@0 167 CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,GraphPanel.this,modelUpdater,selectedElements);
f@0 168 edgePopup = pop;
f@0 169 pop.show(GraphPanel.this, event.getX(), event.getY());
f@0 170 }else{ // click near an attached nodes, prompt for name change, set end label and select arrow head
f@0 171 CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater,selectedElements);
f@0 172 edgePopup = pop;
f@0 173 pop.show(GraphPanel.this, event.getX(), event.getY());
f@0 174 }
f@0 175 }
f@0 176 }else {
f@0 177 return;
f@0 178 }
f@0 179 }
f@0 180
f@0 181 /* - one click && palette == select - */
f@0 182 else if (tool == null){
f@0 183 if(n != null){ // node selected
f@0 184 if (isCtrl){
f@0 185 addElementToSelection(n,false);
f@0 186 }else{
f@0 187 setElementSelected(n);
f@0 188 }
f@0 189 dragMode = DRAG_NODE;
f@0 190 }else if (e != null){ // edge selected
f@0 191 if (isCtrl){
f@0 192 addElementToSelection(e,false);
f@0 193 dragMode = DRAG_NODE;
f@0 194 }else{
f@0 195 setElementSelected(e);
f@0 196 modelUpdater.startMove(e, mousePoint,DiagramEventSource.GRPH);
f@0 197 dragMode = DRAG_EDGE;
f@0 198 }
f@0 199 }else{ // nothing selected : make selection lasso
f@0 200 if (!isCtrl)
f@0 201 clearSelection();
f@0 202 dragMode = DRAG_LASSO;
f@0 203 }
f@0 204 }
f@0 205 /* - one click && palette == node - */
f@0 206 else {
f@0 207 /* click on an already existing node = select it*/
f@0 208 if (n != null){
f@0 209 if (isCtrl)
f@0 210 addElementToSelection(n,false);
f@0 211 else
f@0 212 setElementSelected(n);
f@0 213 dragMode = DRAG_NODE;
f@0 214 }else{
f@0 215 Node prototype = (Node) tool;
f@0 216 Node newNode = (Node) prototype.clone();
f@0 217 Rectangle2D bounds = newNode.getBounds();
f@0 218 /* perform the translation from the origin */
f@0 219 newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(),
f@0 220 mousePoint.getY() - bounds.getY(),DiagramEventSource.NONE);
f@0 221 /* log stuff */
f@0 222 iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId()));
f@0 223 /* insert the node into the model (no lock needed) */
f@0 224 modelUpdater.insertInCollection(newNode,DiagramEventSource.GRPH);
f@0 225 }
f@0 226 }
f@0 227
f@0 228 lastMousePoint = mousePoint;
f@0 229 mouseDownPoint = mousePoint;
f@0 230 repaint();
f@0 231 }
f@0 232
f@0 233 @Override
f@0 234 public void mouseReleased(MouseEvent event){
f@0 235 final Point2D mousePoint = new Point2D.Double(
f@0 236 (event.getX()+minX)/zoom,
f@0 237 (event.getY()+minY)/zoom
f@0 238 );
f@0 239 if(lastSelected != null){
f@0 240 if(lastSelected instanceof Node || selectedElements.size() > 1){ // differentiate between translate and edge bending
f@0 241 if(wasMoving){
f@0 242 iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
f@0 243 for(Object element : moveLockedElements){
f@0 244 modelUpdater.stopMove((GraphElement)element,DiagramEventSource.GRPH);
f@0 245 boolean isNode = element instanceof Node;
f@0 246 modelUpdater.yieldLock((DiagramTreeNode)element,
f@0 247 Lock.MOVE,
f@0 248 new DiagramEventActionSource(
f@0 249 DiagramEventSource.GRPH,
f@0 250 isNode ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
f@0 251 ((DiagramElement)element).getId(),((DiagramElement)element).getName()));
f@0 252 }
f@0 253 moveLockedElements.clear();
f@0 254 }
f@0 255 }else{ // instanceof Edge && selectedelements.size() = 1. Bending
f@0 256 if(wasMoving){
f@0 257 iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
f@0 258 if(moveLockedEdge != null){
f@0 259 modelUpdater.stopMove(moveLockedEdge,DiagramEventSource.GRPH);
f@0 260 modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE, new DiagramEventActionSource(
f@0 261 DiagramEventSource.GRPH,
f@0 262 Command.Name.BEND,
f@0 263 moveLockedEdge.getId(),
f@0 264 moveLockedEdge.getName()));
f@0 265 moveLockedEdge = null;
f@0 266 }
f@0 267 }
f@0 268 }
f@0 269 }
f@0 270 dragMode = DRAG_NONE;
f@0 271 wasMoving = false;
f@0 272 repaint();
f@0 273 }
f@0 274 });
f@0 275
f@0 276 addMouseMotionListener(new MouseMotionAdapter(){
f@0 277 public void mouseDragged(MouseEvent event){
f@0 278 Point2D mousePoint = new Point2D.Double(
f@0 279 (event.getX()+minX)/zoom,
f@0 280 (event.getY()+minY)/zoom
f@0 281 );
f@0 282 boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
f@0 283
f@0 284 if (dragMode == DRAG_NODE){
f@0 285 /* translate selected nodes (edges as well) */
f@0 286 double dx = mousePoint.getX() - lastMousePoint.getX();
f@0 287 double dy = mousePoint.getY() - lastMousePoint.getY();
f@0 288 if(!wasMoving){
f@0 289 wasMoving = true;
f@0 290 /* when the motion starts, we need to get the move-lock from the server */
f@0 291 Iterator<DiagramElement> iterator = selectedElements.iterator();
f@0 292 while(iterator.hasNext()){
f@0 293 DiagramElement element = iterator.next();
f@0 294 boolean isNode = element instanceof Node;
f@0 295 if(modelUpdater.getLock(element,
f@0 296 Lock.MOVE,
f@0 297 new DiagramEventActionSource(
f@0 298 DiagramEventSource.GRPH,
f@0 299 isNode ? Command.Name.TRANSLATE_NODE : Command.Name.TRANSLATE_EDGE,
f@0 300 element.getId(),
f@0 301 element.getName()))){
f@0 302 moveLockedElements.add(element);
f@0 303 }else{
f@0 304 iLog("Could not get move lock for element",DiagramElement.toLogString(element));
f@0 305 iterator.remove();
f@0 306 }
f@0 307 }
f@0 308 iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY());
f@0 309 }
f@0 310
f@0 311 for (DiagramElement selected : selectedElements){
f@0 312 if(selected instanceof Node)
f@0 313 modelUpdater.translate((Node)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
f@0 314 else
f@0 315 modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
f@0 316 }
f@0 317 } else if(dragMode == DRAG_EDGE){
f@0 318 if(!wasMoving){
f@0 319 wasMoving = true;
f@0 320 if(modelUpdater.getLock(lastSelected,
f@0 321 Lock.MOVE,
f@0 322 new DiagramEventActionSource(
f@0 323 DiagramEventSource.GRPH,
f@0 324 Command.Name.BEND,
f@0 325 lastSelected.getId(),
f@0 326 lastSelected.getName()))
f@0 327 ){
f@0 328 moveLockedEdge = (Edge)lastSelected;
f@0 329 }else{
f@0 330 iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected));
f@0 331 }
f@0 332 iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY());
f@0 333 }
f@0 334 if(moveLockedEdge != null)
f@0 335 modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()),DiagramEventSource.GRPH);
f@0 336 } else if (dragMode == DRAG_LASSO){
f@0 337 double x1 = mouseDownPoint.getX();
f@0 338 double y1 = mouseDownPoint.getY();
f@0 339 double x2 = mousePoint.getX();
f@0 340 double y2 = mousePoint.getY();
f@0 341 Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2),
f@0 342 Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
f@0 343 for (Node n : GraphPanel.this.nodes){
f@0 344 Rectangle2D bounds = n.getBounds();
f@0 345 if(!isCtrl && !lasso.contains(bounds)){
f@0 346 removeElementFromSelection(n);
f@0 347 }
f@0 348 else if (lasso.contains(bounds)){
f@0 349 addElementToSelection(n,true);
f@0 350 }
f@0 351 }
f@0 352 if(selectedElements.size() != oldLazoSelectedNum){
f@0 353 StringBuilder builder = new StringBuilder();
f@0 354 for(DiagramElement de : selectedElements)
f@0 355 builder.append(DiagramElement.toLogString(de)).append(' ');
f@0 356 iLog("added by lazo",builder.toString());
f@0 357 }
f@0 358 oldLazoSelectedNum = selectedElements.size();
f@0 359 }
f@0 360 lastMousePoint = mousePoint;
f@0 361 }
f@0 362 });
f@0 363 }
f@0 364 /* --------------------------------------------------------------------------- */
f@0 365
f@0 366 @Override
f@0 367 public void paintComponent(Graphics g){
f@0 368 super.paintComponent(g);
f@0 369 paintGraph(g);
f@0 370 }
f@0 371
f@0 372 /**
f@0 373 * Paints the graph on the graphics passed as argument. This function is called
f@0 374 * each time the component is painted.
f@0 375 *
f@0 376 * @see #paintComponent(Graphics)
f@0 377 * @param g the graphics object used to paint this graph
f@0 378 */
f@0 379 public void paintGraph(Graphics g){
f@0 380 Graphics2D g2 = (Graphics2D) g;
f@0 381 g2.translate(-minX, -minY);
f@0 382 g2.scale(zoom, zoom);
f@0 383 Rectangle2D bounds = getBounds();
f@0 384 Rectangle2D graphBounds = getGraphBounds();
f@0 385 if (!hideGrid) grid.draw(g2, new Rectangle2D.Double(minX, minY,
f@0 386 Math.max(bounds.getMaxX() / zoom, graphBounds.getMaxX()),
f@0 387 Math.max(bounds.getMaxY() / zoom, graphBounds.getMaxY())));
f@0 388
f@0 389 /* draw nodes and edges */
f@0 390 for (Edge e : edges)
f@0 391 e.draw(g2);
f@0 392 for (Node n : nodes)
f@0 393 n.draw(g2);
f@0 394
f@0 395 for(DiagramElement selected : selectedElements){
f@0 396 if (selected instanceof Node){
f@0 397 Rectangle2D grabberBounds = ((Node) selected).getBounds();
f@0 398 drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMinY());
f@0 399 drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMaxY());
f@0 400 drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMinY());
f@0 401 drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMaxY());
f@0 402 }
f@0 403 else if (selected instanceof Edge){
f@0 404 for(Point2D p : ((Edge)selected).getConnectionPoints())
f@0 405 drawGrabber(g2, p.getX(), p.getY());
f@0 406 }
f@0 407 }
f@0 408
f@0 409 if (dragMode == DRAG_LASSO){
f@0 410 Color oldColor = g2.getColor();
f@0 411 g2.setColor(GRABBER_COLOR);
f@0 412 double x1 = mouseDownPoint.getX();
f@0 413 double y1 = mouseDownPoint.getY();
f@0 414 double x2 = lastMousePoint.getX();
f@0 415 double y2 = lastMousePoint.getY();
f@0 416 Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2),
f@0 417 Math.min(y1, y2), Math.abs(x1 - x2) , Math.abs(y1 - y2));
f@0 418 g2.draw(lasso);
f@0 419 g2.setColor(oldColor);
f@0 420 repaint();
f@0 421 }
f@0 422 }
f@0 423
f@0 424 /**
f@0 425 * Draws a single "grabber", a filled square
f@0 426 * @param g2 the graphics context
f@0 427 * @param x the x coordinate of the center of the grabber
f@0 428 * @param y the y coordinate of the center of the grabber
f@0 429 */
f@0 430 static void drawGrabber(Graphics2D g2, double x, double y){
f@0 431 final int SIZE = 5;
f@0 432 Color oldColor = g2.getColor();
f@0 433 g2.setColor(GRABBER_COLOR);
f@0 434 g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE));
f@0 435 g2.setColor(oldColor);
f@0 436 }
f@0 437
f@0 438 @Override
f@0 439 public Dimension getPreferredSize(){
f@0 440 Rectangle2D graphBounds = getGraphBounds();
f@0 441 return new Dimension((int) (zoom * graphBounds.getMaxX()),
f@0 442 (int) (zoom * graphBounds.getMaxY()));
f@0 443 }
f@0 444
f@0 445 /**
f@0 446 * Changes the zoom of this panel. The zoom is 1 by default and is multiplied
f@0 447 * by sqrt(2) for each positive stem or divided by sqrt(2) for each negative
f@0 448 * step.
f@0 449 * @param steps the number of steps by which to change the zoom. A positive
f@0 450 * value zooms in, a negative value zooms out.
f@0 451 */
f@0 452 public void changeZoom(int steps){
f@0 453 final double FACTOR = Math.sqrt(2);
f@0 454 for (int i = 1; i <= steps; i++)
f@0 455 zoom *= FACTOR;
f@0 456 for (int i = 1; i <= -steps; i++)
f@0 457 zoom /= FACTOR;
f@0 458 revalidate();
f@0 459 repaint();
f@0 460 }
f@0 461
f@0 462 /**
f@0 463 * Changes the grid size of this panel. The zoom is 10 by default and is
f@0 464 * multiplied by sqrt(2) for each positive stem or divided by sqrt(2) for
f@0 465 * each negative step.
f@0 466 * @param steps the number of steps by which to change the zoom. A positive
f@0 467 * value zooms in, a negative value zooms out.
f@0 468 */
f@0 469 public void changeGridSize(int steps){
f@0 470 final double FACTOR = Math.sqrt(2);
f@0 471 for (int i = 1; i <= steps; i++)
f@0 472 gridSize *= FACTOR;
f@0 473 for (int i = 1; i <= -steps; i++)
f@0 474 gridSize /= FACTOR;
f@0 475 grid.setGrid((int) gridSize, (int) gridSize);
f@0 476 repaint();
f@0 477 }
f@0 478
f@0 479 private void addElementToSelection(DiagramElement element, boolean byLasso){
f@0 480 /* if not added to selected elements by including it in the lasso, the element is moved *
f@0 481 * to the back of the collection so that it will be painted on the top on the next refresh */
f@0 482 if(!byLasso)
f@0 483 if(element instanceof Node){
f@0 484 /* put the node in the last position so that it will be drawn on the top */
f@0 485 nodes.remove(element);
f@0 486 nodes.add((Node)element);
f@0 487 iLog("addeded node to selected",DiagramElement.toLogString(element));
f@0 488 }else{
f@0 489 /* put the edge in the last position so that it will be drawn on the top */
f@0 490 edges.remove(element);
f@0 491 edges.add((Edge)element);
f@0 492 iLog("addeded edge to selected",DiagramElement.toLogString(element));
f@0 493 }
f@0 494 if(selectedElements.contains(element)){
f@0 495 lastSelected = element;
f@0 496 return;
f@0 497 }
f@0 498 lastSelected = element;
f@0 499 selectedElements.add(element);
f@0 500 return;
f@0 501 }
f@0 502
f@0 503 private void removeElementFromSelection(DiagramElement element){
f@0 504 if (element == lastSelected){
f@0 505 lastSelected = null;
f@0 506 }
f@0 507 if(selectedElements.contains(element)){
f@0 508 selectedElements.remove(element);
f@0 509 }
f@0 510 }
f@0 511
f@0 512 private void setElementSelected(DiagramElement element){
f@0 513 /* clear the selection */
f@0 514 selectedElements.clear();
f@0 515 lastSelected = element;
f@0 516 selectedElements.add(element);
f@0 517 if(element instanceof Node){
f@0 518 nodes.remove(element);
f@0 519 nodes.add((Node)element);
f@0 520 iLog("node selected",DiagramElement.toLogString(element));
f@0 521 }else{
f@0 522 edges.remove(element);
f@0 523 edges.add((Edge)element);
f@0 524 iLog("edge selected",DiagramElement.toLogString(element));
f@0 525 }
f@0 526 }
f@0 527
f@0 528 private void clearSelection(){
f@0 529 iLog("selection cleared","");
f@0 530 selectedElements.clear();
f@0 531 lastSelected = null;
f@0 532 }
f@0 533
f@0 534 /**
f@0 535 * Sets the value of the hideGrid property
f@0 536 * @param newValue true if the grid is being hidden
f@0 537 */
f@0 538 public void setHideGrid(boolean newValue){
f@0 539 hideGrid = newValue;
f@0 540 repaint();
f@0 541 }
f@0 542
f@0 543 /**
f@0 544 * Gets the value of the hideGrid property
f@0 545 * @return true if the grid is being hidden
f@0 546 */
f@0 547 public boolean getHideGrid(){
f@0 548 return hideGrid;
f@0 549 }
f@0 550
f@0 551 /**
f@0 552 * Gets the smallest rectangle enclosing the graph
f@0 553 * @return the bounding rectangle
f@0 554 */
f@0 555 public Rectangle2D getMinBounds() { return minBounds; }
f@0 556
f@0 557 /**
f@0 558 * Sets the smallest rectangle enclosing the graph
f@0 559 * @param newValue the bounding rectangle
f@0 560 */
f@0 561 public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; }
f@0 562
f@0 563 /**
f@0 564 * Returns the smallest rectangle enclosing the graph, that is all the nodes
f@0 565 * and all the edges in the diagram.
f@0 566 *
f@0 567 * @return the bounding rectangle
f@0 568 */
f@0 569 public Rectangle2D getGraphBounds(){
f@0 570 Rectangle2D r = minBounds;
f@0 571 for (Node n : nodes){
f@0 572 Rectangle2D b = n.getBounds();
f@0 573 if (r == null) r = b;
f@0 574 else r.add(b);
f@0 575 }
f@0 576 for (Edge e : edges){
f@0 577 r.add(e.getBounds());
f@0 578 }
f@0 579 return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(),
f@0 580 r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY));
f@0 581 }
f@0 582
f@0 583 /**
f@0 584 * Sets the model updater for this graph. The model updater is used
f@0 585 * to make changes to the diagram (e.g. adding, removing, renaming nodes and edges)
f@0 586 *
f@0 587 * @param modelUpdater the model updater for this graph panel
f@0 588 */
f@0 589 public void setModelUpdater(DiagramModelUpdater modelUpdater){
f@0 590 this.modelUpdater = modelUpdater;
f@0 591 }
f@0 592
f@0 593 private void iLog(String action,String args){
f@0 594 InteractionLog.log("GRAPH",action,args);
f@0 595 }
f@0 596
f@0 597 private void checkBounds(DiagramElement de, boolean wasRemoved){
f@0 598 GraphElement ge;
f@0 599 if(de instanceof Node)
f@0 600 ge = (Node)de;
f@0 601 else
f@0 602 ge = (Edge)de;
f@0 603 if(wasRemoved){
f@0 604 if(ge == top){
f@0 605 top = null;
f@0 606 minY = 0;
f@0 607 Rectangle2D bounds;
f@0 608 for(Edge e : edges){
f@0 609 bounds = e.getBounds();
f@0 610 if(bounds.getY() < minY){
f@0 611 top = e;
f@0 612 minY = bounds.getY();
f@0 613 }
f@0 614 }
f@0 615 for(Node n : nodes){
f@0 616 bounds = n.getBounds();
f@0 617 if(bounds.getY() < minY){
f@0 618 top = n;
f@0 619 minY = bounds.getY();
f@0 620 }
f@0 621 }
f@0 622 }
f@0 623 if(ge == left){
f@0 624 minX = 0;
f@0 625 left = null;
f@0 626 synchronized(model.getMonitor()){
f@0 627 Rectangle2D bounds;
f@0 628 for(Edge e : model.getEdges()){
f@0 629 bounds = e.getBounds();
f@0 630 if(bounds.getX() < minX){
f@0 631 left = e;
f@0 632 minX = bounds.getX();
f@0 633 }
f@0 634 }
f@0 635 for(Node n : model.getNodes()){
f@0 636 bounds = n.getBounds();
f@0 637 if(bounds.getX() < minX){
f@0 638 left = n;
f@0 639 minX = bounds.getX();
f@0 640 }
f@0 641 }
f@0 642 }
f@0 643 }
f@0 644 }else{ // was added or translated
f@0 645 Rectangle2D bounds = ge.getBounds();
f@0 646 if(top == null){
f@0 647 if(bounds.getY() < 0){
f@0 648 top = ge;
f@0 649 minY = bounds.getY();
f@0 650 }
f@0 651 }else if(ge == top){ //the top-most has been translated recalculate the new top-most, as itf it were deleted
f@0 652 checkBounds(de, true);
f@0 653 }else if(bounds.getY() < top.getBounds().getY()){
f@0 654 top = ge;
f@0 655 minY = bounds.getY();
f@0 656 }
f@0 657
f@0 658 if(left == null){
f@0 659 if(bounds.getX() < 0){
f@0 660 left = ge;
f@0 661 minX = bounds.getX();
f@0 662 }
f@0 663 }else if(ge == left){
f@0 664 checkBounds(de,true);//the left-most has been translated recalculate the new left-most, as if it were deleted
f@0 665 }
f@0 666 else if(bounds.getX() < left.getBounds().getX()){
f@0 667 left = ge;
f@0 668 minX = bounds.getX();
f@0 669 }
f@0 670 }
f@0 671 }
f@0 672
f@0 673 private class innerEdgeListener implements GraphToolbar.EdgeCreatedListener {
f@0 674 @Override
f@0 675 public void edgeCreated(Edge e) {
f@0 676 ArrayList<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(selectedElements.size());
f@0 677
f@0 678 for(DiagramElement element : selectedElements){
f@0 679 if(element instanceof Node){
f@0 680 if(!modelUpdater.getLock(element,
f@0 681 Lock.MUST_EXIST,
f@0 682 new DiagramEventActionSource(DiagramEventSource.GRPH,
f@0 683 Command.Name.SELECT_NODE_FOR_EDGE_CREATION,
f@0 684 element.getId(),
f@0 685 element.getName()))){
f@0 686 /* unlock the nodes locked so far */
f@0 687 yieldLocks(nodesToConnect);
f@0 688 /* notify user */
f@0 689 JOptionPane.showMessageDialog(GraphPanel.this,
f@0 690 ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.lock_failure.no_edge_creation"));
f@0 691 return;
f@0 692 }
f@0 693 nodesToConnect.add((Node)element);
f@0 694 }
f@0 695 }
f@0 696 try {
f@0 697 e.connect(nodesToConnect);
f@0 698 modelUpdater.insertInCollection(e,DiagramEventSource.GRPH);
f@0 699 /* release the must-exist lock on nodes now that the edge is created */
f@0 700 yieldLocks(nodesToConnect);
f@0 701 } catch (ConnectNodesException cnEx) {
f@0 702 JOptionPane.showMessageDialog(GraphPanel.this,
f@0 703 cnEx.getLocalizedMessage(),
f@0 704 ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"),
f@0 705 JOptionPane.ERROR_MESSAGE);
f@0 706 iLog("insert edge error",cnEx.getMessage());
f@0 707 }
f@0 708 }
f@0 709
f@0 710 /* release all locks */
f@0 711 private void yieldLocks(ArrayList<DiagramNode> nodesToConnect){
f@0 712 for(DiagramNode node : nodesToConnect){
f@0 713 modelUpdater.yieldLock(node,
f@0 714 Lock.MUST_EXIST,
f@0 715 new DiagramEventActionSource(
f@0 716 DiagramEventSource.GRPH,
f@0 717 Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION,
f@0 718 node.getId(),
f@0 719 node.getName())
f@0 720 );
f@0 721 }
f@0 722 }
f@0 723 }
f@0 724
f@0 725 private List<Edge> edges;
f@0 726 private List<Node> nodes;
f@0 727 private DiagramModelUpdater modelUpdater;
f@0 728 private CollectionModel<Node,Edge> model;
f@0 729
f@0 730 private Grid grid;
f@0 731 private GraphToolbar toolbar;
f@0 732 private CCmIPopupMenu.NodePopupMenu nodePopup;
f@0 733 private CCmIPopupMenu.EdgePopupMenu edgePopup;
f@0 734
f@0 735 private double zoom;
f@0 736 private double gridSize;
f@0 737 private boolean hideGrid;
f@0 738 private boolean wasMoving;
f@0 739
f@0 740 private GraphElement top;
f@0 741 private GraphElement left;
f@0 742 private double minX;
f@0 743 private double minY;
f@0 744
f@0 745 private DiagramElement lastSelected;
f@0 746 private Edge moveLockedEdge;
f@0 747 private Set<DiagramElement> selectedElements;
f@0 748 private Set<Object> moveLockedElements;
f@0 749
f@0 750 private Point2D lastMousePoint;
f@0 751 private Point2D mouseDownPoint;
f@0 752 private Rectangle2D minBounds;
f@0 753 private int dragMode;
f@0 754
f@0 755 private int oldLazoSelectedNum;
f@0 756
f@0 757 /* button is not down, mouse motion will habe no effects */
f@0 758 private static final int DRAG_NONE = 0;
f@0 759 /* one or more nodes (and eventually some edges) have been selected, mouse motion will result in a translation */
f@0 760 private static final int DRAG_NODE = 1;
f@0 761 /* one edge has been selected, mouse motion will result in an edge bending */
f@0 762 private static final int DRAG_EDGE = 2;
f@0 763 /* mouse button down but nothing selected, mouse motion will result in a lasso */
f@0 764 private static final int DRAG_LASSO = 3; // multiple selection
f@0 765
f@0 766 private static final int GRID = 10;
f@0 767 private static final double EDGE_END_MIN_CLICK_DIST = 10;
f@0 768
f@0 769 public static final Color GRABBER_COLOR = new Color(0,128,255);
f@0 770 }