X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felementclass%2FFlagClass.java;fp=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felementclass%2FFlagClass.java;h=604fa15a58e04b1714c58e4f15b0d50295e56e79;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hp=15b5194908014303778357ecbef2b96b7c4513ba;hpb=24e2b34260f219f0d1644ca7a138894980e25b14;p=simantics%2Fplatform.git 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 index 15b519490..604fa15a5 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java @@ -1,781 +1,781 @@ -/******************************************************************************* - * 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.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_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 getFirst(); - T getSecond(); - } - - private static class Conn implements Connection { - 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 { - 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 { - 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 getConnection(IElement e) { - return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS); - } - - @Override - public Connection 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); - - 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()); - 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 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)); - } - -} +/******************************************************************************* + * 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.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_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 getFirst(); + T getSecond(); + } + + private static class Conn implements Connection { + 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 { + 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 { + 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 getConnection(IElement e) { + return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS); + } + + @Override + public Connection 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); + + 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()); + 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 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)); + } + +}