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 ).setId(FlagClass.class.getSimpleName());
317 public static ElementClass create(Terminal terminal) {
318 return ElementClass.compile(
321 DefaultTransform.INSTANCE,
323 BorderColorImpl.BLACK,
326 new FlagTerminalTopology(terminal),
327 FlagSceneGraph.INSTANCE,
328 DEFAULT_STATIC_SYMBOL,
329 SimpleElementLayers.INSTANCE
330 ).setId(FlagClass.class.getSimpleName());
333 public static ElementClass create(Terminal terminal, SceneGraph scn) {
334 return ElementClass.compile(
337 DefaultTransform.INSTANCE,
339 BorderColorImpl.BLACK,
342 new FlagTerminalTopology(terminal),
344 DEFAULT_STATIC_SYMBOL,
345 SimpleElementLayers.INSTANCE
346 ).setId(FlagClass.class.getSimpleName());
349 static class Initializer implements LifeCycle {
350 private static final long serialVersionUID = 4404942036933073584L;
352 private final Type type;
353 private final Mode mode;
355 Initializer(Type type, Mode mode) {
363 public void onElementActivated(IDiagram d, IElement e) {
367 public void onElementCreated(IElement e) {
368 e.setHint(KEY_FLAG_TYPE, type);
369 e.setHint(KEY_FLAG_MODE, mode);
370 //e.setHint(ElementHints.KEY_COMPOSITE, AlphaComposite.SrcOver.derive(0.5f));
374 public void onElementDeactivated(IDiagram d, IElement e) {
378 public void onElementDestroyed(IElement e) {
382 public int hashCode() {
383 final int prime = 31;
385 result = prime * result + mode.hashCode();
386 result = prime * result + type.hashCode();
391 public boolean equals(Object obj) {
396 if (getClass() != obj.getClass())
398 Initializer other = (Initializer) obj;
399 if (!mode.equals(other.mode))
401 if (!type.equals(other.type))
407 public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {
408 double hh = height / 2;
412 if (mode instanceof External) {
414 path.lineTo(width, hh);
415 path.lineTo(width+beakLength, 0);
416 path.lineTo(width, -hh);
419 path.moveTo(width, hh);
420 path.lineTo(width, -hh);
421 int count = ((External)mode).count;
423 double shadow=hh*0.25;
424 double ix = beakLength
425 - 0.5*shadow*(1.0 + beakLength/hh);
426 double iy = hh * (ix / beakLength - 1.0);
427 for(int sid=1;sid<=Math.min(count-1, 4);++sid) {
428 double dis = sid*shadow;
429 path.moveTo(dis, hh+dis-shadow);
430 path.lineTo(dis, hh+dis);
431 path.lineTo(dis+width, hh+dis);
432 path.lineTo(dis+width+beakLength, dis);
433 path.lineTo(width + ix + dis, iy + dis);
437 double right = width - 0;
439 path.moveTo(left, 0);
440 path.lineTo(right, 0);
443 } else if (mode == Mode.Internal) {
445 path.lineTo(beakLength, 0);
452 if (mode instanceof External) {
453 path.lineTo(-beakLength, -hh);
454 path.lineTo(-width-beakLength, -hh);
455 path.lineTo(-width-beakLength, hh);
456 path.lineTo(-beakLength, hh);
458 path.moveTo(-beakLength, -hh);
459 path.lineTo(-beakLength, hh);
460 int count = ((External)mode).count;
462 double shadow=hh*0.25;
463 double ix = beakLength
464 - 0.5*shadow*(1.0 + beakLength/hh);
465 double iy = hh * (ix / beakLength - 1.0);
466 double xDisp = -width-beakLength;
467 for(int sid=1;sid<=Math.min(count-1, 4);++sid) {
468 double dis = sid*shadow;
469 path.moveTo(xDisp+dis, hh+dis-shadow);
470 path.lineTo(xDisp+dis, hh+dis);
471 path.lineTo(xDisp+dis+width, hh+dis);
472 path.lineTo(xDisp+dis+width+beakLength, dis);
473 path.lineTo(xDisp+width + ix + dis, iy + dis);
476 double left = -width-beakLength+0;
477 double right = -beakLength-0;
479 path.moveTo(left, 0);
480 path.lineTo(right, 0);
483 } else if (mode == Mode.Internal) {
484 path.lineTo(-beakLength, -hh);
485 path.lineTo(-beakLength, hh);
493 public static Path2D createFlagShape(IElement e) {
494 Type type = getType(e);
495 Mode mode = e.getHint(KEY_FLAG_MODE);
496 double width = e.getHint(KEY_FLAG_WIDTH);
497 double height = e.getHint(KEY_FLAG_HEIGHT);
498 double beakLength = getBeakLength(e);
499 Path2D path = new Path2D.Double();
500 createFlagShape(path, type, mode, width, height, beakLength);
504 static class FlagSize implements InternalSize, Outline, LifeCycle {
506 private static final long serialVersionUID = 829379327756475944L;
508 private final double length;
509 private final double thickness;
510 private final double beakAngle;
512 public FlagSize(double length, double thickness, double beakAngle) {
513 this.length = length;
514 this.thickness = thickness;
515 this.beakAngle = beakAngle;
519 public Shape getElementShape(IElement e) {
520 Shape shape = e.getHint(KEY_SHAPE);
523 return createFlagShape(e);
527 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
529 size = new Rectangle2D.Double();
530 Shape shape = getElementShape(e);
531 size.setFrame(shape.getBounds2D());
536 public void onElementActivated(IDiagram d, IElement e) {
540 public void onElementCreated(IElement e) {
541 e.setHint(KEY_FLAG_WIDTH, length);
542 e.setHint(KEY_FLAG_HEIGHT, thickness);
543 e.setHint(KEY_FLAG_BEAK_ANGLE, beakAngle);
547 public void onElementDeactivated(IDiagram d, IElement e) {
551 public void onElementDestroyed(IElement e) {
555 public int hashCode() {
556 final int prime = 31;
559 temp = Double.doubleToLongBits(beakAngle);
560 result = prime * result + (int) (temp ^ (temp >>> 32));
561 temp = Double.doubleToLongBits(length);
562 result = prime * result + (int) (temp ^ (temp >>> 32));
563 temp = Double.doubleToLongBits(thickness);
564 result = prime * result + (int) (temp ^ (temp >>> 32));
569 public boolean equals(Object obj) {
574 if (getClass() != obj.getClass())
576 FlagSize other = (FlagSize) obj;
577 if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))
579 if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))
581 if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))
587 static class FlagSceneGraph implements SceneGraph {
588 private static final long serialVersionUID = 35208146123929197L;
590 public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();
593 public void cleanup(IElement e) {
594 ElementUtils.removePossibleNode(e, KEY_SG_NODE);
598 public void init(IElement e, G2DParentNode parent) {
599 Color fc = ElementUtils.getFillColor(e, Color.WHITE);
600 Color bc = ElementUtils.getBorderColor(e, Color.BLACK);
601 Color tc = ElementUtils.getTextColor(e, Color.BLACK);
603 Outline outline = e.getElementClass().getSingleItem(Outline.class);
604 Shape shape = outline.getElementShape(e);
605 Type type = getType(e);
606 double dir = getDirection(e);
607 double width = e.getHint(KEY_FLAG_WIDTH);
608 double height = e.getHint(KEY_FLAG_HEIGHT);
609 double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);
611 String[] flagText = e.getHint(KEY_FLAG_TEXT);
612 if (flagText == null) {
614 Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);
616 String text = t.getText(e);
618 flagText = new String[] { text };
623 //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};
625 Rectangle2D textArea = e.getHint(KEY_FLAG_TEXT_AREA);
626 if (textArea == null) {
627 double beakLength = getBeakLength(height, beakAngle);
628 textArea = type == Type.In
629 ? new Rectangle2D.Double(-width-beakLength, -height*0.5, width, height)
630 : new Rectangle2D.Double(0, -height*0.5, width, height);
633 Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);
634 Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);
636 Font font = ElementUtils.getHintOrDefault(e, KEY_FLAG_FONT, FlagNode.DEFAULT_FONT);
638 FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);
650 horizAlign.ordinal(),
653 AffineTransform at = ElementUtils.getTransform(e);
654 if(at != null) flag.setTransform(at);
659 static class TerminalPoint implements Terminal {
662 public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {
663 private static final long serialVersionUID = -4194634598346105458L;
665 public static final Terminal DEFAULT_T0 = new TerminalPoint();
666 public static final FlagTerminalTopology DEFAULT = new FlagTerminalTopology(DEFAULT_T0);
670 public FlagTerminalTopology(Terminal t) {
675 public void getTerminals(IElement e, Collection<Terminal> result) {
680 public AffineTransform getTerminalPosition(IElement node, Terminal t) {
682 return new AffineTransform();
688 public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {
689 Type type = getType(node);
690 double d = getDirection(node);
693 case In: directions.add(d); break;
694 case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;
696 //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));
702 // static final Path2D terminalShape;
706 // Path2D p = new Path2D.Double();
712 // terminalShape = p;
716 public Shape getTerminalShape(IElement node, Terminal t) {
717 //return terminalShape;
719 // For each terminal, return the whole shape of the element.
720 return ElementUtils.getElementShapeOrBounds(node);
724 public int hashCode() {
725 final int prime = 31;
727 result = prime * result + ((T0 == null) ? 0 : T0.hashCode());
732 public boolean equals(Object obj) {
737 if (getClass() != obj.getClass())
739 FlagTerminalTopology other = (FlagTerminalTopology) obj;
741 if (other.T0 != null)
743 } else if (!T0.equals(other.T0))
749 public static AffineTransform getTransform(IElement e) {
750 AffineTransform at = ElementUtils.getTransform(e);
752 return new AffineTransform();
756 public static double getDirection(IElement e) {
757 Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);
758 if (rotate != null) {
759 return rotate.getAngle(e);
764 public static Type getType(IElement e) {
765 Type t = e.getHint(KEY_FLAG_TYPE);
766 return t != null ? t : Type.In;
769 public static Mode getMode(IElement e) {
770 Mode m = e.getHint(KEY_FLAG_MODE);
771 return m != null ? m : Mode.External;
774 public static double getBeakLength(IElement e) {
775 double height = e.getHint(KEY_FLAG_HEIGHT);
776 double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);
777 beakAngle = Math.min(180, Math.max(10, beakAngle));
778 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));
781 public static double getBeakLength(double height, double beakAngle) {
782 beakAngle = Math.min(180, Math.max(10, beakAngle));
783 return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));