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;
16 import java.awt.Shape;
17 import java.awt.geom.AffineTransform;
18 import java.awt.geom.Path2D;
19 import java.awt.geom.Rectangle2D;
20 import java.util.Collection;
22 import org.simantics.g2d.diagram.IDiagram;
23 import org.simantics.g2d.diagram.handler.DataElementMap;
24 import org.simantics.g2d.diagram.handler.Topology.Terminal;
25 import org.simantics.g2d.element.ElementClass;
26 import org.simantics.g2d.element.ElementUtils;
27 import org.simantics.g2d.element.IElement;
28 import org.simantics.g2d.element.SceneGraphNodeKey;
29 import org.simantics.g2d.element.handler.InternalSize;
30 import org.simantics.g2d.element.handler.LifeCycle;
31 import org.simantics.g2d.element.handler.Outline;
32 import org.simantics.g2d.element.handler.Rotate;
33 import org.simantics.g2d.element.handler.SceneGraph;
34 import org.simantics.g2d.element.handler.TerminalLayout;
35 import org.simantics.g2d.element.handler.TerminalTopology;
36 import org.simantics.g2d.element.handler.Text;
37 import org.simantics.g2d.element.handler.impl.BorderColorImpl;
38 import org.simantics.g2d.element.handler.impl.DefaultTransform;
39 import org.simantics.g2d.element.handler.impl.FillColorImpl;
40 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
41 import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;
42 import org.simantics.g2d.element.handler.impl.TextImpl;
43 import org.simantics.g2d.image.Image;
44 import org.simantics.g2d.image.impl.ShapeImage;
45 import org.simantics.g2d.utils.Alignment;
46 import org.simantics.g2d.utils.geom.DirectionSet;
47 import org.simantics.scenegraph.Node;
48 import org.simantics.scenegraph.g2d.G2DParentNode;
49 import org.simantics.scenegraph.g2d.nodes.FlagNode;
50 import org.simantics.utils.datastructures.hints.IHintContext.Key;
51 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
54 * @author Tuukka Lehtonen
56 public class FlagClass {
58 public static enum Type {
59 /// The input part of a pair of flags.
61 /// The output part of a pair of flags.
64 return this == Out ? In: Out;
68 public static class Mode {
69 public static final Mode External = new External(1);
70 public static final Mode Internal = new Mode() {
71 public String toString() { return "Internal"; }
75 public static class External extends Mode {
76 public final int count;
77 public External(int count) {
81 public String toString() {
82 return "External(" + count + ")";
86 private static final double GLOBAL_SCALE = 0.1;
87 private static final double FLAG_SIZE_SCALE = 3 * GLOBAL_SCALE;
89 public static final double DEFAULT_WIDTH = 70 * FLAG_SIZE_SCALE;
90 public static final double DEFAULT_HEIGHT = 20 * FLAG_SIZE_SCALE;
91 public static final double DEFAULT_BEAK_ANGLE = 60;
93 public static final Key KEY_FLAG_TYPE = new KeyOf(Type.class, "FLAG_TYPE");
94 public static final Key KEY_EXTERNAL = new KeyOf(Boolean.class, "FLAG_EXTERNAL");
95 public static final Key KEY_FLAG_MODE = new KeyOf(Mode.class, "FLAG_MODE");
96 public static final Key KEY_FLAG_WIDTH = new KeyOf(Double.class, "FLAG_WIDTH");
97 public static final Key KEY_FLAG_HEIGHT = new KeyOf(Double.class, "FLAG_HEIGHT");
98 public static final Key KEY_FLAG_BEAK_ANGLE = new KeyOf(Double.class, "FLAG_BEAK_ANGLE");
99 public static final Key KEY_FLAG_TEXT = new KeyOf(String[].class, "FLAG_TEXT");
100 public static final Key KEY_FLAG_TEXT_AREA = new KeyOf(Rectangle2D.class, "FLAG_TEXT_AREA_SIZE");
101 public static final Key KEY_SHAPE = new KeyOf(Shape.class, "SHAPE");
102 public static final Key KEY_TEXT_HORIZONTAL_ALIGN = new KeyOf(Alignment.class, "TEXT_HORIZONTAL_ALIGN");
103 public static final Key KEY_TEXT_VERTICAL_ALIGN = new KeyOf(Alignment.class, "TEXT_VERTICAL_ALIGN");
105 public static final Key KEY_SG_NODE = new SceneGraphNodeKey(Node.class, "FLAG_SG_NODE");
108 * Indicates that this flag is connected to another flag.
110 private static final Key KEY_FLAG_CONNECTION_DATA = new KeyOf(DataConnection.class, "FLAG_CONNECTION_DATA");
111 private static final Key KEY_FLAG_CONNECTION_ELEMENTS = new KeyOf(ElementConnection.class, "FLAG_CONNECTION_ELEMENTS");
113 public interface Connection<T> {
118 private static class Conn<T> implements Connection<T> {
119 private final T first;
120 private final T second;
121 public Conn(T first, T second) {
123 this.second = second;
126 public T getFirst() {
130 public T getSecond() {
134 private static class ElementConnection extends Conn<IElement> {
135 public ElementConnection(IElement first, IElement second) {
136 super(first, second);
138 throw new IllegalArgumentException("first is null");
140 throw new IllegalArgumentException("second is null");
143 private static class DataConnection extends Conn<Object> {
144 public DataConnection(Object first, Object second) {
145 super(first, second);
147 throw new IllegalArgumentException("first is null");
148 // Second may be null to indicate "not-connected"
150 public boolean isConnected() {
151 return getSecond() != null;
155 public static final FlagHandler FLAG_HANDLER = new FlagHandler() {
157 private static final long serialVersionUID = -4258875745321808416L;
160 public Type getType(IElement e) {
161 return FlagClass.getType(e);
165 public void setType(IElement e, Type type) {
166 e.setHint(KEY_FLAG_TYPE, type);
170 public boolean isExternal(IElement e) {
171 return Boolean.TRUE.equals(e.getHint(KEY_EXTERNAL));
175 public void setExternal(IElement e, boolean external) {
176 e.setHint(KEY_EXTERNAL, Boolean.valueOf(external));
180 public Connection<IElement> getConnection(IElement e) {
181 return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS);
185 public Connection<Object> getConnectionData(IElement e) {
186 DataConnection dc = e.getHint(KEY_FLAG_CONNECTION_DATA);
187 return (dc != null && dc.isConnected()) ? dc : null;
191 public void connect(IElement e1, IElement e2) {
192 assert e1 != null && e2 != null;
194 ElementConnection ce = new ElementConnection(e1, e2);
195 e1.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);
196 e2.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);
200 public void connectData(IElement e1, Object o1, Object o2) {
201 e1.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
202 e1.setHint(KEY_FLAG_CONNECTION_DATA, new DataConnection(o1, o2));
206 public void disconnect(IElement local) {
207 assert local != null;
208 local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
209 DataConnection c = (DataConnection) local.removeHint(KEY_FLAG_CONNECTION_DATA);
211 IElement remote = otherElement(local, c);
212 if (remote != null) {
213 local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
214 remote.removeHint(KEY_FLAG_CONNECTION_DATA);
220 public boolean isWithinDiagram(IDiagram d, Connection<?> c) {
223 if (c instanceof DataConnection)
224 return bothOnDiagram(d, (DataConnection) c);
225 if (c instanceof ElementConnection)
226 return bothOnDiagram(d, (ElementConnection) c);
231 public IElement getCorrespondence(IElement end) {
233 DataConnection dc = (DataConnection) end.getHint(KEY_FLAG_CONNECTION_DATA);
234 if (dc != null && dc.isConnected())
235 return otherElement(end, dc);
236 ElementConnection ec = (ElementConnection) end.getHint(KEY_FLAG_CONNECTION_ELEMENTS);
238 return otherElement(end, ec);
242 boolean bothOnDiagram(IDiagram d, DataConnection c) {
243 if (!c.isConnected())
246 DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);
247 IElement eout = dem.getElement(d, c.getFirst());
248 IElement ein = dem.getElement(d, c.getSecond());
249 return eout != null && ein != null;
252 boolean bothOnDiagram(IDiagram d, ElementConnection c) {
253 DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);
254 Object o1 = dem.getData(d, c.getFirst());
255 Object o2 = dem.getData(d, c.getSecond());
256 return o1 != null && o2 != null;
259 public IElement otherElement(IElement e, DataConnection c) {
260 if (!c.isConnected())
263 IDiagram d = ElementUtils.peekDiagram(e);
267 DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);
268 Object o = dem.getData(d, e);
269 if (c.getFirst().equals(o))
270 return dem.getElement(d, c.getSecond());
271 if (c.getSecond().equals(o))
272 return dem.getElement(d, c.getFirst());
273 throw new IllegalArgumentException("specified object '" + o + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");
276 public IElement otherElement(IElement e, ElementConnection c) {
277 IElement a = c.getFirst();
278 IElement b = c.getSecond();
283 throw new IllegalArgumentException("specified element '" + e + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");
287 static final Shape staticShape;
290 Path2D path = new Path2D.Double();
292 createFlagShape(path, Type.In, Mode.External, DEFAULT_WIDTH, DEFAULT_HEIGHT, getBeakLength(DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE));
295 public static final BasicStroke STROKE = new BasicStroke(0.15f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
296 static final Image DEFAULT_IMAGE = new ShapeImage(staticShape, null, STROKE);
297 static final StaticSymbolImpl DEFAULT_STATIC_SYMBOL = new StaticSymbolImpl(DEFAULT_IMAGE);
298 static final FlagSize DEFAULT_FLAG_SIZE = new FlagSize(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE);
299 static final Initializer DEFAULT_INITIALIZER = new Initializer(Type.In, Mode.External);
301 public static final ElementClass FLAGCLASS =
302 ElementClass.compile(
305 DefaultTransform.INSTANCE,
307 BorderColorImpl.BLACK,
310 FlagTerminalTopology.DEFAULT,
311 FlagSceneGraph.INSTANCE,
312 DEFAULT_STATIC_SYMBOL
313 ).setId(FlagClass.class.getSimpleName());
315 public static ElementClass create(Terminal terminal) {
316 return ElementClass.compile(
319 DefaultTransform.INSTANCE,
321 BorderColorImpl.BLACK,
324 new FlagTerminalTopology(terminal),
325 FlagSceneGraph.INSTANCE,
326 DEFAULT_STATIC_SYMBOL,
327 SimpleElementLayers.INSTANCE
328 ).setId(FlagClass.class.getSimpleName());
331 public static ElementClass create(Terminal terminal, SceneGraph scn) {
332 return ElementClass.compile(
335 DefaultTransform.INSTANCE,
337 BorderColorImpl.BLACK,
340 new FlagTerminalTopology(terminal),
342 DEFAULT_STATIC_SYMBOL,
343 SimpleElementLayers.INSTANCE
344 ).setId(FlagClass.class.getSimpleName());
347 static class Initializer implements LifeCycle {
348 private static final long serialVersionUID = 4404942036933073584L;
350 private final Type type;
351 private final Mode mode;
353 Initializer(Type type, Mode mode) {
361 public void onElementActivated(IDiagram d, IElement e) {
365 public void onElementCreated(IElement e) {
366 e.setHint(KEY_FLAG_TYPE, type);
367 e.setHint(KEY_FLAG_MODE, mode);
368 //e.setHint(ElementHints.KEY_COMPOSITE, AlphaComposite.SrcOver.derive(0.5f));
372 public void onElementDeactivated(IDiagram d, IElement e) {
376 public void onElementDestroyed(IElement e) {
380 public int hashCode() {
381 final int prime = 31;
383 result = prime * result + mode.hashCode();
384 result = prime * result + type.hashCode();
389 public boolean equals(Object obj) {
394 if (getClass() != obj.getClass())
396 Initializer other = (Initializer) obj;
397 if (!mode.equals(other.mode))
399 if (!type.equals(other.type))
405 public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {
406 double hh = height / 2;
410 if (mode instanceof External) {
412 path.lineTo(width, hh);
413 path.lineTo(width+beakLength, 0);
414 path.lineTo(width, -hh);
417 path.moveTo(width, hh);
418 path.lineTo(width, -hh);
419 int count = ((External)mode).count;
421 double shadow=hh*0.25;
422 double ix = beakLength
423 - 0.5*shadow*(1.0 + beakLength/hh);
424 double iy = hh * (ix / beakLength - 1.0);
425 for(int sid=1;sid<=Math.min(count-1, 4);++sid) {
426 double dis = sid*shadow;
427 path.moveTo(dis, hh+dis-shadow);
428 path.lineTo(dis, hh+dis);
429 path.lineTo(dis+width, hh+dis);
430 path.lineTo(dis+width+beakLength, dis);
431 path.lineTo(width + ix + dis, iy + dis);
435 double right = width - 0;
437 path.moveTo(left, 0);
438 path.lineTo(right, 0);
441 } else if (mode == Mode.Internal) {
443 path.lineTo(beakLength, 0);
450 if (mode instanceof External) {
451 path.lineTo(-beakLength, -hh);
452 path.lineTo(-width-beakLength, -hh);
453 path.lineTo(-width-beakLength, hh);
454 path.lineTo(-beakLength, hh);
456 path.moveTo(-beakLength, -hh);
457 path.lineTo(-beakLength, hh);
458 int count = ((External)mode).count;
460 double shadow=hh*0.25;
461 double ix = beakLength
462 - 0.5*shadow*(1.0 + beakLength/hh);
463 double iy = hh * (ix / beakLength - 1.0);
464 double xDisp = -width-beakLength;
465 for(int sid=1;sid<=Math.min(count-1, 4);++sid) {
466 double dis = sid*shadow;
467 path.moveTo(xDisp+dis, hh+dis-shadow);
468 path.lineTo(xDisp+dis, hh+dis);
469 path.lineTo(xDisp+dis+width, hh+dis);
470 path.lineTo(xDisp+dis+width+beakLength, dis);
471 path.lineTo(xDisp+width + ix + dis, iy + dis);
474 double left = -width-beakLength+0;
475 double right = -beakLength-0;
477 path.moveTo(left, 0);
478 path.lineTo(right, 0);
481 } else if (mode == Mode.Internal) {
482 path.lineTo(-beakLength, -hh);
483 path.lineTo(-beakLength, hh);
491 public static Path2D createFlagShape(IElement e) {
492 Type type = getType(e);
493 Mode mode = e.getHint(KEY_FLAG_MODE);
494 double width = e.getHint(KEY_FLAG_WIDTH);
495 double height = e.getHint(KEY_FLAG_HEIGHT);
496 double beakLength = getBeakLength(e);
497 Path2D path = new Path2D.Double();
498 createFlagShape(path, type, mode, width, height, beakLength);
502 static class FlagSize implements InternalSize, Outline, LifeCycle {
504 private static final long serialVersionUID = 829379327756475944L;
506 private final double length;
507 private final double thickness;
508 private final double beakAngle;
510 public FlagSize(double length, double thickness, double beakAngle) {
511 this.length = length;
512 this.thickness = thickness;
513 this.beakAngle = beakAngle;
517 public Shape getElementShape(IElement e) {
518 Shape shape = e.getHint(KEY_SHAPE);
521 return createFlagShape(e);
525 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
527 size = new Rectangle2D.Double();
528 Shape shape = getElementShape(e);
529 size.setFrame(shape.getBounds2D());
534 public void onElementActivated(IDiagram d, IElement e) {
538 public void onElementCreated(IElement e) {
539 e.setHint(KEY_FLAG_WIDTH, length);
540 e.setHint(KEY_FLAG_HEIGHT, thickness);
541 e.setHint(KEY_FLAG_BEAK_ANGLE, beakAngle);
545 public void onElementDeactivated(IDiagram d, IElement e) {
549 public void onElementDestroyed(IElement e) {
553 public int hashCode() {
554 final int prime = 31;
557 temp = Double.doubleToLongBits(beakAngle);
558 result = prime * result + (int) (temp ^ (temp >>> 32));
559 temp = Double.doubleToLongBits(length);
560 result = prime * result + (int) (temp ^ (temp >>> 32));
561 temp = Double.doubleToLongBits(thickness);
562 result = prime * result + (int) (temp ^ (temp >>> 32));
567 public boolean equals(Object obj) {
572 if (getClass() != obj.getClass())
574 FlagSize other = (FlagSize) obj;
575 if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))
577 if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))
579 if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))
585 static class FlagSceneGraph implements SceneGraph {
586 private static final long serialVersionUID = 35208146123929197L;
588 public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();
591 public void cleanup(IElement e) {
592 ElementUtils.removePossibleNode(e, KEY_SG_NODE);
596 public void init(IElement e, G2DParentNode parent) {
597 Color fc = ElementUtils.getFillColor(e, Color.WHITE);
598 Color bc = ElementUtils.getBorderColor(e, Color.BLACK);
599 Color tc = ElementUtils.getTextColor(e, Color.BLACK);
601 Outline outline = e.getElementClass().getSingleItem(Outline.class);
602 Shape shape = outline.getElementShape(e);
603 Type type = getType(e);
604 double dir = getDirection(e);
605 double width = e.getHint(KEY_FLAG_WIDTH);
606 double height = e.getHint(KEY_FLAG_HEIGHT);
607 double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);
609 String[] flagText = e.getHint(KEY_FLAG_TEXT);
610 if (flagText == null) {
612 Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);
614 String text = t.getText(e);
616 flagText = new String[] { text };
621 //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};
623 Rectangle2D textArea = e.getHint(KEY_FLAG_TEXT_AREA);
624 if (textArea == null) {
625 double beakLength = getBeakLength(height, beakAngle);
626 textArea = type == Type.In
627 ? new Rectangle2D.Double(-width-beakLength, -height*0.5, width, height)
628 : new Rectangle2D.Double(0, -height*0.5, width, height);
631 Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);
632 Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);
634 FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);
646 horizAlign.ordinal(),
647 vertAlign.ordinal());
648 AffineTransform at = ElementUtils.getTransform(e);
649 if(at != null) flag.setTransform(at);
654 static class TerminalPoint implements Terminal {
657 public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {
658 private static final long serialVersionUID = -4194634598346105458L;
660 public static final Terminal DEFAULT_T0 = new TerminalPoint();
661 public static final FlagTerminalTopology DEFAULT = new FlagTerminalTopology(DEFAULT_T0);
665 public FlagTerminalTopology(Terminal t) {
670 public void getTerminals(IElement e, Collection<Terminal> result) {
675 public AffineTransform getTerminalPosition(IElement node, Terminal t) {
677 return new AffineTransform();
683 public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {
684 Type type = getType(node);
685 double d = getDirection(node);
688 case In: directions.add(d); break;
689 case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;
691 //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));
697 // static final Path2D terminalShape;
701 // Path2D p = new Path2D.Double();
707 // terminalShape = p;
711 public Shape getTerminalShape(IElement node, Terminal t) {
712 //return terminalShape;
714 // For each terminal, return the whole shape of the element.
715 return ElementUtils.getElementShapeOrBounds(node);
719 public int hashCode() {
720 final int prime = 31;
722 result = prime * result + ((T0 == null) ? 0 : T0.hashCode());
727 public boolean equals(Object obj) {
732 if (getClass() != obj.getClass())
734 FlagTerminalTopology other = (FlagTerminalTopology) obj;
736 if (other.T0 != null)
738 } else if (!T0.equals(other.T0))
744 public static AffineTransform getTransform(IElement e) {
745 AffineTransform at = ElementUtils.getTransform(e);
747 return new AffineTransform();
751 public static double getDirection(IElement e) {
752 Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);
753 if (rotate != null) {
754 return rotate.getAngle(e);
759 public static Type getType(IElement e) {
760 Type t = e.getHint(KEY_FLAG_TYPE);
761 return t != null ? t : Type.In;
764 public static Mode getMode(IElement e) {
765 Mode m = e.getHint(KEY_FLAG_MODE);
766 return m != null ? m : Mode.External;
769 public static double getBeakLength(IElement e) {
770 double height = e.getHint(KEY_FLAG_HEIGHT);
771 double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);
772 beakAngle = Math.min(180, Math.max(10, beakAngle));
773 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
776 public static double getBeakLength(double height, double beakAngle) {
777 beakAngle = Math.min(180, Math.max(10, beakAngle));
778 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));