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