]> gerrit.simantics Code Review - simantics/sysdyn.git/commitdiff
New routers for flows and dependencies. They are functional, but should be tweaked...
authorlempinen <lempinen@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Fri, 14 Jan 2011 11:16:46 +0000 (11:16 +0000)
committerlempinen <lempinen@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Fri, 14 Jan 2011 11:16:46 +0000 (11:16 +0000)
git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@19373 ac1ea38d-2e2b-0410-8846-a27921b304fc

13 files changed:
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/PointerInteractor.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/SysdynConnectTool.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/DependencyRouter.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java [new file with mode: 0644]
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java [new file with mode: 0644]
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java [deleted file]
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyConnectionFactory.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyEdgeClass.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyNode.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowConnectionFactory.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeClass.java
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java [new file with mode: 0644]
org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowNode.java

index 58860991bda6739f20607258867fd9cfee0f3fc6..70a998824fc545bed5b7b0acad76f27a7fcab379 100644 (file)
@@ -36,10 +36,10 @@ import org.simantics.g2d.event.MouseEvent.MouseButtonPressedEvent;
 import org.simantics.g2d.participant.KeyUtil;\r
 import org.simantics.g2d.participant.MouseUtil;\r
 import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.routing.RouterFactory;\r
 import org.simantics.g2d.utils.GeometryUtils;\r
 import org.simantics.sysdyn.ui.editor.participant.SysdynElementClassProviders.ISysdynElementClassProvider;\r
 import org.simantics.sysdyn.ui.editor.routing.DependencyRouter;\r
+import org.simantics.sysdyn.ui.editor.routing.FlowRouter;\r
 import org.simantics.sysdyn.ui.elements2.AuxiliaryFactory;\r
 import org.simantics.sysdyn.ui.elements2.CloudFactory;\r
 import org.simantics.sysdyn.ui.elements2.InputFactory;\r
@@ -63,92 +63,82 @@ import org.simantics.sysdyn.ui.elements2.connections.ConnectionClasses;
  */\r
 public class PointerInteractor extends org.simantics.g2d.diagram.participant.pointertool.PointerInteractor {\r
 \r
-    @Dependency Selection selection;\r
-    @Dependency KeyUtil keys;\r
-    @Dependency TransformUtil util;\r
-    @Dependency PickContext pickContext;\r
-    @Dependency MouseUtil mice;\r
-    @Reference TerminalPainter terminalPainter;\r
-    \r
-    public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider newConnectionClassProvider) {\r
-        super(clickSelect, boxSelect, dragElement, dndDragElement, connect, doubleClickEdit, newConnectionClassProvider);\r
-    }\r
-\r
-    @EventHandler(priority = TOOL_PRIORITY)\r
-    public boolean handlePress(MouseButtonPressedEvent me) {\r
-        if (!connects()) return false;\r
-//        if (me.button != MouseEvent.LEFT_BUTTON) return false;\r
-        if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return false;\r
-        assertDependencies();\r
-        Point2D         curCanvasPos    = util.controlToCanvas(me.controlPosition, null);\r
-        \r
-        // Pick Terminal\r
-        TerminalInfo ti = pickTerminal(me.controlPosition);\r
-        \r
-        if((me.stateMask & MouseEvent.ALT_MASK) == 0) return false;\r
-        \r
-        if(elementClassProvider != null) {\r
-            ConnectTool2 bsi = null;\r
-            if (ti != null) {\r
-               \r
-                IElement terminalElement = ti.e;\r
-                \r
-//                ElementClass connectionClass = null;\r
-                if( me.button == MouseEvent.LEFT_BUTTON) {\r
-                    if(terminalElement.getElementClass().getId().equals(CloudFactory.class.getSimpleName())) return false;\r
-//                    diagram.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(false, false));\r
-                    diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new DependencyRouter());\r
-//                 connectionClass = elementClassProvider.get(ConnectionClasses.DEPENDENCY);\r
-                    ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider;\r
-                    secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.DEPENDENCY));\r
-                    \r
-                } else if (me.button == MouseEvent.RIGHT_BUTTON) {\r
-                       String id  = terminalElement.getElementClass().getId();\r
-                    if(id.equals(AuxiliaryFactory.class.getSimpleName())\r
-                               || id.equals(InputFactory.class.getSimpleName())\r
-                               || id.equals(ModuleFactory.class.getSimpleName())) return false;\r
-                    diagram.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));\r
-//                    connectionClass = elementClassProvider.get(ConnectionClasses.FLOW);\r
-                    ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider;\r
-                    secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW));\r
-                    \r
-                }\r
-                \r
-                IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
-                if (advisor == null || (advisor != null && advisor.canBeginConnection(null, ti.e, ti.t))) {\r
-//                    bsi = new ConnectTool(diagram, connectionClass, ti.e, ti.t, me.mouseId, curCanvasPos);\r
-                       bsi = new SysdynConnectTool(ti, me.mouseId, curCanvasPos);\r
-                }\r
-            }  \r
-            else if (me.button == MouseEvent.RIGHT_BUTTON) {\r
-               \r
-               /*\r
-                ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
-                if(snapAdvisor != null) \r
-                    snapAdvisor.snap(curCanvasPos);\r
-                */\r
-                // Start connection out of thin air, without a terminal.\r
-               diagram.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, true));\r
-//                bsi = new ConnectTool(diagram, elementClassProvider.get(ConnectionClasses.FLOW), null, null, me.mouseId, curCanvasPos);\r
-                ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider;\r
-                secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW));\r
-               bsi = new SysdynConnectTool(null, me.mouseId, curCanvasPos); // FIXME\r
-            }\r
-            \r
-            if (bsi != null) {\r
-                getContext().add(bsi);\r
-                return true;\r
-            }\r
-            \r
-        }\r
-        return false;\r
-    }\r
-    \r
-    public List<TerminalInfo> pickTerminals(Point2D controlPos)\r
-    {\r
-        Rectangle2D     controlPickRect     = new Rectangle2D.Double(controlPos.getX()-PointerInteractor.PICK_DIST, controlPos.getY()-PointerInteractor.PICK_DIST, PointerInteractor.PICK_DIST*2+1, PointerInteractor.PICK_DIST*2+1);\r
-        Shape           canvasPickRect      = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());\r
-        List<TerminalInfo> ti = TerminalUtil.pickTerminals(diagram, canvasPickRect, false, true);\r
-        return ti;\r
-    }\r
+       @Dependency Selection selection;\r
+       @Dependency KeyUtil keys;\r
+       @Dependency TransformUtil util;\r
+       @Dependency PickContext pickContext;\r
+       @Dependency MouseUtil mice;\r
+       @Reference TerminalPainter terminalPainter;\r
+\r
+       public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider newConnectionClassProvider) {\r
+               super(clickSelect, boxSelect, dragElement, dndDragElement, connect, doubleClickEdit, newConnectionClassProvider);\r
+       }\r
+\r
+       @EventHandler(priority = TOOL_PRIORITY)\r
+       public boolean handlePress(MouseButtonPressedEvent me) {\r
+               if (!connects()) return false;\r
+               if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return false;\r
+               assertDependencies();\r
+               Point2D         curCanvasPos    = util.controlToCanvas(me.controlPosition, null);\r
+\r
+               // Pick Terminal\r
+               TerminalInfo ti = pickTerminal(me.controlPosition);\r
+\r
+               if((me.stateMask & MouseEvent.ALT_MASK) == 0) return false;\r
+\r
+               if(elementClassProvider != null) {\r
+                       ConnectTool2 bsi = null;\r
+                       if (ti != null) {\r
+\r
+                               IElement terminalElement = ti.e;\r
+\r
+                               if( me.button == MouseEvent.LEFT_BUTTON) {\r
+                                       if(terminalElement.getElementClass().getId().equals(CloudFactory.class.getSimpleName())) return false;\r
+                                       diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new DependencyRouter());\r
+                                       diagram.setHint(DiagramHints.KEY_USE_CONNECTION_FLAGS, false);\r
+                                       ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider;\r
+                                       secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.DEPENDENCY));\r
+\r
+                               } else if (me.button == MouseEvent.RIGHT_BUTTON) {\r
+                                       String id  = terminalElement.getElementClass().getId();\r
+                                       if(id.equals(AuxiliaryFactory.class.getSimpleName())\r
+                                                       || id.equals(InputFactory.class.getSimpleName())\r
+                                                       || id.equals(ModuleFactory.class.getSimpleName())) return false;\r
+                                       diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new FlowRouter(false));\r
+                                       diagram.setHint(DiagramHints.KEY_USE_CONNECTION_FLAGS, true);\r
+                                       ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider;\r
+                                       secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW));\r
+\r
+                               }\r
+\r
+                               IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
+                               if (advisor == null || (advisor != null && advisor.canBeginConnection(null, ti.e, ti.t))) {\r
+                                       bsi = new SysdynConnectTool(ti, me.mouseId, curCanvasPos);\r
+                               }\r
+                       }  \r
+                       else if (me.button == MouseEvent.RIGHT_BUTTON) {\r
+                                // Start connection out of thin air, without a terminal.\r
+                                diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new FlowRouter(false));\r
+                                diagram.setHint(DiagramHints.KEY_USE_CONNECTION_FLAGS, true);\r
+                                ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider;\r
+                                secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW));\r
+                                bsi = new SysdynConnectTool(null, me.mouseId, curCanvasPos); // FIXME\r
+                       }\r
+\r
+                       if (bsi != null) {\r
+                               getContext().add(bsi);\r
+                               return true;\r
+                       }\r
+\r
+               }\r
+               return false;\r
+       }\r
+\r
+       public List<TerminalInfo> pickTerminals(Point2D controlPos)\r
+       {\r
+               Rectangle2D     controlPickRect     = new Rectangle2D.Double(controlPos.getX()-PointerInteractor.PICK_DIST, controlPos.getY()-PointerInteractor.PICK_DIST, PointerInteractor.PICK_DIST*2+1, PointerInteractor.PICK_DIST*2+1);\r
+               Shape           canvasPickRect      = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());\r
+               List<TerminalInfo> ti = TerminalUtil.pickTerminals(diagram, canvasPickRect, false, true);\r
+               return ti;\r
+       }\r
 }\r
index 38f0b7de2f38ffbabc8325febbd28ad75e47f7c9..ad4e83e78c032191bc7262e94b5d9865a75cba10 100644 (file)
@@ -5,6 +5,7 @@ import java.awt.Color;
 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.ArrayList;\r
 import java.util.Collection;\r
 import java.util.Collections;\r
@@ -26,7 +27,6 @@ import org.simantics.diagram.ui.DiagramModelHints;
 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.handler.Topology.Terminal;\r
 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
@@ -54,6 +54,8 @@ import org.simantics.scenegraph.g2d.G2DParentNode;
 import org.simantics.scenegraph.g2d.nodes.ShapeNode;\r
 import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
 import org.simantics.sysdyn.SysdynResource;\r
+import org.simantics.sysdyn.ui.editor.routing.FlowRouter;\r
+import org.simantics.sysdyn.ui.elements2.ValveFactory.ValveSceneGraph;\r
 import org.simantics.sysdyn.ui.elements2.connections.ConnectionClasses;\r
 import org.simantics.ui.SimanticsUI;\r
 import org.simantics.utils.datastructures.Callback;\r
@@ -140,6 +142,8 @@ public class SysdynConnectTool extends ConnectTool2 {
         return segments;\r
     }\r
     \r
