]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / elementclass / FlagClass.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.elementclass;
13
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;
21
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;
52
53 /**
54  * @author Tuukka Lehtonen
55  */
56 public class FlagClass {
57
58     public static enum Type {
59         /// The input part of a pair of flags.
60         In,
61         /// The output part of a pair of flags.
62         Out;
63         public Type other() {
64             return this == Out ? In: Out;
65         }
66     }
67
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"; }
72         };
73     }
74
75     public static class External extends Mode {
76         public final int count;
77         public External(int count) {
78             this.count = count;
79         }
80         @Override
81         public String toString() {
82             return "External(" + count + ")";
83         }
84     }
85
86     private static final double       GLOBAL_SCALE                 = 0.1;
87     private static final double       FLAG_SIZE_SCALE              = 3 * GLOBAL_SCALE;
88
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;
92
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");
104
105     public static final Key          KEY_SG_NODE                  = new SceneGraphNodeKey(Node.class, "FLAG_SG_NODE");
106
107     /**
108      * Indicates that this flag is connected to another flag.
109      */
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");
112
113     public interface Connection<T> {
114         T getFirst();
115         T getSecond();
116     }
117
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) {
122             this.first = first;
123             this.second = second;
124         }
125         @Override
126         public T getFirst() {
127             return first;
128         }
129         @Override
130         public T getSecond() {
131             return second;
132         }
133     }
134     private static class ElementConnection extends Conn<IElement> {
135         public ElementConnection(IElement first, IElement second) {
136             super(first, second);
137             if (first == null)
138                 throw new IllegalArgumentException("first is null");
139             if (second == null)
140                 throw new IllegalArgumentException("second is null");
141         }
142     }
143     private static class DataConnection extends Conn<Object> {
144         public DataConnection(Object first, Object second) {
145             super(first, second);
146             if (first == null)
147                 throw new IllegalArgumentException("first is null");
148             // Second may be null to indicate "not-connected"
149         }
150         public boolean isConnected() {
151             return getSecond() != null;
152         }
153     }
154
155     public static final FlagHandler  FLAG_HANDLER = new FlagHandler() {
156
157         private static final long serialVersionUID = -4258875745321808416L;
158
159         @Override
160         public Type getType(IElement e) {
161             return FlagClass.getType(e);
162         }
163
164         @Override
165         public void setType(IElement e, Type type) {
166             e.setHint(KEY_FLAG_TYPE, type);
167         }
168
169         @Override
170         public boolean isExternal(IElement e) {
171             return Boolean.TRUE.equals(e.getHint(KEY_EXTERNAL));
172         }
173
174         @Override
175         public void setExternal(IElement e, boolean external) {
176             e.setHint(KEY_EXTERNAL, Boolean.valueOf(external));
177         }
178
179         @Override
180         public Connection<IElement> getConnection(IElement e) {
181             return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS);
182         }
183
184         @Override
185         public Connection<Object> getConnectionData(IElement e) {
186             DataConnection dc = e.getHint(KEY_FLAG_CONNECTION_DATA);
187             return (dc != null && dc.isConnected()) ? dc : null;
188         }
189
190         @Override
191         public void connect(IElement e1, IElement e2) {
192             assert e1 != null && e2 != null;
193
194             ElementConnection ce = new ElementConnection(e1, e2);
195             e1.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);
196             e2.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);
197         }
198
199         @Override
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));
203         }
204
205         @Override
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);
210             if (c != null) {
211                 IElement remote = otherElement(local, c);
212                 if (remote != null) {
213                     local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);
214                     remote.removeHint(KEY_FLAG_CONNECTION_DATA);
215                 }
216             }
217         }
218
219         @Override
220         public boolean isWithinDiagram(IDiagram d, Connection<?> c) {
221             assert d != null;
222             assert c != null;
223             if (c instanceof DataConnection)
224                 return bothOnDiagram(d, (DataConnection) c);
225             if (c instanceof ElementConnection)
226                 return bothOnDiagram(d, (ElementConnection) c);
227             return false;
228         }
229
230         @Override
231         public IElement getCorrespondence(IElement end) {
232             assert end != null;
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);
237             if (ec != null)
238                 return otherElement(end, ec);
239             return null;
240         }
241
242         boolean bothOnDiagram(IDiagram d, DataConnection c) {
243             if (!c.isConnected())
244                 return false;
245
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;
250         }
251
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;
257         }
258
259         public IElement otherElement(IElement e, DataConnection c) {
260             if (!c.isConnected())
261                 return null;
262
263             IDiagram d = ElementUtils.peekDiagram(e);
264             if (d == null)
265                 return null;
266
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() + "'");
274         }
275
276         public IElement otherElement(IElement e, ElementConnection c) {
277             IElement a = c.getFirst();
278             IElement b = c.getSecond();
279             if (e == a)
280                 return b;
281             if (e == b)
282                 return a;
283             throw new IllegalArgumentException("specified element '" + e + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");
284         }
285     };
286
287     static final Shape staticShape;
288
289     static {
290         Path2D path = new Path2D.Double();
291         staticShape = path;
292         createFlagShape(path, Type.In, Mode.External, DEFAULT_WIDTH, DEFAULT_HEIGHT, getBeakLength(DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE));
293     }
294
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);
300
301     public static final ElementClass FLAGCLASS =
302         ElementClass.compile(
303                 DEFAULT_INITIALIZER,
304                 FLAG_HANDLER,
305                 DefaultTransform.INSTANCE,
306                 DEFAULT_FLAG_SIZE,
307                 BorderColorImpl.BLACK,
308                 FillColorImpl.WHITE,
309                 TextImpl.INSTANCE,
310                 FlagTerminalTopology.DEFAULT,
311                 FlagSceneGraph.INSTANCE,
312                 DEFAULT_STATIC_SYMBOL
313         ).setId(FlagClass.class.getSimpleName());
314
315     public static ElementClass create(Terminal terminal) {
316         return ElementClass.compile(
317                 DEFAULT_INITIALIZER,
318                 FLAG_HANDLER,
319                 DefaultTransform.INSTANCE,
320                 DEFAULT_FLAG_SIZE,
321                 BorderColorImpl.BLACK,
322                 FillColorImpl.WHITE,
323                 TextImpl.INSTANCE,
324                 new FlagTerminalTopology(terminal),
325                 FlagSceneGraph.INSTANCE,
326                 DEFAULT_STATIC_SYMBOL,
327                 SimpleElementLayers.INSTANCE
328         ).setId(FlagClass.class.getSimpleName());
329     }
330
331     public static ElementClass create(Terminal terminal, SceneGraph scn) {
332         return ElementClass.compile(
333                 DEFAULT_INITIALIZER,
334                 FLAG_HANDLER,
335                 DefaultTransform.INSTANCE,
336                 DEFAULT_FLAG_SIZE,
337                 BorderColorImpl.BLACK,
338                 FillColorImpl.WHITE,
339                 TextImpl.INSTANCE,
340                 new FlagTerminalTopology(terminal),
341                 scn,
342                 DEFAULT_STATIC_SYMBOL,
343                 SimpleElementLayers.INSTANCE
344         ).setId(FlagClass.class.getSimpleName());
345     }
346
347     static class Initializer implements LifeCycle {
348         private static final long serialVersionUID = 4404942036933073584L;
349
350         private final Type type;
351         private final Mode mode;
352
353         Initializer(Type type, Mode mode) {
354             assert type != null;
355             assert mode != null;
356             this.type = type;
357             this.mode = mode;
358         }
359
360         @Override
361         public void onElementActivated(IDiagram d, IElement e) {
362         }
363
364         @Override
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));
369         }
370
371         @Override
372         public void onElementDeactivated(IDiagram d, IElement e) {
373         }
374
375         @Override
376         public void onElementDestroyed(IElement e) {
377         }
378
379         @Override
380         public int hashCode() {
381             final int prime = 31;
382             int result = 1;
383             result = prime * result + mode.hashCode();
384             result = prime * result + type.hashCode();
385             return result;
386         }
387
388         @Override
389         public boolean equals(Object obj) {
390             if (this == obj)
391                 return true;
392             if (obj == null)
393                 return false;
394             if (getClass() != obj.getClass())
395                 return false;
396             Initializer other = (Initializer) obj;
397             if (!mode.equals(other.mode))
398                 return false;
399             if (!type.equals(other.type))
400                 return false;
401             return true;
402         }
403     };
404
405     public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {
406         double hh = height / 2;        
407         path.reset();
408         switch (type) {
409             case Out:
410                 if (mode instanceof External) {
411                     path.moveTo(0, hh);
412                     path.lineTo(width, hh);
413                     path.lineTo(width+beakLength, 0);
414                     path.lineTo(width, -hh);
415                     path.lineTo(0, -hh);
416                     path.closePath();
417                     path.moveTo(width, hh);
418                     path.lineTo(width, -hh);    
419                     int count = ((External)mode).count;
420                     if(count > 1) {
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);
432                         }
433                     } else {
434                         double left = 0;
435                         double right = width - 0;
436                         if (left < right) {
437                             path.moveTo(left, 0);
438                             path.lineTo(right, 0);
439                         }
440                     }
441                 } else if (mode == Mode.Internal) {
442                     path.moveTo(0, hh);
443                     path.lineTo(beakLength, 0);
444                     path.lineTo(0, -hh);
445                     path.closePath();
446                 }
447                 break;
448             case In:
449                 path.moveTo(0, 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);
455                     path.closePath();
456                     path.moveTo(-beakLength, -hh);
457                     path.lineTo(-beakLength, hh);  
458                     int count = ((External)mode).count;
459                     if(count > 1) {
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);
472                         }
473                     } else {
474                         double left = -width-beakLength+0;
475                         double right = -beakLength-0;       
476                         if (left < right) {
477                             path.moveTo(left, 0);
478                             path.lineTo(right, 0);
479                         }
480                     }
481                 } else if (mode == Mode.Internal) {
482                     path.lineTo(-beakLength, -hh);
483                     path.lineTo(-beakLength, hh);
484                     path.closePath();
485                 }
486                 break;
487         }
488         return path;
489     }
490
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);
499         return path;
500     }
501
502     static class FlagSize implements InternalSize, Outline, LifeCycle {
503
504         private static final long serialVersionUID = 829379327756475944L;
505
506         private final double length;
507         private final double thickness;
508         private final double beakAngle;
509
510         public FlagSize(double length, double thickness, double beakAngle) {
511             this.length = length;
512             this.thickness = thickness;
513             this.beakAngle = beakAngle;
514         }
515
516         @Override
517         public Shape getElementShape(IElement e) {
518             Shape shape = e.getHint(KEY_SHAPE);
519             if (shape != null)
520                 return shape;
521             return createFlagShape(e);
522         }
523
524         @Override
525         public Rectangle2D getBounds(IElement e, Rectangle2D size) {
526             if (size == null)
527                 size = new Rectangle2D.Double();
528             Shape shape = getElementShape(e);
529             size.setFrame(shape.getBounds2D());
530             return size;
531         }
532
533         @Override
534         public void onElementActivated(IDiagram d, IElement e) {
535         }
536
537         @Override
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);
542         }
543
544         @Override
545         public void onElementDeactivated(IDiagram d, IElement e) {
546         }
547
548         @Override
549         public void onElementDestroyed(IElement e) {
550         }
551
552         @Override
553         public int hashCode() {
554             final int prime = 31;
555             int result = 1;
556             long temp;
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));
563             return result;
564         }
565
566         @Override
567         public boolean equals(Object obj) {
568             if (this == obj)
569                 return true;
570             if (obj == null)
571                 return false;
572             if (getClass() != obj.getClass())
573                 return false;
574             FlagSize other = (FlagSize) obj;
575             if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))
576                 return false;
577             if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))
578                 return false;
579             if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))
580                 return false;
581             return true;
582         }
583     }
584
585     static class FlagSceneGraph implements SceneGraph {
586         private static final long serialVersionUID = 35208146123929197L;
587
588         public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();
589
590         @Override
591         public void cleanup(IElement e) {
592             ElementUtils.removePossibleNode(e, KEY_SG_NODE);
593         }
594
595         @Override
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);
600
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);
608
609             String[] flagText = e.getHint(KEY_FLAG_TEXT);
610             if (flagText == null) {
611                 // fallback option.
612                 Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);
613                 if (t != null) {
614                     String text = t.getText(e);
615                     if (text != null)
616                         flagText = new String[] { text };
617                 }
618             }
619
620             // DEBUG TEXT
621             //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};
622
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);
629             }
630
631             Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);
632             Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);
633
634             FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);
635             flag.init(shape,
636                     flagText,
637                     STROKE,
638                     bc,
639                     fc,
640                     tc,
641                     (float) width,
642                     (float) height,
643                     (float) dir,
644                     (float) beakAngle,
645                     textArea,
646                     horizAlign.ordinal(),
647                     vertAlign.ordinal());
648             AffineTransform at = ElementUtils.getTransform(e);
649             if(at != null) flag.setTransform(at);
650
651         }
652     }
653
654     static class TerminalPoint implements Terminal {
655     }
656
657     public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {
658         private static final long    serialVersionUID = -4194634598346105458L;
659
660         public static final Terminal             DEFAULT_T0       = new TerminalPoint();
661         public static final FlagTerminalTopology DEFAULT          = new FlagTerminalTopology(DEFAULT_T0);
662
663         final Terminal T0;
664
665         public FlagTerminalTopology(Terminal t) {
666             this.T0 = t;
667         }
668
669         @Override
670         public void getTerminals(IElement e, Collection<Terminal> result) {
671             result.add(T0);
672         }
673
674         @Override
675         public AffineTransform getTerminalPosition(IElement node, Terminal t) {
676             if (t == T0) {
677                 return new AffineTransform();
678             }
679             return null;
680         }
681
682         @Override
683         public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {
684             Type type = getType(node);
685             double d = getDirection(node);
686             if (t == T0) {
687                 switch (type) {
688                     case In: directions.add(d); break;
689                     case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;
690                 }
691                 //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));
692                 return true;
693             }
694             return false;
695         }
696
697 //        static final Path2D terminalShape;
698 //
699 //        static {
700 //            double s = .5;
701 //            Path2D p = new Path2D.Double();
702 //            p.moveTo(s, s);
703 //            p.lineTo(s, -s);
704 //            p.lineTo(-s, -s);
705 //            p.lineTo(-s, s);
706 //            p.closePath();
707 //            terminalShape = p;
708 //        }
709
710         @Override
711         public Shape getTerminalShape(IElement node, Terminal t) {
712             //return terminalShape;
713             //return null;
714             // For each terminal, return the whole shape of the element.
715             return ElementUtils.getElementShapeOrBounds(node);
716         }
717
718         @Override
719         public int hashCode() {
720             final int prime = 31;
721             int result = 1;
722             result = prime * result + ((T0 == null) ? 0 : T0.hashCode());
723             return result;
724         }
725
726         @Override
727         public boolean equals(Object obj) {
728             if (this == obj)
729                 return true;
730             if (obj == null)
731                 return false;
732             if (getClass() != obj.getClass())
733                 return false;
734             FlagTerminalTopology other = (FlagTerminalTopology) obj;
735             if (T0 == null) {
736                 if (other.T0 != null)
737                     return false;
738             } else if (!T0.equals(other.T0))
739                 return false;
740             return true;
741         }
742     }
743
744     public static AffineTransform getTransform(IElement e) {
745         AffineTransform at = ElementUtils.getTransform(e);
746         if (at == null)
747             return new AffineTransform();
748         return at;
749     }
750
751     public static double getDirection(IElement e) {
752         Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);
753         if (rotate != null) {
754             return rotate.getAngle(e);
755         }
756         return 0.0;
757     }
758
759     public static Type getType(IElement e) {
760         Type t = e.getHint(KEY_FLAG_TYPE);
761         return t != null ? t : Type.In;
762     }
763
764     public static Mode getMode(IElement e) {
765         Mode m = e.getHint(KEY_FLAG_MODE);
766         return m != null ? m : Mode.External;
767     }
768
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));
774     }
775
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));
779     }
780
781 }