--- /dev/null
+/*******************************************************************************\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