]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java
ConnectionTool fixes for overlapping terminals
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / ConnectTool2.java
index 2165794e89c489c34e34daddf5613fa22d13e9ae..38bdc706cb88e8b20a959b03d18bfac02ec17de9 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 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
- *******************************************************************************/\r
-package org.simantics.diagram.participant;\r
-\r
-import java.awt.AlphaComposite;\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Composite;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Path2D;\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.Arrays;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Deque;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-\r
-import org.simantics.Simantics;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.UniqueRead;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.NameUtils;\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.RouteTerminal;\r
-import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
-import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
-import org.simantics.diagram.content.ResourceTerminal;\r
-import org.simantics.diagram.stubs.DiagramResource;\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.DependencyReflection.Reference;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\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.PickContext;\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.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.BranchPoint;\r
-import org.simantics.g2d.elementclass.BranchPoint.Direction;\r
-import org.simantics.g2d.elementclass.FlagClass;\r
-import org.simantics.g2d.elementclass.FlagHandler;\r
-import org.simantics.g2d.participant.RenderingQualityInteractor;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.utils.geom.DirectionSet;\r
-import org.simantics.modeling.ModelingResources;\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.ShapeNode;\r
-import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
-import org.simantics.scenegraph.utils.GeometryUtils;\r
-import org.simantics.scenegraph.utils.Quality;\r
-import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
-import org.simantics.utils.datastructures.Pair;\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 ConnectTool2 extends AbstractMode {\r
-\r
-    public static final int          PAINT_PRIORITY        = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;\r
-\r
-    @Reference\r
-    protected RenderingQualityInteractor quality;\r
-\r
-    @Dependency\r
-    protected TransformUtil          util;\r
-\r
-    @Dependency\r
-    protected ElementPainter         diagramPainter;\r
-\r
-    @Dependency\r
-    protected PointerInteractor      pi;\r
-\r
-    @Dependency\r
-    protected PickContext            pickContext;\r
-\r
-    /**\r
-     * Start element terminal of the connection. <code>null</code> if connection\r
-     * was started from a flag or a branch point.\r
-     * \r
-     * The value is received by the constructor.\r
-     */\r
-    protected List<TerminalInfo>     startTerminals;\r
-\r
-    /**\r
-     * Refers to any of the possible overlapping start terminals. The value is\r
-     * taken from the first index of {@link #startTerminals} assuming that the\r
-     * first one is the nearest. It is <code>null</code> if\r
-     * {@link #startTerminals} is empty.\r
-     */\r
-    protected TerminalInfo           startTerminal;\r
-\r
-    protected TerminalInfo           startFlag;\r
-\r
-    /**\r
-     * Starting position of the connection, received as an external argument.\r
-     */\r
-    protected final Point2D          startPos;\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
-     * Contains <code>null</code> when a connection is started from a new flag\r
-     * or one of the terminals in {@link #startTerminals} when a connection is\r
-     * being created starting from a terminal or possibly a set of terminals.\r
-     * \r
-     * <p>\r
-     * Note that this is different from {@link #startTerminal} which simply\r
-     * represents the first element of {@link #startTerminals}.\r
-     * \r
-     * <p>\r
-     * Only when this value and {@link #endTerminal} is properly set will a\r
-     * connection be created between two element terminals.\r
-     */\r
-    protected TerminalInfo           selectedStartTerminal;\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 #selectedStartTerminal} and {@link #endTerminal}.\r
-     */\r
-    protected ConnectionJudgement    connectionJudgment;\r
-\r
-    /**\r
-     * The latest connectability judgment from the active\r
-     * {@link IConnectionAdvisor} should the connection happen between\r
-     * {@link #selectedStartTerminal} and {@link #lastRouteGraphTarget}.\r
-     */\r
-    protected ConnectionJudgement    attachToConnectionJudgement;\r
-\r
-    /**\r
-     * If non-null during connection drawing this field tells the direction\r
-     * forced for the current branch point by the user through the UI commands\r
-     * {@link Commands#ROTATE_ELEMENT_CCW} and\r
-     * {@link Commands#ROTATE_ELEMENT_CW}.\r
-     */\r
-    private Direction                forcedBranchPointDirection;\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
-            boolean reflexive = isStartTerminal(ti.e, ti.t);\r
-            if (reflexive && !allowReflexiveConnections())\r
-                return false;\r
-\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 G2DParentNode          ghostNode;\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 RouteGraphTarget         lastRouteGraphTarget;\r
-\r
-    /**\r
-     * @param startTerminal\r
-     * @param mouseId\r
-     * @param startCanvasPos\r
-     */\r
-    public ConnectTool2(TerminalInfo startTerminal, int mouseId, Point2D startCanvasPos) {\r
-        this(startTerminal == null ? Collections.<TerminalInfo> emptyList()\r
-                : Collections.singletonList(startTerminal),\r
-                mouseId,\r
-                startCanvasPos);\r
-    }\r
-\r
-    /**\r
-     * @param startTerminals\r
-     * @param mouseId\r
-     * @param startCanvasPos\r
-     */\r
-    public ConnectTool2(List<TerminalInfo> startTerminals, int mouseId, Point2D startCanvasPos) {\r
-        super(mouseId);\r
-\r
-        if (startCanvasPos == null)\r
-            throw new NullPointerException("null start position");\r
-        if (startTerminals == null)\r
-            throw new NullPointerException("null start terminals");\r
-\r
-        this.startPos = startCanvasPos;\r
-        this.lastMouseCanvasPos.setLocation(startPos);\r
-\r
-        this.startTerminals = startTerminals;\r
-        this.startTerminal = startTerminals.isEmpty() ? null : startTerminals.get(0);\r
-    }\r
-\r
-    @Override\r
-    public void addedToContext(ICanvasContext ctx) {\r
-        super.addedToContext(ctx);\r
-\r
-        if (quality != null)\r
-            quality.setStaticQuality(Quality.LOW);\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
-            startConnection();\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
-        if (quality != null)\r
-            quality.setStaticQuality(null);\r
-\r
-        super.removedFromContext(ctx);\r
-    }\r
-\r
-    protected void startConnection() {\r
-        Point2D startPos = (Point2D) this.startPos.clone();\r
-        ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
-        if (snapAdvisor != null)\r
-            snapAdvisor.snap(startPos);\r
-\r
-        // Resolve the first element and terminal of the connection.\r
-        ControlPoint start = new ControlPoint(startPos);\r
-\r
-        if (startTerminal != null) {\r
-            assert ElementUtils.peekDiagram(startTerminal.e) == diagram;\r
-            Point2D terminalPos = new Point2D.Double(startTerminal.posDia.getTranslateX(),\r
-                    startTerminal.posDia.getTranslateY());\r
-            start.setPosition(terminalPos).setAttachedToTerminal(startTerminal);\r
-        } else {\r
-            // Create TerminalInfo describing the flag to be created.\r
-            if (createFlags) {\r
-                // This prevents connection creation from creating a branch\r
-                // point in place of this flag.\r
-                startFlag = createFlag(EdgeEnd.Begin);\r
-                start.setAttachedToTerminal(startFlag);\r
-                showElement(ghostNode, "startFlag", startFlag.e, startPos);\r
-            }\r
-        }\r
-        controlPoints.add(start);\r
-        controlPoints.add(new ControlPoint(startPos));\r
-\r
-        // Make sure that we are ending with a flag if ALT is pressed.\r
-        // This makes the tool always start with a flag which can be quite\r
-        // cumbersome and is therefore disabled. The current version will not\r
-        // end the connection if the mouse has not moved at all.\r
-        //if (keyUtil.isKeyPressed(java.awt.event.KeyEvent.VK_ALT)) {\r
-        //    endWithoutTerminal(lastMouseCanvasPos, true);\r
-        //}\r
-    }\r
-\r
-    @SGInit\r
-    public void initSG(G2DParentNode parent) {\r
-        ghostNode = parent.addNode(G2DParentNode.class);\r
-        ghostNode.setZIndex(PAINT_PRIORITY);\r
-\r
-        ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);\r
-        pathNode.setColor(new Color(160, 0, 0));\r
-        pathNode.setStroke(new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,\r
-                new float[] { 0.5f, 0.2f }, 0));\r
-        pathNode.setScaleStroke(false);\r
-        pathNode.setZIndex(0);\r
-\r
-        G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);\r
-        points.setZIndex(1);\r
-\r
-        updateSG();\r
-    }\r
-\r
-    private RouteTerminal addControlPoint(RouteGraph routeGraph, ControlPoint cp) {\r
-        TerminalInfo ti = cp.getAttachedTerminal();\r
-        if(ti != null && ti != startFlag && ti != endFlag) {\r
-            Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());\r
-            GeometryUtils.expandRectangle(bounds, 2);\r
-            int allowedDirections = RouteGraphConnectionClass.shortestDirectionOutOfBounds(\r
-                    ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);\r
-            return routeGraph.addTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(),\r
-                    bounds, allowedDirections, PlainLineEndStyle.INSTANCE);\r
-        }\r
-        else {\r
-            double x = cp.getPosition().getX();\r
-            double y = cp.getPosition().getY();\r
-            int allowedDirections = 0xf;\r
-            switch(cp.getDirection()) {\r
-            case Horizontal: allowedDirections = 5; break;\r
-            case Vertical: allowedDirections = 10; break;\r
-            case Any: allowedDirections = 15; break;\r
-            }\r
-            return routeGraph.addTerminal(x, y, x, y, x, y, allowedDirections);\r
-        }\r
-    }\r
-    \r
-    protected void updateSG() {\r
-        if (controlPoints.size() != 2)\r
-            return;\r
-\r
-        ControlPoint begin = controlPoints.getFirst();\r
-        ControlPoint end = controlPoints.getLast();\r
-\r
-        RouteGraph routeGraph = new RouteGraph();\r
-        RouteTerminal a = addControlPoint(routeGraph, begin);\r
-        RouteTerminal b = addControlPoint(routeGraph, end);\r
-        routeGraph.link(a, b);\r
-\r
-        Path2D path = routeGraph.getPath2D();\r
-\r
-        // Create scene graph to visualize the connection.\r
-        ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);\r
-        pathNode.setShape(path);\r
-\r
-        setDirty();\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
-    @SGCleanup\r
-    public void cleanupSG() {\r
-        ghostNode.remove();\r
-        ghostNode = null;\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
-            return rotateLastBranchPoint(ce.command.equals(Commands.ROTATE_ELEMENT_CW));\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
-        List<TerminalInfo> tis = pi.pickTerminals(me.controlPosition);\r
-        tis = TerminalUtil.findNearestOverlappingTerminals(tis);\r
-        if (!tis.isEmpty() && !containsStartTerminal(tis)) {\r
-            //System.out.println("end terminals (" + tis.size() + "):\n" + EString.implode(tis));\r
-            for (TerminalInfo ti : tis) {\r
-                Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);\r
-                if (canConnect != null) {\r
-                    connectionJudgment = canConnect.first;\r
-\r
-                    if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {\r
-                        if (canConnect.second != null) {\r
-                            controlPoints.getFirst()\r
-                            .setPosition(canConnect.second.posDia)\r
-                            .setAttachedToTerminal(canConnect.second);\r
-                        }\r
-                        controlPoints.getLast()\r
-                        .setPosition(ti.posDia)\r
-                        .setAttachedToTerminal(ti);\r
-\r
-                        selectedStartTerminal = canConnect.second;\r
-                        endTerminal = ti;\r
-                    }\r
-\r
-                    // Make sure that we are ending with a flag if ALT is pressed\r
-                    // and no end terminal is defined.\r
-                    if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))\r
-                        updateSG();\r
-                    return false;\r
-                }\r
-            }\r
-        } else {\r
-            RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(\r
-                    diagram,\r
-                    pi.getCanvasPickShape(me.controlPosition),\r
-                    pi.getPickDistance());\r
-            if (cp != null) {\r
-                // Remove branch point highlight from previously picked route graph.\r
-                if (lastRouteGraphTarget != null && cp.getNode() != lastRouteGraphTarget.getNode())\r
-                    cp.getNode().showBranchPoint(null);\r
-                lastRouteGraphTarget = cp;\r
-\r
-                // Validate connection before visualizing connectability\r
-                Point2D isectPos = cp.getIntersectionPosition();\r
-                TerminalInfo ti = TerminalInfo.create(\r
-                        isectPos,\r
-                        cp.getElement(),\r
-                        BranchPointTerminal.existingTerminal(\r
-                                isectPos,\r
-                                DirectionSet.ANY,\r
-                                BranchPointNode.SHAPE),\r
-                        BranchPointNode.SHAPE);\r
-                Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);\r
-                if (canConnect != null) {\r
-                    attachToConnectionJudgement = canConnect.first;\r
-                    controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);\r
-                    endTerminal = ti;\r
-                    cp.getNode().showBranchPoint(isectPos);\r
-                    if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))\r
-                        updateSG();\r
-                    return false;\r
-                }\r
-            } else {\r
-                if (lastRouteGraphTarget != null) {\r
-                    lastRouteGraphTarget.getNode().showBranchPoint(null);\r
-                    lastRouteGraphTarget = null;\r
-                }\r
-            }\r
-        }\r
-\r
-        connectionJudgment = null;\r
-        attachToConnectionJudgement = 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
-            controlPoints.getLast()\r
-            .setPosition(mouseCanvasPos)\r
-            .setDirection(calculateCurrentBranchPointDirection())\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
-            .setDirection(calculateCurrentBranchPointDirection());\r
-        }\r
-\r
-        // Make sure that we are ending with a flag if ALT is pressed and no end\r
-        // terminal is defined.\r
-        if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))\r
-            updateSG();\r
-\r
-        return false;\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
-            if (isEndTerminalDefined() && connectionJudgment != null) {\r
-                // Clicked on an allowed end terminal. End connection & end mode.\r
-                createConnection();\r
-                remove();\r
-                return true;\r
-            } else if (lastRouteGraphTarget != null && attachToConnectionJudgement != null) {\r
-                lastRouteGraphTarget.getNode().showBranchPoint(null);\r
-                attachToConnection();\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.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) && !startTerminals.isEmpty()) {\r
-                    Pair<ConnectionJudgement, TerminalInfo> pair = canConnect(null, null);\r
-                    if (pair != null) {\r
-                        connectionJudgment = (ConnectionJudgement) pair.first;\r
-                        selectedStartTerminal = pair.second;\r
-//                        endFlag = createFlag(EdgeEnd.End);\r
-//                        controlPoints.getLast().setAttachedToTerminal(endFlag);\r
-                        createConnection();\r
-                        setDirty();\r
-                        remove();\r
-                    } else {\r
-                        // Inform the user why connection couldn't be created.\r
-                        String tmsg = terminalsToString(startTerminals);\r
-                        ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection when starting from one of the following terminals:\n" + tmsg, null);\r
-                    }\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(newControlPointWithCalculatedDirection(mouseCanvasPos));\r
-                    resetForcedBranchPointDirection();\r
-                    updateSG();\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
-    private void attachToConnection() {\r
-        ConnectionJudgement judgment = this.attachToConnectionJudgement;\r
-        if (judgment == null) {\r
-            ErrorLogger.defaultLogError("Cannot attach to connection, no judgment available on connection validity", null);\r
-            return;\r
-        }\r
-\r
-        ConnectionBuilder builder = new ConnectionBuilder(this.diagram);\r
-        RouteGraph before = lastRouteGraphTarget.getNode().getRouteGraph();\r
-        THashMap<Object, Object> copyMap = new THashMap<>();\r
-        RouteGraph after = before.copy(copyMap);\r
-\r
-        RouteLine attachTo = (RouteLine) copyMap.get(lastRouteGraphTarget.getLine());\r
-        after.makePersistent(attachTo);\r
-        for (RouteLine line : after.getAllLines()) {\r
-            if (!line.isTransient() && line.isHorizontal() == attachTo.isHorizontal()\r
-                    && line.getPosition() == attachTo.getPosition()) {\r
-                attachTo = line;\r
-                break;\r
-            }\r
-        }\r
-        RouteLine attachToLine = attachTo;\r
-        RouteGraphDelta delta = new RouteGraphDelta(before, after);\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(endTerminal.e);\r
-                if (!delta.isEmpty()) {\r
-                    new RouteGraphConnection(graph, connection).synchronize(graph, before, after, delta);\r
-                }\r
-                Resource line = RouteGraphConnection.deserialize(graph, attachToLine.getData());\r
-                Deque<ControlPoint> cps = new ArrayDeque<>();\r
-                for (Iterator<ControlPoint> iterator = controlPoints.descendingIterator(); iterator.hasNext();)\r
-                    cps.add(iterator.next());\r
-                builder.attachToRouteGraph(graph, judgment, connection, line, cps, startTerminal, FlagClass.Type.In);\r
-            }\r
-        }, parameter -> {\r
-            if (parameter != null)\r
-                ExceptionUtils.logAndShowError(parameter);\r
-        });\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
-        resetForcedBranchPointDirection();\r
-\r
-        updateSG();\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Rotates the last branch point in the created connection in either\r
-     * clockwise or counter-clockwise direction as a response to a user\r
-     * interaction.\r
-     * \r
-     * <p>\r
-     * At the same time it use {@link #forcedBranchPointDirection} to mark the\r
-     * current last branch point to be forcefully oriented according to the\r
-     * users wishes instead of calculating a default value for the orientation\r
-     * from the routed connection path. See\r
-     * {@link #calculateCurrentBranchPointDirection()} for more information on\r
-     * this.\r
-     * \r
-     * <p>\r
-     * The logic of this method goes as follows:\r
-     * <ul>\r
-     * <li>Calculate the current branch point direction</li>\r
-     * <li>If the branch point direction is currently user selected (\r
-     * {@link #forcedBranchPointDirection}</li>\r
-     * <li></li>\r
-     * <li></li>\r
-     * </ul>\r
-     * \r
-     * @param clockwise\r
-     * @return <code>true</code> if the rotation was successful\r
-     */\r
-    protected boolean rotateLastBranchPoint(boolean clockwise) {\r
-        Direction oldDir = calculateCurrentBranchPointDirection();\r
-\r
-        if (forcedBranchPointDirection == null) {\r
-            forcedBranchPointDirection = oldDir.toggleDetermined();\r
-        } else {\r
-            forcedBranchPointDirection = clockwise ? oldDir.cycleNext() : oldDir.cyclePrevious();\r
-        }\r
-\r
-        controlPoints.getLast().setDirection(forcedBranchPointDirection);\r
-\r
-        updateSG();\r
-\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Set preferred direction for a branch/route point element.\r
-     * \r
-     * @param branchPoint the element to set the direction for\r
-     * @param direction the direction to set\r
-     * @return\r
-     */\r
-    protected void setDirection(IElement branchPoint, Direction direction) {\r
-        branchPoint.getElementClass().getSingleItem(BranchPoint.class).setDirectionPreference(branchPoint, direction);\r
-    }\r
-\r
-    protected Direction forcedBranchPointDirection() {\r
-        return forcedBranchPointDirection;\r
-    }\r
-\r
-    protected void resetForcedBranchPointDirection() {\r
-        forcedBranchPointDirection = null;\r
-    }\r
-\r
-    protected void forceBranchPointDirection(Direction direction) {\r
-        forcedBranchPointDirection = direction;\r
-    }\r
-\r
-    /**\r
-     * @return\r
-     */\r
-    protected Direction calculateCurrentBranchPointDirection() {\r
-        // If this is not the first branch point, toggle direction compared to\r
-        // last.\r
-        if (forcedBranchPointDirection != null)\r
-            return forcedBranchPointDirection;\r
-\r
-        if (controlPoints.size() > 2) {\r
-            // This is not the first edge segment, toggle route point\r
-            // directions.\r
-            Iterator<ControlPoint> it = controlPoints.descendingIterator();\r
-            it.next();\r
-            ControlPoint secondLastCp = it.next();\r
-\r
-            Direction dir = secondLastCp.getDirection();\r
-            switch (dir) {\r
-                case Horizontal:\r
-                    return Direction.Vertical;\r
-                case Vertical:\r
-                    return Direction.Horizontal;\r
-                case Any:\r
-            }\r
-        }\r
-\r
-        // If this is the first branch point, calculate based on edge segment\r
-        // angle.\r
-        if (controlPoints.size() > 1) {\r
-            Iterator<ControlPoint> it = controlPoints.descendingIterator();\r
-            ControlPoint last = it.next();\r
-            ControlPoint secondLast = it.next();\r
-\r
-            double angle = Math.atan2(Math.abs(last.getPosition().getY() - secondLast.getPosition().getY()),\r
-                    Math.abs(last.getPosition().getX() - secondLast.getPosition().getX()));\r
-\r
-            if (angle >= 0 && angle < Math.PI / 4) {\r
-                return Direction.Horizontal;\r
-            } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {\r
-                return Direction.Vertical;\r
-            }\r
-        }\r
-\r
-        return Direction.Any;\r
-    }\r
-\r
-    protected boolean isEndingInFlag() {\r
-        return endFlag != null;\r
-    }\r
-\r
-    /**\r
-     * @param mousePos\r
-     * @param altDown\r
-     * @return <code>true</code> if updateSG was executed, <code>false</code>\r
-     *         otherwise\r
-     */\r
-    protected boolean endWithoutTerminal(Point2D mousePos, boolean altDown) {\r
-        // Just go with branch points if flags are not allowed.\r
-        if (!createFlags)\r
-            return false;\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
-                .setDirection(calculateCurrentBranchPointDirection())\r
-                .setAttachedToTerminal(endFlag);\r
-\r
-                // TerminalPainter must refresh\r
-                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
-\r
-                updateSG();\r
-                return true;\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.setDirection(calculateCurrentBranchPointDirection())\r
-                .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();\r
-                return true;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    protected void createConnection() {\r
-        createConnection(\r
-                this.selectedStartTerminal,\r
-                this.endTerminal,\r
-                this.connectionJudgment,\r
-                this.controlPoints);\r
-    }\r
-\r
-    protected void createConnection(\r
-            final TerminalInfo startTerminal,\r
-            final TerminalInfo endTerminal,\r
-            final ConnectionJudgement judgement,\r
-            final Deque<ControlPoint> controlPoints)\r
-    {\r
-        TimeLogger.resetTimeAndLog(getClass(), "createConnection");\r
-        if (judgement == null) {\r
-            // Inform the user why connection couldn't be created.\r
-            String tmsg = terminalsToString(Arrays.asList(startTerminal, endTerminal));\r
-            ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity when connecting the terminals:\n" + tmsg, null);\r
-            return;\r
-        }\r
-\r
-        final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);\r
-\r
-        Simantics.getSession().asyncRequest(new WriteRequest() {\r
-            @Override\r
-            public void perform(WriteGraph graph) throws DatabaseException {\r
-                builder.create(graph, judgement, controlPoints, startTerminal, endTerminal);\r
-            }\r
-        }, parameter -> {\r
-            if (parameter != null)\r
-                ExceptionUtils.logAndShowError(parameter);\r
-        });\r
-    }\r
-\r
-    /**\r
-     * @param canvasPos\r
-     * @return\r
-     */\r
-    protected ControlPoint newControlPointWithCalculatedDirection(Point2D canvasPos) {\r
-        return new ControlPoint(canvasPos, calculateCurrentBranchPointDirection());\r
-    }\r
-\r
-    /**\r
-     * @param e\r
-     * @param t\r
-     * @return <code>true</code> if the specified element terminal matches any\r
-     *         TerminalInfo in {@link #startTerminals}\r
-     */\r
-    protected boolean isStartTerminal(IElement e, Terminal t) {\r
-        if (startTerminal == null)\r
-            return false;\r
-        for (TerminalInfo st : startTerminals) {\r
-            if (st.e == e && st.t == t) {\r
-                return true;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * @param e\r
-     * @param t\r
-     * @return <code>true</code> if the specified element terminal matches any\r
-     *         TerminalInfo in {@link #startTerminals}\r
-     */\r
-    protected boolean containsStartTerminal(List<TerminalInfo> tis) {\r
-        if (startTerminal == null)\r
-            return false;\r
-        for (TerminalInfo st : startTerminals) {\r
-            for (TerminalInfo et : tis) {\r
-                if (st.e == et.e && st.t == et.t) {\r
-                    return true;\r
-                }\r
-            }\r
-        }\r
-        return false;\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
-    protected boolean shouldEndWithFlag(MouseEvent me) {\r
-        return shouldEndWithFlag( me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) );\r
-    }\r
-\r
-    protected boolean shouldEndWithFlag(boolean altPressed) {\r
-        return altPressed && !isEndTerminalDefined() && createFlags && startFlag == null;\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
-    protected boolean allowReflexiveConnections() {\r
-        return false;\r
-    }\r
-\r
-    protected boolean routePointsAllowed() {\r
-        return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));\r
-    }\r
-\r
-    /**\r
-     * @param endElement\r
-     * @param endTerminal\r
-     * @return\r
-     */\r
-    @SuppressWarnings("unchecked")\r
-    protected final Pair<ConnectionJudgement, TerminalInfo> canConnect(IElement endElement, Terminal endTerminal) {\r
-        IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
-        Object judgement = canConnect(advisor, endElement, endTerminal);\r
-        if (judgement == null)\r
-            return null;\r
-        if (judgement instanceof Pair<?, ?>)\r
-            return (Pair<ConnectionJudgement, TerminalInfo>) judgement;\r
-        return Pair.<ConnectionJudgement, TerminalInfo>make((ConnectionJudgement) judgement, startTerminal);\r
-    }\r
-\r
-    protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {\r
-        if (advisor == null)\r
-            return Pair.make(ConnectionJudgement.CANBEMADELEGAL, startTerminal);\r
-        if (startTerminals.isEmpty()) {\r
-            ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, null, null, endElement, endTerminal);\r
-            return obj != null ? Pair.<ConnectionJudgement, TerminalInfo>make(obj, null) : null;\r
-        }\r
-        for (TerminalInfo st : startTerminals) {\r
-            ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, st.e, st.t, endElement, endTerminal);\r
-            if (obj != null) {\r
-                return Pair.make(obj, st);\r
-            }\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * For generating debugging information of what was attempted by the user\r
-     * when a connection couldn't be created.\r
-     * \r
-     * @param ts\r
-     * @return\r
-     */\r
-    private String terminalsToString(final Iterable<TerminalInfo> ts) {\r
-        try {\r
-            return Simantics.sync(new UniqueRead<String>() {\r
-                @Override\r
-                public String perform(ReadGraph graph) throws DatabaseException {\r
-                    DiagramResource DIA = DiagramResource.getInstance(graph);\r
-                    ModelingResources MOD = ModelingResources.getInstance(graph);\r
-                    StringBuilder sb = new StringBuilder();\r
-                    boolean first = true;\r
-                    for (TerminalInfo ti : ts) {\r
-                        if (!first)\r
-                            sb.append("\n");\r
-                        first = false;\r
-                        sb.append("element ");\r
-                        Object o = ElementUtils.getObject(ti.e);\r
-                        if (o instanceof Resource) {\r
-                            Resource er = (Resource) o;\r
-                            Resource cer = graph.getPossibleObject(er, MOD.ElementToComponent);\r
-                            Resource r = cer != null ? cer : er;\r
-                            sb.append(NameUtils.getSafeName(graph, r)).append(" : ");\r
-                            for (Resource type : graph.getPrincipalTypes(r)) {\r
-                                sb.append(NameUtils.getSafeName(graph, type, true));\r
-                            }\r
-                        } else {\r
-                            sb.append(ti.e.toString());\r
-                        }\r
-                        sb.append(", terminal ");\r
-                        if (ti.t instanceof ResourceTerminal) {\r
-                            Resource tr = ((ResourceTerminal) ti.t).getResource();\r
-                            Resource cp = graph.getPossibleObject(tr, DIA.HasConnectionPoint);\r
-                            Resource r = cp != null ? cp : tr;\r
-                            sb.append(NameUtils.getSafeName(graph, r, true));\r
-                        } else {\r
-                            sb.append(ti.t.toString());\r
-                        }\r
-                    }\r
-                    return sb.toString();\r
-                }\r
-            });\r
-        } catch (DatabaseException e) {\r
-            return e.getMessage();\r
-        }\r
-    }\r
-\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.participant;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.UniqueRead;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteGraphConnectionClass;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.delta.RouteGraphDelta;
+import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
+import org.simantics.diagram.content.ResourceTerminal;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.ISynchronizationContext;
+import org.simantics.diagram.synchronization.SynchronizationHints;
+import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.connection.IConnectionAdvisor;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.PickContext;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.diagram.participant.ElementPainter;
+import org.simantics.g2d.diagram.participant.TerminalPainter;
+import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
+import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
+import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementClasses;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.IElementClassProvider;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.SceneGraph;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
+import org.simantics.g2d.element.impl.Element;
+import org.simantics.g2d.elementclass.BranchPoint;
+import org.simantics.g2d.elementclass.BranchPoint.Direction;
+import org.simantics.g2d.elementclass.FlagClass;
+import org.simantics.g2d.elementclass.FlagHandler;
+import org.simantics.g2d.participant.RenderingQualityInteractor;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.utils.geom.DirectionSet;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.KeyEvent;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
+import org.simantics.scenegraph.g2d.nodes.ShapeNode;
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.scenegraph.utils.Quality;
+import org.simantics.structural2.modelingRules.ConnectionJudgement;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.logging.TimeLogger;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.ExceptionUtils;
+
+import gnu.trove.map.hash.THashMap;
+
+/**
+ * A basic tool for making connection on diagrams.
+ * 
+ * This version defines the starting, ending and route points of a connection.
+ * The routing itself is left up to the diagram router employed by
+ * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.
+ * 
+ * Manual:
+ * 
+ * This tool is added to the diagram when a connection sequence is initiated by
+ * another participant. PointerInteractor is one such participant which adds the
+ * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked
+ * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}
+ * ). The connection will be finished when another allowed terminal is clicked
+ * upon or empty canvas space is ALT+clicked. Route points for the connection
+ * can be created by clicking around on non-terminal-occupied canvas space while
+ * connecting.
+ * 
+ * <p>
+ * Connections can be started from and ended in flags by pressing ALT while
+ * left-clicking.
+ * 
+ * @author Tuukka Lehtonen
+ */
+public class ConnectTool2 extends AbstractMode {
+
+    public static final int          PAINT_PRIORITY        = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
+
+    @Reference
+    protected RenderingQualityInteractor quality;
+
+    @Dependency
+    protected TransformUtil          util;
+
+    @Dependency
+    protected ElementPainter         diagramPainter;
+
+    @Dependency
+    protected PointerInteractor      pi;
+
+    @Dependency
+    protected PickContext            pickContext;
+
+    /**
+     * Start element terminal of the connection. <code>null</code> if connection
+     * was started from a flag or a branch point.
+     * 
+     * The value is received by the constructor.
+     */
+    protected List<TerminalInfo>     startTerminals;
+
+    /**
+     * Refers to any of the possible overlapping start terminals. The value is
+     * taken from the first index of {@link #startTerminals} assuming that the
+     * first one is the nearest. It is <code>null</code> if
+     * {@link #startTerminals} is empty.
+     */
+    protected TerminalInfo           startTerminal;
+
+    protected TerminalInfo           startFlag;
+
+    /**
+     * Starting position of the connection, received as an external argument.
+     */
+    protected final Point2D          startPos;
+
+    /**
+     * <code>true</code> if this tool should create connection continuation
+     * flags, <code>false</code> otherwise.
+     */
+    protected boolean                createFlags;
+
+    /**
+     * 
+     */
+    protected IElementClassProvider  elementClassProvider;
+
+    /**
+     * 
+     */
+    protected Deque<ControlPoint>    controlPoints         = new ArrayDeque<ControlPoint>();
+
+    /**
+     * Contains <code>null</code> when a connection is started from a new flag
+     * or one of the terminals in {@link #startTerminals} when a connection is
+     * being created starting from a terminal or possibly a set of terminals.
+     * 
+     * <p>
+     * Note that this is different from {@link #startTerminal} which simply
+     * represents the first element of {@link #startTerminals}.
+     * 
+     * <p>
+     * Only when this value and {@link #endTerminal} is properly set will a
+     * connection be created between two element terminals.
+     */
+    protected TerminalInfo           selectedStartTerminal;
+
+    /**
+     * Element terminal of connection end element. <code>null</code> if
+     * connection cannot be ended where it is currently being attempted to end.
+     */
+    protected TerminalInfo           endTerminal;
+
+    /**
+     * The latest connectability judgment from the active
+     * {@link IConnectionAdvisor} should the connection happen between
+     * {@link #selectedStartTerminal} and {@link #endTerminal}.
+     */
+    protected ConnectionJudgement    connectionJudgment;
+
+    /**
+     * The latest connectability judgment from the active
+     * {@link IConnectionAdvisor} should the connection happen between
+     * {@link #selectedStartTerminal} and {@link #lastRouteGraphTarget}.
+     */
+    protected ConnectionJudgement    attachToConnectionJudgement;
+
+    /**
+     * If non-null during connection drawing this field tells the direction
+     * forced for the current branch point by the user through the UI commands
+     * {@link Commands#ROTATE_ELEMENT_CCW} and
+     * {@link Commands#ROTATE_ELEMENT_CW}.
+     */
+    private Direction                forcedBranchPointDirection;
+
+    /**
+     * A temporary variable for use with
+     * {@link TerminalTopology#getTerminals(IElement, Collection)}.
+     */
+    protected Collection<Terminal>   terminals             = new ArrayList<Terminal>();
+
+    /**
+     * Previous mouse canvas position recorded by
+     * {@link #processMouseMove(MouseMovedEvent)}.
+     */
+    protected Point2D                lastMouseCanvasPos    = new Point2D.Double();
+
+    /**
+     * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been
+     * invoked at least once. This is used to tell whether to allow creation of
+     * branch points or finising the connection in thin air. It will not be
+     * allowed if the mouse has not moved at all since starting the connection.
+     */
+    protected boolean                mouseHasMoved         = false;
+
+    protected TerminalHoverStrategy  originalStrategy      = null;
+
+    protected TerminalHoverStrategy  terminalHoverStrategy = new TerminalHoverStrategy() {
+        @Override
+        public boolean highlightEnabled() {
+            return !isEndingInFlag();
+        }
+
+        @Override
+        public boolean highlight(TerminalInfo ti) {
+            boolean reflexive = isStartTerminal(ti.e, ti.t);
+            if (reflexive && !allowReflexiveConnections())
+                return false;
+
+            return canConnect(ti.e, ti.t) != null;
+        }
+    };
+
+    protected final static Composite ALPHA_COMPOSITE       = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
+
+    /**
+     * Root scene graph node for all visualization performed by this tool.
+     */
+    protected G2DParentNode          ghostNode;
+
+    /**
+     * Indicates whether the connection is about to be ended into a new
+     * flag/branchpoint or not.
+     */
+    protected TerminalInfo           endFlag;
+
+    protected G2DParentNode          endFlagNode;
+
+    private RouteGraphTarget         lastRouteGraphTarget;
+
+    /**
+     * @param startTerminal
+     * @param mouseId
+     * @param startCanvasPos
+     */
+    public ConnectTool2(TerminalInfo startTerminal, int mouseId, Point2D startCanvasPos) {
+        this(startTerminal == null ? Collections.<TerminalInfo> emptyList()
+                : Collections.singletonList(startTerminal),
+                mouseId,
+                startCanvasPos);
+    }
+
+    /**
+     * @param startTerminals
+     * @param mouseId
+     * @param startCanvasPos
+     */
+    public ConnectTool2(List<TerminalInfo> startTerminals, int mouseId, Point2D startCanvasPos) {
+        super(mouseId);
+
+        if (startCanvasPos == null)
+            throw new NullPointerException("null start position");
+        if (startTerminals == null)
+            throw new NullPointerException("null start terminals");
+
+        this.startPos = startCanvasPos;
+        this.lastMouseCanvasPos.setLocation(startPos);
+
+        this.startTerminals = startTerminals;
+        this.startTerminal = startTerminals.isEmpty() ? null : startTerminals.get(0);
+    }
+
+    @Override
+    public void addedToContext(ICanvasContext ctx) {
+        super.addedToContext(ctx);
+
+        if (quality != null)
+            quality.setStaticQuality(Quality.LOW);
+
+        // Force terminals to always be highlighted without pressing certain
+        // keys or key combinations.
+        originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
+        setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
+    }
+
+    @Override
+    protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
+        if (newDiagram != null) {
+            // Get IElementClassProvider
+            ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);
+            if (ctx != null) {
+                this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);
+            }
+
+            // See if flags should be created or not.
+            this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));
+            startConnection();
+        }
+    }
+
+    @Override
+    public void removedFromContext(ICanvasContext ctx) {
+        if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {
+            if (originalStrategy != null)
+                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);
+            else
+                removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
+        }
+
+        if (quality != null)
+            quality.setStaticQuality(null);
+
+        super.removedFromContext(ctx);
+    }
+
+    protected void startConnection() {
+        Point2D startPos = (Point2D) this.startPos.clone();
+        ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+        if (snapAdvisor != null)
+            snapAdvisor.snap(startPos);
+
+        // Resolve the first element and terminal of the connection.
+        ControlPoint start = new ControlPoint(startPos);
+
+        if (startTerminal != null) {
+            assert ElementUtils.peekDiagram(startTerminal.e) == diagram;
+            Point2D terminalPos = new Point2D.Double(startTerminal.posDia.getTranslateX(),
+                    startTerminal.posDia.getTranslateY());
+            start.setPosition(terminalPos).setAttachedToTerminal(startTerminal);
+        } else {
+            // Create TerminalInfo describing the flag to be created.
+            if (createFlags) {
+                // This prevents connection creation from creating a branch
+                // point in place of this flag.
+                startFlag = createFlag(EdgeEnd.Begin);
+                start.setAttachedToTerminal(startFlag);
+                showElement(ghostNode, "startFlag", startFlag.e, startPos);
+            }
+        }
+        controlPoints.add(start);
+        controlPoints.add(new ControlPoint(startPos));
+
+        // Make sure that we are ending with a flag if ALT is pressed.
+        // This makes the tool always start with a flag which can be quite
+        // cumbersome and is therefore disabled. The current version will not
+        // end the connection if the mouse has not moved at all.
+        //if (keyUtil.isKeyPressed(java.awt.event.KeyEvent.VK_ALT)) {
+        //    endWithoutTerminal(lastMouseCanvasPos, true);
+        //}
+    }
+
+    @SGInit
+    public void initSG(G2DParentNode parent) {
+        ghostNode = parent.addNode(G2DParentNode.class);
+        ghostNode.setZIndex(PAINT_PRIORITY);
+
+        ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
+        pathNode.setColor(new Color(160, 0, 0));
+        pathNode.setStroke(new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
+                new float[] { 0.5f, 0.2f }, 0));
+        pathNode.setScaleStroke(false);
+        pathNode.setZIndex(0);
+
+        G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);
+        points.setZIndex(1);
+
+        updateSG();
+    }
+
+    private RouteTerminal addControlPoint(RouteGraph routeGraph, ControlPoint cp) {
+        TerminalInfo ti = cp.getAttachedTerminal();
+        if(ti != null && ti != startFlag && ti != endFlag) {
+            Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
+            GeometryUtils.expandRectangle(bounds, 2);
+            int allowedDirections = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
+                    ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
+            return routeGraph.addTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(),
+                    bounds, allowedDirections, PlainLineEndStyle.INSTANCE);
+        }
+        else {
+            double x = cp.getPosition().getX();
+            double y = cp.getPosition().getY();
+            int allowedDirections = 0xf;
+            switch(cp.getDirection()) {
+            case Horizontal: allowedDirections = 5; break;
+            case Vertical: allowedDirections = 10; break;
+            case Any: allowedDirections = 15; break;
+            }
+            return routeGraph.addTerminal(x, y, x, y, x, y, allowedDirections);
+        }
+    }
+    
+    protected void updateSG() {
+        if (controlPoints.size() != 2)
+            return;
+
+        ControlPoint begin = controlPoints.getFirst();
+        ControlPoint end = controlPoints.getLast();
+
+        RouteGraph routeGraph = new RouteGraph();
+        RouteTerminal a = addControlPoint(routeGraph, begin);
+        RouteTerminal b = addControlPoint(routeGraph, end);
+        routeGraph.link(a, b);
+
+        Path2D path = routeGraph.getPath2D();
+
+        // Create scene graph to visualize the connection.
+        ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
+        pathNode.setShape(path);
+
+        setDirty();
+    }
+
+    private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {
+        return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));
+    }
+
+    private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {
+        G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);
+        elementParent.setTransform(tr);
+        elementParent.removeNodes();
+        for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))
+            sg.init(element, elementParent);
+        return elementParent;
+    }
+
+    @SGCleanup
+    public void cleanupSG() {
+        ghostNode.remove();
+        ghostNode = null;
+    }
+
+    @EventHandler(priority = 200)
+    public boolean handleCommandEvents(CommandEvent ce) {
+        if (ce.command.equals(Commands.CANCEL)) {
+            setDirty();
+            remove();
+            return true;
+        } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {
+            return rotateLastBranchPoint(ce.command.equals(Commands.ROTATE_ELEMENT_CW));
+        }
+        return false;
+    }
+
+    @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
+    public boolean handleKeyEvents(KeyEvent ke) {
+        if (ke instanceof KeyPressedEvent) {
+            // Back-space, cancel prev bend
+            if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)
+                return cancelPreviousBend();
+        }
+
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
+            if (createFlags) {
+                endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
+    public boolean handleEvent(MouseEvent me) {
+        // Only handle events for the connection-initiating mouse
+        if (me.mouseId != mouseId)
+            return false;
+
+        if (me instanceof MouseMovedEvent)
+            return processMouseMove((MouseMovedEvent) me);
+
+        if (me instanceof MouseButtonPressedEvent)
+            return processMouseButtonPress((MouseButtonPressedEvent) me);
+
+        // #7653: Support creating connections between terminals without lifting mouse button in between.
+        if (me instanceof MouseButtonReleasedEvent)
+            return processMouseButtonRelease((MouseButtonReleasedEvent) me);
+
+        return false;
+    }
+
+    protected boolean processMouseMove(MouseMovedEvent me) {
+        mouseHasMoved = true;
+
+        Point2D mouseControlPos = me.controlPosition;
+        Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
+
+        ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+        if (snapAdvisor != null)
+            snapAdvisor.snap(mouseCanvasPos);
+
+        // Record last snapped canvas position of mouse.
+        this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
+
+        if (isEndingInFlag()) {
+            endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
+        }
+
+        List<TerminalInfo> tis = pi.pickTerminals(me.controlPosition);
+        tis = TerminalUtil.findNearestOverlappingTerminals(tis);
+        if (!tis.isEmpty() && !containsStartTerminal(tis)) {
+            //System.out.println("end terminals (" + tis.size() + "):\n" + EString.implode(tis));
+            for (TerminalInfo ti : tis) {
+                Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);
+                if (canConnect != null) {
+                    connectionJudgment = canConnect.first;
+
+                    if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
+                        if (canConnect.second != null) {
+                            controlPoints.getFirst()
+                            .setPosition(canConnect.second.posDia)
+                            .setAttachedToTerminal(canConnect.second);
+                        }
+                        controlPoints.getLast()
+                        .setPosition(ti.posDia)
+                        .setAttachedToTerminal(ti);
+
+                        selectedStartTerminal = canConnect.second;
+                        endTerminal = ti;
+                    }
+
+                    // Make sure that we are ending with a flag if ALT is pressed
+                    // and no end terminal is defined.
+                    if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
+                        updateSG();
+                    return false;
+                }
+            }
+        } else {
+            RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
+                    getContext(),
+                    diagram,
+                    pi.getCanvasPickShape(me.controlPosition),
+                    pi.getPickDistance());
+            if (cp != null) {
+                // Remove branch point highlight from previously picked route graph.
+                if (lastRouteGraphTarget != null && cp.getNode() != lastRouteGraphTarget.getNode())
+                    cp.getNode().showBranchPoint(null);
+                lastRouteGraphTarget = cp;
+
+                // Validate connection before visualizing connectability
+                Point2D isectPos = cp.getIntersectionPosition();
+                TerminalInfo ti = TerminalInfo.create(
+                        isectPos,
+                        cp.getElement(),
+                        BranchPointTerminal.existingTerminal(
+                                isectPos,
+                                DirectionSet.ANY,
+                                BranchPointNode.SHAPE),
+                        BranchPointNode.SHAPE);
+                Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);
+                if (canConnect != null) {
+                    attachToConnectionJudgement = canConnect.first;
+                    controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);
+                    endTerminal = ti;
+                    startTerminal = canConnect.second;
+                    cp.getNode().showBranchPoint(isectPos);
+                    if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
+                        updateSG();
+                    return false;
+                }
+            } else {
+                if (lastRouteGraphTarget != null) {
+                    lastRouteGraphTarget.getNode().showBranchPoint(null);
+                    lastRouteGraphTarget = null;
+                }
+            }
+        }
+
+        connectionJudgment = null;
+        attachToConnectionJudgement = null;
+        if (isEndTerminalDefined()) {
+            // CASE: Mouse was previously on top of a valid terminal to end
+            // the connection. Now the mouse has been moved where there is
+            // no longer a terminal to connect to.
+            //
+            // => Disconnect the last edge segment from the previous
+            // terminal, mark endElement/endTerminal non-existent
+            // and connect the disconnected edge to a new branch point.
+
+            controlPoints.getLast()
+            .setPosition(mouseCanvasPos)
+            .setDirection(calculateCurrentBranchPointDirection())
+            .setAttachedToTerminal(null);
+
+            endTerminal = null;
+        } else {
+            // CASE: Mouse was not previously on top of a valid ending
+            // element terminal.
+            //
+            // => Move and re-orient last branch point.
+
+            controlPoints.getLast()
+            .setPosition(mouseCanvasPos)
+            .setDirection(calculateCurrentBranchPointDirection());
+        }
+
+        // Make sure that we are ending with a flag if ALT is pressed and no end
+        // terminal is defined.
+        if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
+            updateSG();
+
+        return false;
+    }
+
+    protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {
+        MouseButtonEvent me = e;
+
+        // Do nothing before the mouse has moved at least a little.
+        // This prevents the user from ending the connection right where
+        // it started.
+        if (!mouseHasMoved)
+            return true;
+
+        if (me.button == MouseEvent.LEFT_BUTTON) {
+            Point2D mouseControlPos = me.controlPosition;
+            Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
+
+            ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+            if (snapAdvisor != null)
+                snapAdvisor.snap(mouseCanvasPos);
+
+            if (tryEndConnection()) {
+                return true;
+            } else {
+                // Finish connection in thin air only if the
+                // connection was started from a valid terminal.
+                if (me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) && !startTerminals.isEmpty()) {
+                    Pair<ConnectionJudgement, TerminalInfo> pair = canConnect(null, null);
+                    if (pair != null) {
+                        connectionJudgment = (ConnectionJudgement) pair.first;
+                        selectedStartTerminal = pair.second;
+//                        endFlag = createFlag(EdgeEnd.End);
+//                        controlPoints.getLast().setAttachedToTerminal(endFlag);
+                        createConnection();
+                        setDirty();
+                        remove();
+                    } else {
+                        // Inform the user why connection couldn't be created.
+                        String tmsg = terminalsToString(startTerminals);
+                        ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection when starting from one of the following terminals:\n" + tmsg, null);
+                    }
+                    return true;
+                } else if (routePointsAllowed()
+                        && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
+                    // Add new connection control point.
+                    controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));
+                    resetForcedBranchPointDirection();
+                    updateSG();
+                }
+            }
+
+            // Eat the event to prevent other participants from doing
+            // incompatible things while in this connection mode.
+            return true;
+        } else if (me.button == MouseEvent.RIGHT_BUTTON) {
+            return cancelPreviousBend();
+        }
+
+        return false;
+    }
+
+    private int mouseLeftReleaseCount = 0;
+
+    protected boolean processMouseButtonRelease(MouseButtonReleasedEvent me) {
+        if (me.button == MouseEvent.LEFT_BUTTON
+                && ++mouseLeftReleaseCount == 1) {
+            return tryEndConnection();
+        }
+        return false;
+    }
+
+    /**
+     * @return <code>true</code> if connection was successfully ended
+     */
+    private boolean tryEndConnection() {
+        if (isEndTerminalDefined() && connectionJudgment != null) {
+            createConnection();
+            remove();
+            return true;
+        } else if (lastRouteGraphTarget != null && attachToConnectionJudgement != null) {
+            lastRouteGraphTarget.getNode().showBranchPoint(null);
+            attachToConnection();
+            remove();
+            return true;
+        }
+        return false;
+    }
+
+    private void attachToConnection() {
+        ConnectionJudgement judgment = this.attachToConnectionJudgement;
+        if (judgment == null) {
+            ErrorLogger.defaultLogError("Cannot attach to connection, no judgment available on connection validity", null);
+            return;
+        }
+
+        ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
+        RouteGraph before = lastRouteGraphTarget.getNode().getRouteGraph();
+        THashMap<Object, Object> copyMap = new THashMap<>();
+        RouteGraph after = before.copy(copyMap);
+
+        RouteLine attachTo = (RouteLine) copyMap.get(lastRouteGraphTarget.getLine());
+        after.makePersistent(attachTo);
+        for (RouteLine line : after.getAllLines()) {
+            if (!line.isTransient() && line.isHorizontal() == attachTo.isHorizontal()
+                    && line.getPosition() == attachTo.getPosition()) {
+                attachTo = line;
+                break;
+            }
+        }
+        RouteLine attachToLine = attachTo;
+        RouteGraphDelta delta = new RouteGraphDelta(before, after);
+
+        Simantics.getSession().asyncRequest(new WriteRequest() {
+            @Override
+            public void perform(WriteGraph graph) throws DatabaseException {
+                graph.markUndoPoint();
+                Resource connection = ElementUtils.getObject(endTerminal.e);
+                if (!delta.isEmpty()) {
+                    new RouteGraphConnection(graph, connection).synchronize(graph, before, after, delta);
+                }
+                Resource line = RouteGraphConnection.deserialize(graph, attachToLine.getData());
+                Deque<ControlPoint> cps = new ArrayDeque<>();
+                for (Iterator<ControlPoint> iterator = controlPoints.descendingIterator(); iterator.hasNext();)
+                    cps.add(iterator.next());
+                builder.attachToRouteGraph(graph, judgment, connection, line, cps, startTerminal, FlagClass.Type.In);
+            }
+        }, parameter -> {
+            if (parameter != null)
+                ExceptionUtils.logAndShowError(parameter);
+        });
+    }
+
+    protected boolean cancelPreviousBend() {
+        if (!routePointsAllowed())
+            return false;
+
+        // Just to make this code more comprehensible, prevent an editing
+        // case that requires ugly code to work.
+        if (isEndingInFlag())
+            return true;
+
+        // If there are no real route points, cancel whole connection.
+        if (controlPoints.size() <= 2) {
+            setDirty();
+            remove();
+            return true;
+        }
+
+        // Cancel last bend
+        controlPoints.removeLast();
+        controlPoints.getLast().setPosition(lastMouseCanvasPos);
+        resetForcedBranchPointDirection();
+
+        updateSG();
+        return true;
+    }
+
+    /**
+     * Rotates the last branch point in the created connection in either
+     * clockwise or counter-clockwise direction as a response to a user
+     * interaction.
+     * 
+     * <p>
+     * At the same time it use {@link #forcedBranchPointDirection} to mark the
+     * current last branch point to be forcefully oriented according to the
+     * users wishes instead of calculating a default value for the orientation
+     * from the routed connection path. See
+     * {@link #calculateCurrentBranchPointDirection()} for more information on
+     * this.
+     * 
+     * <p>
+     * The logic of this method goes as follows:
+     * <ul>
+     * <li>Calculate the current branch point direction</li>
+     * <li>If the branch point direction is currently user selected (
+     * {@link #forcedBranchPointDirection}</li>
+     * <li></li>
+     * <li></li>
+     * </ul>
+     * 
+     * @param clockwise
+     * @return <code>true</code> if the rotation was successful
+     */
+    protected boolean rotateLastBranchPoint(boolean clockwise) {
+        Direction oldDir = calculateCurrentBranchPointDirection();
+
+        if (forcedBranchPointDirection == null) {
+            forcedBranchPointDirection = oldDir.toggleDetermined();
+        } else {
+            forcedBranchPointDirection = clockwise ? oldDir.cycleNext() : oldDir.cyclePrevious();
+        }
+
+        controlPoints.getLast().setDirection(forcedBranchPointDirection);
+
+        updateSG();
+
+        return true;
+    }
+
+    /**
+     * Set preferred direction for a branch/route point element.
+     * 
+     * @param branchPoint the element to set the direction for
+     * @param direction the direction to set
+     * @return
+     */
+    protected void setDirection(IElement branchPoint, Direction direction) {
+        branchPoint.getElementClass().getSingleItem(BranchPoint.class).setDirectionPreference(branchPoint, direction);
+    }
+
+    protected Direction forcedBranchPointDirection() {
+        return forcedBranchPointDirection;
+    }
+
+    protected void resetForcedBranchPointDirection() {
+        forcedBranchPointDirection = null;
+    }
+
+    protected void forceBranchPointDirection(Direction direction) {
+        forcedBranchPointDirection = direction;
+    }
+
+    /**
+     * @return
+     */
+    protected Direction calculateCurrentBranchPointDirection() {
+        // If this is not the first branch point, toggle direction compared to
+        // last.
+        if (forcedBranchPointDirection != null)
+            return forcedBranchPointDirection;
+
+        if (controlPoints.size() > 2) {
+            // This is not the first edge segment, toggle route point
+            // directions.
+            Iterator<ControlPoint> it = controlPoints.descendingIterator();
+            it.next();
+            ControlPoint secondLastCp = it.next();
+
+            Direction dir = secondLastCp.getDirection();
+            switch (dir) {
+                case Horizontal:
+                    return Direction.Vertical;
+                case Vertical:
+                    return Direction.Horizontal;
+                case Any:
+            }
+        }
+
+        // If this is the first branch point, calculate based on edge segment
+        // angle.
+        if (controlPoints.size() > 1) {
+            Iterator<ControlPoint> it = controlPoints.descendingIterator();
+            ControlPoint last = it.next();
+            ControlPoint secondLast = it.next();
+
+            double angle = Math.atan2(Math.abs(last.getPosition().getY() - secondLast.getPosition().getY()),
+                    Math.abs(last.getPosition().getX() - secondLast.getPosition().getX()));
+
+            if (angle >= 0 && angle < Math.PI / 4) {
+                return Direction.Horizontal;
+            } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {
+                return Direction.Vertical;
+            }
+        }
+
+        return Direction.Any;
+    }
+
+    protected boolean isEndingInFlag() {
+        return endFlag != null;
+    }
+
+    /**
+     * @param mousePos
+     * @param altDown
+     * @return <code>true</code> if updateSG was executed, <code>false</code>
+     *         otherwise
+     */
+    protected boolean endWithoutTerminal(Point2D mousePos, boolean altDown) {
+        // Just go with branch points if flags are not allowed.
+        if (!createFlags)
+            return false;
+
+        boolean endTerminalDefined = isEndTerminalDefined();
+
+        if (altDown) {
+            if (!isEndingInFlag()) {
+                endFlag = createFlag(EdgeEnd.End);
+                endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);
+                controlPoints.getLast()
+                .setDirection(calculateCurrentBranchPointDirection())
+                .setAttachedToTerminal(endFlag);
+
+                // TerminalPainter must refresh
+                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
+
+                updateSG();
+                return true;
+            }
+        } else {
+            if (isEndingInFlag()) {
+                // Currently ending with flag but ALT is no longer down
+                // so that flag must be removed.
+                endFlag = null;
+                endFlagNode.remove();
+                endFlagNode = null;
+
+                ControlPoint cp = controlPoints.getLast();
+                cp.setDirection(calculateCurrentBranchPointDirection())
+                .setAttachedToTerminal(endTerminal);
+
+                if (endTerminalDefined) {
+                    cp.setPosition(endTerminal.posDia);
+                } else {
+                    cp.setPosition(mousePos);
+                }
+
+                // Force TerminalPainter refresh
+                setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
+
+                updateSG();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected void createConnection() {
+        createConnection(
+                this.selectedStartTerminal,
+                this.endTerminal,
+                this.connectionJudgment,
+                this.controlPoints);
+    }
+
+    protected void createConnection(
+            final TerminalInfo startTerminal,
+            final TerminalInfo endTerminal,
+            final ConnectionJudgement judgement,
+            final Deque<ControlPoint> controlPoints)
+    {
+        TimeLogger.resetTimeAndLog(getClass(), "createConnection");
+        if (judgement == null) {
+            // Inform the user why connection couldn't be created.
+            String tmsg = terminalsToString(Arrays.asList(startTerminal, endTerminal));
+            ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity when connecting the terminals:\n" + tmsg, null);
+            return;
+        }
+
+        final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
+
+        Simantics.getSession().asyncRequest(new WriteRequest() {
+            @Override
+            public void perform(WriteGraph graph) throws DatabaseException {
+                builder.create(graph, judgement, controlPoints, startTerminal, endTerminal);
+            }
+        }, parameter -> {
+            if (parameter != null)
+                ExceptionUtils.logAndShowError(parameter);
+        });
+    }
+
+    /**
+     * @param canvasPos
+     * @return
+     */
+    protected ControlPoint newControlPointWithCalculatedDirection(Point2D canvasPos) {
+        return new ControlPoint(canvasPos, calculateCurrentBranchPointDirection());
+    }
+
+    /**
+     * @param e
+     * @param t
+     * @return <code>true</code> if the specified element terminal matches any
+     *         TerminalInfo in {@link #startTerminals}
+     */
+    protected boolean isStartTerminal(IElement e, Terminal t) {
+        if (startTerminal == null)
+            return false;
+        for (TerminalInfo st : startTerminals) {
+            if (st.e == e && st.t == t) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param e
+     * @param t
+     * @return <code>true</code> if the specified element terminal matches any
+     *         TerminalInfo in {@link #startTerminals}
+     */
+    protected boolean containsStartTerminal(List<TerminalInfo> tis) {
+        if (startTerminal == null)
+            return false;
+        for (TerminalInfo st : startTerminals) {
+            for (TerminalInfo et : tis) {
+                if (st.e == et.e && st.t == et.t) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    protected static FlagClass.Type endToFlagType(EdgeEnd end) {
+        switch (end) {
+            case Begin:
+                return FlagClass.Type.In;
+            case End:
+                return FlagClass.Type.Out;
+            default:
+                throw new IllegalArgumentException("unrecognized edge end: " + end);
+        }
+    }
+
+    protected TerminalInfo createFlag(EdgeEnd connectionEnd) {
+        ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);
+        IElement e = Element.spawnNew(flagClass);
+
+        e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));
+        e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);
+
+        TerminalInfo ti = new TerminalInfo();
+        ti.e = e;
+        ti.t = ElementUtils.getSingleTerminal(e);
+        ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);
+        ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);
+
+        return ti;
+    }
+
+    protected boolean shouldEndWithFlag(MouseEvent me) {
+        return shouldEndWithFlag( me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) );
+    }
+
+    protected boolean shouldEndWithFlag(boolean altPressed) {
+        return altPressed && !isEndTerminalDefined() && createFlags && startFlag == null;
+    }
+
+    protected boolean isEndTerminalDefined() {
+        return endTerminal != null;
+    }
+
+    protected boolean isFlagTerminal(TerminalInfo ti) {
+        return ti.e.getElementClass().containsClass(FlagHandler.class);
+    }
+
+    protected boolean allowReflexiveConnections() {
+        return false;
+    }
+
+    protected boolean routePointsAllowed() {
+        return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
+    }
+
+    /**
+     * @param endElement
+     * @param endTerminal
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    protected final Pair<ConnectionJudgement, TerminalInfo> canConnect(IElement endElement, Terminal endTerminal) {
+        IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
+        Object judgement = canConnect(advisor, endElement, endTerminal);
+        if (judgement == null)
+            return null;
+        if (judgement instanceof Pair<?, ?>)
+            return (Pair<ConnectionJudgement, TerminalInfo>) judgement;
+        return Pair.<ConnectionJudgement, TerminalInfo>make((ConnectionJudgement) judgement, startTerminal);
+    }
+
+    protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
+        if (advisor == null)
+            return Pair.make(ConnectionJudgement.CANBEMADELEGAL, startTerminal);
+        if (startTerminals.isEmpty()) {
+            ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, null, null, endElement, endTerminal);
+            return obj != null ? Pair.<ConnectionJudgement, TerminalInfo>make(obj, null) : null;
+        }
+        for (TerminalInfo st : startTerminals) {
+            ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, st.e, st.t, endElement, endTerminal);
+            if (obj != null) {
+                return Pair.make(obj, st);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * For generating debugging information of what was attempted by the user
+     * when a connection couldn't be created.
+     * 
+     * @param ts
+     * @return
+     */
+    private String terminalsToString(final Iterable<TerminalInfo> ts) {
+        try {
+            return Simantics.sync(new UniqueRead<String>() {
+                @Override
+                public String perform(ReadGraph graph) throws DatabaseException {
+                    DiagramResource DIA = DiagramResource.getInstance(graph);
+                    ModelingResources MOD = ModelingResources.getInstance(graph);
+                    StringBuilder sb = new StringBuilder();
+                    boolean first = true;
+                    for (TerminalInfo ti : ts) {
+                        if (!first)
+                            sb.append("\n");
+                        first = false;
+                        sb.append("element ");
+                        Object o = ElementUtils.getObject(ti.e);
+                        if (o instanceof Resource) {
+                            Resource er = (Resource) o;
+                            Resource cer = graph.getPossibleObject(er, MOD.ElementToComponent);
+                            Resource r = cer != null ? cer : er;
+                            sb.append(NameUtils.getSafeName(graph, r)).append(" : ");
+                            for (Resource type : graph.getPrincipalTypes(r)) {
+                                sb.append(NameUtils.getSafeName(graph, type, true));
+                            }
+                        } else {
+                            sb.append(ti.e.toString());
+                        }
+                        sb.append(", terminal ");
+                        if (ti.t instanceof ResourceTerminal) {
+                            Resource tr = ((ResourceTerminal) ti.t).getResource();
+                            Resource cp = graph.getPossibleObject(tr, DIA.HasConnectionPoint);
+                            Resource r = cp != null ? cp : tr;
+                            sb.append(NameUtils.getSafeName(graph, r, true));
+                        } else {
+                            sb.append(ti.t.toString());
+                        }
+                    }
+                    return sb.toString();
+                }
+            });
+        } catch (DatabaseException e) {
+            return e.getMessage();
+        }
+    }
+
 }
\ No newline at end of file