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