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