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