1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.elementclass;
14 import java.awt.BasicStroke;
15 import java.awt.Color;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Path2D;
20 import java.awt.geom.Rectangle2D;
21 import java.util.Collection;
23 import org.simantics.g2d.diagram.IDiagram;
24 import org.simantics.g2d.diagram.handler.DataElementMap;
25 import org.simantics.g2d.diagram.handler.Topology.Terminal;
26 import org.simantics.g2d.element.ElementClass;
27 import org.simantics.g2d.element.ElementUtils;
28 import org.simantics.g2d.element.IElement;
29 import org.simantics.g2d.element.SceneGraphNodeKey;
30 import org.simantics.g2d.element.handler.InternalSize;
31 import org.simantics.g2d.element.handler.LifeCycle;
32 import org.simantics.g2d.element.handler.Outline;
33 import org.simantics.g2d.element.handler.Rotate;
34 import org.simantics.g2d.element.handler.SceneGraph;
35 import org.simantics.g2d.element.handler.TerminalLayout;
36 import org.simantics.g2d.element.handler.TerminalTopology;
37 import org.simantics.g2d.element.handler.Text;
38 import org.simantics.g2d.element.handler.impl.BorderColorImpl;
39 import org.simantics.g2d.element.handler.impl.DefaultTransform;
40 import org.simantics.g2d.element.handler.impl.FillColorImpl;
41 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
42 import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;
43 import org.simantics.g2d.element.handler.impl.TextImpl;
44 import org.simantics.g2d.image.Image;
45 import org.simantics.g2d.image.impl.ShapeImage;
46 import org.simantics.g2d.utils.Alignment;
47 import org.simantics.g2d.utils.geom.DirectionSet;
48 import org.simantics.scenegraph.Node;
49 import org.simantics.scenegraph.g2d.G2DParentNode;
50 import org.simantics.scenegraph.g2d.nodes.FlagNode;
51 import org.simantics.utils.datastructures.hints.IHintContext.Key;
52 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
55 * @author Tuukka Lehtonen
57 public class FlagClass {
59 public static enum Type {
60 /// The input part of a pair of flags.
62 /// The output part of a pair of flags.
65 return this == Out ? In: Out;
69 public static class Mode {
70 public static final Mode External = new External(1);
71 public static final Mode Internal = new Mode() {
72 public String toString() { return "Internal"; }
76 public static class External extends Mode {
77 public final int count;
78 public External(int count) {
82 public String toString() {
83 return "External(" + count + ")";
87 private static final double GLOBAL_SCALE = 0.1;
88 private static final double FLAG_SIZE_SCALE = 3 * GLOBAL_SCALE;
90 public static final double DEFAULT_WIDTH = 70 * FLAG_SIZE_SCALE;
91 public static final double DEFAULT_HEIGHT = 20 * FLAG_SIZE_SCALE;
92 public static final double DEFAULT_BEAK_ANGLE = 60;
94 public static final Key KEY_FLAG_TYPE = new KeyOf(Type.class, "FLAG_TYPE");
95 public static final Key KEY_EXTERNAL = new KeyOf(Boolean.class, "FLAG_EXTERNAL");
96 public static final Key KEY_FLAG_MODE = new KeyOf(Mode.class, "FLAG_MODE");
97 public static final Key KEY_FLAG_WIDTH = new KeyOf(Double.class, "FLAG_WIDTH");
98 public static final Key KEY_FLAG_HEIGHT = new KeyOf(Double.class, "FLAG_HEIGHT");
99 public static final Key KEY_FLAG_BEAK_ANGLE = new KeyOf(Double.class, "FLAG_BEAK_ANGLE");
100 public static final Key KEY_FLAG_TEXT = new KeyOf(String[].class, "FLAG_TEXT");
101 public static final Key KEY_FLAG_TEXT_AREA = new KeyOf(Rectangle2D.class, "FLAG_TEXT_AREA_SIZE");
102 public static final Key KEY_SHAPE = new KeyOf(Shape.class, "SHAPE");
103 public static final Key KEY_TEXT_HORIZONTAL_ALIGN = new KeyOf(Alignment.class, "TEXT_HORIZONTAL_ALIGN");
104 public static final Key KEY_TEXT_VERTICAL_ALIGN = new KeyOf(Alignment.class, "TEXT_VERTICAL_ALIGN");
105 public static final Key KEY_FLAG_FONT = new KeyOf(Font.class, "FLAG_FONT");
107 public static final Key KEY_SG_NODE = new SceneGraphNodeKey(Node.class, "FLAG_SG_NODE");
110 * Indicates that this flag is connected to another flag.
112 private static final Key KEY_FLAG_CONNECTION_DATA = new KeyOf(DataConnection.class, "FLAG_CONNECTION_DATA");
113 private static final Key KEY_FLAG_CONNECTION_ELEMENTS = new KeyOf(ElementConnection.class, "FLAG_CONNECTION_ELEMENTS");
115 public interface Connection<T> {
120 private static class Conn<T> implements Connection<T> {
121 private final T first;
122 private final T second;
123 public Conn(T first, T second) {
125 this.second = second;
128 public T getFirst() {
132 public T getSecond() {
136 private static class ElementConnection extends Conn<IElement> {
137 public ElementConnection(IElement first, IElement second) {
138 super(first, second);
140 throw new IllegalArgumentException("first is null");
142 throw new IllegalArgumentException("second is null");
145 private static class DataConnection extends Conn<Object> {
146 public DataConnection(Object first, Object second) {
147 super(first, second);
149 throw new IllegalArgumentException("first is null");
150 // Second may be null to indicate "not-connected"
152 public boolean isConnected() {
153 return getSecond() != null;
157 public static final FlagHandler FLAG_HANDLER = new FlagHandler() {
159 private static final long serialVersionUID = -4258875745321808416L;
162 public Type getType(IElement e) {
163 return FlagClass.getType(e);
167 public void setType(IElement e, Type type) {
168 e.setHint(KEY_FLAG_TYPE, type);
172 public boolean isExternal(IElement e) {
173 return Boolean.TRUE.equals(e.getHint(KEY_EXTERNAL));
177 public void setExternal(IElement e, boolean external) {
178 e.setHint(KEY_EXTERNAL, Boolean.valueOf(external));
182 public Connection<IElement> getConnection(IElement e) {
183 return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS);
187 public Connection<Object> getConnectionData(IElement e) {
188 DataConnection dc = e.getHint(KEY_FLAG_CONNECTION_DATA);
189 return (dc != null && dc.isConnected()) ? dc : null;
193 public void connect(IElement e1, IElement e2) {
194 assert e1 != null && e2 != null;
196 ElementConnection ce = new ElementConnection(e1, e2);
197 e1.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);
198 e2.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);
202 public void connectData(IElement e1, Object o1, Object o2) {
203 e1.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
204 e1.setHint(KEY_FLAG_CONNECTION_DATA, new DataConnection(o1, o2));
208 public void disconnect(IElement local) {
209 assert local != null;
210 local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
211 DataConnection c = (DataConnection) local.removeHint(KEY_FLAG_CONNECTION_DATA);
213 IElement remote = otherElement(local, c);
214 if (remote != null) {
215 local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
216 remote.removeHint(KEY_FLAG_CONNECTION_DATA);
222 public boolean isWithinDiagram(IDiagram d, Connection<?> c) {
225 if (c instanceof DataConnection)
226 return bothOnDiagram(d, (DataConnection) c);
227 if (c instanceof ElementConnection)
228 return bothOnDiagram(d, (ElementConnection) c);
233 public IElement getCorrespondence(IElement end) {
235 DataConnection dc = (DataConnection) end.getHint(KEY_FLAG_CONNECTION_DATA);
236 if (dc != null && dc.isConnected())
237 return otherElement(end, dc);
238 ElementConnection ec = (ElementConnection) end.getHint(KEY_FLAG_CONNECTION_ELEMENTS);
240 return otherElement(end, ec);
244 boolean bothOnDiagram(IDiagram d, DataConnection c) {
245 if (!c.isConnected())
248 DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);
249 IElement eout = dem.getElement(d, c.getFirst());
250 IElement ein = dem.getElement(d, c.getSecond());
251 return eout != null && ein != null;
254 boolean bothOnDiagram(IDiagram d, ElementConnection c) {
255 DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);
256 Object o1 = dem.getData(d, c.getFirst());
257 Object o2 = dem.getData(d, c.getSecond());
258 return o1 != null && o2 != null;
261 public IElement otherElement(IElement e, DataConnection c) {
262 if (!c.isConnected())
265 IDiagram d = ElementUtils.peekDiagram(e);
269 DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);
270 Object o = dem.getData(d, e);
271 if (c.getFirst().equals(o))
272 return dem.getElement(d, c.getSecond());
273 if (c.getSecond().equals(o))
274 return dem.getElement(d, c.getFirst());
275 throw new IllegalArgumentException("specified object '" + o + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");
278 public IElement otherElement(IElement e, ElementConnection c) {
279 IElement a = c.getFirst();
280 IElement b = c.getSecond();
285 throw new IllegalArgumentException("specified element '" + e + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");
289 static final Shape staticShape;
292 Path2D path = new Path2D.Double();
294 createFlagShape(path, Type.In, Mode.External, DEFAULT_WIDTH, DEFAULT_HEIGHT, getBeakLength(DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE));
297 public static final BasicStroke STROKE = new BasicStroke(0.15f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
298 static final Image DEFAULT_IMAGE = new ShapeImage(staticShape, null, STROKE);
299 static final StaticSymbolImpl DEFAULT_STATIC_SYMBOL = new StaticSymbolImpl(DEFAULT_IMAGE);
300 static final FlagSize DEFAULT_FLAG_SIZE = new FlagSize(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE);
301 static final Initializer DEFAULT_INITIALIZER = new Initializer(Type.In, Mode.External);
303 public static final ElementClass FLAGCLASS =
304 ElementClass.compile(
307 DefaultTransform.INSTANCE,
309 BorderColorImpl.BLACK,
312 FlagTerminalTopology.DEFAULT,
313 FlagSceneGraph.INSTANCE,
314 DEFAULT_STATIC_SYMBOL,
315 SimpleElementLayers.INSTANCE
316 ).setId(FlagClass.class.getSimpleName());
318 public static ElementClass create(Terminal terminal) {
319 return ElementClass.compile(
322 DefaultTransform.INSTANCE,
324 BorderColorImpl.BLACK,
327 new FlagTerminalTopology(terminal),
328 FlagSceneGraph.INSTANCE,
329 DEFAULT_STATIC_SYMBOL,
330 SimpleElementLayers.INSTANCE
331 ).setId(FlagClass.class.getSimpleName());
334 public static ElementClass create(Terminal terminal, SceneGraph scn) {
335 return ElementClass.compile(
338 DefaultTransform.INSTANCE,
340 BorderColorImpl.BLACK,
343 new FlagTerminalTopology(terminal),
345 DEFAULT_STATIC_SYMBOL,
346 SimpleElementLayers.INSTANCE
347 ).setId(FlagClass.class.getSimpleName());
350 static class Initializer implements LifeCycle {
351 private static final long serialVersionUID = 4404942036933073584L;
353 private final Type type;
354 private final Mode mode;
356 Initializer(Type type, Mode mode) {
364 public void onElementActivated(IDiagram d, IElement e) {
368 public void onElementCreated(IElement e) {
369 e.setHint(KEY_FLAG_TYPE, type);
370 e.setHint(KEY_FLAG_MODE, mode);
371 //e.setHint(ElementHints.KEY_COMPOSITE, AlphaComposite.SrcOver.derive(0.5f));
375 public void onElementDeactivated(IDiagram d, IElement e) {
379 public void onElementDestroyed(IElement e) {
383 public int hashCode() {
384 final int prime = 31;
386 result = prime * result + mode.hashCode();
387 result = prime * result + type.hashCode();
392 public boolean equals(Object obj) {
397 if (getClass() != obj.getClass())
399 Initializer other = (Initializer) obj;
400 if (!mode.equals(other.mode))
402 if (!type.equals(other.type))
408 public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {
409 double hh = height / 2;
413 if (mode instanceof External) {
415 path.lineTo(width, hh);
416 path.lineTo(width+beakLength, 0);
417 path.lineTo(width, -hh);
420 path.moveTo(width, hh);
421 path.lineTo(width, -hh);
422 int count = ((External)mode).count;
424 double shadow=hh*0.25;
425 double ix = beakLength
426 - 0.5*shadow*(1.0 + beakLength/hh);
427 double iy = hh * (ix / beakLength - 1.0);
428 for(int sid=1;sid<=Math.min(count-1, 4);++sid) {
429 double dis = sid*shadow;
430 path.moveTo(dis, hh+dis-shadow);
431 path.lineTo(dis, hh+dis);
432 path.lineTo(dis+width, hh+dis);
433 path.lineTo(dis+width+beakLength, dis);
434 path.lineTo(width + ix + dis, iy + dis);
438 double right = width - 0;
440 path.moveTo(left, 0);
441 path.lineTo(right, 0);
444 } else if (mode == Mode.Internal) {
446 path.lineTo(beakLength, 0);
453 if (mode instanceof External) {
454 path.lineTo(-beakLength, -hh);
455 path.lineTo(-width-beakLength, -hh);
456 path.lineTo(-width-beakLength, hh);
457 path.lineTo(-beakLength, hh);
459 path.moveTo(-beakLength, -hh);
460 path.lineTo(-beakLength, hh);
461 int count = ((External)mode).count;
463 double shadow=hh*0.25;
464 double ix = beakLength
465 - 0.5*shadow*(1.0 + beakLength/hh);
466 double iy = hh * (ix / beakLength - 1.0);
467 double xDisp = -width-beakLength;
468 for(int sid=1;sid<=Math.min(count-1, 4);++sid) {
469 double dis = sid*shadow;
470 path.moveTo(xDisp+dis, hh+dis-shadow);
471 path.lineTo(xDisp+dis, hh+dis);
472 path.lineTo(xDisp+dis+width, hh+dis);
473 path.lineTo(xDisp+dis+width+beakLength, dis);
474 path.lineTo(xDisp+width + ix + dis, iy + dis);
477 double left = -width-beakLength+0;
478 double right = -beakLength-0;
480 path.moveTo(left, 0);
481 path.lineTo(right, 0);
484 } else if (mode == Mode.Internal) {
485 path.lineTo(-beakLength, -hh);
486 path.lineTo(-beakLength, hh);
494 public static Path2D createFlagShape(IElement e) {
495 Type type = getType(e);
496 Mode mode = e.getHint(KEY_FLAG_MODE);
497 double width = e.getHint(KEY_FLAG_WIDTH);
498 double height = e.getHint(KEY_FLAG_HEIGHT);
499 double beakLength = getBeakLength(e);
500 Path2D path = new Path2D.Double();
501 createFlagShape(path, type, mode, width, height, beakLength);
505 static class FlagSize implements InternalSize, Outline, LifeCycle {
507 private static final long serialVersionUID = 829379327756475944L;
509 private final double length;
510 private final double thickness;
511 private final double beakAngle;
513 public FlagSize(double length, double thickness, double beakAngle) {
514 this.length = length;
515 this.thickness = thickness;
516 this.beakAngle = beakAngle;
520 public Shape getElementShape(IElement e) {
521 Shape shape = e.getHint(KEY_SHAPE);
524 return createFlagShape(e);
528 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
530 size = new Rectangle2D.Double();
531 Shape shape = getElementShape(e);
532 size.setFrame(shape.getBounds2D());
537 public void onElementActivated(IDiagram d, IElement e) {
541 public void onElementCreated(IElement e) {
542 e.setHint(KEY_FLAG_WIDTH, length);
543 e.setHint(KEY_FLAG_HEIGHT, thickness);
544 e.setHint(KEY_FLAG_BEAK_ANGLE, beakAngle);
548 public void onElementDeactivated(IDiagram d, IElement e) {
552 public void onElementDestroyed(IElement e) {
556 public int hashCode() {
557 final int prime = 31;
560 temp = Double.doubleToLongBits(beakAngle);
561 result = prime * result + (int) (temp ^ (temp >>> 32));
562 temp = Double.doubleToLongBits(length);
563 result = prime * result + (int) (temp ^ (temp >>> 32));
564 temp = Double.doubleToLongBits(thickness);
565 result = prime * result + (int) (temp ^ (temp >>> 32));
570 public boolean equals(Object obj) {
575 if (getClass() != obj.getClass())
577 FlagSize other = (FlagSize) obj;
578 if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))
580 if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))
582 if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))
588 static class FlagSceneGraph implements SceneGraph {
589 private static final long serialVersionUID = 35208146123929197L;
591 public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();
594 public void cleanup(IElement e) {
595 ElementUtils.removePossibleNode(e, KEY_SG_NODE);
599 public void init(IElement e, G2DParentNode parent) {
600 Color fc = ElementUtils.getFillColor(e, Color.WHITE);
601 Color bc = ElementUtils.getBorderColor(e, Color.BLACK);
602 Color tc = ElementUtils.getTextColor(e, Color.BLACK);
604 Outline outline = e.getElementClass().getSingleItem(Outline.class);
605 Shape shape = outline.getElementShape(e);
606 Type type = getType(e);
607 double dir = getDirection(e);
608 double width = e.getHint(KEY_FLAG_WIDTH);
609 double height = e.getHint(KEY_FLAG_HEIGHT);
610 double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);
612 String[] flagText = e.getHint(KEY_FLAG_TEXT);
613 if (flagText == null) {
615 Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);
617 String text = t.getText(e);
619 flagText = new String[] { text };
624 //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};
626 Rectangle2D textArea = e.getHint(KEY_FLAG_TEXT_AREA);
627 if (textArea == null) {
628 double beakLength = getBeakLength(height, beakAngle);
629 textArea = type == Type.In
630 ? new Rectangle2D.Double(-width-beakLength, -height*0.5, width, height)
631 : new Rectangle2D.Double(0, -height*0.5, width, height);
634 Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);
635 Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);
637 Font font = ElementUtils.getHintOrDefault(e, KEY_FLAG_FONT, FlagNode.DEFAULT_FONT);
639 FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);
651 horizAlign.ordinal(),
654 AffineTransform at = ElementUtils.getTransform(e);
655 if(at != null) flag.setTransform(at);
660 static class TerminalPoint implements Terminal {
663 public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {
664 private static final long serialVersionUID = -4194634598346105458L;
666 public static final Terminal DEFAULT_T0 = new TerminalPoint();
667 public static final FlagTerminalTopology DEFAULT = new FlagTerminalTopology(DEFAULT_T0);
671 public FlagTerminalTopology(Terminal t) {
676 public void getTerminals(IElement e, Collection<Terminal> result) {
681 public AffineTransform getTerminalPosition(IElement node, Terminal t) {
683 return new AffineTransform();
689 public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {
690 Type type = getType(node);
691 double d = getDirection(node);
694 case In: directions.add(d); break;
695 case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;
697 //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));
703 // static final Path2D terminalShape;
707 // Path2D p = new Path2D.Double();
713 // terminalShape = p;
717 public Shape getTerminalShape(IElement node, Terminal t) {
718 //return terminalShape;
720 // For each terminal, return the whole shape of the element.
721 return ElementUtils.getElementShapeOrBounds(node);
725 public int hashCode() {
726 final int prime = 31;
728 result = prime * result + ((T0 == null) ? 0 : T0.hashCode());
733 public boolean equals(Object obj) {
738 if (getClass() != obj.getClass())
740 FlagTerminalTopology other = (FlagTerminalTopology) obj;
742 if (other.T0 != null)
744 } else if (!T0.equals(other.T0))
750 public static AffineTransform getTransform(IElement e) {
751 AffineTransform at = ElementUtils.getTransform(e);
753 return new AffineTransform();
757 public static double getDirection(IElement e) {
758 Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);
759 if (rotate != null) {
760 return rotate.getAngle(e);
765 public static Type getType(IElement e) {
766 Type t = e.getHint(KEY_FLAG_TYPE);
767 return t != null ? t : Type.In;
770 public static Mode getMode(IElement e) {
771 Mode m = e.getHint(KEY_FLAG_MODE);
772 return m != null ? m : Mode.External;
775 public static double getBeakLength(IElement e) {
776 double height = e.getHint(KEY_FLAG_HEIGHT);
777 double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);
778 beakAngle = Math.min(180, Math.max(10, beakAngle));
779 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
782 public static double getBeakLength(double height, double beakAngle) {
783 beakAngle = Math.min(180, Math.max(10, beakAngle));
784 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));