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