]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / RouteGraphConnectTool.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
new file mode 100644 (file)
index 0000000..af1f48f
--- /dev/null
@@ -0,0 +1,905 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2016 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *     Semantum Oy - Fixed bug #6364\r
+ *******************************************************************************/\r
+package org.simantics.diagram.participant;\r
+\r
+import java.awt.AlphaComposite;\r
+import java.awt.Composite;\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayDeque;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Deque;\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
+import java.util.Set;\r
+\r
+import org.simantics.Simantics;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.diagram.connection.RouteGraph;\r
+import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
+import org.simantics.diagram.connection.RouteLine;\r
+import org.simantics.diagram.connection.RoutePoint;\r
+import org.simantics.diagram.connection.RouteTerminal;\r
+import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
+import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
+import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;\r
+import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
+import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
+import org.simantics.diagram.synchronization.ISynchronizationContext;\r
+import org.simantics.diagram.synchronization.SynchronizationHints;\r
+import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.connection.IConnectionAdvisor;\r
+import org.simantics.g2d.diagram.DiagramHints;\r
+import org.simantics.g2d.diagram.DiagramUtils;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.PickRequest;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
+import org.simantics.g2d.diagram.participant.ElementPainter;\r
+import org.simantics.g2d.diagram.participant.TerminalPainter;\r
+import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;\r
+import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;\r
+import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;\r
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementClasses;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.IElementClassProvider;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
+import org.simantics.g2d.element.handler.SceneGraph;\r
+import org.simantics.g2d.element.handler.TerminalTopology;\r
+import org.simantics.g2d.element.handler.impl.BranchPointTerminal;\r
+import org.simantics.g2d.element.impl.Element;\r
+import org.simantics.g2d.elementclass.FlagClass;\r
+import org.simantics.g2d.elementclass.FlagHandler;\r
+import org.simantics.g2d.participant.TransformUtil;\r
+import org.simantics.g2d.utils.geom.DirectionSet;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
+import org.simantics.scenegraph.g2d.events.command.Commands;\r
+import org.simantics.scenegraph.g2d.nodes.BranchPointNode;\r
+import org.simantics.scenegraph.g2d.nodes.ConnectionNode;\r
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;\r
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
+import org.simantics.scenegraph.utils.GeometryUtils;\r
+import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
+import org.simantics.utils.datastructures.Callback;\r
+import org.simantics.utils.datastructures.Pair;\r
+import org.simantics.utils.datastructures.Triple;\r
+import org.simantics.utils.logging.TimeLogger;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+import org.simantics.utils.ui.ExceptionUtils;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+/**\r
+ * A basic tool for making connection on diagrams.\r
+ * \r
+ * This version defines the starting, ending and route points of a connection.\r
+ * The routing itself is left up to the diagram router employed by\r
+ * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.\r
+ * \r
+ * Manual:\r
+ * \r
+ * This tool is added to the diagram when a connection sequence is initiated by\r
+ * another participant. PointerInteractor is one such participant which adds the\r
+ * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked\r
+ * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}\r
+ * ). The connection will be finished when another allowed terminal is clicked\r
+ * upon or empty canvas space is ALT+clicked. Route points for the connection\r
+ * can be created by clicking around on non-terminal-occupied canvas space while\r
+ * connecting.\r
+ * \r
+ * <p>\r
+ * Connections can be started from and ended in flags by pressing ALT while\r
+ * left-clicking.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class RouteGraphConnectTool extends AbstractMode {\r
+\r
+    private static final String      END_TERMINAL_DATA     = "END";\r
+\r
+    public static final int          PAINT_PRIORITY        = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;\r
+\r
+    @Dependency\r
+    protected TransformUtil          util;\r
+\r
+    @Dependency\r
+    protected ElementPainter         diagramPainter;\r
+\r
+    @Dependency\r
+    protected PointerInteractor      pi;\r
+\r
+    /**\r
+     * Starting point designation.\r
+     * \r
+     * The value is received by the constructor.\r
+     */\r
+    protected RouteGraphTarget       startingPoint;\r
+\r
+    protected TerminalInfo           startTerminal         = new TerminalInfo();\r
+\r
+    /**\r
+     * <code>true</code> if this tool should create connection continuation\r
+     * flags, <code>false</code> otherwise.\r
+     */\r
+    protected boolean                createFlags;\r
+\r
+    /**\r
+     * \r
+     */\r
+    protected IElementClassProvider  elementClassProvider;\r
+\r
+    /**\r
+     * \r
+     */\r
+    protected Deque<ControlPoint>    controlPoints         = new ArrayDeque<ControlPoint>();\r
+\r
+    /**\r
+     * Element terminal of connection end element. <code>null</code> if\r
+     * connection cannot be ended where it is currently being attempted to end.\r
+     */\r
+    protected TerminalInfo           endTerminal;\r
+\r
+    /**\r
+     * The latest connectability judgment from the active\r
+     * {@link IConnectionAdvisor} should the connection happen between\r
+     * {@link #startTerminal} and {@link #endTerminal}.\r
+     */\r
+    protected ConnectionJudgement    connectionJudgment;\r
+\r
+    /**\r
+     * A temporary variable for use with\r
+     * {@link TerminalTopology#getTerminals(IElement, Collection)}.\r
+     */\r
+    protected Collection<Terminal>   terminals             = new ArrayList<Terminal>();\r
+\r
+    /**\r
+     * Previous mouse canvas position recorded by\r
+     * {@link #processMouseMove(MouseMovedEvent)}.\r
+     */\r
+    protected Point2D                lastMouseCanvasPos    = new Point2D.Double();\r
+\r
+    /**\r
+     * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been\r
+     * invoked at least once. This is used to tell whether to allow creation of\r
+     * branch points or finising the connection in thin air. It will not be\r
+     * allowed if the mouse has not moved at all since starting the connection.\r
+     */\r
+    protected boolean                mouseHasMoved         = false;\r
+\r
+    protected TerminalHoverStrategy  originalStrategy      = null;\r
+\r
+    protected TerminalHoverStrategy  terminalHoverStrategy = new TerminalHoverStrategy() {\r
+        @Override\r
+        public boolean highlightEnabled() {\r
+            return !isEndingInFlag();\r
+        }\r
+\r
+        @Override\r
+        public boolean highlight(TerminalInfo ti) {\r
+            return canConnect(ti.e, ti.t) != null;\r
+        }\r
+    };\r
+\r
+    protected final static Composite ALPHA_COMPOSITE       = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);\r
+\r
+    /**\r
+     * Root scene graph node for all visualization performed by this tool.\r
+     */\r
+    protected ConnectionNode         ghostNode;\r
+\r
+    protected RouteGraphNode           rgNode;\r
+\r
+    protected  RouteGraph               routeGraph;\r
+    protected  RouteTerminal            endRouteTerminal;\r
+    private ILineEndStyle            endTerminalStyle;\r
+\r
+    /**\r
+     * Indicates whether the connection is about to be ended into a new\r
+     * flag/branchpoint or not.\r
+     */\r
+    protected TerminalInfo           endFlag;\r
+\r
+    protected G2DParentNode          endFlagNode;\r
+\r
+    private RouteLine                attachedToRouteLine;\r
+\r
+    protected RouteGraph               beforeRouteGraph;\r
+\r
+    private IRouteGraphRenderer      beforeRenderer;\r
+\r
+    private boolean                  beforeEditable;\r
+\r
+    protected RouteGraphDelta          routeGraphDelta;\r
+\r
+    private Set<Pair<IElement, Terminal>> alreadyConnected;\r
+    \r
+    /**\r
+     * @param startElement\r
+     * @param routeGraphConnection\r
+     * @param mouseId\r
+     * @param startCanvasPos\r
+     */\r
+    public RouteGraphConnectTool(RouteGraphTarget startingPoint, int mouseId) {\r
+        super(mouseId);\r
+\r
+        Point2D intersection = startingPoint.getIntersectionPosition();\r
+\r
+        this.startingPoint = startingPoint;\r
+        this.lastMouseCanvasPos.setLocation(intersection);\r
+\r
+        BranchPointTerminal t = BranchPointTerminal.existingTerminal(\r
+                AffineTransform.getTranslateInstance(intersection.getX(), intersection.getY()),\r
+                DirectionSet.ANY,\r
+                BranchPointNode.SHAPE);\r
+\r
+        Point2D p = startingPoint.getIntersectionPosition();\r
+        AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());\r
+\r
+        startTerminal.e = startingPoint.getElement();\r
+        startTerminal.t = t;\r
+        startTerminal.posElem = at;\r
+        startTerminal.posDia = at;\r
+        startTerminal.shape = t.getShape();\r
+\r
+        controlPoints.add( newControlPoint( startingPoint.getIntersectionPosition() ) );\r
+        controlPoints.add( newControlPoint( startingPoint.getCanvasPosition() ) );\r
+\r
+        alreadyConnected = new HashSet<Pair<IElement,Terminal>>();\r
+        IElement connection = startingPoint.getElement();\r
+        ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+        Collection<Connection> tcs = ce.getTerminalConnections(null);\r
+        for (Connection tc : tcs)\r
+            alreadyConnected.add(Pair.make(tc.node, tc.terminal));\r
+    }\r
+\r
+    @Override\r
+    public void addedToContext(ICanvasContext ctx) {\r
+        super.addedToContext(ctx);\r
+\r
+        // Force terminals to always be highlighted without pressing certain\r
+        // keys or key combinations.\r
+        originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);\r
+        setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
+    }\r
+\r
+    @Override\r
+    protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
+        if (newDiagram != null) {\r
+            // Get IElementClassProvider\r
+            ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);\r
+            if (ctx != null) {\r
+                this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);\r
+            }\r
+\r
+            // See if flags should be created or not.\r
+            this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void removedFromContext(ICanvasContext ctx) {\r
+        if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {\r
+            if (originalStrategy != null)\r
+                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);\r
+            else\r
+                removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);\r
+        }\r
+\r
+        super.removedFromContext(ctx);\r
+    }\r
+\r
+    int straightDirections(RouteLine line) {\r
+        return line.isHorizontal()\r
+                ? (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP)\r
+                : (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);\r
+    }\r
+\r
+    private static RouteTerminal addTerminal(Object data, RouteGraph rg, double x, double y, Rectangle2D bounds, int allowedDirections, ILineEndStyle style) {\r
+        RouteTerminal rt;\r
+        if (bounds != null)\r
+            rt = rg.addTerminal(x, y, bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), allowedDirections, style);\r
+        else\r
+            rt = rg.addTerminal(x, y, x, y, x, y, allowedDirections, style);\r
+        rt.setData( data );\r
+        return rt;\r
+    }\r
+\r
+    private RouteTerminal setEndTerminal(double x, double y, Rectangle2D bounds, int allowedDirections) {\r
+\r
+       // First add then remove to prevent deletion of route lines in case there are 2 only terminals\r
+       \r
+       RouteTerminal toRemove = endRouteTerminal;\r
+       \r
+        endRouteTerminal = addTerminal( END_TERMINAL_DATA, routeGraph, x, y, bounds, allowedDirections, endTerminalStyle );\r
+        routeGraph.link( attachedToRouteLine, endRouteTerminal );\r
+        \r
+        if (toRemove != null)\r
+            routeGraph.remove(toRemove);\r
+        \r
+        return endRouteTerminal;\r
+        \r
+    }\r
+\r
+    protected void setEndTerminal(TerminalInfo ti) {\r
+        Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());\r
+        GeometryUtils.expandRectangle(bounds, 2);\r
+        int dir = RouteGraphConnectionClass.shortestDirectionOutOfBounds(\r
+                ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);\r
+        setEndTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds, dir);\r
+    }\r
+\r
+    @SGInit\r
+    public void initSG(G2DParentNode parent) {\r
+        ghostNode = parent.addNode("branched connection", ConnectionNode.class);\r
+        //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.5f));\r
+        ghostNode.setZIndex(PAINT_PRIORITY);\r
+\r
+        rgNode = ghostNode.addNode("branch", RouteGraphNode.class);\r
+\r
+        double ex = startingPoint.getCanvasPosition().getX();\r
+        double ey = startingPoint.getCanvasPosition().getY();\r
+\r
+        beforeRouteGraph = startingPoint.getNode().getRouteGraph();\r
+        beforeEditable = startingPoint.getNode().isEditable();\r
+\r
+        RouteGraphNode beforeRgNode = startingPoint.getNode();\r
+        beforeRenderer = beforeRgNode.getRenderer();\r
+\r
+        rgNode.setRenderer(beforeRenderer);\r
+        rgNode.setEditable(false);\r
+\r
+        beforeRgNode.setEditable(beforeEditable);\r
+        beforeRgNode.setRenderer(null);\r
+        \r
+        initRG(ex, ey);\r
+        \r
+    }\r
+    \r
+    public void initRG(double ex, double ey) {\r
+       \r
+        THashMap<Object, Object> map = new THashMap<Object, Object>();\r
+        routeGraph = beforeRouteGraph.copy(map);\r
+\r
+        endTerminalStyle = PlainLineEndStyle.INSTANCE;\r
+        for (RouteTerminal t : routeGraph.getTerminals()) {\r
+            if (t.getRenderStyle() instanceof ArrowLineEndStyle) {\r
+                endTerminalStyle = t.getRenderStyle();\r
+                break;\r
+            }\r
+        }\r
+\r
+        attachedToRouteLine = (RouteLine) map.get(startingPoint.getLine());\r
+        routeGraph.makePersistent(attachedToRouteLine);\r
+        for (RouteLine line : routeGraph.getAllLines()) {\r
+            if (!line.isTransient() && line.isHorizontal() == attachedToRouteLine.isHorizontal()\r
+                    && line.getPosition() == attachedToRouteLine.getPosition()) {\r
+                attachedToRouteLine = line;\r
+                break;\r
+            }\r
+        }\r
+        routeGraphDelta = new RouteGraphDelta(beforeRouteGraph, routeGraph);\r
+\r
+//        beforeRouteGraph.print();\r
+//        routeGraph.print();\r
+//        routeGraphDelta.print();\r
+\r
+        setEndTerminal(ex, ey, null, 0xf);\r
+\r
+        rgNode.setRouteGraph(routeGraph);\r
+       \r
+    }\r
+    \r
+    @SGCleanup\r
+    public void cleanupSG() {\r
+        RouteGraphNode beforeRgNode = startingPoint.getNode();\r
+        beforeRgNode.setRouteGraph(beforeRouteGraph);\r
+        beforeRgNode.setRenderer(beforeRenderer);\r
+        beforeRgNode.setEditable(beforeEditable);\r
+\r
+        ghostNode.remove();\r
+        ghostNode = null;\r
+\r
+        setDirty();\r
+    }\r
+\r
+    @EventHandler(priority = 200)\r
+    public boolean handleCommandEvents(CommandEvent ce) {\r
+        if (ce.command.equals(Commands.CANCEL)) {\r
+            setDirty();\r
+            remove();\r
+            return true;\r
+        } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {\r
+            // TODO: rotate flag?\r
+        }\r
+        return false;\r
+    }\r
+\r
+    @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)\r
+    public boolean handleKeyEvents(KeyEvent ke) {\r
+        if (ke instanceof KeyPressedEvent) {\r
+            // Back-space, cancel prev bend\r
+            if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)\r
+                return cancelPreviousBend();\r
+        }\r
+\r
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {\r
+            if (createFlags) {\r
+                endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));\r
+                return true;\r
+            }\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)\r
+    public boolean handleEvent(MouseEvent me) {\r
+        // Only handle events for the connection-initiating mouse\r
+        if (me.mouseId != mouseId)\r
+            return false;\r
+\r
+        if (me instanceof MouseMovedEvent)\r
+            return processMouseMove((MouseMovedEvent) me);\r
+\r
+        if (me instanceof MouseButtonPressedEvent)\r
+            return processMouseButtonPress((MouseButtonPressedEvent) me);\r
+\r
+        return false;\r
+    }\r
+\r
+    protected boolean processMouseMove(MouseMovedEvent me) {\r
+        mouseHasMoved = true;\r
+\r
+        Point2D mouseControlPos = me.controlPosition;\r
+        Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());\r
+\r
+        ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
+        if (snapAdvisor != null)\r
+            snapAdvisor.snap(mouseCanvasPos);\r
+\r
+        // Record last snapped canvas position of mouse.\r
+        this.lastMouseCanvasPos.setLocation(mouseCanvasPos);\r
+\r
+        if (isEndingInFlag()) {\r
+            endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));\r
+        }\r
+\r
+        TerminalInfo ti = pi.pickTerminal(me.controlPosition);\r
+        if (ti != null) {\r
+            Object canConnect = canConnect(ti.e, ti.t);\r
+            if (canConnect != null) {\r
+                connectionJudgment = (ConnectionJudgement) canConnect;\r
+\r
+                if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {\r
+                    controlPoints.getLast()\r
+                    .setPosition(ti.posDia)\r
+                    .setAttachedToTerminal(ti);\r
+\r
+                    endTerminal = ti;\r
+\r
+                    connect(ti);\r
+                    \r
+                }\r
+\r
+                // Make sure that we are ending with a flag if ALT is pressed\r
+                // and no end terminal is defined.\r
+                endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));\r
+\r
+                updateSG(new Point2D.Double(ti.posDia.getTranslateX(), ti.posDia.getTranslateY()));\r
+                return false;\r
+            }\r
+        }\r
+\r
+        connectionJudgment = null;\r
+        if (isEndTerminalDefined()) {\r
+            // CASE: Mouse was previously on top of a valid terminal to end\r
+            // the connection. Now the mouse has been moved where there is\r
+            // no longer a terminal to connect to.\r
+            //\r
+            // => Disconnect the last edge segment from the previous\r
+            // terminal, mark endElement/endTerminal non-existent\r
+            // and connect the disconnected edge to a new branch point.\r
+\r
+            disconnect(mouseCanvasPos);\r
+            \r
+            controlPoints.getLast()\r
+            .setPosition(mouseCanvasPos)\r
+            .setAttachedToTerminal(null);\r
+\r
+            endTerminal = null;\r
+        } else {\r
+            // CASE: Mouse was not previously on top of a valid ending\r
+            // element terminal.\r
+            //\r
+            // => Move and re-orient last branch point.\r
+\r
+            controlPoints.getLast()\r
+            .setPosition(mouseCanvasPos);\r
+        }\r
+\r
+        // Make sure that we are ending with a flag if ALT is pressed and no end\r
+        // terminal is defined.\r
+        endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));\r
+\r
+        updateSG(lastMouseCanvasPos);\r
+\r
+        return false;\r
+    }\r
+\r
+    protected void connect(TerminalInfo ti) {\r
+        setEndTerminal(ti);\r
+    }\r
+    \r
+    protected void disconnect(Point2D mouseCanvasPos) {\r
+        setEndTerminal(mouseCanvasPos.getX(), mouseCanvasPos.getY(), null, 0xf);\r
+    }\r
+    \r
+    protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {\r
+        MouseButtonEvent me = e;\r
+\r
+        // Do nothing before the mouse has moved at least a little.\r
+        // This prevents the user from ending the connection right where\r
+        // it started.\r
+        if (!mouseHasMoved)\r
+            return true;\r
+\r
+        if (me.button == MouseEvent.LEFT_BUTTON) {\r
+            Point2D mouseControlPos = me.controlPosition;\r
+            Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());\r
+\r
+            ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
+            if (snapAdvisor != null)\r
+                snapAdvisor.snap(mouseCanvasPos);\r
+\r
+            // Clicked on an allowed end terminal. End connection & end mode.\r
+            if (isEndTerminalDefined()) {\r
+                createConnection();\r
+                remove();\r
+                return true;\r
+            } else {\r
+                // Finish connection in thin air only if the\r
+                // connection was started from a valid terminal.\r
+                if ((me.stateMask & MouseEvent.ALT_MASK) != 0 && startTerminal != null) {\r
+                    connectionJudgment = (ConnectionJudgement) canConnect(null, null);\r
+                    if (connectionJudgment == null)\r
+                        return true;\r
+                    createConnection();\r
+                    remove();\r
+                    return true;\r
+                } else if (routePointsAllowed()\r
+                        && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {\r
+                    // Add new connection control point.\r
+                    controlPoints.add(newControlPoint(mouseCanvasPos));\r
+                    updateSG(mouseCanvasPos);\r
+                }\r
+            }\r
+\r
+            // Eat the event to prevent other participants from doing\r
+            // incompatible things while in this connection mode.\r
+            return true;\r
+        } else if (me.button == MouseEvent.RIGHT_BUTTON) {\r
+            return cancelPreviousBend();\r
+        }\r
+\r
+        return false;\r
+    }\r
+\r
+    protected boolean cancelPreviousBend() {\r
+        if (!routePointsAllowed())\r
+            return false;\r
+\r
+        // Just to make this code more comprehensible, prevent an editing\r
+        // case that requires ugly code to work.\r
+        if (isEndingInFlag())\r
+            return true;\r
+\r
+        // If there are no real route points, cancel whole connection.\r
+        if (controlPoints.size() <= 2) {\r
+            setDirty();\r
+            remove();\r
+            return true;\r
+        }\r
+\r
+        // Cancel last bend\r
+        controlPoints.removeLast();\r
+        controlPoints.getLast().setPosition(lastMouseCanvasPos);\r
+\r
+        updateSG(lastMouseCanvasPos);\r
+        return true;\r
+    }\r
+\r
+    protected void updateSG(Point2D mousePos) {\r
+        routeGraph.setLocation(endRouteTerminal, mousePos.getX(), mousePos.getY());\r
+        //routeGraph.print(System.err);\r
+        setDirty();\r
+    }\r
+\r
+    protected boolean shouldEndWithFlag(MouseEvent me) {\r
+        return shouldEndWithFlag((me.stateMask & MouseEvent.ALT_MASK) != 0);\r
+    }\r
+\r
+    protected boolean shouldEndWithFlag(boolean altPressed) {\r
+        return altPressed && !isEndTerminalDefined() && createFlags;\r
+    }\r
+\r
+    protected boolean isEndTerminalDefined() {\r
+        return endTerminal != null;\r
+    }\r
+\r
+    protected boolean isFlagTerminal(TerminalInfo ti) {\r
+        return ti.e.getElementClass().containsClass(FlagHandler.class);\r
+    }\r
+\r
+\r
+    protected boolean isEndingInFlag() {\r
+        return endFlag != null;\r
+    }\r
+\r
+    protected void endWithoutTerminal(Point2D mousePos, boolean altDown) {\r
+        // Just go with branch points if flags are not allowed.\r
+        if (!createFlags)\r
+            return;\r
+\r
+        boolean endTerminalDefined = isEndTerminalDefined();\r
+\r
+        if (altDown) {\r
+            if (!isEndingInFlag()) {\r
+                endFlag = createFlag(EdgeEnd.End);\r
+                endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);\r
+                controlPoints.getLast()\r
+                .setAttachedToTerminal(endFlag);\r
+\r
+                // TerminalPainter must refresh\r
+                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
+\r
+                updateSG(mousePos);\r
+            }\r
+        } else {\r
+            if (isEndingInFlag()) {\r
+                // Currently ending with flag but ALT is no longer down\r
+                // so that flag must be removed.\r
+                endFlag = null;\r
+                endFlagNode.remove();\r
+                endFlagNode = null;\r
+\r
+                ControlPoint cp = controlPoints.getLast();\r
+                cp.setAttachedToTerminal(endTerminal);\r
+\r
+                if (endTerminalDefined) {\r
+                    cp.setPosition(endTerminal.posDia);\r
+                } else {\r
+                    cp.setPosition(mousePos);\r
+                }\r
+\r
+                // Force Terminalpainter refresh\r
+                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
+\r
+                updateSG(mousePos);\r
+            }\r
+        }\r
+    }\r
+\r
+    private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {\r
+        return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));\r
+    }\r
+\r
+    private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {\r
+        G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);\r
+        elementParent.setTransform(tr);\r
+        elementParent.removeNodes();\r
+        for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))\r
+            sg.init(element, elementParent);\r
+        return elementParent;\r
+    }\r
+\r
+    /**\r
+     * @param canvasPos\r
+     * @return\r
+     */\r
+    protected ControlPoint newControlPoint(Point2D canvasPos) {\r
+        return new ControlPoint(canvasPos);\r
+    }\r
+\r
+    protected Triple<RouteGraph,RouteGraph,RouteGraphDelta> prepareRouteGraphDelta() {\r
+        // Prevent route graph connection synchronization from crashing on the\r
+        // transient route terminal that doesn't exist yet. It is created after\r
+        // persisting the route line, which is the only purpose of this route\r
+        // graph synchronization.\r
+        routeGraph.remove(endRouteTerminal);\r
+        return Triple.make(beforeRouteGraph, routeGraph, routeGraphDelta);\r
+    }\r
+\r
+    protected void createConnection() {\r
+        TimeLogger.resetTimeAndLog(getClass(), "createConnection");\r
+        final ConnectionJudgement judgment = this.connectionJudgment;\r
+        if (judgment == null) {\r
+            ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity", null);\r
+            return;\r
+        }\r
+\r
+        final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);\r
+        final Triple<RouteGraph,RouteGraph,RouteGraphDelta> rgs = prepareRouteGraphDelta();\r
+\r
+        Simantics.getSession().asyncRequest(new WriteRequest() {\r
+            @Override\r
+            public void perform(WriteGraph graph) throws DatabaseException {\r
+                graph.markUndoPoint();\r
+                Resource connection = ElementUtils.getObject(startTerminal.e);\r
+                if (!rgs.third.isEmpty()) {\r
+                    new RouteGraphConnection(graph, connection).synchronize(graph, rgs.first, rgs.second, rgs.third);\r
+                }\r
+                Resource attachToLine = RouteGraphConnection.deserialize(graph, attachedToRouteLine.getData());\r
+                builder.attachToRouteGraph(graph, judgment, connection, attachToLine, controlPoints, endTerminal);\r
+            }\r
+        }, new Callback<DatabaseException>() {\r
+            @Override\r
+            public void run(DatabaseException parameter) {\r
+                if (parameter != null)\r
+                    ExceptionUtils.logAndShowError(parameter);\r
+            }\r
+        });\r
+    }\r
+\r
+    protected boolean routePointsAllowed() {\r
+        return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));\r
+    }\r
+\r
+    protected Object canConnect(IElement endElement, Terminal endTerminal) {\r
+        IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
+        return canConnect(advisor, endElement, endTerminal);\r
+    }\r
+\r
+    protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {\r
+        if (advisor == null)\r
+            return ConnectionJudgement.CANBEMADELEGAL;\r
+        if (alreadyConnected.contains(Pair.make(endElement, endTerminal)))\r
+            return null;\r
+        return advisor.canBeConnected(null, startTerminal.e, startTerminal.t, endElement, endTerminal);\r
+    }\r
+\r
+    protected static FlagClass.Type endToFlagType(EdgeEnd end) {\r
+        switch (end) {\r
+            case Begin:\r
+                return FlagClass.Type.In;\r
+            case End:\r
+                return FlagClass.Type.Out;\r
+            default:\r
+                throw new IllegalArgumentException("unrecognized edge end: " + end);\r
+        }\r
+    }\r
+\r
+    protected TerminalInfo createFlag(EdgeEnd connectionEnd) {\r
+        ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);\r
+        IElement e = Element.spawnNew(flagClass);\r
+\r
+        e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));\r
+        e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);\r
+\r
+        TerminalInfo ti = new TerminalInfo();\r
+        ti.e = e;\r
+        ti.t = ElementUtils.getSingleTerminal(e);\r
+        ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);\r
+        ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);\r
+\r
+        return ti;\r
+    }\r
+\r
+    // ------------------------------------------------------------------------\r
+\r
+    static RouteGraphTarget pickRouteGraphConnection(IDiagram diagram, Shape pickShape, double pickDistance) {\r
+        ArrayList<IElement> elements = new ArrayList<IElement>();\r
+        PickRequest req = new PickRequest(pickShape);\r
+        DiagramUtils.pick(diagram, req, elements);\r
+        for (Iterator<IElement> it = elements.iterator(); it.hasNext();) {\r
+            IElement e = it.next();\r
+            RouteGraphNode rgn = e.getHint(RouteGraphConnectionClass.KEY_RG_NODE);\r
+            if (rgn == null || rgn.getRouteGraph() == null)\r
+                it.remove();\r
+        }\r
+        if (elements.isEmpty())\r
+            return null;\r
+\r
+        Rectangle2D pickRect = pickShape.getBounds2D();\r
+        final double x = pickRect.getCenterX();\r
+        final double y = pickRect.getCenterY();\r
+\r
+        return pickNearestRouteGraphConnection(elements, x, y, pickDistance);\r
+    }\r
+\r
+    private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList<IElement> elements, double x, double y, double pd) {\r
+        // Find the nearest distance at which we get hits.\r
+        double hi = pd + 1;\r
+        double lo = hi * .01;\r
+        double limit = 0.5;\r
+        while (true) {\r
+            double delta = (hi - lo);\r
+            if (delta <= limit)\r
+                break;\r
+\r
+            pd = (lo + hi) * .5;\r
+\r
+            boolean hit = false;\r
+            for (IElement connection : elements) {\r
+                RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);\r
+                RouteLine line = rgn.getRouteGraph().pickLine(x, y, pd);\r
+                if (line != null) {\r
+                    hit = true;\r
+                    break;\r
+                }\r
+            }\r
+\r
+            if (hit)\r
+                hi = pd;\r
+            else\r
+                lo = pd;\r
+        }\r
+\r
+        // Now that the nearest hitting distance is found, find the nearest intersection.\r
+        RouteGraphTarget nearestTarget = null;\r
+        double nearest = Double.MAX_VALUE;\r
+        for (IElement connection : elements) {\r
+            RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);\r
+            RouteLine line = rgn.getRouteGraph().pickLine(x, y, pd);\r
+            if (line == null)\r
+                continue;\r
+\r
+            Point2D intersection = intersectionPoint(x, y, line);\r
+            if (intersection == null)\r
+                continue;\r
+\r
+            double dx = intersection.getX() - x;\r
+            double dy = intersection.getY() - y;\r
+            double dist = Math.sqrt(dx*dx + dy*dy);\r
+            if (dist < nearest) {\r
+                nearest = dist;\r
+                nearestTarget = new RouteGraphTarget(connection, rgn, line, new Point2D.Double(x, y), intersection);\r
+            }\r
+        }\r
+\r
+        return nearestTarget;\r
+    }\r
+\r
+    static Point2D intersectionPoint(double x, double y, RouteLine line) {\r
+        Collection<RoutePoint> points = line.getPoints();\r
+        if (points.size() < 2)\r
+            return null;\r
+        RoutePoint s = line.getBegin();\r
+        RoutePoint e = line.getEnd();\r
+        return GeometryUtils.intersectionToLine(s.getX(), s.getY(), e.getX(), e.getY(), x, y);\r
+    }\r
+\r
+}
\ No newline at end of file