]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java
(refs #7180) Configurable font for FlagNode
[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         ).setId(FlagClass.class.getSimpleName());
316
317     public static ElementClass create(Terminal terminal) {
318         return ElementClass.compile(
319                 DEFAULT_INITIALIZER,
320                 FLAG_HANDLER,
321                 DefaultTransform.INSTANCE,
322                 DEFAULT_FLAG_SIZE,
323                 BorderColorImpl.BLACK,
324                 FillColorImpl.WHITE,
325                 TextImpl.INSTANCE,
326                 new FlagTerminalTopology(terminal),
327                 FlagSceneGraph.INSTANCE,
328                 DEFAULT_STATIC_SYMBOL,
329                 SimpleElementLayers.INSTANCE
330         ).setId(FlagClass.class.getSimpleName());
331     }
332
333     public static ElementClass create(Terminal terminal, SceneGraph scn) {
334         return ElementClass.compile(
335                 DEFAULT_INITIALIZER,
336                 FLAG_HANDLER,
337                 DefaultTransform.INSTANCE,
338                 DEFAULT_FLAG_SIZE,
339                 BorderColorImpl.BLACK,
340                 FillColorImpl.WHITE,
341                 TextImpl.INSTANCE,
342                 new FlagTerminalTopology(terminal),
343                 scn,
344                 DEFAULT_STATIC_SYMBOL,
345                 SimpleElementLayers.INSTANCE
346         ).setId(FlagClass.class.getSimpleName());
347     }
348
349     static class Initializer implements LifeCycle {
350         private static final long serialVersionUID = 4404942036933073584L;
351
352         private final Type type;
353         private final Mode mode;
354
355         Initializer(Type type, Mode mode) {
356             assert type != null;
357             assert mode != null;
358             this.type = type;
359             this.mode = mode;
360         }
361
362         @Override
363         public void onElementActivated(IDiagram d, IElement e) {
364         }
365
366         @Override
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));
371         }
372
373         @Override
374         public void onElementDeactivated(IDiagram d, IElement e) {
375         }
376
377         @Override
378         public void onElementDestroyed(IElement e) {
379         }
380
381         @Override
382         public int hashCode() {
383             final int prime = 31;
384             int result = 1;
385             result = prime * result + mode.hashCode();
386             result = prime * result + type.hashCode();
387             return result;
388         }
389
390         @Override
391         public boolean equals(Object obj) {
392             if (this == obj)
393                 return true;
394             if (obj == null)
395                 return false;
396             if (getClass() != obj.getClass())
397                 return false;
398             Initializer other = (Initializer) obj;
399             if (!mode.equals(other.mode))
400                 return false;
401             if (!type.equals(other.type))
402                 return false;
403             return true;
404         }
405     };
406
407     public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {
408         double hh = height / 2;        
409         path.reset();
410         switch (type) {
411             case Out:
412                 if (mode instanceof External) {
413                     path.moveTo(0, hh);
414                     path.lineTo(width, hh);
415                     path.lineTo(width+beakLength, 0);
416                     path.lineTo(width, -hh);
417                     path.lineTo(0, -hh);
418                     path.closePath();
419                     path.moveTo(width, hh);
420                     path.lineTo(width, -hh);    
421                     int count = ((External)mode).count;
422                     if(count > 1) {
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);
434                         }
435                     } else {
436                         double left = 0;
437                         double right = width - 0;
438                         if (left < right) {
439                             path.moveTo(left, 0);
440                             path.lineTo(right, 0);
441                         }
442                     }
443                 } else if (mode == Mode.Internal) {
444                     path.moveTo(0, hh);
445                     path.lineTo(beakLength, 0);
446                     path.lineTo(0, -hh);
447                     path.closePath();
448                 }
449                 break;
450             case In:
451                 path.moveTo(0, 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);
457                     path.closePath();
458                     path.moveTo(-beakLength, -hh);
459                     path.lineTo(-beakLength, hh);  
460                     int count = ((External)mode).count;
461                     if(count > 1) {
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);
474                         }
475                     } else {
476                         double left = -width-beakLength+0;
477                         double right = -beakLength-0;       
478                         if (left < right) {
479                             path.moveTo(left, 0);
480                             path.lineTo(right, 0);
481                         }
482                     }
483                 } else if (mode == Mode.Internal) {
484                     path.lineTo(-beakLength, -hh);
485                     path.lineTo(-beakLength, hh);
486                     path.closePath();
487                 }
488                 break;
489         }
490         return path;
491     }
492
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);
501         return path;
502     }
503
504     static class FlagSize implements InternalSize, Outline, LifeCycle {
505
506         private static final long serialVersionUID = 829379327756475944L;
507
508         private final double length;
509         private final double thickness;
510         private final double beakAngle;
511
512         public FlagSize(double length, double thickness, double beakAngle) {
513             this.length = length;
514             this.thickness = thickness;
515             this.beakAngle = beakAngle;
516         }
517
518         @Override
519         public Shape getElementShape(IElement e) {
520             Shape shape = e.getHint(KEY_SHAPE);
521             if (shape != null)
522                 return shape;
523             return createFlagShape(e);
524         }
525
526         @Override
527         public Rectangle2D getBounds(IElement e, Rectangle2D size) {
528             if (size == null)
529                 size = new Rectangle2D.Double();
530             Shape shape = getElementShape(e);
531             size.setFrame(shape.getBounds2D());
532             return size;
533         }
534
535         @Override
536         public void onElementActivated(IDiagram d, IElement e) {
537         }
538
539         @Override
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);
544         }
545
546         @Override
547         public void onElementDeactivated(IDiagram d, IElement e) {
548         }
549
550         @Override
551         public void onElementDestroyed(IElement e) {
552         }
553
554         @Override
555         public int hashCode() {
556             final int prime = 31;
557             int result = 1;
558             long temp;
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));
565             return result;
566         }
567
568         @Override
569         public boolean equals(Object obj) {
570             if (this == obj)
571                 return true;
572             if (obj == null)
573                 return false;
574             if (getClass() != obj.getClass())
575                 return false;
576             FlagSize other = (FlagSize) obj;
577             if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))
578                 return false;
579             if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))
580                 return false;
581             if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))
582                 return false;
583             return true;
584         }
585     }
586
587     static class FlagSceneGraph implements SceneGraph {
588         private static final long serialVersionUID = 35208146123929197L;
589
590         public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();
591
592         @Override
593         public void cleanup(IElement e) {
594             ElementUtils.removePossibleNode(e, KEY_SG_NODE);
595         }
596
597         @Override
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);
602
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);
610
611             String[] flagText = e.getHint(KEY_FLAG_TEXT);
612             if (flagText == null) {
613                 // fallback option.
614                 Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);
615                 if (t != null) {
616                     String text = t.getText(e);
617                     if (text != null)
618                         flagText = new String[] { text };
619                 }
620             }
621
622             // DEBUG TEXT
623             //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};
624
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);
631             }
632
633             Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);
634             Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);
635
636             Font font  = ElementUtils.getHintOrDefault(e, KEY_FLAG_FONT, FlagNode.DEFAULT_FONT); 
637             
638             FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);
639             flag.init(shape,
640                     flagText,
641                     STROKE,
642                     bc,
643                     fc,
644                     tc,
645                     (float) width,
646                     (float) height,
647                     (float) dir,
648                     (float) beakAngle,
649                     textArea,
650                     horizAlign.ordinal(),
651                     vertAlign.ordinal(),
652                     font);
653             AffineTransform at = ElementUtils.getTransform(e);
654             if(at != null) flag.setTransform(at);
655
656         }
657     }
658
659     static class TerminalPoint implements Terminal {
660     }
661
662     public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {
663         private static final long    serialVersionUID = -4194634598346105458L;
664
665         public static final Terminal             DEFAULT_T0       = new TerminalPoint();
666         public static final FlagTerminalTopology DEFAULT          = new FlagTerminalTopology(DEFAULT_T0);
667
668         final Terminal T0;
669
670         public FlagTerminalTopology(Terminal t) {
671             this.T0 = t;
672         }
673
674         @Override
675         public void getTerminals(IElement e, Collection<Terminal> result) {
676             result.add(T0);
677         }
678
679         @Override
680         public AffineTransform getTerminalPosition(IElement node, Terminal t) {
681             if (t == T0) {
682                 return new AffineTransform();
683             }
684             return null;
685         }
686
687         @Override
688         public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {
689             Type type = getType(node);
690             double d = getDirection(node);
691             if (t == T0) {
692                 switch (type) {
693                     case In: directions.add(d); break;
694                     case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;
695                 }
696                 //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));
697                 return true;
698             }
699             return false;
700         }
701
702 //        static final Path2D terminalShape;
703 //
704 //        static {
705 //            double s = .5;
706 //            Path2D p = new Path2D.Double();
707 //            p.moveTo(s, s);
708 //            p.lineTo(s, -s);
709 //            p.lineTo(-s, -s);
710 //            p.lineTo(-s, s);
711 //            p.closePath();
712 //            terminalShape = p;
713 //        }
714
715         @Override
716         public Shape getTerminalShape(IElement node, Terminal t) {
717             //return terminalShape;
718             //return null;
719             // For each terminal, return the whole shape of the element.
720             return ElementUtils.getElementShapeOrBounds(node);
721         }
722
723         @Override
724         public int hashCode() {
725             final int prime = 31;
726             int result = 1;
727             result = prime * result + ((T0 == null) ? 0 : T0.hashCode());
728             return result;
729         }
730
731         @Override
732         public boolean equals(Object obj) {
733             if (this == obj)
734                 return true;
735             if (obj == null)
736                 return false;
737             if (getClass() != obj.getClass())
738                 return false;
739             FlagTerminalTopology other = (FlagTerminalTopology) obj;
740             if (T0 == null) {
741                 if (other.T0 != null)
742                     return false;
743             } else if (!T0.equals(other.T0))
744                 return false;
745             return true;
746         }
747     }
748
749     public static AffineTransform getTransform(IElement e) {
750         AffineTransform at = ElementUtils.getTransform(e);
751         if (at == null)
752             return new AffineTransform();
753         return at;
754     }
755
756     public static double getDirection(IElement e) {
757         Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);
758         if (rotate != null) {
759             return rotate.getAngle(e);
760         }
761         return 0.0;
762     }
763
764     public static Type getType(IElement e) {
765         Type t = e.getHint(KEY_FLAG_TYPE);
766         return t != null ? t : Type.In;
767     }
768
769     public static Mode getMode(IElement e) {
770         Mode m = e.getHint(KEY_FLAG_MODE);
771         return m != null ? m : Mode.External;
772     }
773
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));
779     }
780
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));
784     }
785
786 }