+    public interface SysdynConnection extends IConnection { }\r
+    \r
     protected void updateSG() {\r
         if (controlPoints.isEmpty())\r
             return;\r
@@ -149,7 +153,7 @@ public class SysdynConnectTool extends ConnectTool2 {
         final List<Segment> segments = toSegments(controlPoints);\r
         //System.out.println("controlpoints: " + controlPoints);\r
         //System.out.println("segments: " + segments);\r
-        router.route(new IConnection() {\r
+        router.route(new SysdynConnection() {\r
             @Override\r
             public Collection<? extends Object> getSegments() {\r
                 return segments;\r
@@ -166,17 +170,24 @@ public class SysdynConnectTool extends ConnectTool2 {
             }\r
 \r
             private Connector getConnector(ControlPoint cp) {\r
-                Connector c = new Connector();\r
+               Connector c = new Connector();\r
                 c.x = cp.getPosition().getX();\r
                 c.y = cp.getPosition().getY();\r
 \r
                 TerminalInfo ti = cp.getAttachedTerminal();\r
                 if (ti != null && (ti == startFlag || ti != endFlag)) {\r
-                    //System.out.println("CP1: " + cp);\r
-                    c.parentObstacle = DiagramUtils.getObstacleShape(ti.e);\r
+                       if(ti.e.getElementClass().containsClass(ValveSceneGraph.class)) {\r
+                               Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D();\r
+                               c.parentObstacle = new Rectangle2D.Double(\r
+                                               bounds.getCenterX() - FlowRouter.OFFSET,\r
+                                               bounds.getCenterY() - FlowRouter.OFFSET, \r
+                                               FlowRouter.OFFSET * 2,\r
+                                               FlowRouter.OFFSET * 2);\r
+                       } else {\r
+                               c.parentObstacle =  ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D();\r
+                       }\r
                     ConnectionDirectionUtil.determineAllowedDirections(c);\r
                 } else {\r
-                    //System.out.println("CP2: " + cp);\r
                     c.parentObstacle = GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),\r
                             BranchPointClass.DEFAULT_IMAGE2.getBounds());\r
                     c.allowedDirections = toAllowedDirections(cp.getDirection());\r
@@ -230,6 +241,11 @@ public class SysdynConnectTool extends ConnectTool2 {
         final Terminal st = startTerminal != null ? startTerminal.t : null;\r
 \r
                if(se.equals(endElement)) return null;\r
+               if(Boolean.FALSE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS)) && endElement == null) {\r
+                       return null;\r
+               } else {\r
+                       System.out.println("FOK");\r
+               }\r
 \r
                if(endElement == null && endTerminal == null)\r
                        return advisor.canBeConnected(null, se, st, endElement, endTerminal);\r
@@ -371,7 +387,9 @@ public class SysdynConnectTool extends ConnectTool2 {
     }\r
 \r
     @Override\r
-    protected void createConnection() {\r
+       protected void createConnection() {\r
+       \r
+       if(this.connectionJudgment == null) return;\r
        \r
         final UndoContext uctx = diagram.getHint(DiagramModelHints.KEY_UNDO_CONTEXT);\r
         final ConnectionJudgement judgment = this.connectionJudgment;\r
index 24e527a9964972956699e394fdac8ffe7e92a958..e7d42fc533d48c0c88eea6529bf345af4bbb2d56 100644 (file)
@@ -2,11 +2,13 @@ package org.simantics.sysdyn.ui.editor.routing;
 \r
 import java.awt.geom.Arc2D;\r
 import java.awt.geom.Path2D;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
 \r
 import org.simantics.g2d.routing.IConnection;\r
 import org.simantics.g2d.routing.IConnection.Connector;\r
 import org.simantics.g2d.routing.IRouter2;\r
-import org.simantics.sysdyn.ui.elements2.connections.Dependencies;\r
+import org.simantics.sysdyn.ui.elements2.connections.Arcs;\r
 import org.simantics.utils.datastructures.Pair;\r
 \r
 public class DependencyRouter implements IRouter2 {\r
@@ -20,7 +22,7 @@ public class DependencyRouter implements IRouter2 {
         Connector end = connection.getEnd(seg);\r
         \r
         Pair<Arc2D, Path2D> shapes = new Pair<Arc2D, Path2D>(new Arc2D.Double(), new Path2D.Double());\r
-        Dependencies.createArrowShape(shapes,\r
+        createArrowShape(shapes,\r
                        begin.parentObstacle,\r
                        end.parentObstacle,\r
                        0.1);\r
@@ -31,4 +33,120 @@ public class DependencyRouter implements IRouter2 {
         connection.setPath(seg, path);\r
        }\r
 \r
+       \r
+       /*\r
+        * Total length of the arrow is ARROW_LENGTH1 + ARROW_LENGTH2\r
+        */\r
+    public static double ARROW_LENGTH1 = 0.2;\r
+    public static double ARROW_LENGTH2 = 1.0;\r
+    public static double ARROW_WIDTH = 0.5;\r
+    \r
+\r
+       private static Path2D createArrow(Path2D shape, double x, double y, double dx, double dy) {\r
+               if(shape == null)\r
+                       shape = new Path2D.Double();\r
+               else\r
+                       shape.reset();\r
+               \r
+        shape.moveTo(x+ARROW_LENGTH1*dx, y+ARROW_LENGTH1*dy);\r
+        x -= ARROW_LENGTH2*dx;\r
+        y -= ARROW_LENGTH2*dy;\r
+        shape.lineTo(x-ARROW_WIDTH*dy, y+ARROW_WIDTH*dx);\r
+        shape.lineTo(x+ARROW_WIDTH*dy, y-ARROW_WIDTH*dx);\r
+        shape.closePath();\r
+        return shape;\r
+    }\r
+       \r
+       public static Arc2D createArc(Arc2D arc, Rectangle2D tail, Rectangle2D head, double angle) {\r
+               double x0 = tail.getCenterX();\r
+        double y0 = tail.getCenterY();\r
+        double x1 = head.getCenterX();\r
+        double y1 = head.getCenterY();\r
+        \r
+//        System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1);\r
+        \r
+        double offset = \r
+            Math.abs(angle) < 1.0e-6\r
+            ? 1e3 * Math.signum(angle)\r
+            : Math.tan(Math.PI*0.5-angle)*0.5;\r
+            \r
+        double cx = 0.5*(x0+x1) + offset * (y1-y0);\r
+        double cy = 0.5*(y0+y1) + offset * (x0-x1);\r
+        double dx0 = x0 - cx;\r
+        double dy0 = y0 - cy;\r
+        double dx1 = x1 - cx;\r
+        double dy1 = y1 - cy;\r
+        \r
+        double r = Math.sqrt(dx0*dx0 + dy0*dy0);\r
+        \r
+//        Rectangle2D bounds = new Rectangle2D.Double();\r
+//        tail.getBounds(bounds);\r
+        double angle0 = Arcs.nextIntersectingAngle(cx, cy, r, \r
+            Math.atan2(-dy0, dx0), tail, angle < 0.0);\r
+//        head.getBounds(bounds);\r
+        double angle1 = Arcs.nextIntersectingAngle(cx, cy, r, \r
+            Math.atan2(-dy1, dx1), head, angle > 0.0);\r
+        double extent = angle1-angle0;\r
+        //double arcAngle = angle0;\r
+        if(angle < 0.0) {\r
+            double temp = angle0;            \r
+            angle0 = angle1;\r
+            angle1 = temp;\r
+            extent = -extent;\r
+        }                \r
+        if(extent < 0)\r
+            extent += Math.PI*2.0;\r
+        else if(extent >= 360.0)\r
+            extent -= Math.PI*2.0;\r
+        if(arc == null)\r
+               arc = new Arc2D.Double();\r
+        arc.setArc(cx-r, cy-r, 2*r, 2*r, \r
+            Math.toDegrees(angle0), \r
+            Math.toDegrees(extent), \r
+            Arc2D.OPEN);\r
+//        \r
+               return arc;\r
+       }\r
+       \r
+       public static Point2D computeCenter(Rectangle2D tail, Rectangle2D head, double angle) {\r
+\r
+               double x0 = tail.getCenterX();\r
+        double y0 = tail.getCenterY();\r
+        double x1 = head.getCenterX();\r
+        double y1 = head.getCenterY();\r
+        \r
+//        System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1);\r
+        \r
+        double offset = \r
+            Math.abs(angle) < 1.0e-6\r
+            ? 1e3 * Math.signum(angle)\r
+            : Math.tan(Math.PI*0.5-angle)*0.5;\r
+            \r
+        double cx = 0.5*(x0+x1) + offset * (y1-y0);\r
+        double cy = 0.5*(y0+y1) + offset * (x0-x1);\r
+        \r
+        return new Point2D.Double(cx, cy);\r
+               \r
+       }\r
+       \r
+       public static Pair<Arc2D, Path2D> createArrowShape(Pair<Arc2D, Path2D> shapes, Rectangle2D tail, Rectangle2D head, double angle) {\r
+               if(shapes == null || shapes.first == null || shapes.second == null) {\r
+                       shapes = new Pair<Arc2D, Path2D>(new Arc2D.Double(), new Path2D.Double());\r
+               }\r
+\r
+               createArc(shapes.first, tail, head, angle);\r
+               \r
+        double angle0 =  Math.toRadians(shapes.first.getAngleStart());\r
+        double angle1 = Math.toRadians(shapes.first.getAngleStart() + shapes.first.getAngleExtent());\r
+        double x = Math.cos(angle > 0.0 ? angle1 : angle0);\r
+        double y = -Math.sin(angle > 0.0 ? angle1 : angle0);\r
+        double r = shapes.first.getHeight() / 2;\r
+        \r
+        createArrow(shapes.second, shapes.first.getCenterX() + r*x, shapes.first.getCenterY() + r*y, \r
+            angle < 0.0 ? -y : y, \r
+            angle > 0.0 ? -x : x);\r
+\r
+        return shapes;\r
+        \r
+       }\r
 }\r
diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java
new file mode 100644 (file)
index 0000000..5769797
--- /dev/null
@@ -0,0 +1,184 @@
+package org.simantics.sysdyn.ui.editor.routing;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Collection;\r
+\r
+import org.simantics.g2d.routing.Constants;\r
+import org.simantics.g2d.routing.IConnection;\r
+import org.simantics.g2d.routing.IConnection.Connector;\r
+import org.simantics.g2d.routing.IRouter2;\r
+import org.simantics.sysdyn.ui.editor.participant.SysdynConnectTool.SysdynConnection;\r
+import org.simantics.sysdyn.ui.elements2.connections.Flows;\r
+\r
+public class FlowRouter implements IRouter2{\r
+\r
+       SysdynLocalRouter localRouter;\r
+       \r
+       public static final double OFFSET = 1.0;\r
+\r
+    public FlowRouter() {\r
+        this(false);\r
+    }\r
+\r
+    public FlowRouter(boolean roundCorners) {\r
+        this.localRouter = new SysdynLocalRouter();\r
+    }\r
+\r
+    private Path2D route(double beginX, double beginY, int sDir, Rectangle2D beginObstacle,\r
+            double endX, double endY, int tDir, Rectangle2D endObstacle) {\r
+        localRouter.sx = beginX;\r
+        localRouter.sy = beginY;\r
+        if(beginObstacle == null) {\r
+            localRouter.aMinX = beginX;\r
+            localRouter.aMinY = beginY;\r
+            localRouter.aMaxX = beginX;\r
+            localRouter.aMaxY = beginY;\r
+        }\r
+        else {\r
+            localRouter.aMinX = beginObstacle.getMinX();\r
+            localRouter.aMinY = beginObstacle.getMinY();\r
+            localRouter.aMaxX = beginObstacle.getMaxX();\r
+            localRouter.aMaxY = beginObstacle.getMaxY();\r
+        }\r
+        localRouter.sourceDirection = sDir;\r
+\r
+        localRouter.tx = endX;\r
+        localRouter.ty = endY;\r
+        if(endObstacle == null) {\r
+            localRouter.bMinX = endX;\r
+            localRouter.bMinY = endY;\r
+            localRouter.bMaxX = endX;\r
+            localRouter.bMaxY = endY;\r
+        }\r
+        else {\r
+            localRouter.bMinX = endObstacle.getMinX();\r
+            localRouter.bMinY = endObstacle.getMinY();\r
+            localRouter.bMaxX = endObstacle.getMaxX();\r
+            localRouter.bMaxY = endObstacle.getMaxY();\r
+        }\r
+        localRouter.targetDirection = tDir;\r
+        \r
+        // adjust flows to start and stop within the obstacle\r
+        if(sDir == Constants.EAST || sDir == Constants.WEST) {\r
+               localRouter.aMinY = localRouter.aMinY + OFFSET;\r
+               localRouter.aMaxY = localRouter.aMaxY - OFFSET;\r
+        }\r
+        if(tDir == Constants.EAST || tDir == Constants.WEST) {\r
+               localRouter.bMinY = localRouter.bMinY + OFFSET;\r
+               localRouter.bMaxY = localRouter.bMaxY - OFFSET;\r
+        }\r
+        if(sDir == Constants.SOUTH || sDir == Constants.NORTH) {\r
+               localRouter.aMinX = localRouter.aMinX + OFFSET;\r
+               localRouter.aMaxX = localRouter.aMaxX - OFFSET;\r
+        }\r
+        if(tDir == Constants.SOUTH || tDir == Constants.NORTH) {\r
+               localRouter.bMinX = localRouter.bMinX + OFFSET;\r
+               localRouter.bMaxX = localRouter.bMaxX - OFFSET;\r
+        }\r
+        if(localRouter.sx > localRouter.aMaxX)\r
+               localRouter.sx = localRouter.aMaxX;\r
+        if(localRouter.sx < localRouter.aMinX)\r
+               localRouter.sx = localRouter.aMinX;\r
+        if(localRouter.sy > localRouter.aMaxY)\r
+               localRouter.sy = localRouter.aMaxY;\r
+        if(localRouter.sy < localRouter.aMinY)\r
+               localRouter.sy = localRouter.aMinY;\r
+        if(localRouter.tx > localRouter.bMaxX)\r
+               localRouter.tx = localRouter.bMaxX;\r
+        if(localRouter.tx < localRouter.bMinX)\r
+               localRouter.tx = localRouter.bMinX;\r
+        if(localRouter.ty > localRouter.bMaxY)\r
+               localRouter.ty = localRouter.bMaxY;\r
+        if(localRouter.ty < localRouter.bMinY)\r
+               localRouter.ty = localRouter.bMinY;\r
+\r
+        localRouter.route();\r
+\r
+        Path2D completePath = new Path2D.Double();\r
+        \r
+        double[] coordinates = new double[localRouter.points.size()];\r
+        for(int i = 0; i < localRouter.points.size() ; i++)\r
+               coordinates[i] = localRouter.points.get(i);\r
+        \r
+        boolean vertical = false;\r
+        \r
+        if(coordinates.length > 3) {\r
+               if(coordinates[0] == coordinates[2]) {\r
+                       // starts to south or north\r
+                       double[] tempCoordinates = new double[coordinates.length - 1];\r
+                for(int i = 0; i < coordinates.length - 1 ; i++)\r
+                       tempCoordinates[i] = coordinates[i + 1];\r
+                \r
+                coordinates = new double[tempCoordinates.length];\r
+                for(int i = 0; i < tempCoordinates.length ; i++)\r
+                       coordinates[i] = tempCoordinates[i];\r
+                vertical = true;\r
+               }\r
+        }        \r
+\r
+       Flows.createOffsetLine(completePath, vertical, OFFSET, coordinates);\r
+       Flows.createOffsetLine(completePath, vertical, -OFFSET, coordinates);\r
+//        return localRouter.path;\r
+       \r
+//     Flows.createLines(completePath, false, beginObstacle, endObstacle);\r
+        return completePath;\r
+    }\r
+\r
+    @Override\r
+    public void route(IConnection connection) {\r
+       \r
+       if(!(connection instanceof SysdynConnection)) {\r
+               return;\r
+       }\r
+        Collection<?> segments = connection.getSegments();\r
+        if(segments.size() == 1)\r
+            for(Object seg : segments) {\r
+                Connector begin = connection.getBegin(seg);\r
+                Connector end = connection.getEnd(seg);\r
+\r
+                double bestLength = Double.POSITIVE_INFINITY;\r
+                Path2D bestPath = null;\r
+\r
+//                for(int sDir : Constants.POSSIBLE_DIRECTIONS[begin.allowedDirections])\r
+                for(int sDir : Constants.POSSIBLE_DIRECTIONS[15])\r
+//                    for(int tDir : Constants.POSSIBLE_DIRECTIONS[end.allowedDirections]) {\r
+                    for(int tDir : Constants.POSSIBLE_DIRECTIONS[15]) {\r
+                        Path2D path = route(begin.x, begin.y, sDir, begin.parentObstacle,\r
+                                end.x, end.y, tDir, end.parentObstacle);\r
+\r
+                        double length = pathCost(path);\r
+                        if(length < bestLength) {\r
+                            bestLength = length;\r
+                            bestPath = path;\r
+                        }\r
+                    }\r
+\r
+                if(bestPath != null)\r
+                    connection.setPath(seg, bestPath);\r
+            }\r
+    }\r
+\r
+    final static AffineTransform IDENTITY = new AffineTransform();\r
+\r
+    static double pathCost(Path2D path) {\r
+        double length = 0.0;\r
+        PathIterator it = path.getPathIterator(IDENTITY);\r
+        double[] temp = new double[6];\r
+        double x=0.0, y=0.0;\r
+        double bendCount = 0.0;\r
+        while(!it.isDone()) {\r
+            bendCount += 1.0;\r
+            if(it.currentSegment(temp) != PathIterator.SEG_MOVETO)\r
+                length += Math.abs(x - temp[0] + y - temp[1]);\r
+            x = temp[0];\r
+            y = temp[1];\r
+            it.next();\r
+        }\r
+        //return length * (6.0 + bendCount);\r
+        return bendCount - 1.0 / length;\r
+    }\r
+\r
+}\r
diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java
new file mode 100644 (file)
index 0000000..26a3d97
--- /dev/null
@@ -0,0 +1,476 @@
+package org.simantics.sysdyn.ui.editor.routing;\r
+\r
+import java.awt.geom.Path2D;\r
+import java.util.ArrayList;\r
+\r
+import org.simantics.g2d.routing.Constants;\r
+\r
+public class SysdynLocalRouter {\r
+\r
+       static final double OFFSET = 1.0;\r
+       \r
+       double aMinX;\r
+       double aMinY;\r
+       double aMaxX;\r
+       double aMaxY;\r
+\r
+       double bMinX;\r
+       double bMinY;\r
+       double bMaxX;\r
+       double bMaxY;\r
+\r
+       double sx;\r
+       double sy;\r
+\r
+       double tx;\r
+       double ty;\r
+\r
+       int sourceDirection;\r
+       int targetDirection;\r
+\r
+       ArrayList<Double> points;\r
+       Path2D path;\r
+\r
+       public SysdynLocalRouter() {\r
+       }\r
+\r
+       /**\r
+        * Case where both source and target connection directions are to east.\r
+        */\r
+       void routeEast() {\r
+               if (bMinX >= aMaxX || tx >= 0 && !(bMaxY < aMinY || aMaxY < bMinY)) {\r
+                       if (ty != 0.0) {\r
+                /*  ______           ______\r
+                 * |      |         |      |\r
+                 * |      |----\    |      |\r
+                 * |      |    \--->|      |\r
+                 * |______|         |______|\r
+                 */\r
+                               double mx = 0.5 * (aMaxX + bMinX);\r
+                               point(mx, 0.0);\r
+                               point(mx, ty);\r
+                       } else\r
+                               ; // Just a straight line\r
+               } else {\r
+                       double x0 = bMinX;\r
+                       double x1 = aMaxX;\r
+                       double my;\r
+            /*      ______\r
+             *     |      |\r
+             *     |      |\r
+             *  /->|      |\r
+             *  |  |______|\r
+             *  |\r
+             *  \-------------\\r
+             *        ______  |\r
+             *       |      | |\r
+             *       |      |-/\r
+             *       |      |\r
+             *       |______|\r
+             * \r
+             * If the elements are separated in Y-direction,\r
+             * route between the elements (this is always the shortest path).\r
+             */\r
+                       if (bMaxY < aMinY)\r
+                               my = 0.5 * (aMinY + bMaxY);\r
+                       else if (aMaxY < bMinY)\r
+                               my = 0.5 * (aMaxY + bMinY);\r
+                       else {\r
+                /*\r
+                 *  /------------------------\\r
+                 *  |   ______      ______   |\r
+                 *  |  |      |    |      |  |\r
+                 *  |  |      |    |      |--+\r
+                 *  +->|      |    |      |  |\r
+                 *  |  |______|    |______|  |\r
+                 *  |                        |\r
+                 *  \------------------------/\r
+                 * \r
+                 * or\r
+                 * \r
+                 *    /-----------\\r
+                 *    |   ______  |\r
+                 *    |  |      | |\r
+                 *    |  |      | |\r
+                 * /--+->|      | |\r
+                 * |  ___|______| |\r
+                 * | |      |     |\r
+                 * | |      |-+---/\r
+                 * | |      | |\r
+                 * | |______| |\r
+                 * |          |\r
+                 * \----------/\r
+                 * \r
+                 * We may choose either lower or upper path.\r
+                 */\r
+                               double upperX0 = bMinX;\r
+                               double upperX1 = aMaxX;\r
+                               double lowerX0 = bMinX;\r
+                               double lowerX1 = aMaxX;\r
+                               double upperY = Math.min(aMinY, bMinY);\r
+                               double lowerY = Math.max(aMaxY, bMaxY);\r
+\r
+                               if (aMinX < bMinX) {\r
+                                       if (ty < 0.5 * (aMinY + aMaxY))\r
+                                               lowerX0 = aMinX;\r
+                                       else\r
+                                               upperX0 = aMinX;\r
+                               }\r
+\r
+                               if (bMaxX > aMaxX) {\r
+                                       if (ty < 0.5 * (aMinY + aMaxY))\r
+                                               upperX1 = bMaxX;\r
+                                       else\r
+                                               lowerX1 = bMaxX;\r
+                               }\r
+\r
+                               double upperLength = upperX1 - upperY + (upperX1 - upperX0)\r
+                                               + (ty - upperY) + (tx - upperX0);\r
+                               double lowerLength = lowerX1 + lowerY + (lowerX1 - lowerX0)\r
+                                               + (lowerY - ty) + (tx - lowerX0);\r
+\r
+                               if (upperLength < lowerLength) {\r
+                                       x0 = upperX0;\r
+                                       x1 = upperX1;\r
+                                       my = upperY;\r
+                               } else {\r
+                                       x0 = lowerX0;\r
+                                       x1 = lowerX1;\r
+                                       my = lowerY;\r
+                               }\r
+                       }\r
+                       point(x1, 0.0);\r
+                       point(x1, my);\r
+                       point(x0, my);\r
+                       point(x0, ty);\r
+               }\r
+       }\r
+\r
+       void routeWest() {\r
+               if (tx >= 0.0) {\r
+                       double fx = Math.max(aMaxX, bMaxX);\r
+                       double mx = 0.5 * (aMaxX + bMinX);\r
+                       if (bMinY >= 0.0 || bMaxY <= 0.0 || mx < 0.0) {\r
+                /*                   ______\r
+                 *                  |      |\r
+                 *                  |      |\r
+                 *                  |      |<-\\r
+                 *  ______          |______|  |\r
+                 * |      |                   |\r
+                 * |      |-------------------/\r
+                 * |      |\r
+                 * |______|\r
+                 */\r
+                point(fx, 0.0);\r
+            }\r
+            else {\r
+                /*             /-------------\\r
+                 *             |    ______   |\r
+                 *             |   |      |  |\r
+                 *  ______     |   |      |  |\r
+                 * |      |    |   |      |<-+\r
+                 * |      |----+   |______|  |\r
+                 * |      |    |             |\r
+                 * |______|    \-------------/\r
+                 * \r
+                 * We may choose either upper or lower path\r
+                 * by the path length.\r
+                 */\r
+                               double my = Math.abs(bMinY) + Math.abs(ty - bMinY) < Math\r
+                                               .abs(bMaxY) + Math.abs(ty - bMaxY) ? bMinY : bMaxY;\r
+                               point(mx, 0.0);\r
+                               point(mx, my);\r
+                               point(fx, my);\r
+                       }\r
+                       point(fx, ty);\r
+               } else {\r
+                       double fx = Math.max(aMaxX, bMaxX);\r
+                       double mx = 0.5 * (aMinX + bMaxX);\r
+                       point(fx, 0.0);\r
+                       if (ty <= aMinY || ty >= aMaxY\r
+                                       || (tx >= mx && ty >= aMinY && ty <= aMaxY)) {\r
+                /*                   ______\r
+                 *                  |      |\r
+                 *                  |      |\r
+                 *                  |      |--\\r
+                 *  ______          |______|  |\r
+                 * |      |                   |\r
+                 * |      |<------------------/\r
+                 * |      |\r
+                 * |______|\r
+                 */\r
+                point(fx, ty);\r
+            }\r
+            else {\r
+                /*             /-------------\\r
+                 *             |    ______   |\r
+                 *             |   |      |  |\r
+                 *  ______     |   |      |  |\r
+                 * |      |    |   |      |--+\r
+                 * |      |<---+   |______|  |\r
+                 * |      |    |             |\r
+                 * |______|    \-------------/\r
+                 * \r
+                 * We may choose either upper or lower path\r
+                 * by the path length.\r
+                 */\r
+                               double my = Math.abs(aMinY) + Math.abs(ty - aMinY) < Math\r
+                                               .abs(aMaxY) + Math.abs(ty - aMaxY) ? aMinY : aMaxY;\r
+                               point(fx, my);\r
+                               point(mx, my);\r
+                               point(mx, ty);\r
+                       }\r
+               }\r
+       }\r
+\r
+       void routeSouth() {\r
+               if (tx > 0.0 && (bMinY >= 0.0 || (ty > 0.0 && bMinX <= aMaxX)))\r
+                       point(tx, 0.0);\r
+               else if (bMinX > aMaxX) {\r
+                       double mx = 0.5 * (aMaxX + bMinX);\r
+                       point(mx, 0.0);\r
+                       point(mx, bMinY);\r
+                       point(tx, bMinY);\r
+               } else {\r
+                       double fx = aMaxX;\r
+                       double my = 0.5 * (aMaxY + bMinY);\r
+                       if (my < aMaxY && (tx < aMinX || ty < aMinY)) {\r
+                               my = Math.min(aMinY, bMinY);\r
+                               if (bMaxX > aMaxX)\r
+                                       fx = bMaxX;\r
+                       }\r
+                       point(fx, 0.0);\r
+                       point(fx, my);\r
+                       point(tx, my);\r
+               }\r
+       }\r
+\r
+       double xx, xy, yx, yy;\r
+\r
+       void point(double x, double y) {\r
+               lineTo(x * xx + y * yx + sx, x * xy + y * yy + sy);\r
+       }\r
+\r
+       /*\r
+        * should draw only horizontal or vertical lines. Determine the offset and\r
+        * draw both lines.\r
+        */\r
+       void lineTo(double x, double y) {\r
+               double cx = path.getCurrentPoint().getX();\r
+               double cy = path.getCurrentPoint().getY();\r
+               \r
+               if (Math.abs(cx - x) < 1e-5) {\r
+                       // Vertical line\r
+                       if(points.size() % 2 == 0) {\r
+                               points.add(points.get(points.size()-2));\r
+                       }\r
+                       points.add(y);\r
+               } else if (Math.abs(cy - y) < 1e-5) {\r
+                       // Horizontal line\r
+                       if(points.size() % 2 != 0) {\r
+                               points.add(cy);\r
+                       }\r
+                       points.add(x);\r
+               }\r
+               path.lineTo(x, y);\r
+       }\r
+       void rotate() {\r
+               double temp;\r
+\r
+               temp = tx;\r
+               tx = ty;\r
+               ty = -temp;\r
+\r
+               temp = aMinX;\r
+               aMinX = aMinY;\r
+               aMinY = -aMaxX;\r
+               aMaxX = aMaxY;\r
+               aMaxY = -temp;\r
+\r
+               temp = bMinX;\r
+               bMinX = bMinY;\r
+               bMinY = -bMaxX;\r
+               bMaxX = bMaxY;\r
+               bMaxY = -temp;\r
+\r
+               temp = xx;\r
+               xx = -xy;\r
+               xy = temp;\r
+\r
+               temp = yx;\r
+               yx = -yy;\r
+               yy = temp;\r
+\r
+               --targetDirection;\r
+               if (targetDirection < 0)\r
+                       targetDirection += 4;\r
+               --sourceDirection;\r
+       }\r
+\r
+       void flip() {\r
+               double temp;\r
+\r
+               ty = -ty;\r
+\r
+               temp = aMinY;\r
+               aMinY = -aMaxY;\r
+               aMaxY = -temp;\r
+\r
+               temp = bMinY;\r
+               bMinY = -bMaxY;\r
+               bMaxY = -temp;\r
+\r
+               yx = -yx;\r
+               yy = -yy;\r
+\r
+               targetDirection = (targetDirection + 2) % 4;\r
+       }\r
+\r
+       /*\r
+        * Puts source terminal to origo and rotates the situation so that the\r
+        * connection leaves to east. Finally, the case where target direction is to\r
+        * south is eliminated by optionally flipping the situation.\r
+        */\r
+       void canonicalize() {\r
+               aMinX -= sx;\r
+               aMinY -= sy;\r
+               aMaxX -= sx;\r
+               aMaxY -= sy;\r
+               bMinX -= sx;\r
+               bMinY -= sy;\r
+               bMaxX -= sx;\r
+               bMaxY -= sy;\r
+               tx -= sx;\r
+               ty -= sy;\r
+               xx = yy = 1.0;\r
+               xy = yx = 0.0;\r
+               while (sourceDirection > 0)\r
+                       rotate();\r
+\r
+               if (targetDirection == Constants.SOUTH)\r
+                       flip();\r
+       }\r
+\r
+       public void route() {\r
+               /*\r
+                * Three cases: 1. Obstacles share X-axis at some point 2. Obstacles\r
+                * share Y-Axis at some point 3. Obstacles don't share axis => Have to\r
+                * make corners.\r
+                */\r
+               if (aMinX < bMinX && aMaxX > bMinX || aMinX < bMaxX && aMaxX > bMaxX) {\r
+                       // Obstacles share x-axis => no corner\r
+                       double minX = aMinX > bMinX ? aMinX : bMinX;\r
+                       double maxX = aMaxX < bMaxX ? aMaxX : bMaxX;\r
+                       double middle = minX + (maxX - minX) / 2;\r
+                       sx = middle;\r
+                       tx = middle;\r
+                       if (sy > ty) {\r
+                               sy = aMinY;\r
+                               ty = bMaxY;\r
+                       } else {\r
+                               sy = aMaxY;\r
+                               ty = bMinY;\r
+                       }\r
+               } else if (aMinY < bMinY && aMaxY > bMinY || aMinY < bMaxY\r
+                               && aMaxY > bMaxY) {\r
+                       // Obstacles share y-axis => no corner\r
+                       double minY = aMinY > bMinY ? aMinY : bMinY;\r
+                       double maxY = aMaxY < bMaxY ? aMaxY : bMaxY;\r
+                       double middle = minY + (maxY - minY) / 2;\r
+                       sy = middle;\r
+                       ty = middle;\r
+                       if (sx > tx) {\r
+                               sx = aMinX;\r
+                               tx = bMaxX;\r
+                       } else {\r
+                               sx = aMaxX;\r
+                               tx = bMinX;\r
+                       }\r
+               } else {\r
+                       // Move starting point to the edge of the start element\r
+                       switch (sourceDirection) {\r
+                       case Constants.WEST:\r
+                               sy = ty > aMaxY ? aMaxY : ty < aMinY ? aMinY : ty;\r
+                               sx = aMinX;\r
+                               break;\r
+                       case Constants.EAST:\r
+                               sy = ty > aMaxY ? aMaxY : ty < aMinY ? aMinY : ty;\r
+                               sx = aMaxX;\r
+                               break;\r
+                       case Constants.NORTH:\r
+                               sy = aMaxY;\r
+                               sx = tx > aMaxX ? aMaxX : tx < aMinX ? aMinX : tx;\r
+                               break;\r
+                       case Constants.SOUTH:\r
+                               sy = aMinY;\r
+                               sx = tx > aMaxX ? aMaxX : tx < aMinX ? aMinX : tx;\r
+                               break;\r
+                       }\r
+\r
+                       // Move target point to the edge of the ending element\r
+                       switch (targetDirection) {\r
+                       case Constants.EAST:\r
+                               ty = sy > bMaxY ? bMaxY : sy < bMinY ? bMinY : sy;\r
+                               tx = bMaxX;\r
+                               break;\r
+                       case Constants.WEST:\r
+                               ty = sy > bMaxY ? bMaxY : sy < bMinY ? bMinY : sy;\r
+                               tx = bMinX;\r
+                               break;\r
+                       case Constants.NORTH:\r
+                               ty = bMinY;\r
+                               tx = sx > bMaxX ? bMaxX : sx < bMinX ? bMinX : sx;\r
+                               break;\r
+                       case Constants.SOUTH:\r
+                               ty = bMaxY;\r
+                               tx = sx > bMaxX ? bMaxX : sx < bMinX ? bMinX : sx;\r
+                               break;\r
+                       }\r
+               }\r
+\r
+               path = new Path2D.Double();\r
+               points = new ArrayList<Double>();\r
+\r
+               path.moveTo(sx, sy);\r
+               points.add(sx);\r
+               points.add(sy);\r
+\r
+               // Vertical and horizontal cases\r
+               if ((Math.abs(sx - tx) < 1e-5 && isVertical())\r
+                       || (Math.abs(sy - ty) < 1e-5 && isHorizontal())) {\r
+                       lineTo(tx, ty);\r
+                       return;\r
+               }\r
+               \r
+\r
+               canonicalize();\r
+               switch (targetDirection) {\r
+               case Constants.EAST:\r
+                       routeWest();\r
+                       break;\r
+               case Constants.WEST:\r
+                       routeEast();\r
+                       break;\r
+               case Constants.NORTH:\r
+                       routeSouth();\r
+                       break;\r
+               }\r
+\r
+               point(tx, ty);\r
+\r
+       }\r
+       \r
+       private boolean isVertical() {\r
+               return\r
+               (sourceDirection == Constants.SOUTH && targetDirection == Constants.SOUTH)\r
+               ||\r
+               (sourceDirection == Constants.NORTH && targetDirection == Constants.NORTH);\r
+       }\r
+       \r
+       private boolean isHorizontal() {\r
+               return\r
+               (sourceDirection == Constants.EAST && targetDirection == Constants.EAST)\r
+               ||\r
+               (sourceDirection == Constants.WEST && targetDirection == Constants.WEST);\r
+       }\r
+}\r
diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java
deleted file mode 100644 (file)
index bb0a4bd..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-/*******************************************************************************\r
- * Copyright (c) 2010 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.sysdyn.ui.elements2.connections;\r
-\r
-import java.awt.geom.Arc2D;\r
-import java.awt.geom.Path2D;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-\r
-import org.simantics.utils.datastructures.Pair;\r
-\r
-public class Dependencies {\r
-       \r
-       /*\r
-        * Total length of the arrow is ARROW_LENGTH1 + ARROW_LENGTH2\r
-        */\r
-    public static double ARROW_LENGTH1 = 0.2;\r
-    public static double ARROW_LENGTH2 = 1.0;\r
-    public static double ARROW_WIDTH = 0.5;\r
-    \r
-//\r
-//     // Auxiliary    \r
-//     double angle0;\r
-//     double angle1;\r
-//     double cx;\r
-//     double cy;\r
-//     double r;\r
-//     \r
-//     // Scene graph\r
-//     ShapeNode arcNode;\r
-//     FilledShapeNode arrowNode;\r
-//     \r
-//     public Dependencies() {     \r
-//     }\r
-//     \r
-//     public Dependencies(Connectable tail, Connectable head) {\r
-//             super();\r
-//             this.tail = tail;\r
-//             this.head = head;\r
-//     }\r
-//     \r
-//     @Override\r
-//     public void elementUpdated(IElement element) {\r
-//             update();               \r
-//     }       \r
-//\r
-//     @Override\r
-//     public void remove() {\r
-//             arcNode.remove();\r
-//             arrowNode.remove();\r
-//             tail.removeListener(this);\r
-//             head.removeListener(this);\r
-//             super.remove();\r
-//     }\r
-//\r
-//     @Override\r
-//     public void init(G2DParentNode parent) {\r
-//             tail.addListener(this);\r
-//             head.addListener(this);\r
-//             \r
-//             arcNode = parent.addNode(ShapeNode.class);\r
-//             arcNode.setScaleStroke(true);\r
-//             arcNode.setStroke(new BasicStroke(1));\r
-//             arrowNode = parent.addNode(FilledShapeNode.class);\r
-//             update();\r
-//     }\r
-//     \r
-       \r
-       private static Path2D createArrow(Path2D shape, double x, double y, double dx, double dy) {\r
-               if(shape == null)\r
-                       shape = new Path2D.Double();\r
-               else\r
-                       shape.reset();\r
-               \r
-        shape.moveTo(x+ARROW_LENGTH1*dx, y+ARROW_LENGTH1*dy);\r
-        x -= ARROW_LENGTH2*dx;\r
-        y -= ARROW_LENGTH2*dy;\r
-        shape.lineTo(x-ARROW_WIDTH*dy, y+ARROW_WIDTH*dx);\r
-        shape.lineTo(x+ARROW_WIDTH*dy, y-ARROW_WIDTH*dx);\r
-        shape.closePath();\r
-        return shape;\r
-    }\r
-       \r
-       public static Arc2D createArc(Arc2D arc, Rectangle2D tail, Rectangle2D head, double angle) {\r
-               double x0 = tail.getCenterX();\r
-        double y0 = tail.getCenterY();\r
-        double x1 = head.getCenterX();\r
-        double y1 = head.getCenterY();\r
-        \r
-//        System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1);\r
-        \r
-        double offset = \r
-            Math.abs(angle) < 1.0e-6\r
-            ? 1e3 * Math.signum(angle)\r
-            : Math.tan(Math.PI*0.5-angle)*0.5;\r
-            \r
-        double cx = 0.5*(x0+x1) + offset * (y1-y0);\r
-        double cy = 0.5*(y0+y1) + offset * (x0-x1);\r
-        double dx0 = x0 - cx;\r
-        double dy0 = y0 - cy;\r
-        double dx1 = x1 - cx;\r
-        double dy1 = y1 - cy;\r
-        \r
-        double r = Math.sqrt(dx0*dx0 + dy0*dy0);\r
-        \r
-//        Rectangle2D bounds = new Rectangle2D.Double();\r
-//        tail.getBounds(bounds);\r
-        double angle0 = Arcs.nextIntersectingAngle(cx, cy, r, \r
-            Math.atan2(-dy0, dx0), tail, angle < 0.0);\r
-//        head.getBounds(bounds);\r
-        double angle1 = Arcs.nextIntersectingAngle(cx, cy, r, \r
-            Math.atan2(-dy1, dx1), head, angle > 0.0);\r
-        double extent = angle1-angle0;\r
-        //double arcAngle = angle0;\r
-        if(angle < 0.0) {\r
-            double temp = angle0;            \r
-            angle0 = angle1;\r
-            angle1 = temp;\r
-            extent = -extent;\r
-        }                \r
-        if(extent < 0)\r
-            extent += Math.PI*2.0;\r
-        else if(extent >= 360.0)\r
-            extent -= Math.PI*2.0;\r
-        if(arc == null)\r
-               arc = new Arc2D.Double();\r
-        arc.setArc(cx-r, cy-r, 2*r, 2*r, \r
-            Math.toDegrees(angle0), \r
-            Math.toDegrees(extent), \r
-            Arc2D.OPEN);\r
-//        \r
-               return arc;\r
-       }\r
-       \r
-       public static Point2D computeCenter(Rectangle2D tail, Rectangle2D head, double angle) {\r
-\r
-               double x0 = tail.getCenterX();\r
-        double y0 = tail.getCenterY();\r
-        double x1 = head.getCenterX();\r
-        double y1 = head.getCenterY();\r
-        \r
-//        System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1);\r
-        \r
-        double offset = \r
-            Math.abs(angle) < 1.0e-6\r
-            ? 1e3 * Math.signum(angle)\r
-            : Math.tan(Math.PI*0.5-angle)*0.5;\r
-            \r
-        double cx = 0.5*(x0+x1) + offset * (y1-y0);\r
-        double cy = 0.5*(y0+y1) + offset * (x0-x1);\r
-        \r
-        return new Point2D.Double(cx, cy);\r
-               \r
-       }\r
-       \r
-       public static Pair<Arc2D, Path2D> createArrowShape(Pair<Arc2D, Path2D> shapes, Rectangle2D tail, Rectangle2D head, double angle) {\r
-               if(shapes == null || shapes.first == null || shapes.second == null) {\r
-                       shapes = new Pair<Arc2D, Path2D>(new Arc2D.Double(), new Path2D.Double());\r
-               }\r
-\r
-               createArc(shapes.first, tail, head, angle);\r
-               \r
-        double angle0 =  Math.toRadians(shapes.first.getAngleStart());\r
-        double angle1 = Math.toRadians(shapes.first.getAngleStart() + shapes.first.getAngleExtent());\r
-        double x = Math.cos(angle > 0.0 ? angle1 : angle0);\r
-        double y = -Math.sin(angle > 0.0 ? angle1 : angle0);\r
-        double r = shapes.first.getHeight() / 2;\r
-        \r
-        createArrow(shapes.second, shapes.first.getCenterX() + r*x, shapes.first.getCenterY() + r*y, \r
-            angle < 0.0 ? -y : y, \r
-            angle > 0.0 ? -x : x);\r
-\r
-        return shapes;\r
-        \r
-       }\r
-//     \r
-//     public void update() {\r
-//             if(arcNode != null)\r
-//                     updateSceneGraph();\r
-//             fireElementUpdated();\r
-//     }\r
-//             \r
-//     @Override\r
-//     public void getBounds(Rectangle2D bounds) {\r
-//             bounds.setFrame(arcNode.getBounds());\r
-//     }\r
-//\r
-//     @Override\r
-//     public boolean hitTest(double x, double y, double tolerance) {\r
-//        double dx = x-cx;\r
-//        double dy = y-cy;\r
-//        double dist = dx*dx + dy*dy;\r
-//        if(dist < (r+tolerance)*(r+tolerance) &&\r
-//            dist > (r-tolerance)*(r-tolerance)) {\r
-//            double angle = Arcs.normalizeAngle(Math.atan2(-dy, dx));\r
-//            if(Arcs.areClockwiseOrdered(angle0, angle, angle1))\r
-//                return true;\r
-//        }\r
-//        return false;\r
-//     }\r
-//     \r
-//     class EventHandler extends DragEventHandler {\r
-//             @Override\r
-//             protected boolean begin(IDiagramEditor editor, DragEvent event) {\r
-//                     return event.startModifiers.equals("left");\r
-//             }\r
-//             \r
-//             @Override\r
-//             protected void update(IDiagramEditor editor, DragEvent event) {\r
-//                 if(event == null)\r
-//                     return;\r
-//                     angle = Arcs.angleOfArc(\r
-//                                     tail.getOrigo().getX(), tail.getOrigo().getY(), \r
-//                                     event.current.getX(), event.current.getY(), \r
-//                                     head.getOrigo().getX(), head.getOrigo().getY()\r
-//                                     );\r
-//                     Dependencies.this.update();\r
-//                     editor.requestRepaint();\r
-//             }\r
-//     }\r
-//\r
-//     @SuppressWarnings("unchecked")\r
-//    @Override\r
-//     public <T> T getInterface(Class<T> clazz) {\r
-//             if(clazz == IEventHandler.class) \r
-//                     return (T)new EventHandler();\r
-//             return super.getInterface(clazz);\r
-//     }\r
-//\r
-//     @Override\r
-//     public void elementRemoved(IElement element) {\r
-//             remove();               \r
-//     }\r
-       \r
-}\r
index 71eb30f693a20304c13125522f7d3a059cf26ee1..9ef4fa9ff9cd4375889fe1a35cbf688d9ca64d53 100644 (file)
@@ -29,8 +29,8 @@ import org.simantics.g2d.diagram.IDiagram;
 import org.simantics.g2d.element.ElementClass;\r
 import org.simantics.g2d.element.IElement;\r
 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;\r
-import org.simantics.g2d.routing.RouterFactory;\r
 import org.simantics.layer0.Layer0;\r
+import org.simantics.sysdyn.ui.editor.routing.DependencyRouter;\r
 import org.simantics.utils.datastructures.Pair;\r
 \r
 /**\r
@@ -65,7 +65,7 @@ public class DependencyConnectionFactory extends ElementFactoryAdapter {
        final AtomicInteger ready = new AtomicInteger(1);\r
        final ConcurrentSkipListMap<String, Pair<Resource, Object>> properties = new ConcurrentSkipListMap<String, Pair<Resource, Object>>();\r
        \r
-        element.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(false, false));\r
+        element.setHint(DiagramHints.ROUTE_ALGORITHM, new DependencyRouter());\r
 \r
        graph.forEachPredicate(elementResource, new AsyncMultiProcedure<Resource>() {\r
 \r
index ab652a57e4e35d895cf0b2bfecf073814b1c6445..284a7bd48ae906ffa7a60dab00ea301392c7819f 100644 (file)
@@ -48,6 +48,7 @@ import org.simantics.g2d.elementclass.connection.EdgeClass.EdgeHandler;
 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;\r
 import org.simantics.scenegraph.g2d.G2DParentNode;\r
 import org.simantics.scenegraph.utils.NodeUtil;\r
+import org.simantics.sysdyn.ui.editor.routing.DependencyRouter;\r
 import org.simantics.utils.datastructures.Callback;\r
 import org.simantics.utils.datastructures.Pair;\r
 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
@@ -72,7 +73,6 @@ public class DependencyEdgeClass {
                System.out.println("pickTest no node!");\r
                return false;\r
             }\r
-            \r
             return Arcs.hitTest(node.getBeginBounds(), node.getEndBounds(), node.getAngle(), pickRect.getCenterX(), pickRect.getCenterY(), 3.0);\r
             \r
            }\r
@@ -105,7 +105,9 @@ public class DependencyEdgeClass {
 \r
         @Override\r
         public void init(IElement e, G2DParentNode parent) {\r
-            ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), DependencyNode.class);\r
+            DependencyNode node = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), DependencyNode.class);\r
+            node.setAngle(0.1);\r
+            \r
             update(e);\r
         }\r
 \r
@@ -118,7 +120,6 @@ public class DependencyEdgeClass {
                \r
             DependencyNode node = e.getHint(KEY_SG_NODE);\r
             if(node == null) return;\r
-            \r
             final IDiagram diagram = ElementUtils.peekDiagram(e);\r
             \r
             node.setFieldListener(new PropertyChangeListener() {\r
@@ -167,7 +168,7 @@ public class DependencyEdgeClass {
             node.setEndBounds(endTerminalShape.getBounds2D());\r
             node.setStroke(stroke);\r
             node.setColor(c);\r
-            node.setAngle(0.1);\r
+            node.setShapes(DependencyRouter.createArrowShape(node.getShapes(), node.getBeginBounds(), node.getEndBounds(), node.getAngle()));\r
 \r
             Map<String, Pair<Resource, Object>> properties = e.getHint(DiagramHints.PROPERTIES);\r
             if(properties != null) {\r
@@ -183,7 +184,7 @@ public class DependencyEdgeClass {
                path = new Path2D.Double();\r
             else\r
                path.reset();\r
-            path.append(node.getArc(), false);\r
+            path.append(node.getShapes().first, false);\r
             eh.setPath(e, path);\r
 \r
         }\r
index 1ccb00d2bcbfd3719c28580669985c219f35f509..04fc95bb481f462311ede652ad49c70f9549202f 100644 (file)
@@ -28,6 +28,7 @@ import org.simantics.scenegraph.ISelectionPainterNode;
 import org.simantics.scenegraph.g2d.G2DNode;\r
 import org.simantics.scenegraph.g2d.events.ISGMouseEvent;\r
 import org.simantics.scenegraph.utils.NodeUtil;\r
+import org.simantics.sysdyn.ui.editor.routing.DependencyRouter;\r
 import org.simantics.utils.datastructures.Pair;\r
 \r
 public class DependencyNode extends G2DNode implements ISelectionPainterNode, MouseListener, MouseMotionListener {\r
@@ -100,6 +101,14 @@ public class DependencyNode extends G2DNode implements ISelectionPainterNode, Mo
     @SyncField("angle")\r
     public void setAngle(Double angle) {\r
         this.angle = angle.doubleValue();\r
+        if(this.beginBounds != null && this.endBounds != null)\r
+               this.shapes = DependencyRouter.createArrowShape(this.shapes, this.beginBounds, this.endBounds, this.angle);\r
+    }\r
+    \r
+    @PropertySetter("shapes")\r
+    @SyncField("shapes")\r
+    public void setShapes(Pair<Arc2D, Path2D> shapes) {\r
+       this.shapes = shapes;\r
     }\r
 \r
     public Color getColor() {\r
@@ -122,10 +131,12 @@ public class DependencyNode extends G2DNode implements ISelectionPainterNode, Mo
         return angle;\r
     }\r
     \r
-    public Arc2D getArc() {\r
-       return shapes.first;\r
+    public Pair<Arc2D, Path2D> getShapes() {\r
+        return shapes;\r
     }\r
     \r
+\r
+    \r
     @Override\r
     public void render(Graphics2D g) {\r
         if(beginBounds == null || endBounds == null) return;\r
@@ -133,8 +144,6 @@ public class DependencyNode extends G2DNode implements ISelectionPainterNode, Mo
         // Removed to let the global control handle rendering quality issues.\r
         //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
 \r
-        Dependencies.createArrowShape(shapes, beginBounds, endBounds, angle);\r
-\r
         boolean selected = NodeUtil.isSelected(this, 2);\r
         if(selected) {\r
             g.setColor(Color.PINK);\r
index 3040913f951c551ef2c5e6f79dfcab52ccd50deb..c770d6028fb3fd411af507cc64947051dfdb09fe 100644 (file)
@@ -23,7 +23,7 @@ import org.simantics.g2d.element.ElementClass;
 import org.simantics.g2d.element.IElement;\r
 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;\r
 import org.simantics.g2d.elementclass.connection.ConnectionClass;\r
-import org.simantics.g2d.routing.RouterFactory;\r
+import org.simantics.sysdyn.ui.editor.routing.FlowRouter;\r
 \r
 /**\r
  * An element class for single connection entity elements. A connection entity\r
@@ -53,7 +53,7 @@ public class FlowConnectionFactory extends ElementFactoryAdapter {
     @Override\r
     public void load(AsyncReadGraph graph, ICanvasContext canvas, IDiagram diagram, Resource elementResource,\r
             final IElement element, final AsyncProcedure<IElement> procedure) {\r
-        element.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, true));\r
+        element.setHint(DiagramHints.ROUTE_ALGORITHM, new FlowRouter(false));\r
         procedure.execute(graph, element);\r
     }\r
 \r
index be35e1747f5f57d256bbbdb5d5ce23b95e536a7e..c1026fd01b389b52c8401f1dd25cc5e532133edd 100644 (file)
  *******************************************************************************/\r
 package org.simantics.sysdyn.ui.elements2.connections;\r
 \r
-import java.awt.BasicStroke;\r
 import java.awt.Color;\r
-import java.awt.Shape;\r
 import java.awt.Stroke;\r
-import java.util.Map;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.List;\r
 \r
-import org.simantics.db.Resource;\r
 import org.simantics.g2d.diagram.DiagramHints;\r
 import org.simantics.g2d.diagram.IDiagram;\r
 import org.simantics.g2d.diagram.handler.Topology;\r
 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
 import org.simantics.g2d.element.ElementClass;\r
 import org.simantics.g2d.element.ElementUtils;\r
 import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.SceneGraphNodeKey;\r
+import org.simantics.g2d.element.handler.BendsHandler;\r
+import org.simantics.g2d.element.handler.EdgeVisuals;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType;\r
 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
-import org.simantics.g2d.element.handler.SceneGraph;\r
-import org.simantics.g2d.element.handler.TerminalLayout;\r
-import org.simantics.g2d.element.handler.Transform;\r
 import org.simantics.g2d.element.handler.impl.ConfigurableEdgeVisuals;\r
 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;\r
 import org.simantics.g2d.element.handler.impl.FillColorImpl;\r
 import org.simantics.g2d.element.handler.impl.ParentImpl;\r
 import org.simantics.g2d.element.handler.impl.ShapePick;\r
 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;\r
+import org.simantics.g2d.elementclass.BranchPoint;\r
 import org.simantics.g2d.elementclass.connection.EdgeClass.EdgeHandler;\r
 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;\r
+import org.simantics.g2d.elementclass.connection.EdgeSceneGraph;\r
+import org.simantics.g2d.routing.ConnectionDirectionUtil;\r
+import org.simantics.g2d.routing.Constants;\r
+import org.simantics.g2d.routing.IRouter2;\r
+import org.simantics.g2d.utils.PathUtils;\r
 import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
+import org.simantics.scenegraph.g2d.nodes.EdgeNode;\r
+import org.simantics.sysdyn.ui.editor.participant.SysdynConnectTool.SysdynConnection;\r
+import org.simantics.sysdyn.ui.editor.routing.FlowRouter;\r
 import org.simantics.sysdyn.ui.elements2.ValveFactory.ValveSceneGraph;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
 \r
-/**\r
- * @author Toni Kalajainen\r
- */\r
 public class FlowEdgeClass {\r
 \r
-    // TODO scale, rotate, move, transform\r
-    public static final ElementClass CLASS =\r
-        ElementClass.compile(\r
-                SysdynEdgeSceneGraph.INSTANCE,\r
-                EdgeHandler.INSTANCE,\r
-                ConfigurableEdgeVisuals.DEFAULT,\r
-                FillColorImpl.BLACK,\r
-                FixedTransform.INSTANCE,\r
-                ShapePick.INSTANCE,\r
-                ConnectionSelectionOutline.INSTANCE,\r
-                SimpleElementLayers.INSTANCE,\r
-                ParentImpl.INSTANCE\r
-        ).setId("EdgeClass.STRAIGHT");\r
-\r
-    public static class SysdynEdgeSceneGraph implements SceneGraph {\r
-\r
-        private static final long serialVersionUID = 2914383071126238996L;\r
-\r
-        public static final SysdynEdgeSceneGraph INSTANCE = new SysdynEdgeSceneGraph();\r
-\r
-        public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
-\r
-        public static final Key KEY_SG_NODE = new SceneGraphNodeKey(FlowNode.class, "EDGE_NODE");\r
-\r
-        @Override\r
-        public void init(IElement e, G2DParentNode parent) {\r
-            ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), FlowNode.class);\r
-            update(e);\r
-        }\r
-\r
-        @Override\r
-        public void cleanup(IElement e) {\r
-            ElementUtils.removePossibleNode(e, KEY_SG_NODE);\r
-        }\r
-\r
-        public void update(final IElement e) {\r
-               \r
-            FlowNode node = e.getHint(KEY_SG_NODE);\r
-            if(node == null) return;\r
-\r
-            final IDiagram diagram = ElementUtils.peekDiagram(e);\r
-            \r
-            // Flownode does not change any property, hence listener is not needed\r
-//            node.setFieldListener(new PropertyChangeListener() {\r
-//\r
-//                @Override\r
-//                public void propertyChange(final PropertyChangeEvent event) {\r
-//                     \r
-//                     DiagramUtils.mutateDiagram(diagram, new Callback<DiagramMutator>() {\r
-//                                             \r
-//                                             @Override\r
-//                                             public void run(DiagramMutator mutator) {\r
-//\r
-//                                                     String field = event.getPropertyName();\r
-//                                         Map<String, Pair<Resource, Object>> properties = e.getHint(DiagramHints.PROPERTIES);\r
-//                                         Pair<Resource, Object> property = properties.get(field);\r
-//                                                     \r
-//                                                     mutator.modifyProperty(e, property.first, event.getNewValue());\r
-//                                                     \r
-//                                             }\r
-//                                             \r
-//                                     });\r
-//                     \r
-//                }\r
-//                \r
-//                     });\r
-            \r
-            Stroke stroke = new BasicStroke(0.1f);\r
-            Color c = ElementUtils.getFillColor(e, Color.BLACK);\r
-\r
-            Shape beginTerminalShape = null;\r
-            Shape endTerminalShape = null;\r
-            boolean toValve = false;\r
-            if (diagram != null) {\r
-                Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
-                if (topology != null) {\r
-                    Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin);\r
-                    Connection endConnection = topology.getConnection(e, EdgeEnd.End);\r
-                    beginTerminalShape = getCanvasTerminalShape(beginConnection);\r
-                    endTerminalShape = getCanvasTerminalShape(endConnection);\r
-                    toValve = endConnection.node.getElementClass().containsClass(ValveSceneGraph.class);\r
-//                    System.out.println("end connection class = " + endConnection.node.getElementClass());\r
-                }\r
-            }\r
-\r
-            if(beginTerminalShape == null || endTerminalShape == null) return;\r
-            \r
-            node.setBeginBounds(beginTerminalShape.getBounds2D());\r
-            node.setEndBounds(endTerminalShape.getBounds2D());\r
-            node.setStroke(stroke);\r
-            node.setColor(c);\r
-            node.setToValve(toValve);\r
-//            System.out.println("set toValve = " + toValve);\r
-\r
-            Map<String, Pair<Resource, Object>> properties = e.getHint(DiagramHints.PROPERTIES);\r
-            if(properties != null) {\r
-                       for(Map.Entry<String, Pair<Resource, Object>> entry : properties.entrySet()) {\r
-                               NodeUtil.setPropertyIfSupported(entry.getKey(), entry.getValue().second, node);                         \r
-//                             node.setProperty(entry.getKey(), entry.getValue().second);\r
-                       }\r
-            }\r
-            \r
-        }\r
-\r
-        private static Shape getCanvasTerminalShape(Connection connection) {\r
-            if (connection != null && connection.node != null && connection.terminal != null) {\r
-                TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class);\r
-                if (layout != null) {\r
-                    //return layout.getTerminalShape(connection.node, connection.terminal);\r
-                    Shape shp = layout.getTerminalShape(connection.node, connection.terminal);\r
-                    Transform tr = connection.node.getElementClass().getAtMostOneItemOfClass(Transform.class);\r
-                    if (tr == null)\r
-                        return shp;\r
-\r
-                    return tr.getTransform(connection.node).createTransformedShape(shp);\r
-                    \r
-                }\r
-            }\r
-            return null;\r
-        }\r
-\r
-    }\r
+       // TODO scale, rotate, move, transform\r
+       public static final ElementClass CLASS = ElementClass.compile(\r
+                       FlowEdgeSceneGraph.INSTANCE, EdgeHandler.INSTANCE,\r
+                       ConfigurableEdgeVisuals.DEFAULT, FillColorImpl.BLACK,\r
+                       FixedTransform.INSTANCE, ShapePick.INSTANCE,\r
+                       ConnectionSelectionOutline.INSTANCE, SimpleElementLayers.INSTANCE,\r
+                       ParentImpl.INSTANCE).setId("FlowEdgeClass");\r
+\r
+       public static class FlowEdgeSceneGraph extends EdgeSceneGraph {\r
+\r
+               private static final long serialVersionUID = -8737581995034992604L;\r
+\r
+               public static final EdgeSceneGraph INSTANCE = new FlowEdgeSceneGraph();\r
+\r
+               @Override\r
+               public void init(IElement e, G2DParentNode parent) {\r
+                       ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE,\r
+                                       "edge_" + e.hashCode(), FlowEdgeNode.class);\r
+                       final IDiagram diagram = ElementUtils.peekDiagram(e);\r
+\r
+                       boolean toValve = false;\r
+                       if (diagram != null) {\r
+                               Topology topology = diagram.getDiagramClass()\r
+                                               .getAtMostOneItemOfClass(Topology.class);\r
+                               if (topology != null) {\r
+                                       Connection endConnection = topology.getConnection(e,\r
+                                                       EdgeEnd.End);\r
+                                       toValve = endConnection.node.getElementClass()\r
+                                                       .containsClass(ValveSceneGraph.class);\r
+                               }\r
+                       }\r
+\r
+                       ConfigurableEdgeVisuals cev = e.getElementClass()\r
+                                       .getAtMostOneItemOfClass(ConfigurableEdgeVisuals.class);\r
+                       if (cev != null) {\r
+                               if (toValve)\r
+                                       cev.setArrowType(e, EdgeEnd.End, ArrowType.None);\r
+                               else {\r
+                                       cev.setArrowType(e, EdgeEnd.End, ArrowType.Fill);\r
+                                       cev.setArrowSize(e, EdgeEnd.End, 2);\r
+                               }\r
+                       }\r
+\r
+                       updateRoute(e);\r
+                       update(e);\r
+\r
+               }\r
+\r
+               private static Path2D trimLineToArrows(Path2D line,\r
+                               ArrowType endArrowType, double endArrowSize) {\r
+                       Path2D result = new Path2D.Double();\r
+                       PathIterator pi = line.getPathIterator(null);\r
+\r
+                       double lineTo[] = new double[2];\r
+                       double prevLine[] = new double[2];\r
+                       double dummy[] = new double[2];\r
+\r
+                       while (!pi.isDone()) {\r
+                               int type = pi.currentSegment(lineTo);\r
+                               pi.next();\r
+                               int nextType = pi.currentSegment(dummy);\r
+\r
+                               if (type == PathIterator.SEG_LINETO\r
+                                               && nextType == PathIterator.SEG_MOVETO) {\r
+                                       // this is the end of one line\r
+                                       if (endArrowType == ArrowType.Fill) {\r
+                                               double dx = (lineTo[0] - prevLine[0]);\r
+                                               double dy = (lineTo[1] - prevLine[1]);\r
+                                               double x2 = dx * dx;\r
+                                               double y2 = dy * dy;\r
+                                               double len = Math.sqrt(x2 + y2);\r
+                                               if (len > endArrowSize) {\r
+                                                       double scale = endArrowSize / len;\r
+                                                       lineTo[0] -= dx * scale;\r
+                                                       lineTo[1] -= dy * scale;\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                               if (type == 0) {\r
+                                       result.moveTo(lineTo[0], lineTo[1]);\r
+                               } else if (type == 1) {\r
+                                       result.lineTo(lineTo[0], lineTo[1]);\r
+                               } else {\r
+                                       throw new UnsupportedOperationException(\r
+                                                       "invalid path segment type: " + type);\r
+                               }\r
+                               prevLine[0] = lineTo[0];\r
+                               prevLine[1] = lineTo[1];\r
+\r
+                       }\r
+\r
+                       result.setWindingRule(line.getWindingRule());\r
+                       return result;\r
+               }\r
+\r
+               private void updateRoute(final IElement e) {\r
+\r
+                       final List<IElement> segments = new ArrayList<IElement>();\r
+                       segments.add(e);\r
+\r
+                       IRouter2 router = ElementUtils.getHintOrDefault(e,\r
+                                       DiagramHints.ROUTE_ALGORITHM, new FlowRouter());\r
+\r
+                       router.route(new SysdynConnection() {\r
+\r
+                               IDiagram diagram = ElementUtils.peekDiagram(e);\r
+\r
+                               final Topology topology = diagram.getDiagramClass()\r
+                                               .getSingleItem(Topology.class);\r
+\r
+                               @Override\r
+                               public Connector getBegin(Object seg) {\r
+                                       IElement e = (IElement) seg;\r
+                                       Connection begin = topology.getConnection(e, EdgeEnd.Begin);\r
+                                       return getConnector(begin);\r
+                               }\r
+\r
+                               @Override\r
+                               public Connector getEnd(Object seg) {\r
+                                       IElement e = (IElement) seg;\r
+                                       Connection begin = topology.getConnection(e, EdgeEnd.End);\r
+                                       return getConnector(begin);\r
+                               }\r
+\r
+                               private Connector getConnector(Connection connection) {\r
+                                       Connector c = new Connector();\r
+\r
+                                       AffineTransform at = TerminalUtil.getTerminalPosOnDiagram(\r
+                                                       connection.node, connection.terminal);\r
+                                       c.x = at.getTranslateX();\r
+                                       c.y = at.getTranslateY();\r
+\r
+                                       if (connection.node.getElementClass().containsClass(\r
+                                                       ValveSceneGraph.class)) {\r
+                                               Rectangle2D bounds = ElementUtils\r
+                                                               .getElementBoundsOnDiagram(connection.node)\r
+                                                               .getBounds2D();\r
+                                               c.parentObstacle = new Rectangle2D.Double(bounds\r
+                                                               .getCenterX() - FlowRouter.OFFSET, bounds\r
+                                                               .getCenterY() - FlowRouter.OFFSET,\r
+                                                               FlowRouter.OFFSET * 2, FlowRouter.OFFSET * 2);\r
+                                       } else {\r
+                                               c.parentObstacle = ElementUtils\r
+                                                               .getElementBoundsOnDiagram(connection.node)\r
+                                                               .getBounds2D();\r
+                                       }\r
+                                       ConnectionDirectionUtil.determineAllowedDirections(c);\r
+\r
+                                       return c;\r
+                               }\r
+\r
+                               private int toAllowedDirections(BranchPoint.Direction direction) {\r
+                                       switch (direction) {\r
+                                       case Any:\r
+                                               return 0xf;\r
+                                       case Horizontal:\r
+                                               return Constants.EAST_FLAG | Constants.WEST_FLAG;\r
+                                       case Vertical:\r
+                                               return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;\r
+                                       default:\r
+                                               throw new IllegalArgumentException(\r
+                                                               "unrecognized direction: " + direction);\r
+                                       }\r
+                               }\r
+\r
+                               @Override\r
+                               public Collection<? extends Object> getSegments() {\r
+                                       return segments;\r
+                               }\r
+\r
+                               @Override\r
+                               public void setPath(Object seg, Path2D path) {\r
+                                       IElement e = (IElement) seg;\r
+                                       BendsHandler bends = e.getElementClass()\r
+                                                       .getAtMostOneItemOfClass(BendsHandler.class);\r
+                                       AffineTransform elementTransform = ElementUtils\r
+                                                       .getInvTransform(e);\r
+                                       path = (Path2D) path.clone();\r
+                                       path.transform(elementTransform);\r
+                                       bends.setPath(e, path);\r
+                               }\r
+                       });\r
+\r
+               }\r
+\r
+               public void update(final IElement e) {\r
+                       EdgeNode node = e.getHint(KEY_SG_NODE);\r
+                       if (node == null)\r
+                               return;\r
+\r
+                       EdgeVisuals vh = e.getElementClass().getSingleItem(\r
+                                       EdgeVisuals.class);\r
+                       ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin);\r
+                       ArrowType at2 = vh.getArrowType(e, EdgeEnd.End);\r
+                       Stroke stroke = vh.getStroke(e);\r
+                       // StrokeType strokeType = vh.getStrokeType(e);\r
+                       double as1 = vh.getArrowSize(e, EdgeEnd.Begin);\r
+                       double as2 = vh.getArrowSize(e, EdgeEnd.End);\r
+\r
+                       Color c = ElementUtils.getFillColor(e, Color.BLACK);\r
+\r
+                       // Get terminal shape for clipping the painted edge to its bounds.\r
+                       IDiagram diagram = ElementUtils.peekDiagram(e);\r
+                       if (diagram != null) {\r
+                               Topology topology = diagram.getDiagramClass()\r
+                                               .getAtMostOneItemOfClass(Topology.class);\r
+                               if (topology != null) {\r
+                                       Connection beginConnection = topology.getConnection(e,\r
+                                                       EdgeEnd.Begin);\r
+                                       Connection endConnection = topology.getConnection(e,\r
+                                                       EdgeEnd.End);\r
+                                       int beginBranchDegree = getBranchPointDegree(\r
+                                                       beginConnection, topology);\r
+                                       int endBranchDegree = getBranchPointDegree(endConnection,\r
+                                                       topology);\r
+                                       if (beginBranchDegree > 0 && beginBranchDegree < 3) {\r
+                                               at1 = ArrowType.None;\r
+                                       }\r
+                                       if (endBranchDegree > 0 && endBranchDegree < 3) {\r
+                                               at2 = ArrowType.None;\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       // Read bends\r
+                       BendsHandler bh = e.getElementClass().getSingleItem(\r
+                                       BendsHandler.class);\r
+                       Path2D line = bh.getPath(e);\r
+\r
+                       boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None;\r
+                       // line = clipLineEnds(line, beginTerminalShape, endTerminalShape);\r
+\r
+                       Point2D first = new Point2D.Double();\r
+                       Point2D dir1 = new Point2D.Double();\r
+                       Point2D last = new Point2D.Double();\r
+                       Point2D dir2 = new Point2D.Double();\r
+                       PathIterator pi = line.getPathIterator(null);\r
+                       drawArrows &= getPathArrows(pi, first, dir1, last, dir2);\r
+\r
+                       if (drawArrows) {\r
+                               line = trimLineToArrows(line, at2, as2);\r
+                       }\r
+\r
+                       EdgeNode.ArrowType pat1 = convert(at1);\r
+                       EdgeNode.ArrowType pat2 = convert(at2);\r
+\r
+                       node.init(new GeneralPath(line), stroke, c, dir1, dir2, first,\r
+                                       last, as1, as2, pat1, pat2, null, null);\r
+               }\r
+\r
+               private boolean getPathArrows(PathIterator pi, Point2D begin,\r
+                               Point2D beginDirection, Point2D end, Point2D endDirection) {\r
+\r
+                       Iterator<double[]> i = PathUtils.toLineIterator(pi);\r
+\r
+                       double first1[] = null, last1[] = null;\r
+                       double first2[] = null, last2[] = null;\r
+\r
+                       // double current[] = new double[2];\r
+\r
+                       double[] previous = null;\r
+\r
+                       while (i.hasNext()) {\r
+                               double[] current = i.next();\r
+\r
+                               // Start of the first path\r
+                               if (first1 == null) {\r
+                                       first1 = current;\r
+                                       last1 = current;\r
+                               }\r
+\r
+                               // Command was moveTo => start of the second path\r
+                               else if (previous != null\r
+                                               && (previous[2] != current[0] || previous[3] != current[1])) {\r
+                                       first2 = current;\r
+                                       last2 = current;\r
+                               }\r
+\r
+                               // first2 == null => still in the first line\r
+                               else if (first2 == null) {\r
+                                       last1 = current;\r
+                               }\r
+\r
+                               // second path\r
+                               else if (!i.hasNext()) {\r
+                                       last2 = current;\r
+                               }\r
+\r
+                               previous = current;\r
+                       }\r
+\r
+                       if (first1 == null || last1 == null || first2 == null\r
+                                       || last2 == null)\r
+                               return false;\r
+\r
+                       double[] first = { mean(first1[0], first2[0]),\r
+                                       mean(first1[1], first2[1]), mean(first1[2], first2[2]),\r
+                                       mean(first1[3], first2[3]) };\r
+                       double[] last = { mean(last1[0], last2[0]),\r
+                                       mean(last1[1], last2[1]), mean(last1[2], last2[2]),\r
+                                       mean(last1[3], last2[3]) };\r
+\r
+                       begin.setLocation(PathUtils.getLinePos(first, 0));\r
+                       beginDirection.setLocation(PathUtils.getLineTangent(first, 0));\r
+                       end.setLocation(PathUtils.getLinePos(last, 1));\r
+                       Point2D endTangent = PathUtils.getLineTangent(last, 1);\r
+                       endDirection.setLocation(-endTangent.getX(), -endTangent.getY());\r
+\r
+                       return true;\r
+               }\r
+\r
+               private double mean(double c1, double c2) {\r
+                       return c1 + (c2 - c1) / 2;\r
+               }\r
+\r
+               private static EdgeNode.ArrowType convert(ArrowType at) {\r
+                       switch (at) {\r
+                       case None:\r
+                               return EdgeNode.ArrowType.None;\r
+                       case Stroke:\r
+                               return EdgeNode.ArrowType.Stroke;\r
+                       case Fill:\r
+                               return EdgeNode.ArrowType.Fill;\r
+                       default:\r
+                               throw new IllegalArgumentException("unsupported arrow type: "\r
+                                               + at);\r
+                       }\r
+               }\r
+\r
+               private final Collection<Connection> connectionsTemp = new ArrayList<Connection>();\r
+\r
+               private int getBranchPointDegree(Connection connection,\r
+                               Topology topology) {\r
+                       if (connection != null && connection.node != null) {\r
+                               if (connection.node.getElementClass().containsClass(\r
+                                               BranchPoint.class)) {\r
+                                       connectionsTemp.clear();\r
+                                       topology.getConnections(connection.node,\r
+                                                       connection.terminal, connectionsTemp);\r
+                                       int degree = connectionsTemp.size();\r
+                                       connectionsTemp.clear();\r
+                                       return degree;\r
+                               }\r
+                       }\r
+                       return -1;\r
+               }\r
+\r
+       }\r
 \r
 }\r
diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java
new file mode 100644 (file)
index 0000000..e6640f1
--- /dev/null
@@ -0,0 +1,78 @@
+package org.simantics.sysdyn.ui.elements2.connections;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.Stroke;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.GeneralPath;\r
+\r
+import org.simantics.scenegraph.ISelectionPainterNode;\r
+import org.simantics.scenegraph.g2d.nodes.EdgeNode;\r
+import org.simantics.scenegraph.utils.NodeUtil;\r
+\r
+public class FlowEdgeNode extends EdgeNode implements ISelectionPainterNode {\r
+       \r
+       private static final long serialVersionUID = -6774653631527343539L;\r
+\r
+       private static final BasicStroke SELECTION_STROKE = new BasicStroke(1.0f);\r
+       \r
+       @Override\r
+    public void render(Graphics2D g) {\r
+        if(color != null) g.setColor(color);\r
+        if(stroke == null || shape == null)  return;\r
+\r
+        if(alphaComposite != null) {\r
+            g.setComposite(alphaComposite);\r
+        }\r
+\r
+        Stroke effectiveStroke = stroke;\r
+        if(dynamicStroke != null) {\r
+            effectiveStroke = dynamicStroke;\r
+        }\r
+\r
+        Color effectiveColor = color;\r
+        if(dynamicColor != null) {\r
+            effectiveColor = dynamicColor;\r
+        }\r
+\r
+        g.setStroke(effectiveStroke);\r
+\r
+        // Draw line\r
+        boolean selected = NodeUtil.isSelected(this, 2);\r
+        if(selected) {\r
+            g.setColor(Color.PINK);\r
+            g.setStroke(SELECTION_STROKE);\r
+            g.draw(shape);\r
+        }\r
+        g.setColor(effectiveColor);\r
+        g.setStroke(effectiveStroke);\r
+        g.draw(shape);\r
+\r
+        // Draw the ending arrow if necessary\r
+        if(last_at == ArrowType.Fill) {\r
+               AffineTransform at = g.getTransform();\r
+\r
+               g.setStroke(ARROW_STROKE);\r
+\r
+               double theta = Math.atan2(lastdir.getY(), lastdir.getX()) - Math.PI/2;\r
+               g.translate(last.getX(), last.getY());\r
+               g.rotate(theta);\r
+               g.scale(lastsize, lastsize);\r
+               g.fill(FLOW_ARROW);\r
+               g.setTransform(at);\r
+        }\r
+\r
+    }\r
+       \r
+    public transient final static GeneralPath FLOW_ARROW;\r
+\r
+    static {\r
+       FLOW_ARROW = new GeneralPath();\r
+       FLOW_ARROW.moveTo(-1f, 1.6f);\r
+        FLOW_ARROW.lineTo(   0f, 0f);\r
+        FLOW_ARROW.lineTo( 1f, 1.6f);\r
+        FLOW_ARROW.closePath();\r
+    }\r
+\r
+}\r
index 3c5a08f4432e64f3333ebdfd97b0b50a68c48732..25cec6400cf13f65eb011247db9be45c4ee4faaf 100644 (file)
@@ -32,9 +32,8 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode {
     private Stroke stroke;\r
     private Rectangle2D beginBounds;\r
     private Rectangle2D endBounds;\r
-    private Boolean toValve;\r
-    private transient Path2D lines;\r
-    private transient Path2D arrow;\r
+    private Path2D lines;\r
+    private Path2D arrow;\r
 \r
     @PropertySetter("color")\r
     @SyncField("color")\r
@@ -59,11 +58,17 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode {
     public void setEndBounds(Rectangle2D endBounds) {\r
         this.endBounds = endBounds;\r
     }\r
-\r
-    @PropertySetter("toValve")\r
-    @SyncField("toValve")\r
-    public void setToValve(Boolean toValve) {\r
-        this.toValve = toValve;\r
+    \r
+    @PropertySetter("lines")\r
+    @SyncField("lines")\r
+    public void setLines(Path2D lines) {\r
+        this.lines = lines;\r
+    }\r
+    \r
+    @PropertySetter("arrow")\r
+    @SyncField("arrow")\r
+    public void setArrow(Path2D arrow) {\r
+        this.arrow = arrow;\r
     }\r
 \r
     public Color getColor() {\r
@@ -82,10 +87,14 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode {
         return endBounds;\r
     }\r
 \r
-    public Boolean getBoolean() {\r
-        return toValve;\r
+    public Path2D getLines() {\r
+       return lines;\r
     }\r
-\r
+    \r
+    public Path2D getArrow() {\r
+       return arrow;\r
+    }\r
+    \r
     @Override\r
     public void render(Graphics2D g) {\r
         // Removed to let the global control handle rendering quality issues.\r
@@ -97,15 +106,6 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode {
          * -In the second case there is an arrow and valve is beginBounds\r
          */\r
 \r
-//        System.out.println("FlowNode.render toValve = " + toValve);\r
-\r
-        if(toValve) {\r
-            lines = Flows.createLines(lines, false, endBounds, beginBounds);\r
-        } else {\r
-            lines = Flows.createLines(lines, true, beginBounds, endBounds);\r
-            arrow = Flows.createArrow(arrow, beginBounds, endBounds);\r
-        }\r
-\r
         boolean selected = NodeUtil.isSelected(this, 2);\r
         if(selected) {\r
             g.setColor(Color.PINK);\r
@@ -113,12 +113,12 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode {
             g.draw(lines);\r
             if(color != null) g.setColor(color);\r
             g.setStroke(stroke);\r
-            g.draw(lines);\r
+            if(lines!= null) g.draw(lines);\r
             if(arrow != null) g.fill(arrow);\r
         } else {\r
             if(color != null) g.setColor(color);\r
             if(stroke != null) g.setStroke(stroke);\r
-            g.draw(lines);\r
+            if(lines!= null) g.draw(lines);\r
             if(arrow != null) g.fill(arrow);\r
         }\r
 \r