]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / ConnectTool2.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 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  *******************************************************************************/
12 package org.simantics.diagram.participant;
13
14 import java.awt.AlphaComposite;
15 import java.awt.BasicStroke;
16 import java.awt.Color;
17 import java.awt.Composite;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Path2D;
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.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Deque;
28 import java.util.Iterator;
29 import java.util.List;
30
31 import org.simantics.Simantics;
32 import org.simantics.db.ReadGraph;
33 import org.simantics.db.Resource;
34 import org.simantics.db.WriteGraph;
35 import org.simantics.db.common.request.UniqueRead;
36 import org.simantics.db.common.request.WriteRequest;
37 import org.simantics.db.common.utils.NameUtils;
38 import org.simantics.db.exception.DatabaseException;
39 import org.simantics.diagram.connection.RouteGraph;
40 import org.simantics.diagram.connection.RouteGraphConnectionClass;
41 import org.simantics.diagram.connection.RouteLine;
42 import org.simantics.diagram.connection.RouteTerminal;
43 import org.simantics.diagram.connection.delta.RouteGraphDelta;
44 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
45 import org.simantics.diagram.content.ResourceTerminal;
46 import org.simantics.diagram.stubs.DiagramResource;
47 import org.simantics.diagram.synchronization.ISynchronizationContext;
48 import org.simantics.diagram.synchronization.SynchronizationHints;
49 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
50 import org.simantics.g2d.canvas.ICanvasContext;
51 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
52 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
53 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
54 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
55 import org.simantics.g2d.connection.IConnectionAdvisor;
56 import org.simantics.g2d.diagram.DiagramHints;
57 import org.simantics.g2d.diagram.DiagramUtils;
58 import org.simantics.g2d.diagram.IDiagram;
59 import org.simantics.g2d.diagram.handler.PickContext;
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.ElementUtils;
71 import org.simantics.g2d.element.IElement;
72 import org.simantics.g2d.element.IElementClassProvider;
73 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
74 import org.simantics.g2d.element.handler.SceneGraph;
75 import org.simantics.g2d.element.handler.TerminalTopology;
76 import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
77 import org.simantics.g2d.element.impl.Element;
78 import org.simantics.g2d.elementclass.BranchPoint;
79 import org.simantics.g2d.elementclass.BranchPoint.Direction;
80 import org.simantics.g2d.elementclass.FlagClass;
81 import org.simantics.g2d.elementclass.FlagHandler;
82 import org.simantics.g2d.participant.RenderingQualityInteractor;
83 import org.simantics.g2d.participant.TransformUtil;
84 import org.simantics.g2d.utils.geom.DirectionSet;
85 import org.simantics.modeling.ModelingResources;
86 import org.simantics.scenegraph.g2d.G2DParentNode;
87 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
88 import org.simantics.scenegraph.g2d.events.KeyEvent;
89 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
90 import org.simantics.scenegraph.g2d.events.MouseEvent;
91 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;
92 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
93 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
94 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
95 import org.simantics.scenegraph.g2d.events.command.Commands;
96 import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
97 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
98 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
99 import org.simantics.scenegraph.utils.GeometryUtils;
100 import org.simantics.scenegraph.utils.Quality;
101 import org.simantics.structural2.modelingRules.ConnectionJudgement;
102 import org.simantics.utils.datastructures.Pair;
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 ConnectTool2 extends AbstractMode {
134
135     public static final int          PAINT_PRIORITY        = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
136
137     @Reference
138     protected RenderingQualityInteractor quality;
139
140     @Dependency
141     protected TransformUtil          util;
142
143     @Dependency
144     protected ElementPainter         diagramPainter;
145
146     @Dependency
147     protected PointerInteractor      pi;
148
149     @Dependency
150     protected PickContext            pickContext;
151
152     /**
153      * Start element terminal of the connection. <code>null</code> if connection
154      * was started from a flag or a branch point.
155      * 
156      * The value is received by the constructor.
157      */
158     protected List<TerminalInfo>     startTerminals;
159
160     /**
161      * Refers to any of the possible overlapping start terminals. The value is
162      * taken from the first index of {@link #startTerminals} assuming that the
163      * first one is the nearest. It is <code>null</code> if
164      * {@link #startTerminals} is empty.
165      */
166     protected TerminalInfo           startTerminal;
167
168     protected TerminalInfo           startFlag;
169
170     /**
171      * Starting position of the connection, received as an external argument.
172      */
173     protected final Point2D          startPos;
174
175     /**
176      * <code>true</code> if this tool should create connection continuation
177      * flags, <code>false</code> otherwise.
178      */
179     protected boolean                createFlags;
180
181     /**
182      * 
183      */
184     protected IElementClassProvider  elementClassProvider;
185
186     /**
187      * 
188      */
189     protected Deque<ControlPoint>    controlPoints         = new ArrayDeque<ControlPoint>();
190
191     /**
192      * Contains <code>null</code> when a connection is started from a new flag
193      * or one of the terminals in {@link #startTerminals} when a connection is
194      * being created starting from a terminal or possibly a set of terminals.
195      * 
196      * <p>
197      * Note that this is different from {@link #startTerminal} which simply
198      * represents the first element of {@link #startTerminals}.
199      * 
200      * <p>
201      * Only when this value and {@link #endTerminal} is properly set will a
202      * connection be created between two element terminals.
203      */
204     protected TerminalInfo           selectedStartTerminal;
205
206     /**
207      * Element terminal of connection end element. <code>null</code> if
208      * connection cannot be ended where it is currently being attempted to end.
209      */
210     protected TerminalInfo           endTerminal;
211
212     /**
213      * The latest connectability judgment from the active
214      * {@link IConnectionAdvisor} should the connection happen between
215      * {@link #selectedStartTerminal} and {@link #endTerminal}.
216      */
217     protected ConnectionJudgement    connectionJudgment;
218
219     /**
220      * The latest connectability judgment from the active
221      * {@link IConnectionAdvisor} should the connection happen between
222      * {@link #selectedStartTerminal} and {@link #lastRouteGraphTarget}.
223      */
224     protected ConnectionJudgement    attachToConnectionJudgement;
225
226     /**
227      * If non-null during connection drawing this field tells the direction
228      * forced for the current branch point by the user through the UI commands
229      * {@link Commands#ROTATE_ELEMENT_CCW} and
230      * {@link Commands#ROTATE_ELEMENT_CW}.
231      */
232     private Direction                forcedBranchPointDirection;
233
234     /**
235      * A temporary variable for use with
236      * {@link TerminalTopology#getTerminals(IElement, Collection)}.
237      */
238     protected Collection<Terminal>   terminals             = new ArrayList<Terminal>();
239
240     /**
241      * Previous mouse canvas position recorded by
242      * {@link #processMouseMove(MouseMovedEvent)}.
243      */
244     protected Point2D                lastMouseCanvasPos    = new Point2D.Double();
245
246     /**
247      * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been
248      * invoked at least once. This is used to tell whether to allow creation of
249      * branch points or finising the connection in thin air. It will not be
250      * allowed if the mouse has not moved at all since starting the connection.
251      */
252     protected boolean                mouseHasMoved         = false;
253
254     protected TerminalHoverStrategy  originalStrategy      = null;
255
256     protected TerminalHoverStrategy  terminalHoverStrategy = new TerminalHoverStrategy() {
257         @Override
258         public boolean highlightEnabled() {
259             return !isEndingInFlag();
260         }
261
262         @Override
263         public boolean highlight(TerminalInfo ti) {
264             boolean reflexive = isStartTerminal(ti.e, ti.t);
265             if (reflexive && !allowReflexiveConnections())
266                 return false;
267
268             return canConnect(ti.e, ti.t) != null;
269         }
270     };
271
272     protected final static Composite ALPHA_COMPOSITE       = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
273
274     /**
275      * Root scene graph node for all visualization performed by this tool.
276      */
277     protected G2DParentNode          ghostNode;
278
279     /**
280      * Indicates whether the connection is about to be ended into a new
281      * flag/branchpoint or not.
282      */
283     protected TerminalInfo           endFlag;
284
285     protected G2DParentNode          endFlagNode;
286
287     private RouteGraphTarget         lastRouteGraphTarget;
288
289     /**
290      * @param startTerminal
291      * @param mouseId
292      * @param startCanvasPos
293      */
294     public ConnectTool2(TerminalInfo startTerminal, int mouseId, Point2D startCanvasPos) {
295         this(startTerminal == null ? Collections.<TerminalInfo> emptyList()
296                 : Collections.singletonList(startTerminal),
297                 mouseId,
298                 startCanvasPos);
299     }
300
301     /**
302      * @param startTerminals
303      * @param mouseId
304      * @param startCanvasPos
305      */
306     public ConnectTool2(List<TerminalInfo> startTerminals, int mouseId, Point2D startCanvasPos) {
307         super(mouseId);
308
309         if (startCanvasPos == null)
310             throw new NullPointerException("null start position");
311         if (startTerminals == null)
312             throw new NullPointerException("null start terminals");
313
314         this.startPos = startCanvasPos;
315         this.lastMouseCanvasPos.setLocation(startPos);
316
317         this.startTerminals = startTerminals;
318         this.startTerminal = startTerminals.isEmpty() ? null : startTerminals.get(0);
319     }
320
321     @Override
322     public void addedToContext(ICanvasContext ctx) {
323         super.addedToContext(ctx);
324
325         if (quality != null)
326             quality.setStaticQuality(Quality.LOW);
327
328         // Force terminals to always be highlighted without pressing certain
329         // keys or key combinations.
330         originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
331         setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
332     }
333
334     @Override
335     protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
336         if (newDiagram != null) {
337             // Get IElementClassProvider
338             ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);
339             if (ctx != null) {
340                 this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);
341             }
342
343             // See if flags should be created or not.
344             this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));
345             startConnection();
346         }
347     }
348
349     @Override
350     public void removedFromContext(ICanvasContext ctx) {
351         if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {
352             if (originalStrategy != null)
353                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);
354             else
355                 removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
356         }
357
358         if (quality != null)
359             quality.setStaticQuality(null);
360
361         super.removedFromContext(ctx);
362     }
363
364     protected void startConnection() {
365         Point2D startPos = (Point2D) this.startPos.clone();
366         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
367         if (snapAdvisor != null)
368             snapAdvisor.snap(startPos);
369
370         // Resolve the first element and terminal of the connection.
371         ControlPoint start = new ControlPoint(startPos);
372
373         if (startTerminal != null) {
374             assert ElementUtils.peekDiagram(startTerminal.e) == diagram;
375             Point2D terminalPos = new Point2D.Double(startTerminal.posDia.getTranslateX(),
376                     startTerminal.posDia.getTranslateY());
377             start.setPosition(terminalPos).setAttachedToTerminal(startTerminal);
378         } else {
379             // Create TerminalInfo describing the flag to be created.
380             if (createFlags) {
381                 // This prevents connection creation from creating a branch
382                 // point in place of this flag.
383                 startFlag = createFlag(EdgeEnd.Begin);
384                 start.setAttachedToTerminal(startFlag);
385                 showElement(ghostNode, "startFlag", startFlag.e, startPos);
386             }
387         }
388         controlPoints.add(start);
389         controlPoints.add(new ControlPoint(startPos));
390
391         // Make sure that we are ending with a flag if ALT is pressed.
392         // This makes the tool always start with a flag which can be quite
393         // cumbersome and is therefore disabled. The current version will not
394         // end the connection if the mouse has not moved at all.
395         //if (keyUtil.isKeyPressed(java.awt.event.KeyEvent.VK_ALT)) {
396         //    endWithoutTerminal(lastMouseCanvasPos, true);
397         //}
398     }
399
400     @SGInit
401     public void initSG(G2DParentNode parent) {
402         ghostNode = parent.addNode(G2DParentNode.class);
403         ghostNode.setZIndex(PAINT_PRIORITY);
404
405         ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
406         pathNode.setColor(new Color(160, 0, 0));
407         pathNode.setStroke(new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
408                 new float[] { 0.5f, 0.2f }, 0));
409         pathNode.setScaleStroke(false);
410         pathNode.setZIndex(0);
411
412         G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);
413         points.setZIndex(1);
414
415         updateSG();
416     }
417
418     private RouteTerminal addControlPoint(RouteGraph routeGraph, ControlPoint cp) {
419         TerminalInfo ti = cp.getAttachedTerminal();
420         if(ti != null && ti != startFlag && ti != endFlag) {
421             Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
422             GeometryUtils.expandRectangle(bounds, 2);
423             int allowedDirections = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
424                     ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
425             return routeGraph.addTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(),
426                     bounds, allowedDirections, PlainLineEndStyle.INSTANCE);
427         }
428         else {
429             double x = cp.getPosition().getX();
430             double y = cp.getPosition().getY();
431             int allowedDirections = 0xf;
432             switch(cp.getDirection()) {
433             case Horizontal: allowedDirections = 5; break;
434             case Vertical: allowedDirections = 10; break;
435             case Any: allowedDirections = 15; break;
436             }
437             return routeGraph.addTerminal(x, y, x, y, x, y, allowedDirections);
438         }
439     }
440     
441     protected void updateSG() {
442         if (controlPoints.size() != 2)
443             return;
444
445         ControlPoint begin = controlPoints.getFirst();
446         ControlPoint end = controlPoints.getLast();
447
448         RouteGraph routeGraph = new RouteGraph();
449         RouteTerminal a = addControlPoint(routeGraph, begin);
450         RouteTerminal b = addControlPoint(routeGraph, end);
451         routeGraph.link(a, b);
452
453         Path2D path = routeGraph.getPath2D();
454
455         // Create scene graph to visualize the connection.
456         ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
457         pathNode.setShape(path);
458
459         setDirty();
460     }
461
462     private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {
463         return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));
464     }
465
466     private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {
467         G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);
468         elementParent.setTransform(tr);
469         elementParent.removeNodes();
470         for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))
471             sg.init(element, elementParent);
472         return elementParent;
473     }
474
475     @SGCleanup
476     public void cleanupSG() {
477         ghostNode.remove();
478         ghostNode = null;
479     }
480
481     @EventHandler(priority = 200)
482     public boolean handleCommandEvents(CommandEvent ce) {
483         if (ce.command.equals(Commands.CANCEL)) {
484             setDirty();
485             remove();
486             return true;
487         } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {
488             return rotateLastBranchPoint(ce.command.equals(Commands.ROTATE_ELEMENT_CW));
489         }
490         return false;
491     }
492
493     @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
494     public boolean handleKeyEvents(KeyEvent ke) {
495         if (ke instanceof KeyPressedEvent) {
496             // Back-space, cancel prev bend
497             if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)
498                 return cancelPreviousBend();
499         }
500
501         if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
502             if (createFlags) {
503                 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));
504                 return true;
505             }
506         }
507
508         return false;
509     }
510
511     @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
512     public boolean handleEvent(MouseEvent me) {
513         // Only handle events for the connection-initiating mouse
514         if (me.mouseId != mouseId)
515             return false;
516
517         if (me instanceof MouseMovedEvent)
518             return processMouseMove((MouseMovedEvent) me);
519
520         if (me instanceof MouseButtonPressedEvent)
521             return processMouseButtonPress((MouseButtonPressedEvent) me);
522
523         return false;
524     }
525
526     protected boolean processMouseMove(MouseMovedEvent me) {
527         mouseHasMoved = true;
528
529         Point2D mouseControlPos = me.controlPosition;
530         Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
531
532         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
533         if (snapAdvisor != null)
534             snapAdvisor.snap(mouseCanvasPos);
535
536         // Record last snapped canvas position of mouse.
537         this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
538
539         if (isEndingInFlag()) {
540             endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
541         }
542
543         List<TerminalInfo> tis = pi.pickTerminals(me.controlPosition);
544         tis = TerminalUtil.findNearestOverlappingTerminals(tis);
545         if (!tis.isEmpty() && !containsStartTerminal(tis)) {
546             //System.out.println("end terminals (" + tis.size() + "):\n" + EString.implode(tis));
547             for (TerminalInfo ti : tis) {
548                 Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);
549                 if (canConnect != null) {
550                     connectionJudgment = canConnect.first;
551
552                     if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
553                         if (canConnect.second != null) {
554                             controlPoints.getFirst()
555                             .setPosition(canConnect.second.posDia)
556                             .setAttachedToTerminal(canConnect.second);
557                         }
558                         controlPoints.getLast()
559                         .setPosition(ti.posDia)
560                         .setAttachedToTerminal(ti);
561
562                         selectedStartTerminal = canConnect.second;
563                         endTerminal = ti;
564                     }
565
566                     // Make sure that we are ending with a flag if ALT is pressed
567                     // and no end terminal is defined.
568                     if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
569                         updateSG();
570                     return false;
571                 }
572             }
573         } else {
574             RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
575                     diagram,
576                     pi.getCanvasPickShape(me.controlPosition),
577                     pi.getPickDistance());
578             if (cp != null) {
579                 // Remove branch point highlight from previously picked route graph.
580                 if (lastRouteGraphTarget != null && cp.getNode() != lastRouteGraphTarget.getNode())
581                     cp.getNode().showBranchPoint(null);
582                 lastRouteGraphTarget = cp;
583
584                 // Validate connection before visualizing connectability
585                 Point2D isectPos = cp.getIntersectionPosition();
586                 TerminalInfo ti = TerminalInfo.create(
587                         isectPos,
588                         cp.getElement(),
589                         BranchPointTerminal.existingTerminal(
590                                 isectPos,
591                                 DirectionSet.ANY,
592                                 BranchPointNode.SHAPE),
593                         BranchPointNode.SHAPE);
594                 Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);
595                 if (canConnect != null) {
596                     attachToConnectionJudgement = canConnect.first;
597                     controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);
598                     endTerminal = ti;
599                     cp.getNode().showBranchPoint(isectPos);
600                     if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
601                         updateSG();
602                     return false;
603                 }
604             } else {
605                 if (lastRouteGraphTarget != null) {
606                     lastRouteGraphTarget.getNode().showBranchPoint(null);
607                     lastRouteGraphTarget = null;
608                 }
609             }
610         }
611
612         connectionJudgment = null;
613         attachToConnectionJudgement = null;
614         if (isEndTerminalDefined()) {
615             // CASE: Mouse was previously on top of a valid terminal to end
616             // the connection. Now the mouse has been moved where there is
617             // no longer a terminal to connect to.
618             //
619             // => Disconnect the last edge segment from the previous
620             // terminal, mark endElement/endTerminal non-existent
621             // and connect the disconnected edge to a new branch point.
622
623             controlPoints.getLast()
624             .setPosition(mouseCanvasPos)
625             .setDirection(calculateCurrentBranchPointDirection())
626             .setAttachedToTerminal(null);
627
628             endTerminal = null;
629         } else {
630             // CASE: Mouse was not previously on top of a valid ending
631             // element terminal.
632             //
633             // => Move and re-orient last branch point.
634
635             controlPoints.getLast()
636             .setPosition(mouseCanvasPos)
637             .setDirection(calculateCurrentBranchPointDirection());
638         }
639
640         // Make sure that we are ending with a flag if ALT is pressed and no end
641         // terminal is defined.
642         if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
643             updateSG();
644
645         return false;
646     }
647
648     protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {
649         MouseButtonEvent me = e;
650
651         // Do nothing before the mouse has moved at least a little.
652         // This prevents the user from ending the connection right where
653         // it started.
654         if (!mouseHasMoved)
655             return true;
656
657         if (me.button == MouseEvent.LEFT_BUTTON) {
658             Point2D mouseControlPos = me.controlPosition;
659             Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
660
661             ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
662             if (snapAdvisor != null)
663                 snapAdvisor.snap(mouseCanvasPos);
664
665             if (isEndTerminalDefined() && connectionJudgment != null) {
666                 // Clicked on an allowed end terminal. End connection & end mode.
667                 createConnection();
668                 remove();
669                 return true;
670             } else if (lastRouteGraphTarget != null && attachToConnectionJudgement != null) {
671                 lastRouteGraphTarget.getNode().showBranchPoint(null);
672                 attachToConnection();
673                 remove();
674                 return true;
675             } else {
676                 // Finish connection in thin air only if the
677                 // connection was started from a valid terminal.
678                 if (me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) && !startTerminals.isEmpty()) {
679                     Pair<ConnectionJudgement, TerminalInfo> pair = canConnect(null, null);
680                     if (pair != null) {
681                         connectionJudgment = (ConnectionJudgement) pair.first;
682                         selectedStartTerminal = pair.second;
683 //                        endFlag = createFlag(EdgeEnd.End);
684 //                        controlPoints.getLast().setAttachedToTerminal(endFlag);
685                         createConnection();
686                         setDirty();
687                         remove();
688                     } else {
689                         // Inform the user why connection couldn't be created.
690                         String tmsg = terminalsToString(startTerminals);
691                         ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection when starting from one of the following terminals:\n" + tmsg, null);
692                     }
693                     return true;
694                 } else if (routePointsAllowed()
695                         && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
696                     // Add new connection control point.
697                     controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));
698                     resetForcedBranchPointDirection();
699                     updateSG();
700                 }
701             }
702
703             // Eat the event to prevent other participants from doing
704             // incompatible things while in this connection mode.
705             return true;
706         } else if (me.button == MouseEvent.RIGHT_BUTTON) {
707             return cancelPreviousBend();
708         }
709
710         return false;
711     }
712
713     private void attachToConnection() {
714         ConnectionJudgement judgment = this.attachToConnectionJudgement;
715         if (judgment == null) {
716             ErrorLogger.defaultLogError("Cannot attach to connection, no judgment available on connection validity", null);
717             return;
718         }
719
720         ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
721         RouteGraph before = lastRouteGraphTarget.getNode().getRouteGraph();
722         THashMap<Object, Object> copyMap = new THashMap<>();
723         RouteGraph after = before.copy(copyMap);
724
725         RouteLine attachTo = (RouteLine) copyMap.get(lastRouteGraphTarget.getLine());
726         after.makePersistent(attachTo);
727         for (RouteLine line : after.getAllLines()) {
728             if (!line.isTransient() && line.isHorizontal() == attachTo.isHorizontal()
729                     && line.getPosition() == attachTo.getPosition()) {
730                 attachTo = line;
731                 break;
732             }
733         }
734         RouteLine attachToLine = attachTo;
735         RouteGraphDelta delta = new RouteGraphDelta(before, after);
736
737         Simantics.getSession().asyncRequest(new WriteRequest() {
738             @Override
739             public void perform(WriteGraph graph) throws DatabaseException {
740                 graph.markUndoPoint();
741                 Resource connection = ElementUtils.getObject(endTerminal.e);
742                 if (!delta.isEmpty()) {
743                     new RouteGraphConnection(graph, connection).synchronize(graph, before, after, delta);
744                 }
745                 Resource line = RouteGraphConnection.deserialize(graph, attachToLine.getData());
746                 Deque<ControlPoint> cps = new ArrayDeque<>();
747                 for (Iterator<ControlPoint> iterator = controlPoints.descendingIterator(); iterator.hasNext();)
748                     cps.add(iterator.next());
749                 builder.attachToRouteGraph(graph, judgment, connection, line, cps, startTerminal, FlagClass.Type.In);
750             }
751         }, parameter -> {
752             if (parameter != null)
753                 ExceptionUtils.logAndShowError(parameter);
754         });
755     }
756
757     protected boolean cancelPreviousBend() {
758         if (!routePointsAllowed())
759             return false;
760
761         // Just to make this code more comprehensible, prevent an editing
762         // case that requires ugly code to work.
763         if (isEndingInFlag())
764             return true;
765
766         // If there are no real route points, cancel whole connection.
767         if (controlPoints.size() <= 2) {
768             setDirty();
769             remove();
770             return true;
771         }
772
773         // Cancel last bend
774         controlPoints.removeLast();
775         controlPoints.getLast().setPosition(lastMouseCanvasPos);
776         resetForcedBranchPointDirection();
777
778         updateSG();
779         return true;
780     }
781
782     /**
783      * Rotates the last branch point in the created connection in either
784      * clockwise or counter-clockwise direction as a response to a user
785      * interaction.
786      * 
787      * <p>
788      * At the same time it use {@link #forcedBranchPointDirection} to mark the
789      * current last branch point to be forcefully oriented according to the
790      * users wishes instead of calculating a default value for the orientation
791      * from the routed connection path. See
792      * {@link #calculateCurrentBranchPointDirection()} for more information on
793      * this.
794      * 
795      * <p>
796      * The logic of this method goes as follows:
797      * <ul>
798      * <li>Calculate the current branch point direction</li>
799      * <li>If the branch point direction is currently user selected (
800      * {@link #forcedBranchPointDirection}</li>
801      * <li></li>
802      * <li></li>
803      * </ul>
804      * 
805      * @param clockwise
806      * @return <code>true</code> if the rotation was successful
807      */
808     protected boolean rotateLastBranchPoint(boolean clockwise) {
809         Direction oldDir = calculateCurrentBranchPointDirection();
810
811         if (forcedBranchPointDirection == null) {
812             forcedBranchPointDirection = oldDir.toggleDetermined();
813         } else {
814             forcedBranchPointDirection = clockwise ? oldDir.cycleNext() : oldDir.cyclePrevious();
815         }
816
817         controlPoints.getLast().setDirection(forcedBranchPointDirection);
818
819         updateSG();
820
821         return true;
822     }
823
824     /**
825      * Set preferred direction for a branch/route point element.
826      * 
827      * @param branchPoint the element to set the direction for
828      * @param direction the direction to set
829      * @return
830      */
831     protected void setDirection(IElement branchPoint, Direction direction) {
832         branchPoint.getElementClass().getSingleItem(BranchPoint.class).setDirectionPreference(branchPoint, direction);
833     }
834
835     protected Direction forcedBranchPointDirection() {
836         return forcedBranchPointDirection;
837     }
838
839     protected void resetForcedBranchPointDirection() {
840         forcedBranchPointDirection = null;
841     }
842
843     protected void forceBranchPointDirection(Direction direction) {
844         forcedBranchPointDirection = direction;
845     }
846
847     /**
848      * @return
849      */
850     protected Direction calculateCurrentBranchPointDirection() {
851         // If this is not the first branch point, toggle direction compared to
852         // last.
853         if (forcedBranchPointDirection != null)
854             return forcedBranchPointDirection;
855
856         if (controlPoints.size() > 2) {
857             // This is not the first edge segment, toggle route point
858             // directions.
859             Iterator<ControlPoint> it = controlPoints.descendingIterator();
860             it.next();
861             ControlPoint secondLastCp = it.next();
862
863             Direction dir = secondLastCp.getDirection();
864             switch (dir) {
865                 case Horizontal:
866                     return Direction.Vertical;
867                 case Vertical:
868                     return Direction.Horizontal;
869                 case Any:
870             }
871         }
872
873         // If this is the first branch point, calculate based on edge segment
874         // angle.
875         if (controlPoints.size() > 1) {
876             Iterator<ControlPoint> it = controlPoints.descendingIterator();
877             ControlPoint last = it.next();
878             ControlPoint secondLast = it.next();
879
880             double angle = Math.atan2(Math.abs(last.getPosition().getY() - secondLast.getPosition().getY()),
881                     Math.abs(last.getPosition().getX() - secondLast.getPosition().getX()));
882
883             if (angle >= 0 && angle < Math.PI / 4) {
884                 return Direction.Horizontal;
885             } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {
886                 return Direction.Vertical;
887             }
888         }
889
890         return Direction.Any;
891     }
892
893     protected boolean isEndingInFlag() {
894         return endFlag != null;
895     }
896
897     /**
898      * @param mousePos
899      * @param altDown
900      * @return <code>true</code> if updateSG was executed, <code>false</code>
901      *         otherwise
902      */
903     protected boolean endWithoutTerminal(Point2D mousePos, boolean altDown) {
904         // Just go with branch points if flags are not allowed.
905         if (!createFlags)
906             return false;
907
908         boolean endTerminalDefined = isEndTerminalDefined();
909
910         if (altDown) {
911             if (!isEndingInFlag()) {
912                 endFlag = createFlag(EdgeEnd.End);
913                 endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);
914                 controlPoints.getLast()
915                 .setDirection(calculateCurrentBranchPointDirection())
916                 .setAttachedToTerminal(endFlag);
917
918                 // TerminalPainter must refresh
919                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
920
921                 updateSG();
922                 return true;
923             }
924         } else {
925             if (isEndingInFlag()) {
926                 // Currently ending with flag but ALT is no longer down
927                 // so that flag must be removed.
928                 endFlag = null;
929                 endFlagNode.remove();
930                 endFlagNode = null;
931
932                 ControlPoint cp = controlPoints.getLast();
933                 cp.setDirection(calculateCurrentBranchPointDirection())
934                 .setAttachedToTerminal(endTerminal);
935
936                 if (endTerminalDefined) {
937                     cp.setPosition(endTerminal.posDia);
938                 } else {
939                     cp.setPosition(mousePos);
940                 }
941
942                 // Force TerminalPainter refresh
943                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
944
945                 updateSG();
946                 return true;
947             }
948         }
949         return false;
950     }
951
952     protected void createConnection() {
953         createConnection(
954                 this.selectedStartTerminal,
955                 this.endTerminal,
956                 this.connectionJudgment,
957                 this.controlPoints);
958     }
959
960     protected void createConnection(
961             final TerminalInfo startTerminal,
962             final TerminalInfo endTerminal,
963             final ConnectionJudgement judgement,
964             final Deque<ControlPoint> controlPoints)
965     {
966         TimeLogger.resetTimeAndLog(getClass(), "createConnection");
967         if (judgement == null) {
968             // Inform the user why connection couldn't be created.
969             String tmsg = terminalsToString(Arrays.asList(startTerminal, endTerminal));
970             ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity when connecting the terminals:\n" + tmsg, null);
971             return;
972         }
973
974         final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
975
976         Simantics.getSession().asyncRequest(new WriteRequest() {
977             @Override
978             public void perform(WriteGraph graph) throws DatabaseException {
979                 builder.create(graph, judgement, controlPoints, startTerminal, endTerminal);
980             }
981         }, parameter -> {
982             if (parameter != null)
983                 ExceptionUtils.logAndShowError(parameter);
984         });
985     }
986
987     /**
988      * @param canvasPos
989      * @return
990      */
991     protected ControlPoint newControlPointWithCalculatedDirection(Point2D canvasPos) {
992         return new ControlPoint(canvasPos, calculateCurrentBranchPointDirection());
993     }
994
995     /**
996      * @param e
997      * @param t
998      * @return <code>true</code> if the specified element terminal matches any
999      *         TerminalInfo in {@link #startTerminals}
1000      */
1001     protected boolean isStartTerminal(IElement e, Terminal t) {
1002         if (startTerminal == null)
1003             return false;
1004         for (TerminalInfo st : startTerminals) {
1005             if (st.e == e && st.t == t) {
1006                 return true;
1007             }
1008         }
1009         return false;
1010     }
1011
1012     /**
1013      * @param e
1014      * @param t
1015      * @return <code>true</code> if the specified element terminal matches any
1016      *         TerminalInfo in {@link #startTerminals}
1017      */
1018     protected boolean containsStartTerminal(List<TerminalInfo> tis) {
1019         if (startTerminal == null)
1020             return false;
1021         for (TerminalInfo st : startTerminals) {
1022             for (TerminalInfo et : tis) {
1023                 if (st.e == et.e && st.t == et.t) {
1024                     return true;
1025                 }
1026             }
1027         }
1028         return false;
1029     }
1030
1031     protected static FlagClass.Type endToFlagType(EdgeEnd end) {
1032         switch (end) {
1033             case Begin:
1034                 return FlagClass.Type.In;
1035             case End:
1036                 return FlagClass.Type.Out;
1037             default:
1038                 throw new IllegalArgumentException("unrecognized edge end: " + end);
1039         }
1040     }
1041
1042     protected TerminalInfo createFlag(EdgeEnd connectionEnd) {
1043         ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);
1044         IElement e = Element.spawnNew(flagClass);
1045
1046         e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));
1047         e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);
1048
1049         TerminalInfo ti = new TerminalInfo();
1050         ti.e = e;
1051         ti.t = ElementUtils.getSingleTerminal(e);
1052         ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);
1053         ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);
1054
1055         return ti;
1056     }
1057
1058     protected boolean shouldEndWithFlag(MouseEvent me) {
1059         return shouldEndWithFlag( me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) );
1060     }
1061
1062     protected boolean shouldEndWithFlag(boolean altPressed) {
1063         return altPressed && !isEndTerminalDefined() && createFlags && startFlag == null;
1064     }
1065
1066     protected boolean isEndTerminalDefined() {
1067         return endTerminal != null;
1068     }
1069
1070     protected boolean isFlagTerminal(TerminalInfo ti) {
1071         return ti.e.getElementClass().containsClass(FlagHandler.class);
1072     }
1073
1074     protected boolean allowReflexiveConnections() {
1075         return false;
1076     }
1077
1078     protected boolean routePointsAllowed() {
1079         return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
1080     }
1081
1082     /**
1083      * @param endElement
1084      * @param endTerminal
1085      * @return
1086      */
1087     @SuppressWarnings("unchecked")
1088     protected final Pair<ConnectionJudgement, TerminalInfo> canConnect(IElement endElement, Terminal endTerminal) {
1089         IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
1090         Object judgement = canConnect(advisor, endElement, endTerminal);
1091         if (judgement == null)
1092             return null;
1093         if (judgement instanceof Pair<?, ?>)
1094             return (Pair<ConnectionJudgement, TerminalInfo>) judgement;
1095         return Pair.<ConnectionJudgement, TerminalInfo>make((ConnectionJudgement) judgement, startTerminal);
1096     }
1097
1098     protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
1099         if (advisor == null)
1100             return Pair.make(ConnectionJudgement.CANBEMADELEGAL, startTerminal);
1101         if (startTerminals.isEmpty()) {
1102             ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, null, null, endElement, endTerminal);
1103             return obj != null ? Pair.<ConnectionJudgement, TerminalInfo>make(obj, null) : null;
1104         }
1105         for (TerminalInfo st : startTerminals) {
1106             ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, st.e, st.t, endElement, endTerminal);
1107             if (obj != null) {
1108                 return Pair.make(obj, st);
1109             }
1110         }
1111         return null;
1112     }
1113
1114     /**
1115      * For generating debugging information of what was attempted by the user
1116      * when a connection couldn't be created.
1117      * 
1118      * @param ts
1119      * @return
1120      */
1121     private String terminalsToString(final Iterable<TerminalInfo> ts) {
1122         try {
1123             return Simantics.sync(new UniqueRead<String>() {
1124                 @Override
1125                 public String perform(ReadGraph graph) throws DatabaseException {
1126                     DiagramResource DIA = DiagramResource.getInstance(graph);
1127                     ModelingResources MOD = ModelingResources.getInstance(graph);
1128                     StringBuilder sb = new StringBuilder();
1129                     boolean first = true;
1130                     for (TerminalInfo ti : ts) {
1131                         if (!first)
1132                             sb.append("\n");
1133                         first = false;
1134                         sb.append("element ");
1135                         Object o = ElementUtils.getObject(ti.e);
1136                         if (o instanceof Resource) {
1137                             Resource er = (Resource) o;
1138                             Resource cer = graph.getPossibleObject(er, MOD.ElementToComponent);
1139                             Resource r = cer != null ? cer : er;
1140                             sb.append(NameUtils.getSafeName(graph, r)).append(" : ");
1141                             for (Resource type : graph.getPrincipalTypes(r)) {
1142                                 sb.append(NameUtils.getSafeName(graph, type, true));
1143                             }
1144                         } else {
1145                             sb.append(ti.e.toString());
1146                         }
1147                         sb.append(", terminal ");
1148                         if (ti.t instanceof ResourceTerminal) {
1149                             Resource tr = ((ResourceTerminal) ti.t).getResource();
1150                             Resource cp = graph.getPossibleObject(tr, DIA.HasConnectionPoint);
1151                             Resource r = cp != null ? cp : tr;
1152                             sb.append(NameUtils.getSafeName(graph, r, true));
1153                         } else {
1154                             sb.append(ti.t.toString());
1155                         }
1156                     }
1157                     return sb.toString();
1158                 }
1159             });
1160         } catch (DatabaseException e) {
1161             return e.getMessage();
1162         }
1163     }
1164
1165 }