]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / elementclass / connection / EdgeSceneGraph.java
index b12ad68685e66ae6495ce5cb20224de6744b66d4..941b9272fe7df0bde5bfc75152f0543029cb1bd6 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.elementclass.connection;\r
-\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Shape;\r
-import java.awt.Stroke;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
-import java.awt.geom.Line2D;\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
-\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.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.Rotate;\r
-import org.simantics.g2d.element.handler.SceneGraph;\r
-import org.simantics.g2d.element.handler.TerminalLayout;\r
-import org.simantics.g2d.elementclass.BranchPoint;\r
-import org.simantics.g2d.utils.PathUtils;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.nodes.EdgeNode;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-\r
-/**\r
- * Generic edge painter\r
- *\r
- * @author J-P Laine\r
- */\r
-public class EdgeSceneGraph implements SceneGraph {\r
-\r
-    private static final long serialVersionUID = 2914383071126238996L;\r
-\r
-    public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph();\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(EdgeNode.class, "EDGE_SG_NODE");\r
-\r
-    @Override\r
-    public void init(IElement e, G2DParentNode parent) {\r
-        ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.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
-        EdgeNode node = e.getHint(KEY_SG_NODE);\r
-        if(node == null) return;\r
-\r
-        EdgeVisuals vh = e.getElementClass().getSingleItem(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
-        Shape beginTerminalShape = null;\r
-        Shape endTerminalShape = null;\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 = getTerminalShape(beginConnection);\r
-                endTerminalShape = getTerminalShape(endConnection);\r
-                int beginBranchDegree = getBranchPointDegree(beginConnection, topology);\r
-                int endBranchDegree = getBranchPointDegree(endConnection, 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(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 &= PathUtils.getPathArrows(pi, first, dir1, last, dir2);\r
-\r
-        if (drawArrows) {\r
-            line = trimLineToArrows(line, at1, as1, 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, last, as1, as2, pat1, pat2, null, null);\r
-    }\r
-\r
-    private static EdgeNode.ArrowType convert(ArrowType at) {\r
-        switch (at) {\r
-            case None: return EdgeNode.ArrowType.None;\r
-            case Stroke: return EdgeNode.ArrowType.Stroke;\r
-            case Fill: return EdgeNode.ArrowType.Fill;\r
-            default:\r
-                throw new IllegalArgumentException("unsupported arrow type: " + at);\r
-        }\r
-    }\r
-\r
-    private static final Rectangle2D EMPTY = new Rectangle2D.Double();\r
-\r
-    private static Shape getTerminalShape(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
-                Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class);\r
-                if (rotate == null)\r
-                    return shp;\r
-\r
-                double theta = rotate.getAngle(connection.node);\r
-                return AffineTransform.getRotateInstance(theta).createTransformedShape(shp);\r
-            }\r
-        }\r
-        return null;\r
-    }\r
-\r
-    private final Collection<Connection> connectionsTemp = new ArrayList<Connection>();\r
-    private int getBranchPointDegree(Connection connection, Topology topology) {\r
-        if (connection != null && connection.node != null) {\r
-            if (connection.node.getElementClass().containsClass(BranchPoint.class)) {\r
-                connectionsTemp.clear();\r
-                topology.getConnections(connection.node, connection.terminal, connectionsTemp);\r
-                int degree = connectionsTemp.size();\r
-                connectionsTemp.clear();\r
-                return degree;\r
-            }\r
-        }\r
-        return -1;\r
-    }\r
-\r
-    private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) {\r
-        if (beginTerminalShape == null && endTerminalShape == null)\r
-            return line;\r
-\r
-        Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY;\r
-        Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY;\r
-        // If the terminal shape doesn't contain its own coordinate system\r
-        // origin, just ignore the terminal shape.\r
-        if (bb != EMPTY && !bb.contains(0, 0))\r
-            bb = EMPTY;\r
-        if (eb != EMPTY && !eb.contains(0, 0))\r
-            eb = EMPTY;\r
-        if (bb.isEmpty() && eb.isEmpty())\r
-            return line;\r
-\r
-        Path2D result = new Path2D.Double();\r
-\r
-        PathIterator pi = line.getPathIterator(null);\r
-        Iterator<double[]> it = PathUtils.toLineIterator(pi);\r
-        boolean first = true;\r
-        while (it.hasNext()) {\r
-            double[] seg = it.next();\r
-            int degree = PathUtils.getLineDegree(seg);\r
-            //System.out.println("SEG: " + Arrays.toString(seg));\r
-\r
-            if (first) {\r
-                first = false;\r
-                Point2D start = PathUtils.getLinePos(seg, 0);\r
-                Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start);\r
-                if (sp != null) {\r
-                    result.moveTo(sp.getX(), sp.getY());\r
-                } else {\r
-                    result.moveTo(start.getX(), start.getY());\r
-                }\r
-            }\r
-            if (!it.hasNext()) {\r
-                // this is the last segment\r
-                Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1));\r
-                //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1));\r
-                if (ep != null) {\r
-                    seg[degree * 2] = ep.getX();\r
-                    seg[degree * 2 + 1] = ep.getY();\r
-                }\r
-            }\r
-\r
-            if (degree == 1) {\r
-                result.lineTo(seg[2], seg[3]);\r
-            } else if (degree == 2) {\r
-                result.quadTo(seg[2], seg[3], seg[4], seg[5]);\r
-            } else if (degree == 3) {\r
-                result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);\r
-            } else {\r
-                throw new UnsupportedOperationException("invalid path segment degree: " + degree);\r
-            }\r
-        }\r
-\r
-        result.setWindingRule(line.getWindingRule());\r
-        return result;\r
-    }\r
-\r
-    private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) {\r
-        Path2D result = new Path2D.Double();\r
-        PathIterator pi = line.getPathIterator(null);\r
-        Iterator<double[]> it = PathUtils.toLineIterator(pi);\r
-        boolean first = true;\r
-\r
-        while (it.hasNext()) {\r
-            double[] seg = it.next();\r
-            int degree = PathUtils.getLineDegree(seg);\r
-\r
-            if (first) {\r
-                first = false;\r
-\r
-                if (beginArrowType == ArrowType.Fill) {\r
-                    Point2D t = PathUtils.getLineTangent(seg, 0);\r
-                    double len = Math.sqrt(lensq(t, null));\r
-                    if (len > beginArrowSize) {\r
-                        double scale = beginArrowSize / len;\r
-                        seg[0] += t.getX() * scale;\r
-                        seg[1] += t.getY() * scale;\r
-                    } else {\r
-                        // Remove the first segment completely if the segment is too\r
-                        // small to be noticed from under the arrow.\r
-                        result.moveTo(seg[degree * 2], seg[degree * 2 + 1]);\r
-                        continue;\r
-                    }\r
-                }\r
-\r
-                result.moveTo(seg[0], seg[1]);\r
-            }\r
-            if (!it.hasNext()) {\r
-                // this is the last segment\r
-                if (endArrowType == ArrowType.Fill) {\r
-                    Point2D t = PathUtils.getLineTangent(seg, 1);\r
-                    double len = Math.sqrt(lensq(t, null));\r
-                    if (len > endArrowSize) {\r
-                        double scale = endArrowSize / len;\r
-                        seg[degree * 2] -= t.getX() * scale;\r
-                        seg[degree * 2 + 1] -= t.getY() * scale;\r
-                    }\r
-                }\r
-            }\r
-\r
-            if (degree == 1) {\r
-                result.lineTo(seg[2], seg[3]);\r
-            } else if (degree == 2) {\r
-                result.quadTo(seg[2], seg[3], seg[4], seg[5]);\r
-            } else if (degree == 3) {\r
-                result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);\r
-            } else {\r
-                throw new UnsupportedOperationException("invalid path segment degree: " + degree);\r
-            }\r
-        }\r
-\r
-        result.setWindingRule(line.getWindingRule());\r
-        return result;\r
-    }\r
-\r
-    private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) {\r
-        if (bounds.isEmpty())\r
-            return p2;\r
-\r
-        Line2D line = new Line2D.Double(p1, p2);\r
-        Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY());\r
-        Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY());\r
-        Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX());\r
-        Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX());\r
-\r
-        int i = 0;\r
-        Point2D[] intersections = { null, null, null, null };\r
-        if (vi1 != null)\r
-            intersections[i++] = vi1;\r
-        if (vi2 != null)\r
-            intersections[i++] = vi2;\r
-        if (hi1 != null)\r
-            intersections[i++] = hi1;\r
-        if (hi2 != null)\r
-            intersections[i++] = hi2;\r
-\r
-        //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections));\r
-\r
-        if (i == 0)\r
-            return p2;\r
-        if (i == 1)\r
-            return intersections[0];\r
-\r
-        // Find the intersection i for which applies\r
-        // lensq(p1, p2) >= lensq(p1, i) &\r
-        // for all intersections j != i: lensq(p1, i) > lensq(p1, j)\r
-        double len = lensq(p1, p2);\r
-        //System.out.println("original line lensq: " + len);\r
-        Point2D nearestIntersection = null;\r
-        double nearestLen = -1;\r
-        for (int j = 0; j < i; ++j) {\r
-            double l = lensq(p1, intersections[j]);\r
-            //System.out.println("intersected line lensq: " + l);\r
-            if (l <= len && l > nearestLen) {\r
-                nearestIntersection = intersections[j];\r
-                nearestLen = l;\r
-                //System.out.println("nearest");\r
-            }\r
-        }\r
-        return nearestIntersection;\r
-    }\r
-\r
-    private static double lensq(Point2D p1, Point2D p2) {\r
-        double dx = p1.getX();\r
-        double dy = p1.getY();\r
-        if (p2 != null) {\r
-            dx = p2.getX() - dx;\r
-            dy = p2.getY() - dy;\r
-        }\r
-        return dx*dx + dy*dy;\r
-    }\r
-\r
-    private static Point2D intersectWithHorizontalLine(Line2D l, double y) {\r
-        double dx = l.getX2() - l.getX1();\r
-        double dy = l.getY2() - l.getY1();\r
-\r
-        if (Math.abs(dy) < 1e-5) {\r
-            // input line as horizontal, no intersection.\r
-            return null;\r
-        }\r
-        double a = dx / dy;\r
-        return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y);\r
-    }\r
-\r
-    private static Point2D intersectWithVerticalLine(Line2D l, double x) {\r
-        double dx = l.getX2() - l.getX1();\r
-        double dy = l.getY2() - l.getY1();\r
-\r
-        if (Math.abs(dx) < 1e-5) {\r
-            // input line as vertical, no intersection.\r
-            return null;\r
-        }\r
-        double a = dy / dx;\r
-        return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1());\r
-    }\r
-\r
-\r
-    public final static Path2D NORMAL_ARROW;\r
-    public final static Path2D FILLED_ARROW;\r
-\r
-    static {\r
-        FILLED_ARROW = new Path2D.Double();\r
-        FILLED_ARROW.moveTo(-0.5, 1);\r
-        FILLED_ARROW.lineTo(   0, 0);\r
-        FILLED_ARROW.lineTo( 0.5, 1);\r
-        FILLED_ARROW.closePath();\r
-\r
-        NORMAL_ARROW = new Path2D.Double();\r
-        NORMAL_ARROW.moveTo(-0.5, 1);\r
-        NORMAL_ARROW.lineTo(   0, 0);\r
-        NORMAL_ARROW.lineTo( 0.5, 1);\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g2d.elementclass.connection;
+
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.Topology;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.SceneGraphNodeKey;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.EdgeVisuals;
+import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.Rotate;
+import org.simantics.g2d.element.handler.SceneGraph;
+import org.simantics.g2d.element.handler.TerminalLayout;
+import org.simantics.g2d.elementclass.BranchPoint;
+import org.simantics.g2d.utils.PathUtils;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.nodes.EdgeNode;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+
+/**
+ * Generic edge painter
+ *
+ * @author J-P Laine
+ */
+public class EdgeSceneGraph implements SceneGraph {
+
+    private static final long serialVersionUID = 2914383071126238996L;
+
+    public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph();
+
+    public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+
+    public static final Key KEY_SG_NODE = new SceneGraphNodeKey(EdgeNode.class, "EDGE_SG_NODE");
+
+    @Override
+    public void init(IElement e, G2DParentNode parent) {
+        ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.class);
+        update(e);
+    }
+
+    @Override
+    public void cleanup(IElement e) {
+        ElementUtils.removePossibleNode(e, KEY_SG_NODE);
+    }
+
+    public void update(final IElement e) {
+        EdgeNode node = e.getHint(KEY_SG_NODE);
+        if(node == null) return;
+
+        EdgeVisuals vh = e.getElementClass().getSingleItem(EdgeVisuals.class);
+        ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin);
+        ArrowType at2 = vh.getArrowType(e, EdgeEnd.End);
+        Stroke stroke = vh.getStroke(e);
+        //StrokeType strokeType = vh.getStrokeType(e);
+        double as1 = vh.getArrowSize(e, EdgeEnd.Begin);
+        double as2 = vh.getArrowSize(e, EdgeEnd.End);
+
+        Color c = ElementUtils.getFillColor(e, Color.BLACK);
+
+        // Get terminal shape for clipping the painted edge to its bounds.
+        IDiagram diagram = ElementUtils.peekDiagram(e);
+        Shape beginTerminalShape = null;
+        Shape endTerminalShape = null;
+        if (diagram != null) {
+            Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
+            if (topology != null) {
+                Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin);
+                Connection endConnection = topology.getConnection(e, EdgeEnd.End);
+                beginTerminalShape = getTerminalShape(beginConnection);
+                endTerminalShape = getTerminalShape(endConnection);
+                int beginBranchDegree = getBranchPointDegree(beginConnection, topology);
+                int endBranchDegree = getBranchPointDegree(endConnection, topology);
+                if (beginBranchDegree > 0 && beginBranchDegree < 3) {
+                    at1 = ArrowType.None;
+                }
+                if (endBranchDegree > 0 && endBranchDegree < 3) {
+                    at2 = ArrowType.None;
+                }
+            }
+        }
+
+        // Read bends
+        BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
+        Path2D line = bh.getPath(e);
+
+        boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None;
+        //line = clipLineEnds(line, beginTerminalShape, endTerminalShape);
+
+        Point2D first       = new Point2D.Double();
+        Point2D dir1        = new Point2D.Double();
+        Point2D last        = new Point2D.Double();
+        Point2D dir2        = new Point2D.Double();
+        PathIterator pi     = line.getPathIterator(null);
+        drawArrows &= PathUtils.getPathArrows(pi, first, dir1, last, dir2);
+
+        if (drawArrows) {
+            line = trimLineToArrows(line, at1, as1, at2, as2);
+        }
+
+        EdgeNode.ArrowType pat1 = convert(at1);
+        EdgeNode.ArrowType pat2 = convert(at2);
+
+        node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, last, as1, as2, pat1, pat2, null, null);
+    }
+
+    private static EdgeNode.ArrowType convert(ArrowType at) {
+        switch (at) {
+            case None: return EdgeNode.ArrowType.None;
+            case Stroke: return EdgeNode.ArrowType.Stroke;
+            case Fill: return EdgeNode.ArrowType.Fill;
+            default:
+                throw new IllegalArgumentException("unsupported arrow type: " + at);
+        }
+    }
+
+    private static final Rectangle2D EMPTY = new Rectangle2D.Double();
+
+    private static Shape getTerminalShape(Connection connection) {
+        if (connection != null && connection.node != null && connection.terminal != null) {
+            TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class);
+            if (layout != null) {
+                //return layout.getTerminalShape(connection.node, connection.terminal);
+                Shape shp = layout.getTerminalShape(connection.node, connection.terminal);
+                Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class);
+                if (rotate == null)
+                    return shp;
+
+                double theta = rotate.getAngle(connection.node);
+                return AffineTransform.getRotateInstance(theta).createTransformedShape(shp);
+            }
+        }
+        return null;
+    }
+
+    private final Collection<Connection> connectionsTemp = new ArrayList<Connection>();
+    private int getBranchPointDegree(Connection connection, Topology topology) {
+        if (connection != null && connection.node != null) {
+            if (connection.node.getElementClass().containsClass(BranchPoint.class)) {
+                connectionsTemp.clear();
+                topology.getConnections(connection.node, connection.terminal, connectionsTemp);
+                int degree = connectionsTemp.size();
+                connectionsTemp.clear();
+                return degree;
+            }
+        }
+        return -1;
+    }
+
+    private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) {
+        if (beginTerminalShape == null && endTerminalShape == null)
+            return line;
+
+        Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY;
+        Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY;
+        // If the terminal shape doesn't contain its own coordinate system
+        // origin, just ignore the terminal shape.
+        if (bb != EMPTY && !bb.contains(0, 0))
+            bb = EMPTY;
+        if (eb != EMPTY && !eb.contains(0, 0))
+            eb = EMPTY;
+        if (bb.isEmpty() && eb.isEmpty())
+            return line;
+
+        Path2D result = new Path2D.Double();
+
+        PathIterator pi = line.getPathIterator(null);
+        Iterator<double[]> it = PathUtils.toLineIterator(pi);
+        boolean first = true;
+        while (it.hasNext()) {
+            double[] seg = it.next();
+            int degree = PathUtils.getLineDegree(seg);
+            //System.out.println("SEG: " + Arrays.toString(seg));
+
+            if (first) {
+                first = false;
+                Point2D start = PathUtils.getLinePos(seg, 0);
+                Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start);
+                if (sp != null) {
+                    result.moveTo(sp.getX(), sp.getY());
+                } else {
+                    result.moveTo(start.getX(), start.getY());
+                }
+            }
+            if (!it.hasNext()) {
+                // this is the last segment
+                Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1));
+                //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1));
+                if (ep != null) {
+                    seg[degree * 2] = ep.getX();
+                    seg[degree * 2 + 1] = ep.getY();
+                }
+            }
+
+            if (degree == 1) {
+                result.lineTo(seg[2], seg[3]);
+            } else if (degree == 2) {
+                result.quadTo(seg[2], seg[3], seg[4], seg[5]);
+            } else if (degree == 3) {
+                result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);
+            } else {
+                throw new UnsupportedOperationException("invalid path segment degree: " + degree);
+            }
+        }
+
+        result.setWindingRule(line.getWindingRule());
+        return result;
+    }
+
+    private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) {
+        Path2D result = new Path2D.Double();
+        PathIterator pi = line.getPathIterator(null);
+        Iterator<double[]> it = PathUtils.toLineIterator(pi);
+        boolean first = true;
+
+        while (it.hasNext()) {
+            double[] seg = it.next();
+            int degree = PathUtils.getLineDegree(seg);
+
+            if (first) {
+                first = false;
+
+                if (beginArrowType == ArrowType.Fill) {
+                    Point2D t = PathUtils.getLineTangent(seg, 0);
+                    double len = Math.sqrt(lensq(t, null));
+                    if (len > beginArrowSize) {
+                        double scale = beginArrowSize / len;
+                        seg[0] += t.getX() * scale;
+                        seg[1] += t.getY() * scale;
+                    } else {
+                        // Remove the first segment completely if the segment is too
+                        // small to be noticed from under the arrow.
+                        result.moveTo(seg[degree * 2], seg[degree * 2 + 1]);
+                        continue;
+                    }
+                }
+
+                result.moveTo(seg[0], seg[1]);
+            }
+            if (!it.hasNext()) {
+                // this is the last segment
+                if (endArrowType == ArrowType.Fill) {
+                    Point2D t = PathUtils.getLineTangent(seg, 1);
+                    double len = Math.sqrt(lensq(t, null));
+                    if (len > endArrowSize) {
+                        double scale = endArrowSize / len;
+                        seg[degree * 2] -= t.getX() * scale;
+                        seg[degree * 2 + 1] -= t.getY() * scale;
+                    }
+                }
+            }
+
+            if (degree == 1) {
+                result.lineTo(seg[2], seg[3]);
+            } else if (degree == 2) {
+                result.quadTo(seg[2], seg[3], seg[4], seg[5]);
+            } else if (degree == 3) {
+                result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);
+            } else {
+                throw new UnsupportedOperationException("invalid path segment degree: " + degree);
+            }
+        }
+
+        result.setWindingRule(line.getWindingRule());
+        return result;
+    }
+
+    private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) {
+        if (bounds.isEmpty())
+            return p2;
+
+        Line2D line = new Line2D.Double(p1, p2);
+        Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY());
+        Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY());
+        Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX());
+        Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX());
+
+        int i = 0;
+        Point2D[] intersections = { null, null, null, null };
+        if (vi1 != null)
+            intersections[i++] = vi1;
+        if (vi2 != null)
+            intersections[i++] = vi2;
+        if (hi1 != null)
+            intersections[i++] = hi1;
+        if (hi2 != null)
+            intersections[i++] = hi2;
+
+        //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections));
+
+        if (i == 0)
+            return p2;
+        if (i == 1)
+            return intersections[0];
+
+        // Find the intersection i for which applies
+        // lensq(p1, p2) >= lensq(p1, i) &
+        // for all intersections j != i: lensq(p1, i) > lensq(p1, j)
+        double len = lensq(p1, p2);
+        //System.out.println("original line lensq: " + len);
+        Point2D nearestIntersection = null;
+        double nearestLen = -1;
+        for (int j = 0; j < i; ++j) {
+            double l = lensq(p1, intersections[j]);
+            //System.out.println("intersected line lensq: " + l);
+            if (l <= len && l > nearestLen) {
+                nearestIntersection = intersections[j];
+                nearestLen = l;
+                //System.out.println("nearest");
+            }
+        }
+        return nearestIntersection;
+    }
+
+    private static double lensq(Point2D p1, Point2D p2) {
+        double dx = p1.getX();
+        double dy = p1.getY();
+        if (p2 != null) {
+            dx = p2.getX() - dx;
+            dy = p2.getY() - dy;
+        }
+        return dx*dx + dy*dy;
+    }
+
+    private static Point2D intersectWithHorizontalLine(Line2D l, double y) {
+        double dx = l.getX2() - l.getX1();
+        double dy = l.getY2() - l.getY1();
+
+        if (Math.abs(dy) < 1e-5) {
+            // input line as horizontal, no intersection.
+            return null;
+        }
+        double a = dx / dy;
+        return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y);
+    }
+
+    private static Point2D intersectWithVerticalLine(Line2D l, double x) {
+        double dx = l.getX2() - l.getX1();
+        double dy = l.getY2() - l.getY1();
+
+        if (Math.abs(dx) < 1e-5) {
+            // input line as vertical, no intersection.
+            return null;
+        }
+        double a = dy / dx;
+        return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1());
+    }
+
+
+    public final static Path2D NORMAL_ARROW;
+    public final static Path2D FILLED_ARROW;
+
+    static {
+        FILLED_ARROW = new Path2D.Double();
+        FILLED_ARROW.moveTo(-0.5, 1);
+        FILLED_ARROW.lineTo(   0, 0);
+        FILLED_ARROW.lineTo( 0.5, 1);
+        FILLED_ARROW.closePath();
+
+        NORMAL_ARROW = new Path2D.Double();
+        NORMAL_ARROW.moveTo(-0.5, 1);
+        NORMAL_ARROW.lineTo(   0, 0);
+        NORMAL_ARROW.lineTo( 0.5, 1);
+    }
+
+}