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