]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / elementclass / FlagClass.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java
new file mode 100644 (file)
index 0000000..ab80abf
--- /dev/null
@@ -0,0 +1,784 @@
+/*******************************************************************************\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;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Collection;\r
+\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.DataElementMap;\r
+import org.simantics.g2d.diagram.handler.Topology.Terminal;\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.InternalSize;\r
+import org.simantics.g2d.element.handler.LifeCycle;\r
+import org.simantics.g2d.element.handler.Outline;\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.element.handler.TerminalTopology;\r
+import org.simantics.g2d.element.handler.Text;\r
+import org.simantics.g2d.element.handler.impl.BorderColorImpl;\r
+import org.simantics.g2d.element.handler.impl.DefaultTransform;\r
+import org.simantics.g2d.element.handler.impl.FillColorImpl;\r
+import org.simantics.g2d.element.handler.impl.SimpleElementLayers;\r
+import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;\r
+import org.simantics.g2d.element.handler.impl.TextImpl;\r
+import org.simantics.g2d.image.Image;\r
+import org.simantics.g2d.image.impl.ShapeImage;\r
+import org.simantics.g2d.utils.Alignment;\r
+import org.simantics.g2d.utils.geom.DirectionSet;\r
+import org.simantics.scenegraph.Node;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.nodes.FlagNode;\r
+import org.simantics.scenegraph.g2d.nodes.TextNode;\r
+import org.simantics.ui.colors.Colors;\r
+import org.simantics.ui.fonts.Fonts;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class FlagClass {\r
+\r
+    public static enum Type {\r
+        /// The input part of a pair of flags.\r
+        In,\r
+        /// The output part of a pair of flags.\r
+        Out;\r
+        public Type other() {\r
+            return this == Out ? In: Out;\r
+        }\r
+    }\r
+\r
+    public static class Mode {\r
+        public static final Mode External = new External(1);\r
+        public static final Mode Internal = new Mode() {\r
+            public String toString() { return "Internal"; }\r
+        };\r
+    }\r
+\r
+    public static class External extends Mode {\r
+        public final int count;\r
+        public External(int count) {\r
+            this.count = count;\r
+        }\r
+        @Override\r
+        public String toString() {\r
+            return "External(" + count + ")";\r
+        }\r
+    }\r
+\r
+    private static final double       GLOBAL_SCALE                 = 0.1;\r
+    private static final double       FLAG_SIZE_SCALE              = 3 * GLOBAL_SCALE;\r
+\r
+    public static final double        DEFAULT_WIDTH                = 70 * FLAG_SIZE_SCALE;\r
+    public static final double        DEFAULT_HEIGHT               = 20 * FLAG_SIZE_SCALE;\r
+    public static final double        DEFAULT_BEAK_ANGLE           = 60;\r
+\r
+    public static final Key           KEY_FLAG_TYPE                = new KeyOf(Type.class, "FLAG_TYPE");\r
+    public static final Key           KEY_EXTERNAL                 = new KeyOf(Boolean.class, "FLAG_EXTERNAL");\r
+    public static final Key           KEY_FLAG_MODE                = new KeyOf(Mode.class, "FLAG_MODE");\r
+    public static final Key           KEY_FLAG_WIDTH               = new KeyOf(Double.class, "FLAG_WIDTH");\r
+    public static final Key           KEY_FLAG_HEIGHT              = new KeyOf(Double.class, "FLAG_HEIGHT");\r
+    public static final Key           KEY_FLAG_BEAK_ANGLE          = new KeyOf(Double.class, "FLAG_BEAK_ANGLE");\r
+    public static final Key           KEY_FLAG_TEXT                = new KeyOf(String[].class, "FLAG_TEXT");\r
+    public static final Key           KEY_FLAG_TEXT_AREA           = new KeyOf(Rectangle2D.class, "FLAG_TEXT_AREA_SIZE");\r
+    public static final Key           KEY_SHAPE                    = new KeyOf(Shape.class, "SHAPE");\r
+    public static final Key           KEY_TEXT_HORIZONTAL_ALIGN    = new KeyOf(Alignment.class, "TEXT_HORIZONTAL_ALIGN");\r
+    public static final Key           KEY_TEXT_VERTICAL_ALIGN      = new KeyOf(Alignment.class, "TEXT_VERTICAL_ALIGN");\r
+\r
+    public static final Key          KEY_SG_NODE                  = new SceneGraphNodeKey(Node.class, "FLAG_SG_NODE");\r
+\r
+    /**\r
+     * Indicates that this flag is connected to another flag.\r
+     */\r
+    private static final Key KEY_FLAG_CONNECTION_DATA     = new KeyOf(DataConnection.class, "FLAG_CONNECTION_DATA");\r
+    private static final Key KEY_FLAG_CONNECTION_ELEMENTS = new KeyOf(ElementConnection.class, "FLAG_CONNECTION_ELEMENTS");\r
+\r
+    public interface Connection<T> {\r
+        T getFirst();\r
+        T getSecond();\r
+    }\r
+\r
+    private static class Conn<T> implements Connection<T> {\r
+        private final T first;\r
+        private final T second;\r
+        public Conn(T first, T second) {\r
+            this.first = first;\r
+            this.second = second;\r
+        }\r
+        @Override\r
+        public T getFirst() {\r
+            return first;\r
+        }\r
+        @Override\r
+        public T getSecond() {\r
+            return second;\r
+        }\r
+    }\r
+    private static class ElementConnection extends Conn<IElement> {\r
+        public ElementConnection(IElement first, IElement second) {\r
+            super(first, second);\r
+            if (first == null)\r
+                throw new IllegalArgumentException("first is null");\r
+            if (second == null)\r
+                throw new IllegalArgumentException("second is null");\r
+        }\r
+    }\r
+    private static class DataConnection extends Conn<Object> {\r
+        public DataConnection(Object first, Object second) {\r
+            super(first, second);\r
+            if (first == null)\r
+                throw new IllegalArgumentException("first is null");\r
+            // Second may be null to indicate "not-connected"\r
+        }\r
+        public boolean isConnected() {\r
+            return getSecond() != null;\r
+        }\r
+    }\r
+\r
+    public static final FlagHandler  FLAG_HANDLER = new FlagHandler() {\r
+\r
+        private static final long serialVersionUID = -4258875745321808416L;\r
+\r
+        @Override\r
+        public Type getType(IElement e) {\r
+            return FlagClass.getType(e);\r
+        }\r
+\r
+        @Override\r
+        public void setType(IElement e, Type type) {\r
+            e.setHint(KEY_FLAG_TYPE, type);\r
+        }\r
+\r
+        @Override\r
+        public boolean isExternal(IElement e) {\r
+            return Boolean.TRUE.equals(e.getHint(KEY_EXTERNAL));\r
+        }\r
+\r
+        @Override\r
+        public void setExternal(IElement e, boolean external) {\r
+            e.setHint(KEY_EXTERNAL, Boolean.valueOf(external));\r
+        }\r
+\r
+        @Override\r
+        public Connection<IElement> getConnection(IElement e) {\r
+            return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
+        }\r
+\r
+        @Override\r
+        public Connection<Object> getConnectionData(IElement e) {\r
+            DataConnection dc = e.getHint(KEY_FLAG_CONNECTION_DATA);\r
+            return (dc != null && dc.isConnected()) ? dc : null;\r
+        }\r
+\r
+        @Override\r
+        public void connect(IElement e1, IElement e2) {\r
+            assert e1 != null && e2 != null;\r
+\r
+            ElementConnection ce = new ElementConnection(e1, e2);\r
+            e1.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);\r
+            e2.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);\r
+        }\r
+\r
+        @Override\r
+        public void connectData(IElement e1, Object o1, Object o2) {\r
+            e1.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
+            e1.setHint(KEY_FLAG_CONNECTION_DATA, new DataConnection(o1, o2));\r
+        }\r
+\r
+        @Override\r
+        public void disconnect(IElement local) {\r
+            assert local != null;\r
+            local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
+            DataConnection c = (DataConnection) local.removeHint(KEY_FLAG_CONNECTION_DATA);\r
+            if (c != null) {\r
+                IElement remote = otherElement(local, c);\r
+                if (remote != null) {\r
+                    local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
+                    remote.removeHint(KEY_FLAG_CONNECTION_DATA);\r
+                }\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public boolean isWithinDiagram(IDiagram d, Connection<?> c) {\r
+            assert d != null;\r
+            assert c != null;\r
+            if (c instanceof DataConnection)\r
+                return bothOnDiagram(d, (DataConnection) c);\r
+            if (c instanceof ElementConnection)\r
+                return bothOnDiagram(d, (ElementConnection) c);\r
+            return false;\r
+        }\r
+\r
+        @Override\r
+        public IElement getCorrespondence(IElement end) {\r
+            assert end != null;\r
+            DataConnection dc = (DataConnection) end.getHint(KEY_FLAG_CONNECTION_DATA);\r
+            if (dc != null && dc.isConnected())\r
+                return otherElement(end, dc);\r
+            ElementConnection ec = (ElementConnection) end.getHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
+            if (ec != null)\r
+                return otherElement(end, ec);\r
+            return null;\r
+        }\r
+\r
+        boolean bothOnDiagram(IDiagram d, DataConnection c) {\r
+            if (!c.isConnected())\r
+                return false;\r
+\r
+            DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
+            IElement eout = dem.getElement(d, c.getFirst());\r
+            IElement ein = dem.getElement(d, c.getSecond());\r
+            return eout != null && ein != null;\r
+        }\r
+\r
+        boolean bothOnDiagram(IDiagram d, ElementConnection c) {\r
+            DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
+            Object o1 = dem.getData(d, c.getFirst());\r
+            Object o2 = dem.getData(d, c.getSecond());\r
+            return o1 != null && o2 != null;\r
+        }\r
+\r
+        public IElement otherElement(IElement e, DataConnection c) {\r
+            if (!c.isConnected())\r
+                return null;\r
+\r
+            IDiagram d = ElementUtils.peekDiagram(e);\r
+            if (d == null)\r
+                return null;\r
+\r
+            DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
+            Object o = dem.getData(d, e);\r
+            if (c.getFirst().equals(o))\r
+                return dem.getElement(d, c.getSecond());\r
+            if (c.getSecond().equals(o))\r
+                return dem.getElement(d, c.getFirst());\r
+            throw new IllegalArgumentException("specified object '" + o + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");\r
+        }\r
+\r
+        public IElement otherElement(IElement e, ElementConnection c) {\r
+            IElement a = c.getFirst();\r
+            IElement b = c.getSecond();\r
+            if (e == a)\r
+                return b;\r
+            if (e == b)\r
+                return a;\r
+            throw new IllegalArgumentException("specified element '" + e + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");\r
+        }\r
+    };\r
+\r
+    static final Shape staticShape;\r
+\r
+    static {\r
+        Path2D path = new Path2D.Double();\r
+        staticShape = path;\r
+        createFlagShape(path, Type.In, Mode.External, DEFAULT_WIDTH, DEFAULT_HEIGHT, getBeakLength(DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE));\r
+    }\r
+\r
+    public static final BasicStroke         STROKE                = new BasicStroke(0.15f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
+    static final Image               DEFAULT_IMAGE         = new ShapeImage(staticShape, null, STROKE);\r
+    static final StaticSymbolImpl    DEFAULT_STATIC_SYMBOL = new StaticSymbolImpl(DEFAULT_IMAGE);\r
+    static final FlagSize            DEFAULT_FLAG_SIZE     = new FlagSize(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE);\r
+    static final Initializer         DEFAULT_INITIALIZER   = new Initializer(Type.In, Mode.External);\r
+\r
+    public static final ElementClass FLAGCLASS =\r
+        ElementClass.compile(\r
+                DEFAULT_INITIALIZER,\r
+                FLAG_HANDLER,\r
+                DefaultTransform.INSTANCE,\r
+                DEFAULT_FLAG_SIZE,\r
+                BorderColorImpl.BLACK,\r
+                FillColorImpl.WHITE,\r
+                TextImpl.INSTANCE,\r
+                FlagTerminalTopology.DEFAULT,\r
+                FlagSceneGraph.INSTANCE,\r
+                DEFAULT_STATIC_SYMBOL\r
+        ).setId(FlagClass.class.getSimpleName());\r
+\r
+    public static ElementClass create(Terminal terminal) {\r
+        return ElementClass.compile(\r
+                DEFAULT_INITIALIZER,\r
+                FLAG_HANDLER,\r
+                DefaultTransform.INSTANCE,\r
+                DEFAULT_FLAG_SIZE,\r
+                BorderColorImpl.BLACK,\r
+                FillColorImpl.WHITE,\r
+                TextImpl.INSTANCE,\r
+                new FlagTerminalTopology(terminal),\r
+                FlagSceneGraph.INSTANCE,\r
+                DEFAULT_STATIC_SYMBOL,\r
+                SimpleElementLayers.INSTANCE\r
+        ).setId(FlagClass.class.getSimpleName());\r
+    }\r
+\r
+    public static ElementClass create(Terminal terminal, SceneGraph scn) {\r
+        return ElementClass.compile(\r
+                DEFAULT_INITIALIZER,\r
+                FLAG_HANDLER,\r
+                DefaultTransform.INSTANCE,\r
+                DEFAULT_FLAG_SIZE,\r
+                BorderColorImpl.BLACK,\r
+                FillColorImpl.WHITE,\r
+                TextImpl.INSTANCE,\r
+                new FlagTerminalTopology(terminal),\r
+                scn,\r
+                DEFAULT_STATIC_SYMBOL,\r
+                SimpleElementLayers.INSTANCE\r
+        ).setId(FlagClass.class.getSimpleName());\r
+    }\r
+\r
+    static class Initializer implements LifeCycle {\r
+        private static final long serialVersionUID = 4404942036933073584L;\r
+\r
+        private final Type type;\r
+        private final Mode mode;\r
+\r
+        Initializer(Type type, Mode mode) {\r
+            assert type != null;\r
+            assert mode != null;\r
+            this.type = type;\r
+            this.mode = mode;\r
+        }\r
+\r
+        @Override\r
+        public void onElementActivated(IDiagram d, IElement e) {\r
+        }\r
+\r
+        @Override\r
+        public void onElementCreated(IElement e) {\r
+            e.setHint(KEY_FLAG_TYPE, type);\r
+            e.setHint(KEY_FLAG_MODE, mode);\r
+            //e.setHint(ElementHints.KEY_COMPOSITE, AlphaComposite.SrcOver.derive(0.5f));\r
+        }\r
+\r
+        @Override\r
+        public void onElementDeactivated(IDiagram d, IElement e) {\r
+        }\r
+\r
+        @Override\r
+        public void onElementDestroyed(IElement e) {\r
+        }\r
+\r
+        @Override\r
+        public int hashCode() {\r
+            final int prime = 31;\r
+            int result = 1;\r
+            result = prime * result + mode.hashCode();\r
+            result = prime * result + type.hashCode();\r
+            return result;\r
+        }\r
+\r
+        @Override\r
+        public boolean equals(Object obj) {\r
+            if (this == obj)\r
+                return true;\r
+            if (obj == null)\r
+                return false;\r
+            if (getClass() != obj.getClass())\r
+                return false;\r
+            Initializer other = (Initializer) obj;\r
+            if (!mode.equals(other.mode))\r
+                return false;\r
+            if (!type.equals(other.type))\r
+                return false;\r
+            return true;\r
+        }\r
+    };\r
+\r
+    public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {\r
+        double hh = height / 2;        \r
+        path.reset();\r
+        switch (type) {\r
+            case Out:\r
+                if (mode instanceof External) {\r
+                    path.moveTo(0, hh);\r
+                    path.lineTo(width, hh);\r
+                    path.lineTo(width+beakLength, 0);\r
+                    path.lineTo(width, -hh);\r
+                    path.lineTo(0, -hh);\r
+                    path.closePath();\r
+                    path.moveTo(width, hh);\r
+                    path.lineTo(width, -hh);    \r
+                    int count = ((External)mode).count;\r
+                    if(count > 1) {\r
+                        double shadow=hh*0.25;\r
+                        double ix = beakLength \r
+                                - 0.5*shadow*(1.0 + beakLength/hh);\r
+                        double iy = hh * (ix / beakLength - 1.0);\r
+                        for(int sid=1;sid<=Math.min(count-1, 4);++sid) {\r
+                            double dis = sid*shadow;\r
+                            path.moveTo(dis, hh+dis-shadow);\r
+                            path.lineTo(dis, hh+dis);\r
+                            path.lineTo(dis+width, hh+dis);\r
+                            path.lineTo(dis+width+beakLength, dis);\r
+                            path.lineTo(width + ix + dis, iy + dis);\r
+                        }\r
+                    } else {\r
+                        double left = 0;\r
+                        double right = width - 0;\r
+                        if (left < right) {\r
+                            path.moveTo(left, 0);\r
+                            path.lineTo(right, 0);\r
+                        }\r
+                    }\r
+                } else if (mode == Mode.Internal) {\r
+                    path.moveTo(0, hh);\r
+                    path.lineTo(beakLength, 0);\r
+                    path.lineTo(0, -hh);\r
+                    path.closePath();\r
+                }\r
+                break;\r
+            case In:\r
+                path.moveTo(0, 0);\r
+                if (mode instanceof External) {\r
+                    path.lineTo(-beakLength, -hh);\r
+                    path.lineTo(-width-beakLength, -hh);\r
+                    path.lineTo(-width-beakLength, hh);\r
+                    path.lineTo(-beakLength, hh);\r
+                    path.closePath();\r
+                    path.moveTo(-beakLength, -hh);\r
+                    path.lineTo(-beakLength, hh);  \r
+                    int count = ((External)mode).count;\r
+                    if(count > 1) {\r
+                        double shadow=hh*0.25;\r
+                        double ix = beakLength \r
+                                - 0.5*shadow*(1.0 + beakLength/hh);\r
+                        double iy = hh * (ix / beakLength - 1.0);\r
+                        double xDisp = -width-beakLength;\r
+                        for(int sid=1;sid<=Math.min(count-1, 4);++sid) {\r
+                            double dis = sid*shadow;\r
+                            path.moveTo(xDisp+dis, hh+dis-shadow);\r
+                            path.lineTo(xDisp+dis, hh+dis);\r
+                            path.lineTo(xDisp+dis+width, hh+dis);\r
+                            path.lineTo(xDisp+dis+width+beakLength, dis);                            \r
+                            path.lineTo(xDisp+width + ix + dis, iy + dis);\r
+                        }\r
+                    } else {\r
+                        double left = -width-beakLength+0;\r
+                        double right = -beakLength-0;       \r
+                        if (left < right) {\r
+                            path.moveTo(left, 0);\r
+                            path.lineTo(right, 0);\r
+                        }\r
+                    }\r
+                } else if (mode == Mode.Internal) {\r
+                    path.lineTo(-beakLength, -hh);\r
+                    path.lineTo(-beakLength, hh);\r
+                    path.closePath();\r
+                }\r
+                break;\r
+        }\r
+        return path;\r
+    }\r
+\r
+    public static Path2D createFlagShape(IElement e) {\r
+        Type type = getType(e);\r
+        Mode mode = e.getHint(KEY_FLAG_MODE);\r
+        double width = e.getHint(KEY_FLAG_WIDTH);\r
+        double height = e.getHint(KEY_FLAG_HEIGHT);\r
+        double beakLength = getBeakLength(e);\r
+        Path2D path = new Path2D.Double();\r
+        createFlagShape(path, type, mode, width, height, beakLength);\r
+        return path;\r
+    }\r
+\r
+    static class FlagSize implements InternalSize, Outline, LifeCycle {\r
+\r
+        private static final long serialVersionUID = 829379327756475944L;\r
+\r
+        private final double length;\r
+        private final double thickness;\r
+        private final double beakAngle;\r
+\r
+        public FlagSize(double length, double thickness, double beakAngle) {\r
+            this.length = length;\r
+            this.thickness = thickness;\r
+            this.beakAngle = beakAngle;\r
+        }\r
+\r
+        @Override\r
+        public Shape getElementShape(IElement e) {\r
+            Shape shape = e.getHint(KEY_SHAPE);\r
+            if (shape != null)\r
+                return shape;\r
+            return createFlagShape(e);\r
+        }\r
+\r
+        @Override\r
+        public Rectangle2D getBounds(IElement e, Rectangle2D size) {\r
+            if (size == null)\r
+                size = new Rectangle2D.Double();\r
+            Shape shape = getElementShape(e);\r
+            size.setFrame(shape.getBounds2D());\r
+            return size;\r
+        }\r
+\r
+        @Override\r
+        public void onElementActivated(IDiagram d, IElement e) {\r
+        }\r
+\r
+        @Override\r
+        public void onElementCreated(IElement e) {\r
+            e.setHint(KEY_FLAG_WIDTH, length);\r
+            e.setHint(KEY_FLAG_HEIGHT, thickness);\r
+            e.setHint(KEY_FLAG_BEAK_ANGLE, beakAngle);\r
+        }\r
+\r
+        @Override\r
+        public void onElementDeactivated(IDiagram d, IElement e) {\r
+        }\r
+\r
+        @Override\r
+        public void onElementDestroyed(IElement e) {\r
+        }\r
+\r
+        @Override\r
+        public int hashCode() {\r
+            final int prime = 31;\r
+            int result = 1;\r
+            long temp;\r
+            temp = Double.doubleToLongBits(beakAngle);\r
+            result = prime * result + (int) (temp ^ (temp >>> 32));\r
+            temp = Double.doubleToLongBits(length);\r
+            result = prime * result + (int) (temp ^ (temp >>> 32));\r
+            temp = Double.doubleToLongBits(thickness);\r
+            result = prime * result + (int) (temp ^ (temp >>> 32));\r
+            return result;\r
+        }\r
+\r
+        @Override\r
+        public boolean equals(Object obj) {\r
+            if (this == obj)\r
+                return true;\r
+            if (obj == null)\r
+                return false;\r
+            if (getClass() != obj.getClass())\r
+                return false;\r
+            FlagSize other = (FlagSize) obj;\r
+            if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))\r
+                return false;\r
+            if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))\r
+                return false;\r
+            if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))\r
+                return false;\r
+            return true;\r
+        }\r
+    }\r
+\r
+    static class FlagSceneGraph implements SceneGraph {\r
+        private static final long serialVersionUID = 35208146123929197L;\r
+\r
+        public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();\r
+\r
+        @Override\r
+        public void cleanup(IElement e) {\r
+            ElementUtils.removePossibleNode(e, KEY_SG_NODE);\r
+        }\r
+\r
+        @Override\r
+        public void init(IElement e, G2DParentNode parent) {\r
+            Color fc = ElementUtils.getFillColor(e, Color.WHITE);\r
+            Color bc = ElementUtils.getBorderColor(e, Color.BLACK);\r
+            Color tc = ElementUtils.getTextColor(e, Color.BLACK);\r
+\r
+            Outline outline = e.getElementClass().getSingleItem(Outline.class);\r
+            Shape shape = outline.getElementShape(e);\r
+            Type type = getType(e);\r
+            double dir = getDirection(e);\r
+            double width = e.getHint(KEY_FLAG_WIDTH);\r
+            double height = e.getHint(KEY_FLAG_HEIGHT);\r
+            double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);\r
+\r
+            String[] flagText = e.getHint(KEY_FLAG_TEXT);\r
+            if (flagText == null) {\r
+                // fallback option.\r
+                Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);\r
+                if (t != null) {\r
+                    String text = t.getText(e);\r
+                    if (text != null)\r
+                        flagText = new String[] { text };\r
+                }\r
+            }\r
+\r
+            // DEBUG TEXT\r
+            //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};\r
+\r
+            Rectangle2D textArea = e.getHint(KEY_FLAG_TEXT_AREA);\r
+            if (textArea == null) {\r
+                double beakLength = getBeakLength(height, beakAngle);\r
+                textArea = type == Type.In\r
+                ? new Rectangle2D.Double(-width-beakLength, -height*0.5, width, height)\r
+                : new Rectangle2D.Double(0, -height*0.5, width, height);\r
+            }\r
+\r
+            Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);\r
+            Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);\r
+\r
+            FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);\r
+            flag.init(shape,\r
+                    flagText,\r
+                    STROKE,\r
+                    bc,\r
+                    fc,\r
+                    tc,\r
+                    (float) width,\r
+                    (float) height,\r
+                    (float) dir,\r
+                    (float) beakAngle,\r
+                    textArea,\r
+                    horizAlign.ordinal(),\r
+                    vertAlign.ordinal());\r
+            AffineTransform at = ElementUtils.getTransform(e);\r
+            if(at != null) flag.setTransform(at);\r
+\r
+        }\r
+    }\r
+\r
+    static class TerminalPoint implements Terminal {\r
+    }\r
+\r
+    public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {\r
+        private static final long    serialVersionUID = -4194634598346105458L;\r
+\r
+        public static final Terminal             DEFAULT_T0       = new TerminalPoint();\r
+        public static final FlagTerminalTopology DEFAULT          = new FlagTerminalTopology(DEFAULT_T0);\r
+\r
+        final Terminal T0;\r
+\r
+        public FlagTerminalTopology(Terminal t) {\r
+            this.T0 = t;\r
+        }\r
+\r
+        @Override\r
+        public void getTerminals(IElement e, Collection<Terminal> result) {\r
+            result.add(T0);\r
+        }\r
+\r
+        @Override\r
+        public AffineTransform getTerminalPosition(IElement node, Terminal t) {\r
+            if (t == T0) {\r
+                return new AffineTransform();\r
+            }\r
+            return null;\r
+        }\r
+\r
+        @Override\r
+        public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {\r
+            Type type = getType(node);\r
+            double d = getDirection(node);\r
+            if (t == T0) {\r
+                switch (type) {\r
+                    case In: directions.add(d); break;\r
+                    case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;\r
+                }\r
+                //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));\r
+                return true;\r
+            }\r
+            return false;\r
+        }\r
+\r
+//        static final Path2D terminalShape;\r
+//\r
+//        static {\r
+//            double s = .5;\r
+//            Path2D p = new Path2D.Double();\r
+//            p.moveTo(s, s);\r
+//            p.lineTo(s, -s);\r
+//            p.lineTo(-s, -s);\r
+//            p.lineTo(-s, s);\r
+//            p.closePath();\r
+//            terminalShape = p;\r
+//        }\r
+\r
+        @Override\r
+        public Shape getTerminalShape(IElement node, Terminal t) {\r
+            //return terminalShape;\r
+            //return null;\r
+            // For each terminal, return the whole shape of the element.\r
+            return ElementUtils.getElementShapeOrBounds(node);\r
+        }\r
+\r
+        @Override\r
+        public int hashCode() {\r
+            final int prime = 31;\r
+            int result = 1;\r
+            result = prime * result + ((T0 == null) ? 0 : T0.hashCode());\r
+            return result;\r
+        }\r
+\r
+        @Override\r
+        public boolean equals(Object obj) {\r
+            if (this == obj)\r
+                return true;\r
+            if (obj == null)\r
+                return false;\r
+            if (getClass() != obj.getClass())\r
+                return false;\r
+            FlagTerminalTopology other = (FlagTerminalTopology) obj;\r
+            if (T0 == null) {\r
+                if (other.T0 != null)\r
+                    return false;\r
+            } else if (!T0.equals(other.T0))\r
+                return false;\r
+            return true;\r
+        }\r
+    }\r
+\r
+    public static AffineTransform getTransform(IElement e) {\r
+        AffineTransform at = ElementUtils.getTransform(e);\r
+        if (at == null)\r
+            return new AffineTransform();\r
+        return at;\r
+    }\r
+\r
+    public static double getDirection(IElement e) {\r
+        Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);\r
+        if (rotate != null) {\r
+            return rotate.getAngle(e);\r
+        }\r
+        return 0.0;\r
+    }\r
+\r
+    public static Type getType(IElement e) {\r
+        Type t = e.getHint(KEY_FLAG_TYPE);\r
+        return t != null ? t : Type.In;\r
+    }\r
+\r
+    public static Mode getMode(IElement e) {\r
+        Mode m = e.getHint(KEY_FLAG_MODE);\r
+        return m != null ? m : Mode.External;\r
+    }\r
+\r
+    public static double getBeakLength(IElement e) {\r
+        double height = e.getHint(KEY_FLAG_HEIGHT);\r
+        double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);\r
+        beakAngle = Math.min(180, Math.max(10, beakAngle));\r
+        return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));\r
+    }\r
+\r
+    public static double getBeakLength(double height, double beakAngle) {\r
+        beakAngle = Math.min(180, Math.max(10, beakAngle));\r
+        return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));\r
+    }\r
+\r
+}\r