]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/FlagClass.java
Merge commit '876ede6'
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / elementclass / FlagClass.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.g2d.elementclass;\r
13 \r
14 import java.awt.BasicStroke;\r
15 import java.awt.Color;\r
16 import java.awt.Shape;\r
17 import java.awt.geom.AffineTransform;\r
18 import java.awt.geom.Path2D;\r
19 import java.awt.geom.Rectangle2D;\r
20 import java.util.Collection;\r
21 \r
22 import org.simantics.g2d.diagram.IDiagram;\r
23 import org.simantics.g2d.diagram.handler.DataElementMap;\r
24 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
25 import org.simantics.g2d.element.ElementClass;\r
26 import org.simantics.g2d.element.ElementUtils;\r
27 import org.simantics.g2d.element.IElement;\r
28 import org.simantics.g2d.element.SceneGraphNodeKey;\r
29 import org.simantics.g2d.element.handler.InternalSize;\r
30 import org.simantics.g2d.element.handler.LifeCycle;\r
31 import org.simantics.g2d.element.handler.Outline;\r
32 import org.simantics.g2d.element.handler.Rotate;\r
33 import org.simantics.g2d.element.handler.SceneGraph;\r
34 import org.simantics.g2d.element.handler.TerminalLayout;\r
35 import org.simantics.g2d.element.handler.TerminalTopology;\r
36 import org.simantics.g2d.element.handler.Text;\r
37 import org.simantics.g2d.element.handler.impl.BorderColorImpl;\r
38 import org.simantics.g2d.element.handler.impl.DefaultTransform;\r
39 import org.simantics.g2d.element.handler.impl.FillColorImpl;\r
40 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;\r
41 import org.simantics.g2d.element.handler.impl.StaticSymbolImpl;\r
42 import org.simantics.g2d.element.handler.impl.TextImpl;\r
43 import org.simantics.g2d.image.Image;\r
44 import org.simantics.g2d.image.impl.ShapeImage;\r
45 import org.simantics.g2d.utils.Alignment;\r
46 import org.simantics.g2d.utils.geom.DirectionSet;\r
47 import org.simantics.scenegraph.Node;\r
48 import org.simantics.scenegraph.g2d.G2DParentNode;\r
49 import org.simantics.scenegraph.g2d.nodes.FlagNode;\r
50 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
51 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
52 \r
53 /**\r
54  * @author Tuukka Lehtonen\r
55  */\r
56 public class FlagClass {\r
57 \r
58     public static enum Type {\r
59         /// The input part of a pair of flags.\r
60         In,\r
61         /// The output part of a pair of flags.\r
62         Out;\r
63         public Type other() {\r
64             return this == Out ? In: Out;\r
65         }\r
66     }\r
67 \r
68     public static class Mode {\r
69         public static final Mode External = new External(1);\r
70         public static final Mode Internal = new Mode() {\r
71             public String toString() { return "Internal"; }\r
72         };\r
73     }\r
74 \r
75     public static class External extends Mode {\r
76         public final int count;\r
77         public External(int count) {\r
78             this.count = count;\r
79         }\r
80         @Override\r
81         public String toString() {\r
82             return "External(" + count + ")";\r
83         }\r
84     }\r
85 \r
86     private static final double       GLOBAL_SCALE                 = 0.1;\r
87     private static final double       FLAG_SIZE_SCALE              = 3 * GLOBAL_SCALE;\r
88 \r
89     public static final double        DEFAULT_WIDTH                = 70 * FLAG_SIZE_SCALE;\r
90     public static final double        DEFAULT_HEIGHT               = 20 * FLAG_SIZE_SCALE;\r
91     public static final double        DEFAULT_BEAK_ANGLE           = 60;\r
92 \r
93     public static final Key           KEY_FLAG_TYPE                = new KeyOf(Type.class, "FLAG_TYPE");\r
94     public static final Key           KEY_EXTERNAL                 = new KeyOf(Boolean.class, "FLAG_EXTERNAL");\r
95     public static final Key           KEY_FLAG_MODE                = new KeyOf(Mode.class, "FLAG_MODE");\r
96     public static final Key           KEY_FLAG_WIDTH               = new KeyOf(Double.class, "FLAG_WIDTH");\r
97     public static final Key           KEY_FLAG_HEIGHT              = new KeyOf(Double.class, "FLAG_HEIGHT");\r
98     public static final Key           KEY_FLAG_BEAK_ANGLE          = new KeyOf(Double.class, "FLAG_BEAK_ANGLE");\r
99     public static final Key           KEY_FLAG_TEXT                = new KeyOf(String[].class, "FLAG_TEXT");\r
100     public static final Key           KEY_FLAG_TEXT_AREA           = new KeyOf(Rectangle2D.class, "FLAG_TEXT_AREA_SIZE");\r
101     public static final Key           KEY_SHAPE                    = new KeyOf(Shape.class, "SHAPE");\r
102     public static final Key           KEY_TEXT_HORIZONTAL_ALIGN    = new KeyOf(Alignment.class, "TEXT_HORIZONTAL_ALIGN");\r
103     public static final Key           KEY_TEXT_VERTICAL_ALIGN      = new KeyOf(Alignment.class, "TEXT_VERTICAL_ALIGN");\r
104 \r
105     public static final Key          KEY_SG_NODE                  = new SceneGraphNodeKey(Node.class, "FLAG_SG_NODE");\r
106 \r
107     /**\r
108      * Indicates that this flag is connected to another flag.\r
109      */\r
110     private static final Key KEY_FLAG_CONNECTION_DATA     = new KeyOf(DataConnection.class, "FLAG_CONNECTION_DATA");\r
111     private static final Key KEY_FLAG_CONNECTION_ELEMENTS = new KeyOf(ElementConnection.class, "FLAG_CONNECTION_ELEMENTS");\r
112 \r
113     public interface Connection<T> {\r
114         T getFirst();\r
115         T getSecond();\r
116     }\r
117 \r
118     private static class Conn<T> implements Connection<T> {\r
119         private final T first;\r
120         private final T second;\r
121         public Conn(T first, T second) {\r
122             this.first = first;\r
123             this.second = second;\r
124         }\r
125         @Override\r
126         public T getFirst() {\r
127             return first;\r
128         }\r
129         @Override\r
130         public T getSecond() {\r
131             return second;\r
132         }\r
133     }\r
134     private static class ElementConnection extends Conn<IElement> {\r
135         public ElementConnection(IElement first, IElement second) {\r
136             super(first, second);\r
137             if (first == null)\r
138                 throw new IllegalArgumentException("first is null");\r
139             if (second == null)\r
140                 throw new IllegalArgumentException("second is null");\r
141         }\r
142     }\r
143     private static class DataConnection extends Conn<Object> {\r
144         public DataConnection(Object first, Object second) {\r
145             super(first, second);\r
146             if (first == null)\r
147                 throw new IllegalArgumentException("first is null");\r
148             // Second may be null to indicate "not-connected"\r
149         }\r
150         public boolean isConnected() {\r
151             return getSecond() != null;\r
152         }\r
153     }\r
154 \r
155     public static final FlagHandler  FLAG_HANDLER = new FlagHandler() {\r
156 \r
157         private static final long serialVersionUID = -4258875745321808416L;\r
158 \r
159         @Override\r
160         public Type getType(IElement e) {\r
161             return FlagClass.getType(e);\r
162         }\r
163 \r
164         @Override\r
165         public void setType(IElement e, Type type) {\r
166             e.setHint(KEY_FLAG_TYPE, type);\r
167         }\r
168 \r
169         @Override\r
170         public boolean isExternal(IElement e) {\r
171             return Boolean.TRUE.equals(e.getHint(KEY_EXTERNAL));\r
172         }\r
173 \r
174         @Override\r
175         public void setExternal(IElement e, boolean external) {\r
176             e.setHint(KEY_EXTERNAL, Boolean.valueOf(external));\r
177         }\r
178 \r
179         @Override\r
180         public Connection<IElement> getConnection(IElement e) {\r
181             return e.getHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
182         }\r
183 \r
184         @Override\r
185         public Connection<Object> getConnectionData(IElement e) {\r
186             DataConnection dc = e.getHint(KEY_FLAG_CONNECTION_DATA);\r
187             return (dc != null && dc.isConnected()) ? dc : null;\r
188         }\r
189 \r
190         @Override\r
191         public void connect(IElement e1, IElement e2) {\r
192             assert e1 != null && e2 != null;\r
193 \r
194             ElementConnection ce = new ElementConnection(e1, e2);\r
195             e1.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);\r
196             e2.setHint(KEY_FLAG_CONNECTION_ELEMENTS, ce);\r
197         }\r
198 \r
199         @Override\r
200         public void connectData(IElement e1, Object o1, Object o2) {\r
201             e1.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
202             e1.setHint(KEY_FLAG_CONNECTION_DATA, new DataConnection(o1, o2));\r
203         }\r
204 \r
205         @Override\r
206         public void disconnect(IElement local) {\r
207             assert local != null;\r
208             local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
209             DataConnection c = (DataConnection) local.removeHint(KEY_FLAG_CONNECTION_DATA);\r
210             if (c != null) {\r
211                 IElement remote = otherElement(local, c);\r
212                 if (remote != null) {\r
213                     local.removeHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
214                     remote.removeHint(KEY_FLAG_CONNECTION_DATA);\r
215                 }\r
216             }\r
217         }\r
218 \r
219         @Override\r
220         public boolean isWithinDiagram(IDiagram d, Connection<?> c) {\r
221             assert d != null;\r
222             assert c != null;\r
223             if (c instanceof DataConnection)\r
224                 return bothOnDiagram(d, (DataConnection) c);\r
225             if (c instanceof ElementConnection)\r
226                 return bothOnDiagram(d, (ElementConnection) c);\r
227             return false;\r
228         }\r
229 \r
230         @Override\r
231         public IElement getCorrespondence(IElement end) {\r
232             assert end != null;\r
233             DataConnection dc = (DataConnection) end.getHint(KEY_FLAG_CONNECTION_DATA);\r
234             if (dc != null && dc.isConnected())\r
235                 return otherElement(end, dc);\r
236             ElementConnection ec = (ElementConnection) end.getHint(KEY_FLAG_CONNECTION_ELEMENTS);\r
237             if (ec != null)\r
238                 return otherElement(end, ec);\r
239             return null;\r
240         }\r
241 \r
242         boolean bothOnDiagram(IDiagram d, DataConnection c) {\r
243             if (!c.isConnected())\r
244                 return false;\r
245 \r
246             DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
247             IElement eout = dem.getElement(d, c.getFirst());\r
248             IElement ein = dem.getElement(d, c.getSecond());\r
249             return eout != null && ein != null;\r
250         }\r
251 \r
252         boolean bothOnDiagram(IDiagram d, ElementConnection c) {\r
253             DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
254             Object o1 = dem.getData(d, c.getFirst());\r
255             Object o2 = dem.getData(d, c.getSecond());\r
256             return o1 != null && o2 != null;\r
257         }\r
258 \r
259         public IElement otherElement(IElement e, DataConnection c) {\r
260             if (!c.isConnected())\r
261                 return null;\r
262 \r
263             IDiagram d = ElementUtils.peekDiagram(e);\r
264             if (d == null)\r
265                 return null;\r
266 \r
267             DataElementMap dem = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
268             Object o = dem.getData(d, e);\r
269             if (c.getFirst().equals(o))\r
270                 return dem.getElement(d, c.getSecond());\r
271             if (c.getSecond().equals(o))\r
272                 return dem.getElement(d, c.getFirst());\r
273             throw new IllegalArgumentException("specified object '" + o + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");\r
274         }\r
275 \r
276         public IElement otherElement(IElement e, ElementConnection c) {\r
277             IElement a = c.getFirst();\r
278             IElement b = c.getSecond();\r
279             if (e == a)\r
280                 return b;\r
281             if (e == b)\r
282                 return a;\r
283             throw new IllegalArgumentException("specified element '" + e + "' is neither of the connected objects: first='" + c.getSecond() + "', second='" + c.getFirst() + "'");\r
284         }\r
285     };\r
286 \r
287     static final Shape staticShape;\r
288 \r
289     static {\r
290         Path2D path = new Path2D.Double();\r
291         staticShape = path;\r
292         createFlagShape(path, Type.In, Mode.External, DEFAULT_WIDTH, DEFAULT_HEIGHT, getBeakLength(DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE));\r
293     }\r
294 \r
295     public static final BasicStroke         STROKE                = new BasicStroke(0.15f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
296     static final Image               DEFAULT_IMAGE         = new ShapeImage(staticShape, null, STROKE);\r
297     static final StaticSymbolImpl    DEFAULT_STATIC_SYMBOL = new StaticSymbolImpl(DEFAULT_IMAGE);\r
298     static final FlagSize            DEFAULT_FLAG_SIZE     = new FlagSize(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_BEAK_ANGLE);\r
299     static final Initializer         DEFAULT_INITIALIZER   = new Initializer(Type.In, Mode.External);\r
300 \r
301     public static final ElementClass FLAGCLASS =\r
302         ElementClass.compile(\r
303                 DEFAULT_INITIALIZER,\r
304                 FLAG_HANDLER,\r
305                 DefaultTransform.INSTANCE,\r
306                 DEFAULT_FLAG_SIZE,\r
307                 BorderColorImpl.BLACK,\r
308                 FillColorImpl.WHITE,\r
309                 TextImpl.INSTANCE,\r
310                 FlagTerminalTopology.DEFAULT,\r
311                 FlagSceneGraph.INSTANCE,\r
312                 DEFAULT_STATIC_SYMBOL\r
313         ).setId(FlagClass.class.getSimpleName());\r
314 \r
315     public static ElementClass create(Terminal terminal) {\r
316         return ElementClass.compile(\r
317                 DEFAULT_INITIALIZER,\r
318                 FLAG_HANDLER,\r
319                 DefaultTransform.INSTANCE,\r
320                 DEFAULT_FLAG_SIZE,\r
321                 BorderColorImpl.BLACK,\r
322                 FillColorImpl.WHITE,\r
323                 TextImpl.INSTANCE,\r
324                 new FlagTerminalTopology(terminal),\r
325                 FlagSceneGraph.INSTANCE,\r
326                 DEFAULT_STATIC_SYMBOL,\r
327                 SimpleElementLayers.INSTANCE\r
328         ).setId(FlagClass.class.getSimpleName());\r
329     }\r
330 \r
331     public static ElementClass create(Terminal terminal, SceneGraph scn) {\r
332         return ElementClass.compile(\r
333                 DEFAULT_INITIALIZER,\r
334                 FLAG_HANDLER,\r
335                 DefaultTransform.INSTANCE,\r
336                 DEFAULT_FLAG_SIZE,\r
337                 BorderColorImpl.BLACK,\r
338                 FillColorImpl.WHITE,\r
339                 TextImpl.INSTANCE,\r
340                 new FlagTerminalTopology(terminal),\r
341                 scn,\r
342                 DEFAULT_STATIC_SYMBOL,\r
343                 SimpleElementLayers.INSTANCE\r
344         ).setId(FlagClass.class.getSimpleName());\r
345     }\r
346 \r
347     static class Initializer implements LifeCycle {\r
348         private static final long serialVersionUID = 4404942036933073584L;\r
349 \r
350         private final Type type;\r
351         private final Mode mode;\r
352 \r
353         Initializer(Type type, Mode mode) {\r
354             assert type != null;\r
355             assert mode != null;\r
356             this.type = type;\r
357             this.mode = mode;\r
358         }\r
359 \r
360         @Override\r
361         public void onElementActivated(IDiagram d, IElement e) {\r
362         }\r
363 \r
364         @Override\r
365         public void onElementCreated(IElement e) {\r
366             e.setHint(KEY_FLAG_TYPE, type);\r
367             e.setHint(KEY_FLAG_MODE, mode);\r
368             //e.setHint(ElementHints.KEY_COMPOSITE, AlphaComposite.SrcOver.derive(0.5f));\r
369         }\r
370 \r
371         @Override\r
372         public void onElementDeactivated(IDiagram d, IElement e) {\r
373         }\r
374 \r
375         @Override\r
376         public void onElementDestroyed(IElement e) {\r
377         }\r
378 \r
379         @Override\r
380         public int hashCode() {\r
381             final int prime = 31;\r
382             int result = 1;\r
383             result = prime * result + mode.hashCode();\r
384             result = prime * result + type.hashCode();\r
385             return result;\r
386         }\r
387 \r
388         @Override\r
389         public boolean equals(Object obj) {\r
390             if (this == obj)\r
391                 return true;\r
392             if (obj == null)\r
393                 return false;\r
394             if (getClass() != obj.getClass())\r
395                 return false;\r
396             Initializer other = (Initializer) obj;\r
397             if (!mode.equals(other.mode))\r
398                 return false;\r
399             if (!type.equals(other.type))\r
400                 return false;\r
401             return true;\r
402         }\r
403     };\r
404 \r
405     public static Path2D createFlagShape(Path2D path, Type type, Mode mode, double width, double height, double beakLength) {\r
406         double hh = height / 2;        \r
407         path.reset();\r
408         switch (type) {\r
409             case Out:\r
410                 if (mode instanceof External) {\r
411                     path.moveTo(0, hh);\r
412                     path.lineTo(width, hh);\r
413                     path.lineTo(width+beakLength, 0);\r
414                     path.lineTo(width, -hh);\r
415                     path.lineTo(0, -hh);\r
416                     path.closePath();\r
417                     path.moveTo(width, hh);\r
418                     path.lineTo(width, -hh);    \r
419                     int count = ((External)mode).count;\r
420                     if(count > 1) {\r
421                         double shadow=hh*0.25;\r
422                         double ix = beakLength \r
423                                 - 0.5*shadow*(1.0 + beakLength/hh);\r
424                         double iy = hh * (ix / beakLength - 1.0);\r
425                         for(int sid=1;sid<=Math.min(count-1, 4);++sid) {\r
426                             double dis = sid*shadow;\r
427                             path.moveTo(dis, hh+dis-shadow);\r
428                             path.lineTo(dis, hh+dis);\r
429                             path.lineTo(dis+width, hh+dis);\r
430                             path.lineTo(dis+width+beakLength, dis);\r
431                             path.lineTo(width + ix + dis, iy + dis);\r
432                         }\r
433                     } else {\r
434                         double left = 0;\r
435                         double right = width - 0;\r
436                         if (left < right) {\r
437                             path.moveTo(left, 0);\r
438                             path.lineTo(right, 0);\r
439                         }\r
440                     }\r
441                 } else if (mode == Mode.Internal) {\r
442                     path.moveTo(0, hh);\r
443                     path.lineTo(beakLength, 0);\r
444                     path.lineTo(0, -hh);\r
445                     path.closePath();\r
446                 }\r
447                 break;\r
448             case In:\r
449                 path.moveTo(0, 0);\r
450                 if (mode instanceof External) {\r
451                     path.lineTo(-beakLength, -hh);\r
452                     path.lineTo(-width-beakLength, -hh);\r
453                     path.lineTo(-width-beakLength, hh);\r
454                     path.lineTo(-beakLength, hh);\r
455                     path.closePath();\r
456                     path.moveTo(-beakLength, -hh);\r
457                     path.lineTo(-beakLength, hh);  \r
458                     int count = ((External)mode).count;\r
459                     if(count > 1) {\r
460                         double shadow=hh*0.25;\r
461                         double ix = beakLength \r
462                                 - 0.5*shadow*(1.0 + beakLength/hh);\r
463                         double iy = hh * (ix / beakLength - 1.0);\r
464                         double xDisp = -width-beakLength;\r
465                         for(int sid=1;sid<=Math.min(count-1, 4);++sid) {\r
466                             double dis = sid*shadow;\r
467                             path.moveTo(xDisp+dis, hh+dis-shadow);\r
468                             path.lineTo(xDisp+dis, hh+dis);\r
469                             path.lineTo(xDisp+dis+width, hh+dis);\r
470                             path.lineTo(xDisp+dis+width+beakLength, dis);                            \r
471                             path.lineTo(xDisp+width + ix + dis, iy + dis);\r
472                         }\r
473                     } else {\r
474                         double left = -width-beakLength+0;\r
475                         double right = -beakLength-0;       \r
476                         if (left < right) {\r
477                             path.moveTo(left, 0);\r
478                             path.lineTo(right, 0);\r
479                         }\r
480                     }\r
481                 } else if (mode == Mode.Internal) {\r
482                     path.lineTo(-beakLength, -hh);\r
483                     path.lineTo(-beakLength, hh);\r
484                     path.closePath();\r
485                 }\r
486                 break;\r
487         }\r
488         return path;\r
489     }\r
490 \r
491     public static Path2D createFlagShape(IElement e) {\r
492         Type type = getType(e);\r
493         Mode mode = e.getHint(KEY_FLAG_MODE);\r
494         double width = e.getHint(KEY_FLAG_WIDTH);\r
495         double height = e.getHint(KEY_FLAG_HEIGHT);\r
496         double beakLength = getBeakLength(e);\r
497         Path2D path = new Path2D.Double();\r
498         createFlagShape(path, type, mode, width, height, beakLength);\r
499         return path;\r
500     }\r
501 \r
502     static class FlagSize implements InternalSize, Outline, LifeCycle {\r
503 \r
504         private static final long serialVersionUID = 829379327756475944L;\r
505 \r
506         private final double length;\r
507         private final double thickness;\r
508         private final double beakAngle;\r
509 \r
510         public FlagSize(double length, double thickness, double beakAngle) {\r
511             this.length = length;\r
512             this.thickness = thickness;\r
513             this.beakAngle = beakAngle;\r
514         }\r
515 \r
516         @Override\r
517         public Shape getElementShape(IElement e) {\r
518             Shape shape = e.getHint(KEY_SHAPE);\r
519             if (shape != null)\r
520                 return shape;\r
521             return createFlagShape(e);\r
522         }\r
523 \r
524         @Override\r
525         public Rectangle2D getBounds(IElement e, Rectangle2D size) {\r
526             if (size == null)\r
527                 size = new Rectangle2D.Double();\r
528             Shape shape = getElementShape(e);\r
529             size.setFrame(shape.getBounds2D());\r
530             return size;\r
531         }\r
532 \r
533         @Override\r
534         public void onElementActivated(IDiagram d, IElement e) {\r
535         }\r
536 \r
537         @Override\r
538         public void onElementCreated(IElement e) {\r
539             e.setHint(KEY_FLAG_WIDTH, length);\r
540             e.setHint(KEY_FLAG_HEIGHT, thickness);\r
541             e.setHint(KEY_FLAG_BEAK_ANGLE, beakAngle);\r
542         }\r
543 \r
544         @Override\r
545         public void onElementDeactivated(IDiagram d, IElement e) {\r
546         }\r
547 \r
548         @Override\r
549         public void onElementDestroyed(IElement e) {\r
550         }\r
551 \r
552         @Override\r
553         public int hashCode() {\r
554             final int prime = 31;\r
555             int result = 1;\r
556             long temp;\r
557             temp = Double.doubleToLongBits(beakAngle);\r
558             result = prime * result + (int) (temp ^ (temp >>> 32));\r
559             temp = Double.doubleToLongBits(length);\r
560             result = prime * result + (int) (temp ^ (temp >>> 32));\r
561             temp = Double.doubleToLongBits(thickness);\r
562             result = prime * result + (int) (temp ^ (temp >>> 32));\r
563             return result;\r
564         }\r
565 \r
566         @Override\r
567         public boolean equals(Object obj) {\r
568             if (this == obj)\r
569                 return true;\r
570             if (obj == null)\r
571                 return false;\r
572             if (getClass() != obj.getClass())\r
573                 return false;\r
574             FlagSize other = (FlagSize) obj;\r
575             if (Double.doubleToLongBits(beakAngle) != Double.doubleToLongBits(other.beakAngle))\r
576                 return false;\r
577             if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length))\r
578                 return false;\r
579             if (Double.doubleToLongBits(thickness) != Double.doubleToLongBits(other.thickness))\r
580                 return false;\r
581             return true;\r
582         }\r
583     }\r
584 \r
585     static class FlagSceneGraph implements SceneGraph {\r
586         private static final long serialVersionUID = 35208146123929197L;\r
587 \r
588         public static final FlagSceneGraph INSTANCE = new FlagSceneGraph();\r
589 \r
590         @Override\r
591         public void cleanup(IElement e) {\r
592             ElementUtils.removePossibleNode(e, KEY_SG_NODE);\r
593         }\r
594 \r
595         @Override\r
596         public void init(IElement e, G2DParentNode parent) {\r
597             Color fc = ElementUtils.getFillColor(e, Color.WHITE);\r
598             Color bc = ElementUtils.getBorderColor(e, Color.BLACK);\r
599             Color tc = ElementUtils.getTextColor(e, Color.BLACK);\r
600 \r
601             Outline outline = e.getElementClass().getSingleItem(Outline.class);\r
602             Shape shape = outline.getElementShape(e);\r
603             Type type = getType(e);\r
604             double dir = getDirection(e);\r
605             double width = e.getHint(KEY_FLAG_WIDTH);\r
606             double height = e.getHint(KEY_FLAG_HEIGHT);\r
607             double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);\r
608 \r
609             String[] flagText = e.getHint(KEY_FLAG_TEXT);\r
610             if (flagText == null) {\r
611                 // fallback option.\r
612                 Text t = e.getElementClass().getAtMostOneItemOfClass(Text.class);\r
613                 if (t != null) {\r
614                     String text = t.getText(e);\r
615                     if (text != null)\r
616                         flagText = new String[] { text };\r
617                 }\r
618             }\r
619 \r
620             // DEBUG TEXT\r
621             //flagText = new String[] { String.format("%3.1f", dir) + " deg", "FOO"};\r
622 \r
623             Rectangle2D textArea = e.getHint(KEY_FLAG_TEXT_AREA);\r
624             if (textArea == null) {\r
625                 double beakLength = getBeakLength(height, beakAngle);\r
626                 textArea = type == Type.In\r
627                 ? new Rectangle2D.Double(-width-beakLength, -height*0.5, width, height)\r
628                 : new Rectangle2D.Double(0, -height*0.5, width, height);\r
629             }\r
630 \r
631             Alignment horizAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_HORIZONTAL_ALIGN, Alignment.LEADING);\r
632             Alignment vertAlign = ElementUtils.getHintOrDefault(e, KEY_TEXT_VERTICAL_ALIGN, Alignment.CENTER);\r
633 \r
634             FlagNode flag = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, ElementUtils.generateNodeId(e), FlagNode.class);\r
635             flag.init(shape,\r
636                     flagText,\r
637                     STROKE,\r
638                     bc,\r
639                     fc,\r
640                     tc,\r
641                     (float) width,\r
642                     (float) height,\r
643                     (float) dir,\r
644                     (float) beakAngle,\r
645                     textArea,\r
646                     horizAlign.ordinal(),\r
647                     vertAlign.ordinal());\r
648             AffineTransform at = ElementUtils.getTransform(e);\r
649             if(at != null) flag.setTransform(at);\r
650 \r
651         }\r
652     }\r
653 \r
654     static class TerminalPoint implements Terminal {\r
655     }\r
656 \r
657     public static class FlagTerminalTopology implements TerminalTopology, TerminalLayout {\r
658         private static final long    serialVersionUID = -4194634598346105458L;\r
659 \r
660         public static final Terminal             DEFAULT_T0       = new TerminalPoint();\r
661         public static final FlagTerminalTopology DEFAULT          = new FlagTerminalTopology(DEFAULT_T0);\r
662 \r
663         final Terminal T0;\r
664 \r
665         public FlagTerminalTopology(Terminal t) {\r
666             this.T0 = t;\r
667         }\r
668 \r
669         @Override\r
670         public void getTerminals(IElement e, Collection<Terminal> result) {\r
671             result.add(T0);\r
672         }\r
673 \r
674         @Override\r
675         public AffineTransform getTerminalPosition(IElement node, Terminal t) {\r
676             if (t == T0) {\r
677                 return new AffineTransform();\r
678             }\r
679             return null;\r
680         }\r
681 \r
682         @Override\r
683         public boolean getTerminalDirection(IElement node, Terminal t, DirectionSet directions) {\r
684             Type type = getType(node);\r
685             double d = getDirection(node);\r
686             if (t == T0) {\r
687                 switch (type) {\r
688                     case In: directions.add(d); break;\r
689                     case Out: directions.add(Math.IEEEremainder(d + 180.0, 360.0)); break;\r
690                 }\r
691                 //System.out.println("directions T0: " + Arrays.toString(directions.toArray()));\r
692                 return true;\r
693             }\r
694             return false;\r
695         }\r
696 \r
697 //        static final Path2D terminalShape;\r
698 //\r
699 //        static {\r
700 //            double s = .5;\r
701 //            Path2D p = new Path2D.Double();\r
702 //            p.moveTo(s, s);\r
703 //            p.lineTo(s, -s);\r
704 //            p.lineTo(-s, -s);\r
705 //            p.lineTo(-s, s);\r
706 //            p.closePath();\r
707 //            terminalShape = p;\r
708 //        }\r
709 \r
710         @Override\r
711         public Shape getTerminalShape(IElement node, Terminal t) {\r
712             //return terminalShape;\r
713             //return null;\r
714             // For each terminal, return the whole shape of the element.\r
715             return ElementUtils.getElementShapeOrBounds(node);\r
716         }\r
717 \r
718         @Override\r
719         public int hashCode() {\r
720             final int prime = 31;\r
721             int result = 1;\r
722             result = prime * result + ((T0 == null) ? 0 : T0.hashCode());\r
723             return result;\r
724         }\r
725 \r
726         @Override\r
727         public boolean equals(Object obj) {\r
728             if (this == obj)\r
729                 return true;\r
730             if (obj == null)\r
731                 return false;\r
732             if (getClass() != obj.getClass())\r
733                 return false;\r
734             FlagTerminalTopology other = (FlagTerminalTopology) obj;\r
735             if (T0 == null) {\r
736                 if (other.T0 != null)\r
737                     return false;\r
738             } else if (!T0.equals(other.T0))\r
739                 return false;\r
740             return true;\r
741         }\r
742     }\r
743 \r
744     public static AffineTransform getTransform(IElement e) {\r
745         AffineTransform at = ElementUtils.getTransform(e);\r
746         if (at == null)\r
747             return new AffineTransform();\r
748         return at;\r
749     }\r
750 \r
751     public static double getDirection(IElement e) {\r
752         Rotate rotate = e.getElementClass().getAtMostOneItemOfClass(Rotate.class);\r
753         if (rotate != null) {\r
754             return rotate.getAngle(e);\r
755         }\r
756         return 0.0;\r
757     }\r
758 \r
759     public static Type getType(IElement e) {\r
760         Type t = e.getHint(KEY_FLAG_TYPE);\r
761         return t != null ? t : Type.In;\r
762     }\r
763 \r
764     public static Mode getMode(IElement e) {\r
765         Mode m = e.getHint(KEY_FLAG_MODE);\r
766         return m != null ? m : Mode.External;\r
767     }\r
768 \r
769     public static double getBeakLength(IElement e) {\r
770         double height = e.getHint(KEY_FLAG_HEIGHT);\r
771         double beakAngle = e.getHint(KEY_FLAG_BEAK_ANGLE);\r
772         beakAngle = Math.min(180, Math.max(10, beakAngle));\r
773         return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));\r
774     }\r
775 \r
776     public static double getBeakLength(double height, double beakAngle) {\r
777         beakAngle = Math.min(180, Math.max(10, beakAngle));\r
778         return height / (2*Math.tan(Math.toRadians(beakAngle) / 2));\r
779     }\r
780 \r
781 }\r