]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / RouteGraphConnectTool.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2016 Association for Decentralized Information Management
3  * in 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  *     Semantum Oy - Fixed bug #6364
12  *******************************************************************************/
13 package org.simantics.diagram.participant;
14
15 import java.awt.AlphaComposite;
16 import java.awt.Composite;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Line2D;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.util.ArrayDeque;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Deque;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Set;
30
31 import org.simantics.Simantics;
32 import org.simantics.db.Resource;
33 import org.simantics.db.WriteGraph;
34 import org.simantics.db.common.request.WriteRequest;
35 import org.simantics.db.exception.DatabaseException;
36 import org.simantics.diagram.connection.RouteGraph;
37 import org.simantics.diagram.connection.RouteLine;
38 import org.simantics.diagram.connection.RoutePoint;
39 import org.simantics.diagram.connection.RouteTerminal;
40 import org.simantics.diagram.connection.delta.RouteGraphDelta;
41 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
42 import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
43 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
44 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
45 import org.simantics.diagram.connection.segments.Segment;
46 import org.simantics.diagram.synchronization.ISynchronizationContext;
47 import org.simantics.diagram.synchronization.SynchronizationHints;
48 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
49 import org.simantics.g2d.canvas.ICanvasContext;
50 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
51 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
52 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
53 import org.simantics.g2d.connection.ConnectionEntity;
54 import org.simantics.g2d.connection.IConnectionAdvisor;
55 import org.simantics.g2d.diagram.DiagramHints;
56 import org.simantics.g2d.diagram.DiagramUtils;
57 import org.simantics.g2d.diagram.IDiagram;
58 import org.simantics.g2d.diagram.handler.PickRequest;
59 import org.simantics.g2d.diagram.handler.Topology.Connection;
60 import org.simantics.g2d.diagram.handler.Topology.Terminal;
61 import org.simantics.g2d.diagram.participant.ElementPainter;
62 import org.simantics.g2d.diagram.participant.TerminalPainter;
63 import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
64 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
65 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
66 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
67 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
68 import org.simantics.g2d.element.ElementClass;
69 import org.simantics.g2d.element.ElementClasses;
70 import org.simantics.g2d.element.ElementHints;
71 import org.simantics.g2d.element.ElementUtils;
72 import org.simantics.g2d.element.IElement;
73 import org.simantics.g2d.element.IElementClassProvider;
74 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
75 import org.simantics.g2d.element.handler.SceneGraph;
76 import org.simantics.g2d.element.handler.TerminalTopology;
77 import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
78 import org.simantics.g2d.element.impl.Element;
79 import org.simantics.g2d.elementclass.FlagClass;
80 import org.simantics.g2d.elementclass.FlagHandler;
81 import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
82 import org.simantics.g2d.participant.TransformUtil;
83 import org.simantics.g2d.utils.geom.DirectionSet;
84 import org.simantics.scenegraph.g2d.G2DParentNode;
85 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
86 import org.simantics.scenegraph.g2d.events.KeyEvent;
87 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
88 import org.simantics.scenegraph.g2d.events.MouseEvent;
89 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;
90 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
91 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
92 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
93 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
94 import org.simantics.scenegraph.g2d.events.command.Commands;
95 import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
96 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
97 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
98 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
99 import org.simantics.scenegraph.utils.GeometryUtils;
100 import org.simantics.structural2.modelingRules.ConnectionJudgement;
101 import org.simantics.utils.datastructures.Pair;
102 import org.simantics.utils.datastructures.Triple;
103 import org.simantics.utils.logging.TimeLogger;
104 import org.simantics.utils.ui.ErrorLogger;
105 import org.simantics.utils.ui.ExceptionUtils;
106
107 import gnu.trove.map.hash.THashMap;
108
109 /**
110  * A basic tool for making connection on diagrams.
111  * 
112  * This version defines the starting, ending and route points of a connection.
113  * The routing itself is left up to the diagram router employed by
114  * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.
115  * 
116  * Manual:
117  * 
118  * This tool is added to the diagram when a connection sequence is initiated by
119  * another participant. PointerInteractor is one such participant which adds the
120  * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked
121  * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}
122  * ). The connection will be finished when another allowed terminal is clicked
123  * upon or empty canvas space is ALT+clicked. Route points for the connection
124  * can be created by clicking around on non-terminal-occupied canvas space while
125  * connecting.
126  * 
127  * <p>
128  * Connections can be started from and ended in flags by pressing ALT while
129  * left-clicking.
130  * 
131  * @author Tuukka Lehtonen
132  */
133 public class RouteGraphConnectTool extends AbstractMode {
134
135     private static final String      END_TERMINAL_DATA     = "END";
136
137     public static final int          PAINT_PRIORITY        = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
138
139     @Dependency
140     protected TransformUtil          util;
141
142     @Dependency
143     protected ElementPainter         diagramPainter;
144
145     @Dependency
146     protected PointerInteractor      pi;
147
148     /**
149      * Starting point designation.
150      * 
151      * The value is received by the constructor.
152      */
153     protected RouteGraphTarget       startingPoint;
154
155     protected TerminalInfo           startTerminal         = new TerminalInfo();
156
157     /**
158      * <code>true</code> if this tool should create connection continuation
159      * flags, <code>false</code> otherwise.
160      */
161     protected boolean                createFlags;
162
163     /**
164      * 
165      */
166     protected IElementClassProvider  elementClassProvider;
167
168     /**
169      * 
170      */
171     protected Deque<ControlPoint>    controlPoints         = new ArrayDeque<ControlPoint>();
172
173     /**
174      * Element terminal of connection end element. <code>null</code> if
175      * connection cannot be ended where it is currently being attempted to end.
176      */
177     protected TerminalInfo           endTerminal;
178
179     /**
180      * The latest connectability judgment from the active
181      * {@link IConnectionAdvisor} should the connection happen between
182      * {@link #startTerminal} and {@link #endTerminal}.
183      */
184     protected ConnectionJudgement    connectionJudgment;
185
186     /**
187      * A temporary variable for use with
188      * {@link TerminalTopology#getTerminals(IElement, Collection)}.
189      */
190     protected Collection<Terminal>   terminals             = new ArrayList<Terminal>();
191
192     /**
193      * Previous mouse canvas position recorded by
194      * {@link #processMouseMove(MouseMovedEvent)}.
195      */
196     protected Point2D                lastMouseCanvasPos    = new Point2D.Double();
197
198     /**
199      * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been
200      * invoked at least once. This is used to tell whether to allow creation of
201      * branch points or finising the connection in thin air. It will not be
202      * allowed if the mouse has not moved at all since starting the connection.
203      */
204     protected boolean                mouseHasMoved         = false;
205
206     protected TerminalHoverStrategy  originalStrategy      = null;
207
208     protected TerminalHoverStrategy  terminalHoverStrategy = new TerminalHoverStrategy() {
209         @Override
210         public boolean highlightEnabled() {
211             return !isEndingInFlag();
212         }
213
214         @Override
215         public boolean highlight(TerminalInfo ti) {
216             return canConnect(ti.e, ti.t) != null;
217         }
218     };
219
220     protected final static Composite ALPHA_COMPOSITE       = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
221
222     /**
223      * Root scene graph node for all visualization performed by this tool.
224      */
225     protected ConnectionNode         ghostNode;
226
227     protected RouteGraphNode           rgNode;
228
229     protected  RouteGraph               routeGraph;
230     protected  RouteTerminal            endRouteTerminal;
231     private ILineEndStyle            endTerminalStyle;
232
233     /**
234      * Indicates whether the connection is about to be ended into a new
235      * flag/branchpoint or not.
236      */
237     protected TerminalInfo           endFlag;
238
239     protected G2DParentNode          endFlagNode;
240
241     private RouteLine                attachedToRouteLine;
242
243     protected RouteGraph               beforeRouteGraph;
244
245     private IRouteGraphRenderer      beforeRenderer;
246
247     private boolean                  beforeEditable;
248
249     protected RouteGraphDelta          routeGraphDelta;
250
251     private Set<Pair<IElement, Terminal>> alreadyConnected;
252     
253     /**
254      * @param startElement
255      * @param routeGraphConnection
256      * @param mouseId
257      * @param startCanvasPos
258      */
259     public RouteGraphConnectTool(RouteGraphTarget startingPoint, int mouseId) {
260         super(mouseId);
261
262         Point2D intersection = startingPoint.getIntersectionPosition();
263
264         this.startingPoint = startingPoint;
265         this.lastMouseCanvasPos.setLocation(intersection);
266
267         BranchPointTerminal t = BranchPointTerminal.existingTerminal(
268                 AffineTransform.getTranslateInstance(intersection.getX(), intersection.getY()),
269                 DirectionSet.ANY,
270                 BranchPointNode.SHAPE);
271
272         Point2D p = startingPoint.getIntersectionPosition();
273         AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
274
275         startTerminal.e = startingPoint.getElement();
276         startTerminal.t = t;
277         startTerminal.posElem = at;
278         startTerminal.posDia = at;
279         startTerminal.shape = t.getShape();
280
281         controlPoints.add( newControlPoint( startingPoint.getIntersectionPosition() ) );
282         controlPoints.add( newControlPoint( startingPoint.getCanvasPosition() ) );
283
284         alreadyConnected = new HashSet<Pair<IElement,Terminal>>();
285         IElement connection = startingPoint.getElement();
286         ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
287         Collection<Connection> tcs = ce.getTerminalConnections(null);
288         for (Connection tc : tcs)
289             alreadyConnected.add(Pair.make(tc.node, tc.terminal));
290     }
291
292     @Override
293     public void addedToContext(ICanvasContext ctx) {
294         super.addedToContext(ctx);
295
296         // Force terminals to always be highlighted without pressing certain
297         // keys or key combinations.
298         originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
299         setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
300     }
301
302     @Override
303     protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
304         if (newDiagram != null) {
305             // Get IElementClassProvider
306             ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);
307             if (ctx != null) {
308                 this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);
309             }
310
311             // See if flags should be created or not.
312             this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));
313         }
314     }
315
316     @Override
317     public void removedFromContext(ICanvasContext ctx) {
318         if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {
319             if (originalStrategy != null)
320                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);
321             else
322                 removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
323         }
324
325         super.removedFromContext(ctx);
326     }
327
328     int straightDirections(RouteLine line) {
329         return line.isHorizontal()
330                 ? (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP)
331                 : (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
332     }
333
334     private static RouteTerminal addTerminal(Object data, RouteGraph rg, double x, double y, Rectangle2D bounds, int allowedDirections, ILineEndStyle style) {
335         RouteTerminal rt;
336         if (bounds != null)
337             rt = rg.addTerminal(x, y, bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), allowedDirections, style);
338         else
339             rt = rg.addTerminal(x, y, x, y, x, y, allowedDirections, style);
340         rt.setData( data );
341         return rt;
342     }
343
344     private RouteTerminal setEndTerminal(double x, double y, Rectangle2D bounds, int allowedDirections) {
345
346         // First add then remove to prevent deletion of route lines in case there are 2 only terminals
347         
348         RouteTerminal toRemove = endRouteTerminal;
349         
350         endRouteTerminal = addTerminal( END_TERMINAL_DATA, routeGraph, x, y, bounds, allowedDirections, endTerminalStyle );
351         routeGraph.link( attachedToRouteLine, endRouteTerminal );
352         
353         if (toRemove != null)
354             routeGraph.remove(toRemove);
355         
356         return endRouteTerminal;
357         
358     }
359
360     protected void setEndTerminal(TerminalInfo ti) {
361         Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
362         GeometryUtils.expandRectangle(bounds, 2);
363         int dir = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
364                 ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
365         setEndTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds, dir);
366     }
367
368     @SGInit
369     public void initSG(G2DParentNode parent) {
370         ghostNode = parent.addNode("branched connection", ConnectionNode.class);
371         //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.5f));
372         ghostNode.setZIndex(PAINT_PRIORITY);
373
374         rgNode = ghostNode.addNode("branch", RouteGraphNode.class);
375
376         double ex = startingPoint.getCanvasPosition().getX();
377         double ey = startingPoint.getCanvasPosition().getY();
378
379         beforeRouteGraph = startingPoint.getNode().getRouteGraph();
380         beforeEditable = startingPoint.getNode().isEditable();
381
382         RouteGraphNode beforeRgNode = startingPoint.getNode();
383         beforeRenderer = beforeRgNode.getRenderer();
384
385         rgNode.setRenderer(beforeRenderer);
386         rgNode.setEditable(false);
387
388         beforeRgNode.setEditable(beforeEditable);
389         beforeRgNode.setRenderer(null);
390         
391         initRG(ex, ey);
392         
393     }
394     
395     public void initRG(double ex, double ey) {
396         
397         THashMap<Object, Object> map = new THashMap<Object, Object>();
398         routeGraph = beforeRouteGraph.copy(map);
399
400         endTerminalStyle = PlainLineEndStyle.INSTANCE;
401         for (RouteTerminal t : routeGraph.getTerminals()) {
402             if (t.getRenderStyle() instanceof ArrowLineEndStyle) {
403                 endTerminalStyle = t.getRenderStyle();
404                 break;
405             }
406         }
407
408         attachedToRouteLine = (RouteLine) map.get(startingPoint.getLine());
409         routeGraph.makePersistent(attachedToRouteLine);
410         for (RouteLine line : routeGraph.getAllLines()) {
411             if (!line.isTransient() && line.isHorizontal() == attachedToRouteLine.isHorizontal()
412                     && line.getPosition() == attachedToRouteLine.getPosition()) {
413                 attachedToRouteLine = line;
414                 break;
415             }
416         }
417         routeGraphDelta = new RouteGraphDelta(beforeRouteGraph, routeGraph);
418
419 //        beforeRouteGraph.print();
420 //        routeGraph.print();
421 //        routeGraphDelta.print();
422
423         setEndTerminal(ex, ey, null, 0xf);
424
425         rgNode.setRouteGraph(routeGraph);
426         
427     }
428     
429     @SGCleanup
430     public void cleanupSG() {
431         RouteGraphNode beforeRgNode = startingPoint.getNode();
432         beforeRgNode.setRouteGraph(beforeRouteGraph);
433         beforeRgNode.setRenderer(beforeRenderer);
434         beforeRgNode.setEditable(beforeEditable);
435
436         ghostNode.remove();
437         ghostNode = null;
438
439         setDirty();
440     }
441
442     @EventHandler(priority = 200)
443     public boolean handleCommandEvents(CommandEvent ce) {
444         if (ce.command.equals(Commands.CANCEL)) {
445             setDirty();
446             remove();
447             return true;
448         } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {
449             // TODO: rotate flag?
450         }
451         return false;
452     }
453
454     @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
455     public boolean handleKeyEvents(KeyEvent ke) {
456         if (ke instanceof KeyPressedEvent) {
457             // Back-space, cancel prev bend
458             if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)
459                 return cancelPreviousBend();
460         }
461
462         if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
463             if (createFlags) {
464                 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));
465                 return true;
466             }
467         }
468
469         return false;
470     }
471
472     @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
473     public boolean handleEvent(MouseEvent me) {
474         // Only handle events for the connection-initiating mouse
475         if (me.mouseId != mouseId)
476             return false;
477
478         if (me instanceof MouseMovedEvent)
479             return processMouseMove((MouseMovedEvent) me);
480
481         if (me instanceof MouseButtonPressedEvent)
482             return processMouseButtonPress((MouseButtonPressedEvent) me);
483
484         // #7653: Support creating connections between terminals without lifting mouse button in between.
485         if (me instanceof MouseButtonReleasedEvent)
486             return processMouseButtonRelease((MouseButtonReleasedEvent) me);
487
488         return false;
489     }
490
491     protected boolean processMouseMove(MouseMovedEvent me) {
492         mouseHasMoved = true;
493
494         Point2D mouseControlPos = me.controlPosition;
495         Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
496
497         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
498         if (snapAdvisor != null)
499             snapAdvisor.snap(mouseCanvasPos);
500
501         // Record last snapped canvas position of mouse.
502         this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
503
504         if (isEndingInFlag()) {
505             endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
506         }
507
508         List<TerminalInfo> tis = pi.pickTerminals(me.controlPosition);
509         tis = TerminalUtil.findNearestOverlappingTerminals(tis);
510         if (!tis.isEmpty()) {
511             for (TerminalInfo ti : tis) {
512                 Object canConnect = canConnect(ti.e, ti.t);
513
514                 if (canConnect != null) {
515                     connectionJudgment = (ConnectionJudgement) canConnect;
516
517                     if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
518                         controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);
519
520                         endTerminal = ti;
521
522                         connect(ti);
523
524                     }
525
526                     // Make sure that we are ending with a flag if ALT is pressed
527                     // and no end terminal is defined.
528                     endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
529
530                     updateSG(new Point2D.Double(ti.posDia.getTranslateX(), ti.posDia.getTranslateY()));
531                     return false;
532                 }
533             }
534         }
535
536         connectionJudgment = null;
537         if (isEndTerminalDefined()) {
538             // CASE: Mouse was previously on top of a valid terminal to end
539             // the connection. Now the mouse has been moved where there is
540             // no longer a terminal to connect to.
541             //
542             // => Disconnect the last edge segment from the previous
543             // terminal, mark endElement/endTerminal non-existent
544             // and connect the disconnected edge to a new branch point.
545
546             disconnect(mouseCanvasPos);
547             
548             controlPoints.getLast()
549             .setPosition(mouseCanvasPos)
550             .setAttachedToTerminal(null);
551
552             endTerminal = null;
553         } else {
554             // CASE: Mouse was not previously on top of a valid ending
555             // element terminal.
556             //
557             // => Move and re-orient last branch point.
558
559             controlPoints.getLast()
560             .setPosition(mouseCanvasPos);
561         }
562
563         // Make sure that we are ending with a flag if ALT is pressed and no end
564         // terminal is defined.
565         endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
566
567         updateSG(lastMouseCanvasPos);
568
569         return false;
570     }
571
572     protected void connect(TerminalInfo ti) {
573         setEndTerminal(ti);
574     }
575     
576     protected void disconnect(Point2D mouseCanvasPos) {
577         setEndTerminal(mouseCanvasPos.getX(), mouseCanvasPos.getY(), null, 0xf);
578     }
579
580     protected boolean processMouseButtonPress(MouseButtonEvent me) {
581         // Do nothing before the mouse has moved at least a little.
582         // This prevents the user from ending the connection right where
583         // it started.
584         if (!mouseHasMoved)
585             return true;
586
587         if (me.button == MouseEvent.LEFT_BUTTON) {
588             Point2D mouseControlPos = me.controlPosition;
589             Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
590
591             ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
592             if (snapAdvisor != null)
593                 snapAdvisor.snap(mouseCanvasPos);
594
595             // Clicked on an allowed end terminal. End connection & end mode.
596             if (tryEndConnection()) {
597                 return true;
598             } else {
599                 // Finish connection in thin air only if the
600                 // connection was started from a valid terminal.
601                 if ((me.stateMask & MouseEvent.ALT_MASK) != 0 && startTerminal != null) {
602                     connectionJudgment = (ConnectionJudgement) canConnect(null, null);
603                     if (connectionJudgment == null)
604                         return true;
605                     createConnection();
606                     remove();
607                     return true;
608                 } else if (routePointsAllowed()
609                         && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
610                     // Add new connection control point.
611                     controlPoints.add(newControlPoint(mouseCanvasPos));
612                     updateSG(mouseCanvasPos);
613                 }
614             }
615
616             // Eat the event to prevent other participants from doing
617             // incompatible things while in this connection mode.
618             return true;
619         } else if (me.button == MouseEvent.RIGHT_BUTTON) {
620             return cancelPreviousBend();
621         }
622
623         return false;
624     }
625
626     private int mouseLeftReleaseCount = 0;
627
628     protected boolean processMouseButtonRelease(MouseButtonReleasedEvent me) {
629         if (me.button == MouseEvent.LEFT_BUTTON
630                 && ++mouseLeftReleaseCount == 1) {
631             return tryEndConnection();
632         }
633         return false;
634     }
635
636     /**
637      * @return <code>true</code> if connection was successfully ended
638      */
639     private boolean tryEndConnection() {
640         if (isEndTerminalDefined()) {
641             createConnection();
642             remove();
643             return true;
644         }
645         return false;
646     }
647
648     protected boolean cancelPreviousBend() {
649         if (!routePointsAllowed())
650             return false;
651
652         // Just to make this code more comprehensible, prevent an editing
653         // case that requires ugly code to work.
654         if (isEndingInFlag())
655             return true;
656
657         // If there are no real route points, cancel whole connection.
658         if (controlPoints.size() <= 2) {
659             setDirty();
660             remove();
661             return true;
662         }
663
664         // Cancel last bend
665         controlPoints.removeLast();
666         controlPoints.getLast().setPosition(lastMouseCanvasPos);
667
668         updateSG(lastMouseCanvasPos);
669         return true;
670     }
671
672     protected void updateSG(Point2D mousePos) {
673         routeGraph.setLocation(endRouteTerminal, mousePos.getX(), mousePos.getY());
674         //routeGraph.print(System.err);
675         setDirty();
676     }
677
678     protected boolean shouldEndWithFlag(MouseEvent me) {
679         return shouldEndWithFlag((me.stateMask & MouseEvent.ALT_MASK) != 0);
680     }
681
682     protected boolean shouldEndWithFlag(boolean altPressed) {
683         return altPressed && !isEndTerminalDefined() && createFlags;
684     }
685
686     protected boolean isEndTerminalDefined() {
687         return endTerminal != null;
688     }
689
690     protected boolean isFlagTerminal(TerminalInfo ti) {
691         return ti.e.getElementClass().containsClass(FlagHandler.class);
692     }
693
694
695     protected boolean isEndingInFlag() {
696         return endFlag != null;
697     }
698
699     protected void endWithoutTerminal(Point2D mousePos, boolean altDown) {
700         // Just go with branch points if flags are not allowed.
701         if (!createFlags)
702             return;
703
704         boolean endTerminalDefined = isEndTerminalDefined();
705
706         if (altDown) {
707             if (!isEndingInFlag()) {
708                 endFlag = createFlag(EdgeEnd.End);
709                 endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);
710                 controlPoints.getLast()
711                 .setAttachedToTerminal(endFlag);
712
713                 // TerminalPainter must refresh
714                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
715
716                 updateSG(mousePos);
717             }
718         } else {
719             if (isEndingInFlag()) {
720                 // Currently ending with flag but ALT is no longer down
721                 // so that flag must be removed.
722                 endFlag = null;
723                 endFlagNode.remove();
724                 endFlagNode = null;
725
726                 ControlPoint cp = controlPoints.getLast();
727                 cp.setAttachedToTerminal(endTerminal);
728
729                 if (endTerminalDefined) {
730                     cp.setPosition(endTerminal.posDia);
731                 } else {
732                     cp.setPosition(mousePos);
733                 }
734
735                 // Force Terminalpainter refresh
736                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
737
738                 updateSG(mousePos);
739             }
740         }
741     }
742
743     private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {
744         return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));
745     }
746
747     private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {
748         G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);
749         elementParent.setTransform(tr);
750         elementParent.removeNodes();
751         for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))
752             sg.init(element, elementParent);
753         return elementParent;
754     }
755
756     /**
757      * @param canvasPos
758      * @return
759      */
760     protected ControlPoint newControlPoint(Point2D canvasPos) {
761         return new ControlPoint(canvasPos);
762     }
763
764     protected Triple<RouteGraph,RouteGraph,RouteGraphDelta> prepareRouteGraphDelta() {
765         // Prevent route graph connection synchronization from crashing on the
766         // transient route terminal that doesn't exist yet. It is created after
767         // persisting the route line, which is the only purpose of this route
768         // graph synchronization.
769         routeGraph.remove(endRouteTerminal);
770         return Triple.make(beforeRouteGraph, routeGraph, routeGraphDelta);
771     }
772
773     protected void createConnection() {
774         TimeLogger.resetTimeAndLog(getClass(), "createConnection");
775         final ConnectionJudgement judgment = this.connectionJudgment;
776         if (judgment == null) {
777             ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity", null);
778             return;
779         }
780
781         final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
782         final Triple<RouteGraph,RouteGraph,RouteGraphDelta> rgs = prepareRouteGraphDelta();
783
784         Simantics.getSession().asyncRequest(new WriteRequest() {
785             @Override
786             public void perform(WriteGraph graph) throws DatabaseException {
787                 graph.markUndoPoint();
788                 Resource connection = ElementUtils.getObject(startTerminal.e);
789                 if (!rgs.third.isEmpty()) {
790                     new RouteGraphConnection(graph, connection).synchronize(graph, rgs.first, rgs.second, rgs.third);
791                 }
792                 Resource attachToLine = RouteGraphConnection.deserialize(graph, attachedToRouteLine.getData());
793                 builder.attachToRouteGraph(graph, judgment, connection, attachToLine, controlPoints, endTerminal, FlagClass.Type.Out);
794             }
795         }, e -> {
796             if (e != null)
797                 ExceptionUtils.logAndShowError(e);
798         });
799     }
800
801     protected boolean routePointsAllowed() {
802         return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
803     }
804
805     protected Object canConnect(IElement endElement, Terminal endTerminal) {
806         IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
807         return canConnect(advisor, endElement, endTerminal);
808     }
809
810     protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
811         if (advisor == null)
812             return ConnectionJudgement.CANBEMADELEGAL;
813         if (alreadyConnected.contains(Pair.make(endElement, endTerminal)))
814             return null;
815         return advisor.canBeConnected(null, startTerminal.e, startTerminal.t, endElement, endTerminal);
816     }
817
818     protected static FlagClass.Type endToFlagType(EdgeEnd end) {
819         switch (end) {
820             case Begin:
821                 return FlagClass.Type.In;
822             case End:
823                 return FlagClass.Type.Out;
824             default:
825                 throw new IllegalArgumentException("unrecognized edge end: " + end);
826         }
827     }
828
829     protected TerminalInfo createFlag(EdgeEnd connectionEnd) {
830         ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);
831         IElement e = Element.spawnNew(flagClass);
832
833         e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));
834         e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);
835
836         TerminalInfo ti = new TerminalInfo();
837         ti.e = e;
838         ti.t = ElementUtils.getSingleTerminal(e);
839         ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);
840         ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);
841
842         return ti;
843     }
844
845     // ------------------------------------------------------------------------
846
847     static RouteGraphTarget pickRouteGraphConnection(ICanvasContext ctx, IDiagram diagram, Shape pickShape, double pickDistance) {
848         ArrayList<IElement> elements = new ArrayList<IElement>();
849         PickRequest req = new PickRequest(pickShape).context(ctx);
850         DiagramUtils.pick(diagram, req, elements);
851         for (Iterator<IElement> it = elements.iterator(); it.hasNext();) {
852             IElement e = it.next();
853             RouteGraphNode rgn = e.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
854             if (rgn == null || rgn.getRouteGraph() == null)
855                 it.remove();
856         }
857         if (elements.isEmpty())
858             return null;
859
860         Rectangle2D pickRect = pickShape.getBounds2D();
861         final double x = pickRect.getCenterX();
862         final double y = pickRect.getCenterY();
863
864         return pickNearestRouteGraphConnection(elements, x, y, pickDistance);
865     }
866
867     private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList<IElement> elements, double x, double y, double pd) {
868         Segment nearestSegment = null;
869         RouteLine nearestLine = null;
870         IElement nearestConnection = null;
871
872         double minDistanceSq = Double.MAX_VALUE;
873         
874         for (IElement connection : elements) {
875             RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
876             for (RouteLine line : rgn.getRouteGraph().getAllLines()) {
877                 ArrayList<Segment> segments = new ArrayList<Segment>();
878                 line.collectSegments(segments);
879                 for (Segment segment : segments) {
880                     RoutePoint p1 = segment.p1;
881                     RoutePoint p2 = segment.p2;
882
883                     double distanceSq = Line2D.ptSegDistSq(p1.getX(), p1.getY(), p2.getX(), p2.getY(), x, y);
884                     if (distanceSq < minDistanceSq && distanceSq < Math.pow(pd + rgn.getSelectionStrokeWidth() / 2, 2)) {
885                         minDistanceSq = distanceSq;
886                         nearestSegment = segment;
887                         nearestLine = line;
888                         nearestConnection = connection;
889                     }
890                 }
891             }
892         }
893
894         if (nearestSegment != null) {
895             RoutePoint p1 = nearestSegment.p1;
896             RoutePoint p2 = nearestSegment.p2;
897             double d = Math.pow(p2.getX() - p1.getX(), 2.0) + Math.pow(p2.getY() - p1.getY(), 2.0);
898             Point2D p;
899             if (d == 0) {
900                 p = new Point2D.Double(p1.getX(), p1.getY());
901             } else {
902                 double u = ((x - p1.getX()) * (p2.getX() - p1.getX()) + (y - p1.getY()) * (p2.getY() - p1.getY())) / d;
903                 if (u > 1.0) {
904                     p = new Point2D.Double(p2.getX(), p2.getY());
905                 } else if (u <= 0.0) {
906                     p = new Point2D.Double(p1.getX(), p1.getY());
907                 } else {
908                     p = new Point2D.Double(p2.getX() * u + p1.getX() * (1.0-u), (p2.getY() * u + p1.getY() * (1.0- u)));
909                 }
910             }
911             return new RouteGraphTarget(nearestConnection, nearestConnection.getHint(RouteGraphConnectionClass.KEY_RG_NODE), nearestLine, new Point2D.Double(x, y), p);
912         } else {
913             return null;
914         }
915     }
916
917     static Point2D intersectionPoint(double x, double y, RouteLine line) {
918         Collection<RoutePoint> points = line.getPoints();
919         if (points.size() < 2)
920             return null;
921         RoutePoint s = line.getBegin();
922         RoutePoint e = line.getEnd();
923         return GeometryUtils.intersectionToLine(s.getX(), s.getY(), e.getX(), e.getY(), x, y);
924     }
925
926 }