]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/RouteGraphNode.java
Fix RouteGraphNode styling
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / connection / RouteGraphNode.java
index e231476acfb00ead8f00699ca7f967e8f372ab25..084c0d6582874a7f7cb64439a58e2495e4da12f8 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2011 Association for Decentralized Information Management in\r
- * 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.scenegraph.g2d.nodes.connection;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Graphics2D;\r
-import java.awt.RenderingHints;\r
-import java.awt.Shape;\r
-import java.awt.Stroke;\r
-import java.awt.event.KeyEvent;\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.lang.reflect.Constructor;\r
-import java.util.Collection;\r
-import java.util.Map;\r
-\r
-import org.simantics.diagram.connection.RouteGraph;\r
-import org.simantics.diagram.connection.RouteLine;\r
-import org.simantics.diagram.connection.RouteLink;\r
-import org.simantics.diagram.connection.RouteTerminal;\r
-import org.simantics.diagram.connection.actions.IAction;\r
-import org.simantics.diagram.connection.actions.IReconnectAction;\r
-import org.simantics.diagram.connection.actions.MoveAction;\r
-import org.simantics.diagram.connection.actions.ReconnectLineAction;\r
-import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
-import org.simantics.diagram.connection.rendering.BasicConnectionStyle;\r
-import org.simantics.diagram.connection.rendering.ConnectionStyle;\r
-import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
-import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;\r
-import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
-import org.simantics.diagram.connection.splitting.SplittedRouteGraph;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.ISelectionPainterNode;\r
-import org.simantics.scenegraph.g2d.G2DNode;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.events.EventTypes;\r
-import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\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.GridNode;\r
-import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
-import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action;\r
-import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick;\r
-import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
-import org.simantics.scenegraph.utils.GeometryUtils;\r
-import org.simantics.scenegraph.utils.InitValueSupport;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, InitValueSupport  {\r
-\r
-    private static final long       serialVersionUID = -917194130412280965L;\r
-\r
-    private static final double     TOLERANCE        = IAction.TOLERANCE;\r
-    private static final Stroke     SELECTION_STROKE = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
-    private static final Color      SELECTION_COLOR  = new Color(255, 0, 255, 96);\r
-\r
-    private static final HighlightActionPointsAction highlightActions = new HighlightActionPointsAction(null);\r
-\r
-    protected RouteGraph            rg;\r
-    protected IRouteGraphRenderer   baseRenderer;\r
-    protected IRouteGraphRenderer   renderer;\r
-    protected double                pickTolerance    = TOLERANCE;\r
-    protected boolean               editable         = true;\r
-    private boolean                 branchable       = true;\r
-\r
-    protected IRouteGraphListener   rgListener;\r
-    protected RouteGraphDelta       rgDelta;\r
-\r
-    protected transient double      mouseX;\r
-    protected transient double      mouseY;\r
-    protected transient Point2D     pt               = new Point2D.Double();\r
-\r
-    protected transient MoveAction  dragAction;\r
-    protected transient IAction     currentAction;\r
-    protected transient Rectangle2D bounds;\r
-\r
-    /**\r
-     * Dynamic color for connection rendering.\r
-     */\r
-    protected transient Color       dynamicColor;\r
-\r
-    /**\r
-     * Dynamic stroke for connection rendering.\r
-     */\r
-    protected transient Stroke      dynamicStroke;\r
-\r
-    protected transient Path2D      selectionPath    = new Path2D.Double();\r
-    protected transient Stroke      selectionStroke  = null;\r
-\r
-    protected transient boolean     highlightActionsEnabled = false;\r
-    protected transient AffineTransform lastViewTransform = null;\r
-\r
-    /**\r
-     * x = NaN is used to indicate that possible branch point should not be\r
-     * rendered but interaction has not ended yet.\r
-     */\r
-    protected transient Point2D     newBranchPointPosition = null;\r
-\r
-\r
-    protected transient Map<Object,ILineEndStyle> dynamicStyles = null;\r
-    \r
-    @Override\r
-    public void initValues() {\r
-        dynamicColor = null;\r
-        wrapRenderer();\r
-    }\r
-\r
-    @PropertySetter("color")\r
-    @SyncField(value = {"dynamicColor"})\r
-    public void setDynamicColor(Color color) {\r
-        this.dynamicColor = color;\r
-        wrapRenderer();\r
-    }\r
-\r
-    @PropertySetter("width")\r
-    @SyncField("dynamicStroke")\r
-    public void setDynamicStroke(Stroke stroke) {\r
-        this.dynamicStroke = stroke;\r
-        wrapRenderer();\r
-        createSelectionStroke();\r
-    }\r
-    \r
-    @SyncField(value = {"dynamicStyles"})\r
-    public void setDynamicLineEnd(RouteTerminal terminal, ILineEndStyle style) {\r
-       if (dynamicStyles == null)\r
-               dynamicStyles = new THashMap<Object, ILineEndStyle>();\r
-       terminal.setDynamicStyle(style);\r
-       if (terminal.getData() != null) {\r
-               if (style != null)\r
-                       dynamicStyles.put(terminal.getData(),style);\r
-               else\r
-                       dynamicStyles.remove(terminal.getData());\r
-       }\r
-    }\r
-    \r
-    private void updateLineEnds() {\r
-       if (dynamicStyles == null)\r
-               return;\r
-       for (RouteTerminal t : rg.getTerminals()) {\r
-               if (t.getData() == null)\r
-                       continue;\r
-               ILineEndStyle dynamicStyle = dynamicStyles.get(t.getData());\r
-               if (dynamicStyle != null)\r
-                       t.setDynamicStyle(dynamicStyle);\r
-       }\r
-    }\r
-\r
-    @SyncField(value = {"rg"})\r
-    public void setRouteGraph(RouteGraph graph) {\r
-        this.rg = graph;\r
-        updateLineEnds();\r
-        updateBounds();\r
-    }\r
-\r
-    @SyncField(value = {"rgDelta"})\r
-    public void setRouteGraphDelta(RouteGraphDelta delta) {\r
-        this.rgDelta = delta;\r
-    }\r
-\r
-    @SyncField(value = {"renderer"})\r
-    public void setRenderer(IRouteGraphRenderer renderer) {\r
-\r
-        this.baseRenderer = renderer;\r
-        wrapRenderer();\r
-\r
-        createSelectionStroke();      \r
-    }\r
-    \r
-    private void createSelectionStroke() {\r
-        BasicConnectionStyle style = tryGetStyle();\r
-         selectionStroke = null;\r
-         if (style != null) {\r
-             BasicStroke stroke = (BasicStroke) style.getLineStroke();\r
-             if (stroke != null) {\r
-                float width = Math.max(stroke.getLineWidth() + 0.75f, stroke.getLineWidth()*1.3f);\r
-                 selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());\r
-             }\r
-         } else {\r
-             selectionStroke = SELECTION_STROKE;\r
-         }\r
-    }\r
-    \r
-    private void wrapRenderer() {\r
-        \r
-        if(baseRenderer == null) {\r
-            renderer = null;\r
-            return;\r
-        }\r
-        \r
-        if(dynamicColor != null || dynamicStroke != null) {\r
-            BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);\r
-            try {\r
-               Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class);\r
-               renderer = new StyledRouteGraphRenderer(c.newInstance(\r
-                        dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),\r
-                                baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),\r
-                                    dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), \r
-                                            dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),\r
-                                                    baseStyle.getDegeneratedLineLength()));\r
-            } catch (Exception e) {\r
-               renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(\r
-                        dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),\r
-                                baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),\r
-                                    dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), \r
-                                            dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),\r
-                                                    baseStyle.getDegeneratedLineLength()));\r
-            }\r
-            \r
-            \r
-        } else {\r
-            renderer = baseRenderer;\r
-        }\r
-        \r
-    }\r
-\r
-    @SyncField(value = {"pickTolerance"})\r
-    public void setPickTolerance(double tolerance) {\r
-        this.pickTolerance = tolerance;\r
-    }\r
-\r
-    @SyncField(value = {"editable"})\r
-    public void setEditable(boolean editable) {\r
-        this.editable = editable;\r
-    }\r
-\r
-    @SyncField(value = {"branchable"})\r
-    public void setBranchable(boolean branchable) {\r
-        this.branchable = branchable;\r
-    }\r
-\r
-    public RouteGraph getRouteGraph() {\r
-        return rg;\r
-    }\r
-\r
-    public RouteGraphDelta getRouteGraphDelta() {\r
-        return rgDelta;\r
-    }\r
-\r
-    public IRouteGraphRenderer getRenderer() {\r
-        return renderer;\r
-    }\r
-\r
-    public boolean isEditable() {\r
-        return editable;\r
-    }\r
-\r
-    public boolean isBranchable() {\r
-        return branchable;\r
-    }\r
-    \r
-    public double getPickTolerance() {\r
-               return pickTolerance;\r
-       }\r
-\r
-    /**\r
-     * When in client-server mode, listener is only set on the server side and\r
-     * fireRouteGraphChanged will tell it when rg has changed.\r
-     * \r
-     * @param listener\r
-     */\r
-    public void setRouteGraphListener(IRouteGraphListener listener) {\r
-        this.rgListener = listener;\r
-    }\r
-\r
-    /**\r
-     * @param before\r
-     * @param after\r
-     * @return <code>true</code> if changes were fired\r
-     */\r
-    private boolean setRouteGraphAndFireChanges(RouteGraph before, RouteGraph after) {\r
-        RouteGraphDelta delta = new RouteGraphDelta(before, after);\r
-        if (!delta.isEmpty()) {\r
-            setRouteGraph(after);\r
-            setRouteGraphDelta(delta);\r
-            fireRouteGraphChanged(before, after, delta);\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @ServerSide\r
-    protected void fireRouteGraphChanged(RouteGraph before, RouteGraph after, RouteGraphDelta delta) {\r
-        if (rgListener != null) {\r
-            RouteGraphChangeEvent event = new RouteGraphChangeEvent(this, before, after, delta);\r
-            rgListener.routeGraphChanged(event);\r
-        }\r
-    }\r
-\r
-    public void showBranchPoint(Point2D p) {\r
-        newBranchPointPosition = p;\r
-    }\r
-\r
-    @Override\r
-    public void init() {\r
-        super.init();\r
-        addEventHandler(this);\r
-    }\r
-\r
-    @Override\r
-    public void cleanup() {\r
-        rgListener = null;\r
-        removeEventHandler(this);\r
-        super.cleanup();\r
-    }\r
-\r
-    protected boolean isSelected() {\r
-        return NodeUtil.isSelected(this, 1);\r
-    }\r
-\r
-    @Override\r
-    public void render(Graphics2D g) {\r
-        if (renderer == null)\r
-            return;\r
-\r
-        AffineTransform ot = null;\r
-        AffineTransform t = getTransform();\r
-        if (t != null && !t.isIdentity()) {\r
-            ot = g.getTransform();\r
-            g.transform(getTransform());\r
-        }\r
-\r
-        Object aaHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);\r
-        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);\r
-\r
-        boolean selected = NodeUtil.isSelected(this, 1);\r
-\r
-        if (currentAction != null) {\r
-            currentAction.render(g, renderer, mouseX, mouseY);\r
-        } else {\r
-            if (selected && selectionStroke != null) {\r
-                selectionPath.reset();\r
-                rg.getPath2D(selectionPath);\r
-                Shape selectionShape = selectionStroke.createStrokedShape(selectionPath);\r
-                g.setColor(SELECTION_COLOR);\r
-                g.fill(selectionShape);\r
-            }\r
-\r
-            renderer.render(g, rg);\r
-            if(selected)\r
-                renderer.renderGuides(g, rg);\r
-\r
-            if (selected && highlightActionsEnabled) {\r
-                // Needed for performing actions in #mouseClicked\r
-                this.lastViewTransform = g.getTransform();\r
-                highlightActions.setRouteGraph(rg);\r
-                highlightActions.render(g, renderer, mouseX, mouseY);\r
-                highlightActions.setRouteGraph(null);\r
-            }\r
-        }\r
-\r
-        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);\r
-\r
-        if (editable && branchable && newBranchPointPosition != null && !Double.isNaN(newBranchPointPosition.getX())) {\r
-            ConnectionStyle style = tryGetStyle();\r
-            if(style != null)\r
-                style.drawBranchPoint(g, newBranchPointPosition.getX(), newBranchPointPosition.getY());\r
-        }\r
-\r
-        if (ot != null)\r
-            g.setTransform(ot);\r
-    }\r
-    \r
-    private BasicConnectionStyle tryGetStyle() {\r
-        return tryGetStyle(renderer);\r
-    }\r
-\r
-    private BasicConnectionStyle tryGetStyle(IRouteGraphRenderer renderer) {\r
-        if (renderer instanceof StyledRouteGraphRenderer) {\r
-            ConnectionStyle cs = ((StyledRouteGraphRenderer) renderer).getStyle();\r
-            if (cs instanceof BasicConnectionStyle)\r
-                return (BasicConnectionStyle) cs;\r
-        }\r
-        return null;\r
-    }\r
-\r
-    private double getSelectionStrokeWidth() {\r
-        if (selectionStroke instanceof BasicStroke) {\r
-            BasicStroke bs = (BasicStroke) selectionStroke;\r
-            return bs.getLineWidth();\r
-        }\r
-        return 1.0;\r
-    }\r
-\r
-    @Override\r
-    public Rectangle2D getBoundsInLocal() {\r
-        return bounds;\r
-    }\r
-\r
-    protected void updateBounds() {\r
-        Rectangle2D r = this.bounds;\r
-        if (r == null)\r
-            r = new Rectangle2D.Double();\r
-        this.bounds = calculateBounds(r);\r
-\r
-        // Need to expand to take stroke width into account.\r
-        double sw = getSelectionStrokeWidth() / 2;\r
-        GeometryUtils.expandRectangle(this.bounds, sw, sw);\r
-    }\r
-\r
-    protected Rectangle2D calculateBounds(Rectangle2D rect) {\r
-        RouteGraph rg = this.rg;\r
-        if (currentAction instanceof MoveAction)\r
-            rg = ((MoveAction) currentAction).getRouteGraph();\r
-        rg.getBounds(rect);\r
-        return rect;\r
-    }\r
-\r
-    protected void getMouseLocalPos(MouseEvent e) {\r
-        //System.out.println("m: " + e.controlPosition);\r
-        pt.setLocation(e.controlPosition);\r
-        //System.out.println("parent: " + pt);\r
-        pt = NodeUtil.worldToLocal(this, pt, pt);\r
-        //System.out.println("local: " + pt);\r
-        mouseX = pt.getX();\r
-        mouseY = pt.getY();\r
-    }\r
-\r
-    @Override\r
-    protected boolean mouseDragged(MouseDragBegin e) {\r
-        if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) {\r
-            currentAction = dragAction;\r
-            dragAction = null;\r
-        }\r
-        return updateCurrentAction(e, true);\r
-    }\r
-\r
-    @Override\r
-    protected boolean mouseMoved(MouseMovedEvent e) {\r
-        //System.out.println("mouse moved: " + e);\r
-\r
-        // Handle connection branching visualization.\r
-        getMouseLocalPos(e);\r
-        if (newBranchPointPosition == null && e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {\r
-            if (bounds.contains(mouseX, mouseY)) {\r
-                newBranchPointPosition = new Point2D.Double(Double.NaN, Double.NaN);\r
-            }\r
-        }\r
-        if (newBranchPointPosition != null) {\r
-            RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);\r
-            if (line != null) {\r
-                newBranchPointPosition.setLocation(mouseX, mouseY);\r
-                SplittedRouteGraph.snapToLine(newBranchPointPosition, line);\r
-                repaint();\r
-            } else if (!Double.isNaN(newBranchPointPosition.getX())) {\r
-                newBranchPointPosition.setLocation(Double.NaN, Double.NaN);\r
-                repaint();\r
-            }\r
-        }\r
-\r
-        // Make sure that highlight action rendering is according to mouse hover.\r
-        if (highlightActionsEnabled) {\r
-            if (NodeUtil.isSelected(this, 1)) {\r
-                repaint();\r
-            }\r
-        }\r
-\r
-        return updateCurrentAction(e, false);\r
-    }\r
-\r
-    protected boolean updateCurrentAction(MouseEvent e, boolean updateMousePos) {\r
-        boolean oldHighlight = highlightActionsEnabled;\r
-        highlightActionsEnabled = e.hasAllModifiers(MouseEvent.CTRL_MASK);\r
-        if (oldHighlight != highlightActionsEnabled)\r
-            repaint();\r
-\r
-        if (currentAction != null) {\r
-            if (updateMousePos)\r
-                getMouseLocalPos(e);\r
-            updateBounds();\r
-\r
-            // Repaint, but only if absolutely necessary.\r
-            if (currentAction instanceof MoveAction || bounds.contains(mouseX, mouseY))\r
-                repaint();\r
-\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @Override\r
-    protected boolean mouseClicked(MouseClickEvent e) {\r
-        if (!editable)\r
-            return false;\r
-\r
-        if (e.button == MouseEvent.LEFT_BUTTON) {\r
-            if (isSelected() && highlightActionsEnabled) {\r
-                // Reconnection / segment deletion only available for branched connections. \r
-                if (rg.getTerminals().size() > 2) {\r
-                    Pick pick = highlightActions.pickAction(rg, lastViewTransform, mouseX, mouseY);\r
-                    if (pick.hasAction(Action.REMOVE)) {\r
-                        RemoveLineAction remove = RemoveLineAction.perform(rg, pick.line.getLine(), mouseX, mouseY);\r
-                        if (remove != null) {\r
-                            setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());\r
-                            repaint();\r
-                            return true;\r
-                        }\r
-                    }\r
-                    if (pick.hasAction(Action.RECONNECT)) {\r
-                        currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);\r
-                        if (currentAction != null) {\r
-                            repaint();\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    @Override\r
-    protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {\r
-        if (!editable)\r
-            return false;\r
-\r
-        if (e.button == MouseEvent.LEFT_BUTTON) {\r
-            // Visualize new branch point no longer.\r
-            newBranchPointPosition = null;\r
-\r
-            getMouseLocalPos(e);\r
-            dragAction = null;\r
-//          if(currentAction instanceof HighlightActionPointsAction) {\r
-//              RemoveLineAction remove = RemoveLineAction.perform(rg, mouseX, mouseY);\r
-//              if (remove != null) {\r
-//                  setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());\r
-//                  repaint();\r
-//              } else {\r
-//                  currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);\r
-//                  if (currentAction != null)\r
-//                      repaint();\r
-//              }\r
-//          }\r
-//          else\r
-            if(currentAction instanceof IReconnectAction) {\r
-                RouteGraph originalRg = rg.copy();\r
-                ((IReconnectAction)currentAction).finish(mouseX, mouseY);\r
-                currentAction = null;\r
-\r
-                setRouteGraphAndFireChanges(originalRg, rg);\r
-\r
-                currentAction = null;\r
-                repaint();\r
-                return true;\r
-            }\r
-            else {\r
-                if (!allowConnectionRerouting()) {\r
-                    return false;\r
-                }\r
-                //System.out.println("move action");\r
-                dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, pickTolerance, moveFilter, getSnapAdvisor());\r
-                //System.out.println("DRAG ACTION: " + dragAction);\r
-            }\r
-\r
-            //System.out.println(this + " NEW action: " + currentAction);\r
-            if (currentAction != null)\r
-                return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * Checks the selections data node in the scene graph for any links \r
-     * @return\r
-     */\r
-    private boolean allowConnectionRerouting() {\r
-        final int maxOtherNodesSelected = 1;\r
-\r
-        INode selections = NodeUtil.tryLookup(this, "selections");\r
-        if (!(selections instanceof G2DParentNode))\r
-            return true;\r
-        G2DParentNode p = (G2DParentNode) selections;\r
-        for (IG2DNode selection : p.getNodes()) {\r
-            if (!(selection instanceof G2DParentNode))\r
-                continue;\r
-\r
-            G2DParentNode sp = (G2DParentNode) selection;\r
-            Collection<IG2DNode> links = sp.getNodes();\r
-            if (links.isEmpty())\r
-                return true;\r
-            int othersSelected = 0;\r
-            for (IG2DNode link : links) {\r
-                if (link instanceof LinkNode) {\r
-                    INode node = ((LinkNode) link).getDelegate();\r
-                    if (!NodeUtil.isParentOf(node, this)) {\r
-                        othersSelected++;\r
-                        if (othersSelected > maxOtherNodesSelected)\r
-                            return false;\r
-                    }\r
-                }\r
-            }\r
-            if (othersSelected > maxOtherNodesSelected)\r
-                return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    protected ISnapAdvisor getSnapAdvisor() {\r
-        GridNode grid = lookupNode(GridNode.GRID_NODE_ID, GridNode.class);\r
-        return grid != null ? grid.getSnapAdvisor() : null;\r
-    }\r
-\r
-    MoveAction.TargetFilter moveFilter = new MoveAction.TargetFilter() {\r
-        @Override\r
-        public boolean accept(Object target) {\r
-            return (target instanceof RouteLine) || (target instanceof RouteLink);\r
-        }\r
-    };\r
-\r
-    @Override\r
-    protected boolean handleCommand(CommandEvent e) {\r
-        /*if (Commands.DELETE.equals(e.command)) {\r
-            Object target = rg.pick(mouseX, mouseY, pickTolerance);\r
-            return deleteTarget(target);\r
-        } else if (Commands.SPLIT_CONNECTION.equals(e.command)) {\r
-            Object target = rg.pick(mouseX, mouseY, pickTolerance);\r
-            return splitTarget(target);\r
-        } else */\r
-        if (Commands.CANCEL.equals(e.command)) {\r
-            return cancelCurrentAction();\r
-        }\r
-        return false;\r
-    }\r
-\r
-    protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
-        if (currentAction instanceof MoveAction) {\r
-            MoveAction move = (MoveAction) currentAction;\r
-            RouteGraph originalRg = rg.copy();\r
-            move.finish(mouseX, mouseY);\r
-\r
-            setRouteGraphAndFireChanges(originalRg, rg);\r
-\r
-            currentAction = null;\r
-            repaint();\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @Override\r
-    protected boolean keyPressed(KeyPressedEvent e) {\r
-        if (!editable)\r
-            return false;\r
-\r
-        if (!e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.keyCode == KeyEvent.VK_S) {\r
-            Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES | RouteGraph.PICK_TRANSIENT_LINES);\r
-            return splitTarget(target);\r
-        }\r
-        else if (!e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK | MouseEvent.CTRL_MASK) && (e.keyCode == KeyEvent.VK_R || e.keyCode == KeyEvent.VK_D)) {\r
-            Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES);\r
-            return deleteTarget(target);\r
-        }\r
-        else if (e.keyCode == KeyEvent.VK_ESCAPE) {\r
-            return cancelCurrentAction();\r
-        }\r
-//        else if (e.keyCode == KeyEvent.VK_D) {\r
-//            if (target instanceof RouteTerminal) {\r
-//                RouteTerminal terminal = (RouteTerminal) target;\r
-//                RouteGraph before = rg.copy();\r
-//                rg.toggleDirectLines(terminal);\r
-//                setRouteGraphAndFireChanges(before, rg);\r
-//                repaint();\r
-//            }\r
-//        }\r
-//        else if (target != null && e.getKeyCode() == KeyEvent.VK_P) {\r
-//            rg.print();\r
-//        }\r
-        else if (e.keyCode == KeyEvent.VK_CONTROL) {\r
-            highlightActionsEnabled = true;\r
-            repaint();\r
-        }\r
-        else if (e.keyCode == KeyEvent.VK_ALT) {\r
-            // Begin connection branching visualization.\r
-            RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);\r
-            if (branchable && line != null) {\r
-                newBranchPointPosition = new Point2D.Double(mouseX, mouseY);\r
-                SplittedRouteGraph.snapToLine(newBranchPointPosition, line);\r
-                repaint();\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    @Override\r
-    protected boolean keyReleased(KeyReleasedEvent e) {\r
-        if (e.keyCode == KeyEvent.VK_ALT) {\r
-            // End connection branching visualization.\r
-            if (newBranchPointPosition != null) {\r
-                newBranchPointPosition = null;\r
-                repaint();\r
-            }\r
-        }\r
-        if (e.keyCode == KeyEvent.VK_CONTROL) {\r
-            highlightActionsEnabled = false;\r
-            repaint();\r
-        }\r
-        return false;\r
-    }\r
-\r
-\r
-    private boolean cancelCurrentAction() {\r
-        if (currentAction != null) {\r
-            currentAction = null;\r
-            repaint();\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    private boolean splitTarget(Object target) {\r
-        if (target instanceof RouteLine) {\r
-            RouteLine rLine = (RouteLine)target;\r
-            RouteGraph before = rg.copy();\r
-            rg.split(rLine, rLine.isHorizontal() ? mouseX : mouseY);\r
-            setRouteGraphAndFireChanges(before, rg);\r
-            repaint();\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    private boolean deleteTarget(Object target) {\r
-        boolean changed = false;\r
-        if (target instanceof RouteLine) {\r
-            RouteLine line = (RouteLine) target;\r
-            RouteGraph before = rg.copy();\r
-            rg.merge(line);\r
-            changed = setRouteGraphAndFireChanges(before, rg);\r
-        }\r
-        else if (target instanceof RouteLink) {\r
-            RouteGraph before = rg.copy();\r
-            rg.deleteCorner((RouteLink) target);\r
-            changed = setRouteGraphAndFireChanges(before, rg);\r
-        }\r
-//        else if (target instanceof RouteTerminal) {\r
-//            RouteGraph before = rg.copy();\r
-//            rg.remove((RouteTerminal) target);\r
-//            changed = setRouteGraphAndFireChanges(before, rg);\r
-//        }\r
-        if (changed)\r
-            repaint();\r
-        return changed;\r
-    }\r
-\r
-    /**\r
-     * A version of MoveAction that snaps movements using the specified\r
-     * ISnapAdvisor.\r
-     */\r
-    static class SnappingMoveAction extends MoveAction {\r
-\r
-        private ISnapAdvisor snapAdvisor;\r
-        private Point2D      point = new Point2D.Double();\r
-\r
-        public SnappingMoveAction(RouteGraph rg, Object target, ISnapAdvisor snapAdvisor) {\r
-            super(rg, target);\r
-            this.snapAdvisor = snapAdvisor;\r
-        }\r
-\r
-        protected void move(RouteGraph rg, Object target, double x, double y) {\r
-            point.setLocation(x, y);\r
-            snapAdvisor.snap(point);\r
-            super.move(rg, target, point.getX(), point.getY());\r
-        }\r
-\r
-        public static MoveAction create(RouteGraph rg, double x, double y, double tolerance, TargetFilter filter, ISnapAdvisor snapAdvisor) {\r
-            Object target = rg.pick(x, y, tolerance, RouteGraph.PICK_LINES | RouteGraph.PICK_INTERIOR_POINTS);\r
-            if (target != null && (filter == null || filter.accept(target))) {\r
-                if (snapAdvisor != null)\r
-                    return new SnappingMoveAction(rg, target, snapAdvisor);\r
-                return new MoveAction(rg, target);\r
-            }\r
-            return null;\r
-        }\r
-\r
-    }\r
-\r
-    @Override\r
-    public int getEventMask() {\r
-        return EventTypes.CommandMask | EventTypes.KeyMask | EventTypes.MouseMask;\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2011 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.scenegraph.g2d.nodes.connection;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.event.KeyEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.lang.reflect.Constructor;
+import java.util.Collection;
+import java.util.Map;
+
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RouteLink;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.actions.IAction;
+import org.simantics.diagram.connection.actions.IReconnectAction;
+import org.simantics.diagram.connection.actions.MoveAction;
+import org.simantics.diagram.connection.actions.ReconnectLineAction;
+import org.simantics.diagram.connection.delta.RouteGraphDelta;
+import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
+import org.simantics.diagram.connection.rendering.ConnectionStyle;
+import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
+import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
+import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
+import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.ISelectionPainterNode;
+import org.simantics.scenegraph.g2d.G2DNode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.events.EventTypes;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
+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.GridNode;
+import org.simantics.scenegraph.g2d.nodes.LinkNode;
+import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Action;
+import org.simantics.scenegraph.g2d.nodes.connection.HighlightActionPointsAction.Pick;
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.scenegraph.utils.InitValueSupport;
+import org.simantics.scenegraph.utils.NodeUtil;
+
+import gnu.trove.map.hash.THashMap;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class RouteGraphNode extends G2DNode implements ISelectionPainterNode, InitValueSupport  {
+
+    private static final long       serialVersionUID = -917194130412280965L;
+
+    private static final double     TOLERANCE        = IAction.TOLERANCE;
+    private static final Stroke     SELECTION_STROKE = new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+    private static final Color      SELECTION_COLOR  = new Color(255, 0, 255, 96);
+
+    private static final HighlightActionPointsAction highlightActions = new HighlightActionPointsAction(null);
+
+    protected RouteGraph            rg;
+    protected IRouteGraphRenderer   baseRenderer;
+    protected IRouteGraphRenderer   renderer;
+    protected double                pickTolerance    = TOLERANCE;
+    protected boolean               editable         = true;
+    private boolean                 branchable       = true;
+
+    protected IRouteGraphListener   rgListener;
+    protected RouteGraphDelta       rgDelta;
+
+    protected transient double      mouseX;
+    protected transient double      mouseY;
+    protected transient Point2D     pt               = new Point2D.Double();
+
+    protected transient MoveAction  dragAction;
+    protected transient IAction     currentAction;
+    protected transient Rectangle2D bounds;
+
+    /**
+     * Dynamic color for connection rendering.
+     */
+    protected transient Color       dynamicColor;
+
+    /**
+     * Dynamic stroke for connection rendering.
+     */
+    protected transient Stroke      dynamicStroke;
+
+    protected transient Path2D      selectionPath    = new Path2D.Double();
+    protected transient Stroke      selectionStroke  = null;
+
+    protected transient boolean     highlightActionsEnabled = false;
+    protected transient AffineTransform lastViewTransform = null;
+
+    /**
+     * x = NaN is used to indicate that possible branch point should not be
+     * rendered but interaction has not ended yet.
+     */
+    protected transient Point2D     newBranchPointPosition = null;
+
+
+    protected transient Map<Object,ILineEndStyle> dynamicStyles = null;
+    
+    @Override
+    public void initValues() {
+        dynamicColor = null;
+        wrapRenderer();
+    }
+
+    @PropertySetter("color")
+    @SyncField(value = {"dynamicColor"})
+    public void setDynamicColor(Color color) {
+        this.dynamicColor = color;
+        wrapRenderer();
+    }
+
+    @PropertySetter("width")
+    @SyncField("dynamicStroke")
+    public void setDynamicStroke(Stroke stroke) {
+        this.dynamicStroke = stroke;
+        wrapRenderer();
+        createSelectionStroke();
+    }
+    
+    @SyncField(value = {"dynamicStyles"})
+    public void setDynamicLineEnd(RouteTerminal terminal, ILineEndStyle style) {
+       if (dynamicStyles == null)
+               dynamicStyles = new THashMap<Object, ILineEndStyle>();
+       terminal.setDynamicStyle(style);
+       if (terminal.getData() != null) {
+               if (style != null)
+                       dynamicStyles.put(terminal.getData(),style);
+               else
+                       dynamicStyles.remove(terminal.getData());
+       }
+    }
+    
+    private void updateLineEnds() {
+       if (dynamicStyles == null)
+               return;
+       for (RouteTerminal t : rg.getTerminals()) {
+               if (t.getData() == null)
+                       continue;
+               ILineEndStyle dynamicStyle = dynamicStyles.get(t.getData());
+               if (dynamicStyle != null)
+                       t.setDynamicStyle(dynamicStyle);
+       }
+    }
+
+    @SyncField(value = {"rg"})
+    public void setRouteGraph(RouteGraph graph) {
+        this.rg = graph;
+        updateLineEnds();
+        updateBounds();
+    }
+
+    @SyncField(value = {"rgDelta"})
+    public void setRouteGraphDelta(RouteGraphDelta delta) {
+        this.rgDelta = delta;
+    }
+
+    @SyncField(value = {"renderer"})
+    public void setRenderer(IRouteGraphRenderer renderer) {
+
+        this.baseRenderer = renderer;
+        wrapRenderer();
+
+        createSelectionStroke();      
+    }
+    
+    private void createSelectionStroke() {
+        BasicConnectionStyle style = tryGetStyle();
+         selectionStroke = null;
+         if (style != null) {
+             BasicStroke stroke = (BasicStroke) style.getLineStroke();
+             if (stroke != null) {
+                float width = Math.max(stroke.getLineWidth() + 0.75f, stroke.getLineWidth()*1.3f);
+                 selectionStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, stroke.getLineJoin());
+             }
+         } else {
+             selectionStroke = SELECTION_STROKE;
+         }
+    }
+    
+    private void wrapRenderer() {
+        
+        if(baseRenderer == null) {
+            renderer = null;
+            return;
+        }
+        
+        if(dynamicColor != null || dynamicStroke != null) {
+            BasicConnectionStyle baseStyle = (BasicConnectionStyle)tryGetStyle(baseRenderer);
+            try {
+               Constructor<? extends BasicConnectionStyle> c = baseStyle.getClass().getConstructor(Color.class, Color.class, double.class, Stroke.class, Stroke.class, double.class, double.class);
+               renderer = new StyledRouteGraphRenderer(c.newInstance(
+                        dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
+                                baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
+                                    dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), 
+                                            dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
+                                                    baseStyle.getDegeneratedLineLength(), baseStyle.getRounding()));
+            } catch (Exception e) {
+               renderer = new StyledRouteGraphRenderer(new BasicConnectionStyle(
+                        dynamicColor != null ? dynamicColor : baseStyle.getLineColor(),
+                                baseStyle.getBranchPointColor(), baseStyle.getBranchPointRadius(),
+                                    dynamicStroke != null ? dynamicStroke : baseStyle.getLineStroke(), 
+                                            dynamicStroke != null ? dynamicStroke : baseStyle.getRouteLineStroke(),
+                                                    baseStyle.getDegeneratedLineLength(), baseStyle.getRounding()));
+            }
+            
+            
+        } else {
+            renderer = baseRenderer;
+        }
+        
+    }
+
+    @SyncField(value = {"pickTolerance"})
+    public void setPickTolerance(double tolerance) {
+        this.pickTolerance = tolerance;
+    }
+
+    @SyncField(value = {"editable"})
+    public void setEditable(boolean editable) {
+        this.editable = editable;
+    }
+
+    @SyncField(value = {"branchable"})
+    public void setBranchable(boolean branchable) {
+        this.branchable = branchable;
+    }
+
+    public RouteGraph getRouteGraph() {
+        return rg;
+    }
+
+    public RouteGraphDelta getRouteGraphDelta() {
+        return rgDelta;
+    }
+
+    public IRouteGraphRenderer getRenderer() {
+        return renderer;
+    }
+
+    public boolean isEditable() {
+        return editable;
+    }
+
+    public boolean isBranchable() {
+        return branchable;
+    }
+    
+    public double getPickTolerance() {
+               return pickTolerance;
+       }
+
+    /**
+     * When in client-server mode, listener is only set on the server side and
+     * fireRouteGraphChanged will tell it when rg has changed.
+     * 
+     * @param listener
+     */
+    public void setRouteGraphListener(IRouteGraphListener listener) {
+        this.rgListener = listener;
+    }
+
+    /**
+     * @param before
+     * @param after
+     * @return <code>true</code> if changes were fired
+     */
+    private boolean setRouteGraphAndFireChanges(RouteGraph before, RouteGraph after) {
+        RouteGraphDelta delta = new RouteGraphDelta(before, after);
+        if (!delta.isEmpty()) {
+            setRouteGraph(after);
+            setRouteGraphDelta(delta);
+            fireRouteGraphChanged(before, after, delta);
+            return true;
+        }
+        return false;
+    }
+
+    @ServerSide
+    protected void fireRouteGraphChanged(RouteGraph before, RouteGraph after, RouteGraphDelta delta) {
+        if (rgListener != null) {
+            RouteGraphChangeEvent event = new RouteGraphChangeEvent(this, before, after, delta);
+            rgListener.routeGraphChanged(event);
+        }
+    }
+
+    public void showBranchPoint(Point2D p) {
+        newBranchPointPosition = p;
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        addEventHandler(this);
+    }
+
+    @Override
+    public void cleanup() {
+        rgListener = null;
+        removeEventHandler(this);
+        super.cleanup();
+    }
+
+    protected boolean isSelected() {
+        return NodeUtil.isSelected(this, 1);
+    }
+
+    @Override
+    public void render(Graphics2D g) {
+        if (renderer == null)
+            return;
+
+        AffineTransform ot = null;
+        AffineTransform t = getTransform();
+        if (t != null && !t.isIdentity()) {
+            ot = g.getTransform();
+            g.transform(getTransform());
+        }
+
+        Object aaHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
+
+        boolean selected = NodeUtil.isSelected(this, 1);
+
+        if (currentAction != null) {
+            currentAction.render(g, renderer, mouseX, mouseY);
+        } else {
+            if (selected && selectionStroke != null) {
+                selectionPath.reset();
+                rg.getPath2D(selectionPath);
+                Shape selectionShape = selectionStroke.createStrokedShape(selectionPath);
+                g.setColor(SELECTION_COLOR);
+                g.fill(selectionShape);
+            }
+
+            renderer.render(g, rg);
+            if(selected)
+                renderer.renderGuides(g, rg);
+
+            if (selected && highlightActionsEnabled) {
+                // Needed for performing actions in #mouseClicked
+                this.lastViewTransform = g.getTransform();
+                highlightActions.setRouteGraph(rg);
+                highlightActions.render(g, renderer, mouseX, mouseY);
+                highlightActions.setRouteGraph(null);
+            }
+        }
+
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);
+
+        if (editable && branchable && newBranchPointPosition != null && !Double.isNaN(newBranchPointPosition.getX())) {
+            ConnectionStyle style = tryGetStyle();
+            if(style != null)
+                style.drawBranchPoint(g, newBranchPointPosition.getX(), newBranchPointPosition.getY());
+        }
+
+        if (ot != null)
+            g.setTransform(ot);
+    }
+    
+    private BasicConnectionStyle tryGetStyle() {
+        return tryGetStyle(renderer);
+    }
+
+    private BasicConnectionStyle tryGetStyle(IRouteGraphRenderer renderer) {
+        if (renderer instanceof StyledRouteGraphRenderer) {
+            ConnectionStyle cs = ((StyledRouteGraphRenderer) renderer).getStyle();
+            if (cs instanceof BasicConnectionStyle)
+                return (BasicConnectionStyle) cs;
+        }
+        return null;
+    }
+
+    private double getSelectionStrokeWidth() {
+        if (selectionStroke instanceof BasicStroke) {
+            BasicStroke bs = (BasicStroke) selectionStroke;
+            return bs.getLineWidth();
+        }
+        return 1.0;
+    }
+
+    @Override
+    public Rectangle2D getBoundsInLocal() {
+        return bounds;
+    }
+
+    protected void updateBounds() {
+        Rectangle2D r = this.bounds;
+        if (r == null)
+            r = new Rectangle2D.Double();
+        this.bounds = calculateBounds(r);
+
+        // Need to expand to take stroke width into account.
+        double sw = getSelectionStrokeWidth() / 2;
+        GeometryUtils.expandRectangle(this.bounds, sw, sw);
+    }
+
+    protected Rectangle2D calculateBounds(Rectangle2D rect) {
+        RouteGraph rg = this.rg;
+        if (currentAction instanceof MoveAction)
+            rg = ((MoveAction) currentAction).getRouteGraph();
+        rg.getBounds(rect);
+        return rect;
+    }
+
+    protected void getMouseLocalPos(MouseEvent e) {
+        //System.out.println("m: " + e.controlPosition);
+        pt.setLocation(e.controlPosition);
+        //System.out.println("parent: " + pt);
+        pt = NodeUtil.worldToLocal(this, pt, pt);
+        //System.out.println("local: " + pt);
+        mouseX = pt.getX();
+        mouseY = pt.getY();
+    }
+
+    @Override
+    protected boolean mouseDragged(MouseDragBegin e) {
+        if (dragAction != null && !e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.button == MouseEvent.LEFT_BUTTON) {
+            currentAction = dragAction;
+            dragAction = null;
+        }
+        return updateCurrentAction(e, true);
+    }
+
+    @Override
+    protected boolean mouseMoved(MouseMovedEvent e) {
+        //System.out.println("mouse moved: " + e);
+
+        // Handle connection branching visualization.
+        getMouseLocalPos(e);
+        if (newBranchPointPosition == null && e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) {
+            if (bounds.contains(mouseX, mouseY)) {
+                newBranchPointPosition = new Point2D.Double(Double.NaN, Double.NaN);
+            }
+        }
+        if (newBranchPointPosition != null) {
+            RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);
+            if (line != null) {
+                newBranchPointPosition.setLocation(mouseX, mouseY);
+                SplittedRouteGraph.snapToLine(newBranchPointPosition, line);
+                repaint();
+            } else if (!Double.isNaN(newBranchPointPosition.getX())) {
+                newBranchPointPosition.setLocation(Double.NaN, Double.NaN);
+                repaint();
+            }
+        }
+
+        // Make sure that highlight action rendering is according to mouse hover.
+        if (highlightActionsEnabled) {
+            if (NodeUtil.isSelected(this, 1)) {
+                repaint();
+            }
+        }
+
+        return updateCurrentAction(e, false);
+    }
+
+    protected boolean updateCurrentAction(MouseEvent e, boolean updateMousePos) {
+        boolean oldHighlight = highlightActionsEnabled;
+        highlightActionsEnabled = e.hasAllModifiers(MouseEvent.CTRL_MASK);
+        if (oldHighlight != highlightActionsEnabled)
+            repaint();
+
+        if (currentAction != null) {
+            if (updateMousePos)
+                getMouseLocalPos(e);
+            updateBounds();
+
+            // Repaint, but only if absolutely necessary.
+            if (currentAction instanceof MoveAction || bounds.contains(mouseX, mouseY))
+                repaint();
+
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean mouseClicked(MouseClickEvent e) {
+        if (!editable)
+            return false;
+
+        if (e.button == MouseEvent.LEFT_BUTTON) {
+            if (isSelected() && highlightActionsEnabled) {
+                // Reconnection / segment deletion only available for branched connections. 
+                if (rg.getTerminals().size() > 2) {
+                    Pick pick = highlightActions.pickAction(rg, lastViewTransform, mouseX, mouseY);
+                    if (pick.hasAction(Action.REMOVE)) {
+                        RemoveLineAction remove = RemoveLineAction.perform(rg, pick.line.getLine(), mouseX, mouseY);
+                        if (remove != null) {
+                            setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());
+                            repaint();
+                            return true;
+                        }
+                    }
+                    if (pick.hasAction(Action.RECONNECT)) {
+                        currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);
+                        if (currentAction != null) {
+                            repaint();
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    protected boolean mouseButtonPressed(MouseButtonPressedEvent e) {
+        if (!editable)
+            return false;
+
+        if (e.button == MouseEvent.LEFT_BUTTON) {
+            // Visualize new branch point no longer.
+            newBranchPointPosition = null;
+
+            getMouseLocalPos(e);
+            dragAction = null;
+//          if(currentAction instanceof HighlightActionPointsAction) {
+//              RemoveLineAction remove = RemoveLineAction.perform(rg, mouseX, mouseY);
+//              if (remove != null) {
+//                  setRouteGraphAndFireChanges(remove.getOriginalRouteGraph(), remove.getRouteGraph());
+//                  repaint();
+//              } else {
+//                  currentAction = ReconnectLineAction.create(rg, mouseX, mouseY);
+//                  if (currentAction != null)
+//                      repaint();
+//              }
+//          }
+//          else
+            if(currentAction instanceof IReconnectAction) {
+                RouteGraph originalRg = rg.copy();
+                ((IReconnectAction)currentAction).finish(mouseX, mouseY);
+                currentAction = null;
+
+                setRouteGraphAndFireChanges(originalRg, rg);
+
+                currentAction = null;
+                repaint();
+                return true;
+            }
+            else {
+                if (!allowConnectionRerouting()) {
+                    return false;
+                }
+                //System.out.println("move action");
+                dragAction = SnappingMoveAction.create(rg, mouseX, mouseY, pickTolerance, moveFilter, getSnapAdvisor());
+                //System.out.println("DRAG ACTION: " + dragAction);
+            }
+
+            //System.out.println(this + " NEW action: " + currentAction);
+            if (currentAction != null)
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Checks the selections data node in the scene graph for any links 
+     * @return
+     */
+    private boolean allowConnectionRerouting() {
+        final int maxOtherNodesSelected = 1;
+
+        INode selections = NodeUtil.tryLookup(this, "selections");
+        if (!(selections instanceof G2DParentNode))
+            return true;
+        G2DParentNode p = (G2DParentNode) selections;
+        for (IG2DNode selection : p.getNodes()) {
+            if (!(selection instanceof G2DParentNode))
+                continue;
+
+            G2DParentNode sp = (G2DParentNode) selection;
+            Collection<IG2DNode> links = sp.getNodes();
+            if (links.isEmpty())
+                return true;
+            int othersSelected = 0;
+            for (IG2DNode link : links) {
+                if (link instanceof LinkNode) {
+                    INode node = ((LinkNode) link).getDelegate();
+                    if (!NodeUtil.isParentOf(node, this)) {
+                        othersSelected++;
+                        if (othersSelected > maxOtherNodesSelected)
+                            return false;
+                    }
+                }
+            }
+            if (othersSelected > maxOtherNodesSelected)
+                return false;
+        }
+        return true;
+    }
+
+    protected ISnapAdvisor getSnapAdvisor() {
+        GridNode grid = lookupNode(GridNode.GRID_NODE_ID, GridNode.class);
+        return grid != null ? grid.getSnapAdvisor() : null;
+    }
+
+    MoveAction.TargetFilter moveFilter = new MoveAction.TargetFilter() {
+        @Override
+        public boolean accept(Object target) {
+            return (target instanceof RouteLine) || (target instanceof RouteLink);
+        }
+    };
+
+    @Override
+    protected boolean handleCommand(CommandEvent e) {
+        /*if (Commands.DELETE.equals(e.command)) {
+            Object target = rg.pick(mouseX, mouseY, pickTolerance);
+            return deleteTarget(target);
+        } else if (Commands.SPLIT_CONNECTION.equals(e.command)) {
+            Object target = rg.pick(mouseX, mouseY, pickTolerance);
+            return splitTarget(target);
+        } else */
+        if (Commands.CANCEL.equals(e.command)) {
+            return cancelCurrentAction();
+        }
+        return false;
+    }
+
+    protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
+        if (currentAction instanceof MoveAction) {
+            MoveAction move = (MoveAction) currentAction;
+            RouteGraph originalRg = rg.copy();
+            move.finish(mouseX, mouseY);
+
+            setRouteGraphAndFireChanges(originalRg, rg);
+
+            currentAction = null;
+            repaint();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean keyPressed(KeyPressedEvent e) {
+        if (!editable)
+            return false;
+
+        if (!e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) && e.keyCode == KeyEvent.VK_S) {
+            Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES | RouteGraph.PICK_TRANSIENT_LINES);
+            return splitTarget(target);
+        }
+        else if (!e.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK | MouseEvent.CTRL_MASK) && (e.keyCode == KeyEvent.VK_R || e.keyCode == KeyEvent.VK_D)) {
+            Object target = rg.pick(mouseX, mouseY, pickTolerance, RouteGraph.PICK_PERSISTENT_LINES);
+            return deleteTarget(target);
+        }
+        else if (e.keyCode == KeyEvent.VK_ESCAPE) {
+            return cancelCurrentAction();
+        }
+//        else if (e.keyCode == KeyEvent.VK_D) {
+//            if (target instanceof RouteTerminal) {
+//                RouteTerminal terminal = (RouteTerminal) target;
+//                RouteGraph before = rg.copy();
+//                rg.toggleDirectLines(terminal);
+//                setRouteGraphAndFireChanges(before, rg);
+//                repaint();
+//            }
+//        }
+//        else if (target != null && e.getKeyCode() == KeyEvent.VK_P) {
+//            rg.print();
+//        }
+        else if (e.keyCode == KeyEvent.VK_CONTROL) {
+            highlightActionsEnabled = true;
+            repaint();
+        }
+        else if (e.keyCode == KeyEvent.VK_ALT) {
+            // Begin connection branching visualization.
+            RouteLine line = rg.pickLine(mouseX, mouseY, pickTolerance);
+            if (branchable && line != null) {
+                newBranchPointPosition = new Point2D.Double(mouseX, mouseY);
+                SplittedRouteGraph.snapToLine(newBranchPointPosition, line);
+                repaint();
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    protected boolean keyReleased(KeyReleasedEvent e) {
+        if (e.keyCode == KeyEvent.VK_ALT) {
+            // End connection branching visualization.
+            if (newBranchPointPosition != null) {
+                newBranchPointPosition = null;
+                repaint();
+            }
+        }
+        if (e.keyCode == KeyEvent.VK_CONTROL) {
+            highlightActionsEnabled = false;
+            repaint();
+        }
+        return false;
+    }
+
+
+    private boolean cancelCurrentAction() {
+        if (currentAction != null) {
+            currentAction = null;
+            repaint();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean splitTarget(Object target) {
+        if (target instanceof RouteLine) {
+            RouteLine rLine = (RouteLine)target;
+            RouteGraph before = rg.copy();
+            rg.split(rLine, rLine.isHorizontal() ? mouseX : mouseY);
+            setRouteGraphAndFireChanges(before, rg);
+            repaint();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean deleteTarget(Object target) {
+        boolean changed = false;
+        if (target instanceof RouteLine) {
+            RouteLine line = (RouteLine) target;
+            RouteGraph before = rg.copy();
+            rg.merge(line);
+            changed = setRouteGraphAndFireChanges(before, rg);
+        }
+        else if (target instanceof RouteLink) {
+            RouteGraph before = rg.copy();
+            rg.deleteCorner((RouteLink) target);
+            changed = setRouteGraphAndFireChanges(before, rg);
+        }
+//        else if (target instanceof RouteTerminal) {
+//            RouteGraph before = rg.copy();
+//            rg.remove((RouteTerminal) target);
+//            changed = setRouteGraphAndFireChanges(before, rg);
+//        }
+        if (changed)
+            repaint();
+        return changed;
+    }
+
+    /**
+     * A version of MoveAction that snaps movements using the specified
+     * ISnapAdvisor.
+     */
+    static class SnappingMoveAction extends MoveAction {
+
+        private ISnapAdvisor snapAdvisor;
+        private Point2D      point = new Point2D.Double();
+
+        public SnappingMoveAction(RouteGraph rg, Object target, ISnapAdvisor snapAdvisor) {
+            super(rg, target);
+            this.snapAdvisor = snapAdvisor;
+        }
+
+        protected void move(RouteGraph rg, Object target, double x, double y) {
+            point.setLocation(x, y);
+            snapAdvisor.snap(point);
+            super.move(rg, target, point.getX(), point.getY());
+        }
+
+        public static MoveAction create(RouteGraph rg, double x, double y, double tolerance, TargetFilter filter, ISnapAdvisor snapAdvisor) {
+            Object target = rg.pick(x, y, tolerance, RouteGraph.PICK_LINES | RouteGraph.PICK_INTERIOR_POINTS);
+            if (target != null && (filter == null || filter.accept(target))) {
+                if (snapAdvisor != null)
+                    return new SnappingMoveAction(rg, target, snapAdvisor);
+                return new MoveAction(rg, target);
+            }
+            return null;
+        }
+
+    }
+
+    @Override
+    public int getEventMask() {
+        return EventTypes.CommandMask | EventTypes.KeyMask | EventTypes.MouseMask;
+    }
+
+}