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