]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/RouteGraphNode.java
34b1a635d766070867e0a3d2b3170305d0387e5f
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / connection / RouteGraphNode.java
1 /*******************************************************************************\r
2  * Copyright (c) 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.scenegraph.g2d.nodes.connection;\r
13 \r
14 import gnu.trove.map.hash.THashMap;\r
15 \r
16 import java.awt.BasicStroke;\r
17 import java.awt.Color;\r
18 import java.awt.Graphics2D;\r
19 import java.awt.RenderingHints;\r
20 import java.awt.Shape;\r
21 import java.awt.Stroke;\r
22 import java.awt.event.KeyEvent;\r
23 import java.awt.geom.AffineTransform;\r
24 import java.awt.geom.Path2D;\r
25 import java.awt.geom.Point2D;\r
26 import java.awt.geom.Rectangle2D;\r
27 import java.lang.reflect.Constructor;\r
28 import java.util.Collection;\r
29 import java.util.Map;\r
30 \r
31 import org.simantics.diagram.connection.RouteGraph;\r
32 import org.simantics.diagram.connection.RouteLine;\r
33 import org.simantics.diagram.connection.RouteLink;\r
34 import org.simantics.diagram.connection.RoutePoint;\r
35 import org.simantics.diagram.connection.RouteTerminal;\r
36 import org.simantics.diagram.connection.actions.IAction;\r
37 import org.simantics.diagram.connection.actions.IReconnectAction;\r
38 import org.simantics.diagram.connection.actions.MoveAction;\r
39 import org.simantics.diagram.connection.actions.ReconnectLineAction;\r
40 import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
41 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;\r
42 import org.simantics.diagram.connection.rendering.ConnectionStyle;\r
43 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
44 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;\r
45 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
46 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;\r
47 import org.simantics.scenegraph.INode;\r
48 import org.simantics.scenegraph.ISelectionPainterNode;\r
49 import org.simantics.scenegraph.g2d.G2DNode;\r
50 import org.simantics.scenegraph.g2d.G2DParentNode;\r
51 import org.simantics.scenegraph.g2d.IG2DNode;\r
52 import org.simantics.scenegraph.g2d.events.EventTypes;\r
53 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
54 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;\r
55 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
56 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
57 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
58 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
59 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
60 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
61 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
62 import org.simantics.scenegraph.g2d.events.command.Commands;\r
63 import org.simantics.scenegraph.g2d.nodes.GridNode;\r
64 import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
65 import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action;\r
66 import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick;\r
67 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
68 import org.simantics.scenegraph.utils.GeometryUtils;\r
69 import org.simantics.scenegraph.utils.InitValueSupport;\r
70 import org.simantics.scenegraph.utils.NodeUtil;\r
71 \r
72 /**\r
73  * @author Tuukka Lehtonen\r
74  */\r
75 public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, InitValueSupport  {\r
76 \r
77     private static final long       serialVersionUID = -917194130412280965L;\r
78 \r
79     private static final double     TOLERANCE        = IAction.TOLERANCE;\r
80     private static final Stroke     SELECTION_STROKE = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
81     private static final Color      SELECTION_COLOR  = new Color(255, 0, 255, 96);\r
82 \r
83     private static final HighlightActionPointsAction highlightActions = new HighlightActionPointsAction(null);\r
84 \r
85     protected RouteGraph            rg;\r
86     protected IRouteGraphRenderer   baseRenderer;\r
87     protected IRouteGraphRenderer   renderer;\r
88     protected double                pickTolerance    = TOLERANCE;\r
89     protected boolean               editable         = true;\r
90     private boolean                 branchable       = true;\r
91 \r
92     protected IRouteGraphListener   rgListener;\r
93     protected RouteGraphDelta       rgDelta;\r
94 \r
95     protected transient double      mouseX;\r
96     protected transient double      mouseY;\r
97     protected transient Point2D     pt               = new Point2D.Double();\r
98 \r
99     protected transient MoveAction  dragAction;\r
100     protected transient IAction     currentAction;\r
101     protected transient Rectangle2D bounds;\r
102 \r
103     /**\r
104      * Dynamic color for connection rendering.\r
105      */\r
106     protected transient Color       dynamicColor;\r
107 \r
108     /**\r
109      * Dynamic stroke for connection rendering.\r
110      */\r
111     protected transient Stroke      dynamicStroke;\r
112 \r
113     protected transient Path2D      selectionPath    = new Path2D.Double();\r
114     protected transient Stroke      selectionStroke  = null;\r
115 \r
116     protected transient boolean     highlightActionsEnabled = false;\r
117     protected transient AffineTransform lastViewTransform = null;\r
118 \r
119     /**\r
120      * x = NaN is used to indicate that possible branch point should not be\r
121      * rendered but interaction has not ended yet.\r
122      */\r
123     protected transient Point2D     newBranchPointPosition = null;\r
124 \r
125 \r
126     protected transient Map<Object,ILineEndStyle> dynamicStyles = null;\r
127     \r
128     @Override\r
129     public void initValues() {\r
130         dynamicColor = null;\r
131         wrapRenderer();\r
132     }\r
133 \r
134     @PropertySetter("color")\r
135     @SyncField(value = {"dynamicColor"})\r
136     public void setDynamicColor(Color color) {\r
137         this.dynamicColor = color;\r
138         wrapRenderer();\r
139     }\r
140 \r
141     @PropertySetter("width")\r
142     @SyncField("dynamicStroke")\r
143     public void setDynamicStroke(Stroke stroke) {\r
144         this.dynamicStroke = stroke;\r
145         wrapRenderer();\r
146         createSelectionStroke();\r
147     }\r
148     \r
149     @SyncField(value = {"dynamicStyles"})\r
150     public void setDynamicLineEnd(RouteTerminal terminal, ILineEndStyle style) {\r
151         if (dynamicStyles == null)\r
152                 dynamicStyles = new THashMap<Object, ILineEndStyle>();\r
153         terminal.setDynamicStyle(style);\r
154         if (terminal.getData() != null) {\r
155                 if (style != null)\r
156                         dynamicStyles.put(terminal.getData(),style);\r
157                 else\r
158                         dynamicStyles.remove(terminal.getData());\r
159         }\r
160     }\r
161     \r
162     private void updateLineEnds() {\r
163         if (dynamicStyles == null)\r
164                 return;\r
165         for (RouteTerminal t : rg.getTerminals()) {\r
166                 if (t.getData() == null)\r
167                         continue;\r
168                 ILineEndStyle dynamicStyle = dynamicStyles.get(t.getData());\r
169                 if (dynamicStyle != null)\r
170                         t.setDynamicStyle(dynamicStyle);\r
171         }\r
172     }\r
173 \r
174     @SyncField(value = {"rg"})\r
175     public void setRouteGraph(RouteGraph graph) {\r
176         this.rg = graph;\r
177         updateLineEnds();\r
178         updateBounds();\r
179     }\r
180 \r
181     @SyncField(value = {"rgDelta"})\r
182     public void setRouteGraphDelta(RouteGraphDelta delta) {\r
183         this.rgDelta = delta;\r
184     }\r
185 \r
186     @SyncField(value = {"renderer"})\r
187     public void setRenderer(IRouteGraphRenderer renderer) {\r
188 \r
189         this.baseRenderer = renderer;\r
190         wrapRenderer();\r
191 \r
192         createSelectionStroke();      \r
193     }\r
194     \r
195     private void createSelectionStroke() {\r
196          BasicConnectionStyle style = tryGetStyle();\r
197          selectionStroke = null;\r
198          if (style != null) {\r
199              BasicStroke stroke = (BasicStroke) style.getLineStroke();\r
200              if (stroke != null) {\r
201                  float width = Math.max(stroke.getLineWidth() + 0.75f, stroke.getLineWidth()*1.3f);\r
202                  selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());\r
203              }\r
204          } else {\r
205              selectionStroke = SELECTION_STROKE;\r
206          }\r
207     }\r
208     \r
209     private void wrapRenderer() {\r
210         \r
211         if(baseRenderer == null) {\r
212             renderer = null;\r
213             return;\r
214         }\r
215         \r
216         if(dynamicColor != null || dynamicStroke != null) {\r
217             BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);\r
218             try {\r
219                 Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class);\r
220                 renderer = new StyledRouteGraphRenderer(c.newInstance(\r
221                         dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),\r
222                                 baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),\r
223                                     dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), \r
224                                             dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),\r
225                                                     baseStyle.getDegeneratedLineLength()));\r
226             } catch (Exception e) {\r
227                 renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(\r
228                         dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),\r
229                                 baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),\r
230                                     dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), \r
231                                             dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),\r
232                                                     baseStyle.getDegeneratedLineLength()));\r
233             }\r
234             \r
235             \r
236         } else {\r
237             renderer = baseRenderer;\r
238         }\r
239         \r
240     }\r
241 \r
242     @SyncField(value = {"pickTolerance"})\r
243     public void setPickTolerance(double tolerance) {\r
244         this.pickTolerance = tolerance;\r
245     }\r
246 \r
247     @SyncField(value = {"editable"})\r
248     public void setEditable(boolean editable) {\r
249         this.editable = editable;\r
250     }\r
251 \r
252     @SyncField(value = {"branchable"})\r
253     public void setBranchable(boolean branchable) {\r
254         this.branchable = branchable;\r
255     }\r
256 \r
257     public RouteGraph getRouteGraph() {\r
258         return rg;\r
259     }\r
260 \r
261     public RouteGraphDelta getRouteGraphDelta() {\r
262         return rgDelta;\r
263     }\r
264 \r
265     public IRouteGraphRenderer getRenderer() {\r
266         return renderer;\r
267     }\r
268 \r
269     public boolean isEditable() {\r
270         return editable;\r
271     }\r
272 \r
273     public boolean isBranchable() {\r
274         return branchable;\r
275     }\r
276     \r
277     public double getPickTolerance() {\r
278                 return pickTolerance;\r
279         }\r
280 \r
281     /**\r
282      * When in client-server mode, listener is only set on the server side and\r
283      * fireRouteGraphChanged will tell it when rg has changed.\r
284      * \r
285      * @param listener\r
286      */\r
287     public void setRouteGraphListener(IRouteGraphListener listener) {\r
288         this.rgListener = listener;\r
289     }\r
290 \r
291     /**\r
292      * @param before\r
293      * @param after\r
294      * @return <code>true</code> if changes were fired\r
295      */\r
296     private boolean setRouteGraphAndFireChanges(RouteGraph before, RouteGraph after) {\r
297         RouteGraphDelta delta = new RouteGraphDelta(before, after);\r
298         if (!delta.isEmpty()) {\r
299             setRouteGraph(after);\r
300             setRouteGraphDelta(delta);\r
301             fireRouteGraphChanged(before, after, delta);\r
302             return true;\r
303         }\r
304         return false;\r
305     }\r
306 \r
307     @ServerSide\r
308     protected void fireRouteGraphChanged(RouteGraph before, RouteGraph after, RouteGraphDelta delta) {\r
309         if (rgListener != null) {\r
310             RouteGraphChangeEvent event = new RouteGraphChangeEvent(this, before, after, delta);\r
311             rgListener.routeGraphChanged(event);\r
312         }\r
313     }\r
314 \r
315     @Override\r
316     public void init() {\r
317         super.init();\r
318         addEventHandler(this);\r
319     }\r
320 \r
321     @Override\r
322     public void cleanup() {\r
323         rgListener = null;\r
324         removeEventHandler(this);\r
325         super.cleanup();\r
326     }\r
327 \r
328     protected boolean isSelected() {\r
329         return NodeUtil.isSelected(this, 1);\r
330     }\r
331 \r
332     @Override\r
333     public void render(Graphics2D g) {\r
334         if (renderer == null)\r
335             return;\r
336 \r
337         AffineTransform ot = null;\r
338         AffineTransform t = getTransform();\r
339         if (t != null && !t.isIdentity()) {\r
340             ot = g.getTransform();\r
341             g.transform(getTransform());\r
342         }\r
343 \r
344         Object aaHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);\r
345         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);\r
346 \r
347         boolean selected = NodeUtil.isSelected(this, 1);\r
348 \r
349         if (currentAction != null) {\r
350             currentAction.render(g, renderer, mouseX, mouseY);\r
351         } else {\r
352             if (selected && selectionStroke != null) {\r
353                 selectionPath.reset();\r
354                 rg.getPath2D(selectionPath);\r
355                 Shape selectionShape = selectionStroke.createStrokedShape(selectionPath);\r
356                 g.setColor(SELECTION_COLOR);\r
357                 g.fill(selectionShape);\r
358             }\r
359 \r
360             renderer.render(g, rg);\r
361             if(selected)\r
362                 renderer.renderGuides(g, rg);\r
363 \r
364             if (selected && highlightActionsEnabled) {\r
365                 // Needed for performing actions in #mouseClicked\r
366                 this.lastViewTransform = g.getTransform();\r
367                 highlightActions.setRouteGraph(rg);\r
368                 highlightActions.render(g, renderer, mouseX, mouseY);\r
369                 highlightActions.setRouteGraph(null);\r
370             }\r
371         }\r
372 \r
373         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);\r
374 \r
375         if (editable && branchable && newBranchPointPosition != null && !Double.isNaN(newBranchPointPosition.getX())) {\r
376             ConnectionStyle style = tryGetStyle();\r
377             if(style != null)\r
378                 style.drawBranchPoint(g, newBranchPointPosition.getX(), newBranchPointPosition.getY());\r
379         }\r
380 \r
381         if (ot != null)\r
382             g.setTransform(ot);\r
383     }\r
384     \r
385     private BasicConnectionStyle tryGetStyle() {\r
386         return tryGetStyle(renderer);\r
387     }\r
388 \r
389     private BasicConnectionStyle tryGetStyle(IRouteGraphRenderer renderer) {\r
390         if (renderer instanceof StyledRouteGraphRenderer) {\r
391             ConnectionStyle cs = ((StyledRouteGraphRenderer) renderer).getStyle();\r
392             if (cs instanceof BasicConnectionStyle)\r
393                 return (BasicConnectionStyle) cs;\r
394         }\r
395         return null;\r
396     }\r
397 \r
398     private double getSelectionStrokeWidth() {\r
399         if (selectionStroke instanceof BasicStroke) {\r
400             BasicStroke bs = (BasicStroke) selectionStroke;\r
401             return bs.getLineWidth();\r
402         }\r
403         return 1.0;\r
404     }\r
405 \r
406     @Override\r
407     public Rectangle2D getBoundsInLocal() {\r
408         return bounds;\r
409     }\r
410 \r
411     protected void updateBounds() {\r
412         Rectangle2D r = this.bounds;\r
413         if (r == null)\r
414             r = new Rectangle2D.Double();\r
415         this.bounds = calculateBounds(r);\r
416 \r
417         // Need to expand to take stroke width into account.\r
418         double sw = getSelectionStrokeWidth() / 2;\r
419         GeometryUtils.expandRectangle(this.bounds, sw, sw);\r
420     }\r
421 \r
422     protected Rectangle2D calculateBounds(Rectangle2D rect) {\r
423         RouteGraph rg = this.rg;\r
424         if (currentAction instanceof MoveAction)\r
425             rg = ((MoveAction) currentAction).getRouteGraph();\r
426         rg.getBounds(rect);\r
427         return rect;\r
428     }\r
429 \r
430     protected void getMouseLocalPos(MouseEvent e) {\r
431         //System.out.println("m: " + e.controlPosition);\r
432         pt.setLocation(e.controlPosition);\r
433         //System.out.println("parent: " + pt);\r
434         pt = NodeUtil.worldToLocal(this, pt, pt);\r
435         //System.out.println("local: " + pt);\r
436         mouseX = pt.getX();\r
437         mouseY = pt.getY();\r
438     }\r
439 \r
440     @Override\r
441     protected boolean mouseDragged(MouseDragBegin e) {\r
442         if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) {\r
443             currentAction = dragAction;\r
444             dragAction = null;\r
445         }\r
446         return updateCurrentAction(e, true);\r
447     }\r
448 \r
449     @Override\r
450     protected boolean mouseMoved(MouseMovedEvent e) {\r
451         //System.out.println("mouse moved: " + e);\r
452 \r
453         // Handle connection branching visualization.\r
454         getMouseLocalPos(e);\r
455         if (newBranchPointPosition == null && e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {\r
456             if (bounds.contains(mouseX, mouseY)) {\r
457                 newBranchPointPosition = new Point2D.Double(Double.NaN, Double.NaN);\r
458             }\r
459         }\r
460         if (newBranchPointPosition != null) {\r
461             RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);\r
462             if (line != null) {\r
463                 newBranchPointPosition.setLocation(mouseX, mouseY);\r
464                 SplittedRouteGraph.snapToLine(newBranchPointPosition, line);\r
465                 repaint();\r
466             } else if (!Double.isNaN(newBranchPointPosition.getX())) {\r
467                 newBranchPointPosition.setLocation(Double.NaN, Double.NaN);\r
468                 repaint();\r
469             }\r
470         }\r
471 \r
472         // Make sure that highlight action rendering is according to mouse hover.\r
473         if (highlightActionsEnabled) {\r
474             if (NodeUtil.isSelected(this, 1)) {\r
475                 repaint();\r
476             }\r
477         }\r
478 \r
479         return updateCurrentAction(e, false);\r
480     }\r
481 \r
482     protected boolean updateCurrentAction(MouseEvent e, boolean updateMousePos) {\r
483         boolean oldHighlight = highlightActionsEnabled;\r
484         highlightActionsEnabled = e.hasAllModifiers(MouseEvent.CTRL_MASK);\r
485         if (oldHighlight != highlightActionsEnabled)\r
486             repaint();\r
487 \r
488         if (currentAction != null) {\r
489             if (updateMousePos)\r
490                 getMouseLocalPos(e);\r
491             updateBounds();\r
492 \r
493             // Repaint, but only if absolutely necessary.\r
494             if (currentAction instanceof MoveAction || bounds.contains(mouseX, mouseY))\r
495                 repaint();\r
496 \r
497             return true;\r
498         }\r
499         return false;\r
500     }\r
501 \r
502     @Override\r
503     protected boolean mouseClicked(MouseClickEvent e) {\r
504         if (!editable)\r
505             return false;\r
506 \r
507         if (e.button == MouseEvent.LEFT_BUTTON) {\r
508             if (isSelected() && highlightActionsEnabled) {\r
509                 // Reconnection / segment deletion only available for branched connections. \r
510                 if (rg.getTerminals().size() > 2) {\r
511                     Pick pick = highlightActions.pickAction(rg, lastViewTransform, mouseX, mouseY);\r
512                     if (pick.hasAction(Action.REMOVE)) {\r
513                         RemoveLineAction remove = RemoveLineAction.perform(rg, pick.line.getLine(), mouseX, mouseY);\r
514                         if (remove != null) {\r
515                             setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());\r
516                             repaint();\r
517                             return true;\r
518                         }\r
519                     }\r
520                     if (pick.hasAction(Action.RECONNECT)) {\r
521                         currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);\r
522                         if (currentAction != null) {\r
523                             repaint();\r
524                         }\r
525                     }\r
526                 }\r
527             }\r
528         }\r
529 \r
530         return false;\r
531     }\r
532 \r
533     @Override\r
534     protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
535         if (!editable)\r
536             return false;\r
537 \r
538         if (e.button == MouseEvent.LEFT_BUTTON) {\r
539             // Visualize new branch point no longer.\r
540             newBranchPointPosition = null;\r
541 \r
542             getMouseLocalPos(e);\r
543             dragAction = null;\r
544 //          if(currentAction instanceof HighlightActionPointsAction) {\r
545 //              RemoveLineAction remove = RemoveLineAction.perform(rg, mouseX, mouseY);\r
546 //              if (remove != null) {\r
547 //                  setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());\r
548 //                  repaint();\r
549 //              } else {\r
550 //                  currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);\r
551 //                  if (currentAction != null)\r
552 //                      repaint();\r
553 //              }\r
554 //          }\r
555 //          else\r
556             if(currentAction instanceof IReconnectAction) {\r
557                 RouteGraph originalRg = rg.copy();\r
558                 ((IReconnectAction)currentAction).finish(mouseX, mouseY);\r
559                 currentAction = null;\r
560 \r
561                 setRouteGraphAndFireChanges(originalRg, rg);\r
562 \r
563                 currentAction = null;\r
564                 repaint();\r
565                 return true;\r
566             }\r
567             else {\r
568                 if (!allowConnectionRerouting()) {\r
569                     return false;\r
570                 }\r
571                 //System.out.println("move action");\r
572                 dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, pickTolerance, moveFilter, getSnapAdvisor());\r
573                 //System.out.println("DRAG ACTION: " + dragAction);\r
574             }\r
575 \r
576             //System.out.println(this + " NEW action: " + currentAction);\r
577             if (currentAction != null)\r
578                 return true;\r
579         }\r
580         return false;\r
581     }\r
582 \r
583     /**\r
584      * Checks the selections data node in the scene graph for any links \r
585      * @return\r
586      */\r
587     private boolean allowConnectionRerouting() {\r
588         final int maxOtherNodesSelected = 1;\r
589 \r
590         INode selections = NodeUtil.tryLookup(this, "selections");\r
591         if (!(selections instanceof G2DParentNode))\r
592             return true;\r
593         G2DParentNode p = (G2DParentNode) selections;\r
594         for (IG2DNode selection : p.getNodes()) {\r
595             if (!(selection instanceof G2DParentNode))\r
596                 continue;\r
597 \r
598             G2DParentNode sp = (G2DParentNode) selection;\r
599             Collection<IG2DNode> links = sp.getNodes();\r
600             if (links.isEmpty())\r
601                 return true;\r
602             int othersSelected = 0;\r
603             for (IG2DNode link : links) {\r
604                 if (link instanceof LinkNode) {\r
605                     INode node = ((LinkNode) link).getDelegate();\r
606                     if (!NodeUtil.isParentOf(node, this)) {\r
607                         othersSelected++;\r
608                         if (othersSelected > maxOtherNodesSelected)\r
609                             return false;\r
610                     }\r
611                 }\r
612             }\r
613             if (othersSelected > maxOtherNodesSelected)\r
614                 return false;\r
615         }\r
616         return true;\r
617     }\r
618 \r
619     protected ISnapAdvisor getSnapAdvisor() {\r
620         GridNode grid = lookupNode(GridNode.GRID_NODE_ID, GridNode.class);\r
621         return grid != null ? grid.getSnapAdvisor() : null;\r
622     }\r
623 \r
624     MoveAction.TargetFilter moveFilter = new MoveAction.TargetFilter() {\r
625         @Override\r
626         public boolean accept(Object target) {\r
627             return (target instanceof RouteLine) || (target instanceof RouteLink);\r
628         }\r
629     };\r
630 \r
631     @Override\r
632     protected boolean handleCommand(CommandEvent e) {\r
633         /*if (Commands.DELETE.equals(e.command)) {\r
634             Object target = rg.pick(mouseX, mouseY, pickTolerance);\r
635             return deleteTarget(target);\r
636         } else if (Commands.SPLIT_CONNECTION.equals(e.command)) {\r
637             Object target = rg.pick(mouseX, mouseY, pickTolerance);\r
638             return splitTarget(target);\r
639         } else */\r
640         if (Commands.CANCEL.equals(e.command)) {\r
641             return cancelCurrentAction();\r
642         }\r
643         return false;\r
644     }\r
645 \r
646     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
647         if (currentAction instanceof MoveAction) {\r
648             MoveAction move = (MoveAction) currentAction;\r
649             RouteGraph originalRg = rg.copy();\r
650             move.finish(mouseX, mouseY);\r
651 \r
652             setRouteGraphAndFireChanges(originalRg, rg);\r
653 \r
654             currentAction = null;\r
655             repaint();\r
656             return true;\r
657         }\r
658         return false;\r
659     }\r
660 \r
661     @Override\r
662     protected boolean keyPressed(KeyPressedEvent e) {\r
663         if (!editable)\r
664             return false;\r
665 \r
666         if (!e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.keyCode == KeyEvent.VK_S) {\r
667             Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES | RouteGraph.PICK_TRANSIENT_LINES);\r
668             return splitTarget(target);\r
669         }\r
670         else if (!e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK | MouseEvent.CTRL_MASK) && (e.keyCode == KeyEvent.VK_R || e.keyCode == KeyEvent.VK_D)) {\r
671             Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES);\r
672             return deleteTarget(target);\r
673         }\r
674         else if (e.keyCode == KeyEvent.VK_ESCAPE) {\r
675             return cancelCurrentAction();\r
676         }\r
677 //        else if (e.keyCode == KeyEvent.VK_D) {\r
678 //            if (target instanceof RouteTerminal) {\r
679 //                RouteTerminal terminal = (RouteTerminal) target;\r
680 //                RouteGraph before = rg.copy();\r
681 //                rg.toggleDirectLines(terminal);\r
682 //                setRouteGraphAndFireChanges(before, rg);\r
683 //                repaint();\r
684 //            }\r
685 //        }\r
686 //        else if (target != null && e.getKeyCode() == KeyEvent.VK_P) {\r
687 //            rg.print();\r
688 //        }\r
689         else if (e.keyCode == KeyEvent.VK_CONTROL) {\r
690             highlightActionsEnabled = true;\r
691             repaint();\r
692         }\r
693         else if (e.keyCode == KeyEvent.VK_ALT) {\r
694             // Begin connection branching visualization.\r
695             RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);\r
696             if (branchable && line != null) {\r
697                 newBranchPointPosition = new Point2D.Double(mouseX, mouseY);\r
698                 SplittedRouteGraph.snapToLine(newBranchPointPosition, line);\r
699                 repaint();\r
700             }\r
701         }\r
702 \r
703         return false;\r
704     }\r
705 \r
706     @Override\r
707     protected boolean keyReleased(KeyReleasedEvent e) {\r
708         if (e.keyCode == KeyEvent.VK_ALT) {\r
709             // End connection branching visualization.\r
710             if (newBranchPointPosition != null) {\r
711                 newBranchPointPosition = null;\r
712                 repaint();\r
713             }\r
714         }\r
715         if (e.keyCode == KeyEvent.VK_CONTROL) {\r
716             highlightActionsEnabled = false;\r
717             repaint();\r
718         }\r
719         return false;\r
720     }\r
721 \r
722 \r
723     private boolean cancelCurrentAction() {\r
724         if (currentAction != null) {\r
725             currentAction = null;\r
726             repaint();\r
727             return true;\r
728         }\r
729         return false;\r
730     }\r
731 \r
732     private boolean splitTarget(Object target) {\r
733         if (target instanceof RouteLine) {\r
734             RouteLine rLine = (RouteLine)target;\r
735             RouteGraph before = rg.copy();\r
736             rg.split(rLine, rLine.isHorizontal() ? mouseX : mouseY);\r
737             setRouteGraphAndFireChanges(before, rg);\r
738             repaint();\r
739             return true;\r
740         }\r
741         return false;\r
742     }\r
743 \r
744     private boolean deleteTarget(Object target) {\r
745         boolean changed = false;\r
746         if (target instanceof RouteLine) {\r
747             RouteLine line = (RouteLine) target;\r
748             RouteGraph before = rg.copy();\r
749             rg.merge(line);\r
750             changed = setRouteGraphAndFireChanges(before, rg);\r
751         }\r
752         else if (target instanceof RouteLink) {\r
753             RouteGraph before = rg.copy();\r
754             rg.deleteCorner((RouteLink) target);\r
755             changed = setRouteGraphAndFireChanges(before, rg);\r
756         }\r
757 //        else if (target instanceof RouteTerminal) {\r
758 //            RouteGraph before = rg.copy();\r
759 //            rg.remove((RouteTerminal) target);\r
760 //            changed = setRouteGraphAndFireChanges(before, rg);\r
761 //        }\r
762         if (changed)\r
763             repaint();\r
764         return changed;\r
765     }\r
766 \r
767     /**\r
768      * A version of MoveAction that snaps movements using the specified\r
769      * ISnapAdvisor.\r
770      */\r
771     static class SnappingMoveAction extends MoveAction {\r
772 \r
773         private ISnapAdvisor snapAdvisor;\r
774         private Point2D      point = new Point2D.Double();\r
775 \r
776         public SnappingMoveAction(RouteGraph rg, Object target, ISnapAdvisor snapAdvisor) {\r
777             super(rg, target);\r
778             this.snapAdvisor = snapAdvisor;\r
779         }\r
780 \r
781         protected void move(RouteGraph rg, Object target, double x, double y) {\r
782             point.setLocation(x, y);\r
783             snapAdvisor.snap(point);\r
784             super.move(rg, target, point.getX(), point.getY());\r
785         }\r
786 \r
787         public static MoveAction create(RouteGraph rg, double x, double y, double tolerance, TargetFilter filter, ISnapAdvisor snapAdvisor) {\r
788             Object target = rg.pick(x, y, tolerance, RouteGraph.PICK_LINES | RouteGraph.PICK_INTERIOR_POINTS);\r
789             if (target != null && (filter == null || filter.accept(target))) {\r
790                 if (snapAdvisor != null)\r
791                     return new SnappingMoveAction(rg, target, snapAdvisor);\r
792                 return new MoveAction(rg, target);\r
793             }\r
794             return null;\r
795         }\r
796 \r
797     }\r
798 \r
799     @Override\r
800     public int getEventMask() {\r
801         return EventTypes.CommandMask | EventTypes.KeyMask | EventTypes.MouseMask;\r
802     }\r
803 \r
804 }\r