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