]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java
300e74d31e75e04ee7ecf750ab0b2e58ba513951
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / element / ElementUtils.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.element;
13
14 import java.awt.Color;
15 import java.awt.Font;
16 import java.awt.Shape;
17 import java.awt.Stroke;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Area;
20 import java.awt.geom.NoninvertibleTransformException;
21 import java.awt.geom.Point2D;
22 import java.awt.geom.Rectangle2D;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.function.Consumer;
29
30 import org.simantics.g2d.canvas.ICanvasContext;
31 import org.simantics.g2d.diagram.IDiagram;
32 import org.simantics.g2d.diagram.handler.DataElementMap;
33 import org.simantics.g2d.diagram.handler.PickRequest;
34 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
35 import org.simantics.g2d.diagram.handler.Topology.Terminal;
36 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
37 import org.simantics.g2d.element.handler.Adapter;
38 import org.simantics.g2d.element.handler.AdditionalColor;
39 import org.simantics.g2d.element.handler.BendsHandler;
40 import org.simantics.g2d.element.handler.BendsHandler.Bend;
41 import org.simantics.g2d.element.handler.BorderColor;
42 import org.simantics.g2d.element.handler.Clickable;
43 import org.simantics.g2d.element.handler.Clickable.ClickListener;
44 import org.simantics.g2d.element.handler.Clickable.PressStatus;
45 import org.simantics.g2d.element.handler.EdgeVisuals;
46 import org.simantics.g2d.element.handler.ElementAdapter;
47 import org.simantics.g2d.element.handler.FillColor;
48 import org.simantics.g2d.element.handler.Hover;
49 import org.simantics.g2d.element.handler.InternalSize;
50 import org.simantics.g2d.element.handler.Move;
51 import org.simantics.g2d.element.handler.Outline;
52 import org.simantics.g2d.element.handler.Parameters;
53 import org.simantics.g2d.element.handler.Parent;
54 import org.simantics.g2d.element.handler.Pick;
55 import org.simantics.g2d.element.handler.Resize;
56 import org.simantics.g2d.element.handler.Scale;
57 import org.simantics.g2d.element.handler.Stateful;
58 import org.simantics.g2d.element.handler.TerminalLayout;
59 import org.simantics.g2d.element.handler.TerminalTopology;
60 import org.simantics.g2d.element.handler.Text;
61 import org.simantics.g2d.element.handler.TextColor;
62 import org.simantics.g2d.element.handler.TextEditor;
63 import org.simantics.g2d.element.handler.TextFont;
64 import org.simantics.g2d.element.handler.Transform;
65 import org.simantics.g2d.participant.TransformUtil;
66 import org.simantics.g2d.utils.GeometryUtils;
67 import org.simantics.g2d.utils.geom.DirectionSet;
68 import org.simantics.scenegraph.INode;
69 import org.simantics.scenegraph.ParentNode;
70 import org.simantics.utils.ObjectUtils;
71 import org.simantics.utils.datastructures.hints.IHintContext;
72 import org.simantics.utils.datastructures.hints.IHintContext.Key;
73
74 /**
75  * Utils for element users.
76  * 
77  * @See {@link TerminalUtil}
78  * @See {@link ElementHandlerUtils} Utils for element handler (coders)
79  * @author Toni Kalajainen
80  */
81 public class ElementUtils {
82
83     @SuppressWarnings("unchecked")
84     public static <T> T getObject(IElement e) {
85         return (T) e.getHint(ElementHints.KEY_OBJECT);
86     }
87
88     public static void disable(IElement e)
89     {
90         Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);
91         enabled.setEnabled(e, false);
92     }
93
94     public static void enable(IElement e)
95     {
96         Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);
97         enabled.setEnabled(e, true);
98     }
99
100     /**
101      * @param e
102      * @return
103      */
104     public static boolean isHidden(IElement e) {
105         return e.containsHint(ElementHints.KEY_HIDDEN);
106     }
107
108     /**
109      * @param e
110      * @param state <code>null</code> to remove hidden state
111      * @return
112      */
113     public static void setHidden(IElement e, boolean hidden) {
114         if (hidden)
115             e.setHint(ElementHints.KEY_HIDDEN, HideState.COMPLETELY_HIDDEN);
116         else
117             e.removeHint(ElementHints.KEY_HIDDEN);
118     }
119
120     public static void setText(IElement e, String text)
121     {
122         Text t = e.getElementClass().getSingleItem(Text.class);
123         t.setText(e, text);
124     }
125
126     public static String getText(IElement e)
127     {
128         Text t = e.getElementClass().getSingleItem(Text.class);
129         return t.getText(e);
130     }
131
132     /**
133      * Resizes or scales an element to fit it into a rectangle.
134      * 
135      * @param e element
136      * @param rect rectangle on diagram
137      */
138     public static void fitToRectangle(IElement e, Rectangle2D rect)
139     {
140         ElementClass ec = e.getElementClass();
141         Move m = ec.getSingleItem(Move.class);
142         InternalSize b = ec.getSingleItem(InternalSize.class);
143         Rectangle2D internalSize = b.getBounds(e, null);
144         if (internalSize == null)
145             return;
146
147         Resize rs = ec.getAtMostOneItemOfClass(Resize.class);
148
149         Scale s = ec.getAtMostOneItemOfClass(Scale.class);
150         Point2D scale = s==null?new Point2D.Double(1.0,1.0):s.getScale(e);
151         double width = rect.getWidth();
152         double height = rect.getHeight();
153         double aspectRatio = width/height;
154
155         Double requiredAspectRatio = rs==null?null:rs.getFixedAspectRatio(e);
156         if (requiredAspectRatio!=null)
157         {
158             if (aspectRatio>requiredAspectRatio)
159                 width = height*requiredAspectRatio;
160             else
161                 height = width / requiredAspectRatio;
162         }
163
164         // Resize it
165         if (rs!=null) {
166             m.moveTo(e, rect.getX(), rect.getY());
167             if (scale!=null) {
168                 width /= scale.getX();
169                 height /= scale.getY();
170             }
171             Rectangle2D r = new Rectangle2D.Double(0, 0, width, height);
172             rs.resize(e, r);
173         } else
174             // Scale it
175             if (s!=null) {
176                 double sx = rect.getWidth()  / internalSize.getWidth();
177                 double sy = rect.getHeight() / internalSize.getHeight();
178                 double px = rect.getX() - internalSize.getX()*sx;
179                 double py = rect.getY() - internalSize.getY()*sy;
180                 m.moveTo(e, px, py);
181                 scale.setLocation(sx, sy);
182                 s.setScale(e, scale);
183             }
184     }
185
186     public static void addClickListener(IElement e, ICanvasContext ctx, ClickListener listener)
187     {
188         Clickable clickable = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);
189         clickable.addListener(e, ctx, ctx.getThreadAccess(), listener);
190     }
191
192     public static Point2D getPos(IElement e)
193     {
194         Move m = e.getElementClass().getSingleItem(Move.class);
195         return m.getPosition(e);
196     }
197
198     public static Point2D getPos(IElement e, Point2D result)
199     {
200         Move m = e.getElementClass().getSingleItem(Move.class);
201         if (result == null)
202             result = new Point2D.Double();
203         Point2D p = m.getPosition(e);
204         result.setLocation(p);
205         return result;
206     }
207
208     public static Point2D getAbsolutePos(IElement e)
209     {
210         Transform tr = e.getElementClass().getSingleItem(Transform.class);
211         AffineTransform at = tr.getTransform(e);
212         return new Point2D.Double(at.getTranslateX(), at.getTranslateY());
213     }
214
215     public static Point2D getAbsolutePos(IElement e, Point2D result)
216     {
217         Transform tr = e.getElementClass().getSingleItem(Transform.class);
218         AffineTransform at = tr.getTransform(e);
219         if (result == null)
220             result = new Point2D.Double();
221         result.setLocation(at.getTranslateX(), at.getTranslateY());
222         return result;
223     }
224
225     public static void setPos(IElement e, Point2D newPosition)
226     {
227         Move m = e.getElementClass().getSingleItem(Move.class);
228         m.moveTo(e, newPosition.getX(), newPosition.getY());
229     }
230
231     public static void setPos(IElement e, double x, double y)
232     {
233         Move m = e.getElementClass().getSingleItem(Move.class);
234         m.moveTo(e,x, y);
235     }
236
237     public static IElement getByData(IDiagram d, Object data)
238     {
239         DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);
240         return map.getElement(d, data);
241     }
242
243     public static Object getData(IDiagram d, IElement element)
244     {
245         DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);
246         return map.getData(d, element);
247     }
248
249     /**
250      * Get all terminals of an element.
251      * 
252      * @param e element
253      * @param result a store for the terminals
254      * @param clearResult <code>true</code> to clear the result collection
255      *        before filling it
256      * @return the specified result collection
257      */
258     public static Collection<Terminal> getTerminals(IElement e, Collection<Terminal> result, boolean clearResult) {
259         if (clearResult)
260             result.clear();
261         TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);
262         tt.getTerminals(e, result);
263         return result;
264     }
265
266     /**
267      * Get a terminal of an element assuming there is only a single terminal.
268      * 
269      * @param e element
270      * @param t terminal
271      * @return the only terminal of element e
272      * @throws IllegalArgumentException if there are zero or multiple terminals
273      */
274     public static Terminal getSingleTerminal(IElement e) {
275         ArrayList<Terminal> ts = new ArrayList<Terminal>(4);
276         TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);
277         tt.getTerminals(e, ts);
278         if (ts.size() != 1)
279             throw new IllegalArgumentException("expected 1 terminal, element e has " + ts.size() + " terminals: " + ts);
280         return ts.get(0);
281     }
282
283     /**
284      * Get a terminal of an element assuming there is only a single terminal.
285      * If there are no or multiple terminals, <code>null</code> is returned.
286      * 
287      * @param e element
288      * @param t terminal
289      * @return
290      */
291     public static Terminal peekSingleTerminal(IElement e) {
292         ArrayList<Terminal> ts = new ArrayList<Terminal>(4);
293         TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);
294         tt.getTerminals(e, ts);
295         if (ts.size() != 1)
296             return null;
297         return ts.get(0);
298     }
299
300     /**
301      * Get allowed outward directions of a terminal
302      * @param e element
303      * @param t terminal
304      * @return
305      */
306     public static DirectionSet getTerminalDirection(IElement e, Terminal t)
307     {
308         List<TerminalLayout>    tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
309         DirectionSet    result = new DirectionSet();
310         for (TerminalLayout tl : tls) {
311             tl.getTerminalDirection(e, t, result);
312         }
313         return result;
314     }
315
316     public static AffineTransform getTransform(IElement e)
317     {
318         return e.getElementClass().getSingleItem(Transform.class).getTransform(e);
319     }
320
321     public static AffineTransform getTransform(IElement e, AffineTransform result)
322     {
323         if (e == null)
324             return result;
325         AffineTransform tr = e.getElementClass().getSingleItem(Transform.class).getTransform(e);
326         result.setTransform(tr);
327         return result;
328     }
329
330     /**
331      * @param e the element to get the local transform from
332      * @param result the transform to set to the local transform value or
333      *        <code>null</code> to allocate a new transform if the element
334      *        doesn't provide one. By providing a result transform one can make
335      *        sure that no internal state of the element is returned.
336      * @return the provided result transform or a new transform instance
337      *         depending on the arguments
338      */
339     public static AffineTransform getLocalTransform(IElement e, AffineTransform result)
340     {
341         AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
342         if (result == null)
343             result = new AffineTransform();
344         if (at != null)
345             result.setTransform(at);
346         return result;
347     }
348
349     public static void setTransform(IElement e, AffineTransform at)
350     {
351         e.getElementClass().getSingleItem(Transform.class).setTransform(e, at);
352     }
353
354     public static void setParameters(IElement e, Map<String,Object> parameters)
355     {
356         Parameters ps = e.getElementClass().getSingleItem(Parameters.class);
357         if(ps != null) ps.setParameters(e, parameters);
358     }
359
360     public static Map<String,Object> getParameters(IElement e)
361     {
362         Parameters ps = e.getElementClass().getAtMostOneItemOfClass(Parameters.class);
363         return ps != null ? ps.getParameters(e) : null;
364     }
365
366     public static AffineTransform getInvTransform(IElement e)
367     {
368         try {
369             return e.getElementClass().getSingleItem(Transform.class).getTransform(e).createInverse();
370         } catch (NoninvertibleTransformException e1) {
371             throw new RuntimeException(e1);
372         }
373     }
374
375
376     /**
377      * Element to canvas coordinates
378      * @param e
379      * @param elementPoint
380      * @param canvasPoint
381      * @return
382      */
383     public static Point2D elementToCanvasCoordinate(IElement e, Point2D elementPoint, Point2D canvasPoint)
384     {
385         Transform               t                       = e.getElementClass().getSingleItem(Transform.class);
386         AffineTransform at                      = t.getTransform(e);
387         return at.transform(elementPoint, canvasPoint);
388     }
389
390     /**
391      * Element to control coordinates
392      * @param e
393      * @param ctx
394      * @param elementPoint
395      * @param controlPoint
396      * @return
397      */
398     public static Point2D elementToControlCoordinate(IElement e, ICanvasContext ctx, Point2D elementPoint, Point2D controlPoint)
399     {
400         Transform               t                       = e.getElementClass().getSingleItem(Transform.class);
401         TransformUtil   util            = ctx.getSingleItem(TransformUtil.class);
402         Point2D                 canvasPoint = t.getTransform(e).transform(elementPoint, null);
403         return                                            util.getTransform().transform(elementPoint, canvasPoint);
404     }
405
406     public static Point2D controlToElementCoordinate(IElement e, ICanvasContext ctx, Point2D controlPoint, Point2D elementPoint)
407     {
408         Transform               t                       = e.getElementClass().getSingleItem(Transform.class);
409         AffineTransform at                      = t.getTransform(e);
410         TransformUtil   util            = ctx.getSingleItem(TransformUtil.class);
411         Point2D canvasPoint = util.controlToCanvas(controlPoint, new Point2D.Double());
412         if (elementPoint==null) elementPoint = new Point2D.Double();
413         try {
414             at.inverseTransform(canvasPoint, elementPoint);
415             return elementPoint;
416         } catch (NoninvertibleTransformException e1) {
417             throw new RuntimeException(e1);
418         }
419     }
420
421     public static Point2D controlToCanvasCoordinate(ICanvasContext ctx, Point2D controlPoint, Point2D canvasPoint)
422     {
423         TransformUtil tu = ctx.getSingleItem(TransformUtil.class);
424         return tu.controlToCanvas(controlPoint, canvasPoint);
425     }
426
427
428     public static PressStatus getPressStatus(IElement e, ICanvasContext ctx)
429     {
430         Clickable c = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);
431         if (c==null) return null;
432         return c.getPressStatus(e, ctx);
433     }
434
435     public static Color getBorderColor(IElement e)
436     {
437         return getBorderColor(e, null);
438     }
439
440     public static Color getFillColor(IElement e)
441     {
442         return getFillColor(e, null);
443     }
444
445     public static Color getAdditionalColor(IElement e)
446     {
447         return getAdditionalColor(e, null);
448     }
449
450     public static Color getTextColor(IElement e)
451     {
452         return getTextColor(e, null);
453     }
454
455     /**
456      * Get border color of element of return defaultValue if border color is not
457      * available.
458      * 
459      * @param e
460      * @param defaultValue
461      * @return
462      */
463     public static Color getBorderColor(IElement e, Color defaultValue)
464     {
465         BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class);
466         if (bc==null) return defaultValue;
467         Color c = bc.getBorderColor(e);
468         return c != null ? c : defaultValue;
469     }
470
471     /**
472      * Get fill color of element of return defaultValue if fill color is not
473      * available.
474      * 
475      * @param e
476      * @param defaultValue
477      * @return
478      */
479     public static Color getFillColor(IElement e, Color defaultValue)
480     {
481         FillColor fc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class);
482         if (fc==null) return defaultValue;
483         Color c = fc.getFillColor(e);
484         return c != null ? c : defaultValue;
485     }
486
487     /**
488      * Get additional color of element of return defaultValue if additional
489      * color is not available.
490      * 
491      * @param e
492      * @param defaultValue
493      * @return
494      */
495     public static Color getAdditionalColor(IElement e, Color defaultValue)
496     {
497         AdditionalColor ac = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class);
498         if (ac==null) return null;
499         Color c = ac.getAdditionalColor(e);
500         return c != null ? c : defaultValue;
501     }
502
503     /**
504      * Get text color of element of return defaultValue if text color is not
505      * available.
506      * 
507      * @param e
508      * @param defaultValue
509      * @return
510      */
511     public static Color getTextColor(IElement e, Color defaultValue)
512     {
513         TextColor tc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class);
514         if (tc==null) return defaultValue;
515         Color c = tc.getTextColor(e);
516         return c != null ? c : defaultValue;
517     }
518
519     public static TextEditor getTextEditor(IElement e)
520     {
521         TextEditor ed = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);
522         return ed;
523     }
524
525     public static void setBorderColor(IElement e, Color color)
526     {
527         BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class);
528         if (bc==null) return;
529         bc.setBorderColor(e, color);
530     }
531
532     public static void setFillColor(IElement e, Color color)
533     {
534         FillColor bc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class);
535         if (bc==null) return;
536         bc.setFillColor(e, color);
537     }
538
539     public static void setAdditionalColor(IElement e, Color color)
540     {
541         AdditionalColor bc = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class);
542         if (bc==null) return;
543         bc.setAdditionalColor(e, color);
544     }
545
546     public static void setTextColor(IElement e, Color color)
547     {
548         TextColor bc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class);
549         if (bc==null) return;
550         bc.setTextColor(e, color);
551     }
552
553     public static void setEdgeStroke(IElement e, Stroke s)
554     {
555         EdgeVisuals ev = e.getElementClass().getSingleItem(EdgeVisuals.class);
556         ev.setStroke(e, s);
557     }
558
559     /**
560      * Fill given map with element bounds (the bounds on diagram)
561      * 
562      * @param elements
563      * @param rects structure to be filled or null (instantates new)
564      * @return rects or newly instantiated structure
565      */
566     public static Map<IElement, Rectangle2D> getElementBoundsOnDiagram(Collection<IElement> elements, Map<IElement, Rectangle2D> rects)
567     {
568         if (rects == null) rects = new HashMap<IElement, Rectangle2D>();
569         for (IElement e : elements) {
570             Shape shape = getElementBoundsOnDiagram(e);
571             rects.put(e, shape.getBounds2D());
572         }
573         return rects;
574     }
575
576     /**
577      * get element bounds
578      * @param e element
579      * @return element bounds in element coordinates
580      */
581     public static Rectangle2D getElementBounds(IElement e)
582     {
583         InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);
584         return b.getBounds(e, new Rectangle2D.Double());
585     }
586
587     /**
588      * get element bounds
589      * @param e element
590      * @param result a rectangle for storing the result
591      * @return the specified result rectangle
592      */
593     public static Rectangle2D getElementBounds(IElement e, Rectangle2D result)
594     {
595         InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);
596         return b.getBounds(e, result);
597     }
598
599     /**
600      * Get rough estimation of outer bounds of an element
601      * @param e element
602      * @return bounds on a diagram
603      */
604     public static Shape getElementBoundsOnDiagram(IElement e)
605     {
606         Rectangle2D elementBounds = getElementBounds(e);
607         Transform t = e.getElementClass().getSingleItem(Transform.class);
608         AffineTransform canvasToElement = t.getTransform(e);
609         return GeometryUtils.transformShape(elementBounds, canvasToElement);
610     }
611
612     /**
613      * Get rough estimation of outer bounds of an element
614      * @param e element
615      * @param result a rectangle for storing the result
616      * @return bounds on a diagram
617      */
618     public static Rectangle2D getElementBoundsOnDiagram(IElement e, Rectangle2D result)
619     {
620         result = getElementBounds(e, result);
621         Transform t = e.getElementClass().getSingleItem(Transform.class);
622         AffineTransform canvasToElement = t.getTransform(e);
623         Shape shp = GeometryUtils.transformShape(result, canvasToElement);
624         result.setFrame(shp.getBounds2D());
625         return result;
626     }
627
628     /**
629      * Get union of outer bounds of a set of elements
630      * @param elements
631      * @return Union of element bounds (on diagram) or null
632      */
633     public static Shape getElementBoundsOnDiagram(Collection<IElement> elements)
634     {
635         if (elements.size()==0) return null;
636         if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next());
637         Area a = new Area();
638         for (IElement e : elements) {
639             Shape bounds = getElementBoundsOnDiagram(e);
640             Area ae = bounds instanceof Area ? (Area) bounds : new Area(bounds);
641             a.add(ae);
642         }
643         return a;
644     }
645
646     /**
647      * Get union of outer bounds of a set of elements
648      * @param elements
649      * @return Union of element bounds (on diagram) or null
650      */
651     public static Rectangle2D getSurroundingElementBoundsOnDiagram(Collection<IElement> elements)
652     {
653         if (elements.size()==0) return null;
654         if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next()).getBounds2D();
655         double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, maxX = -Double.MAX_VALUE, maxY = -Double.MAX_VALUE;
656         for (IElement e : elements) {
657             Rectangle2D bounds = getElementBoundsOnDiagram(e).getBounds2D();
658             if (bounds.getMinX() < minX) minX = bounds.getMinX();
659             if (bounds.getMinY() < minY) minY = bounds.getMinY();
660             if (bounds.getMaxX() > maxX) maxX = bounds.getMaxX();
661             if (bounds.getMaxY() > maxY) maxY = bounds.getMaxY();
662         }
663         return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY);
664     }
665
666     /**
667      * Get as accurate shape if available
668      * 
669      * @param e
670      * @return accurate shape of an element or <code>null</code> if shape is not available
671      */
672     public static Shape getElementShape(IElement e)
673     {
674         List<Outline> shapeProviders = e.getElementClass().getItemsByClass(Outline.class);
675         if (shapeProviders.isEmpty()) return null;
676         if (shapeProviders.size()==1) return shapeProviders.iterator().next().getElementShape(e);
677         Area a = new Area();
678         for (Outline es : shapeProviders)
679         {
680             Shape shape = es.getElementShape(e);
681             Area ae = shape instanceof Area ? (Area) shape : new Area(shape);
682             a.add(ae);
683         }
684         return a;
685     }
686
687     public static Shape getElementShapeOnDiagram(IElement e)
688     {
689         Shape shape = getElementShape(e);
690         if (shape == null)
691             return null;
692         Transform t = e.getElementClass().getSingleItem(Transform.class);
693         AffineTransform canvasToElement = t.getTransform(e);
694         return GeometryUtils.transformShape(shape, canvasToElement);
695     }
696
697     /**
698      * Get element shape is one exists otherwise its bounds
699      * @param e
700      * @return shape or bounds
701      */
702     public static Shape getElementShapeOrBounds(IElement e)
703     {
704         Shape shape = getElementShape(e);
705         if (shape!=null) return shape;
706         return getElementBounds(e);
707     }
708
709     /**
710      * Get element shape is one exists otherwise its bounds
711      * @param e
712      * @return shape or bounds
713      */
714     public static Shape getElementShapeOrBoundsOnDiagram(IElement e)
715     {
716         Shape shape = getElementShapeOnDiagram(e);
717         if (shape!=null) return shape;
718         return getElementBoundsOnDiagram(e);
719     }
720
721     public static Shape getElementShapesOnDiagram(Collection<IElement> elements)
722     {
723         if (elements.isEmpty()) return null;
724         if (elements.size()==1) {
725             //ITask task = ThreadLogger.getInstance().begin("single element shape: " + elements.iterator().next() + ")");
726             Shape shp = getElementShapeOrBoundsOnDiagram(elements.iterator().next());
727             //task.finish();
728             return shp;
729         }
730         Area a = new Area();
731         //ITask task = ThreadLogger.getInstance().begin("union of " + elements.size() + " element shapes");
732         for (IElement e : elements) {
733             //ITask task2 = ThreadLogger.getInstance().begin("calculate area of " + e);
734             Shape shape = getElementShapeOrBoundsOnDiagram(e);
735             //task2.finish();
736             //task2 = ThreadLogger.getInstance().begin("construct area from " + shape);
737             Area aa = null;
738             if (shape instanceof Area)
739                 aa = (Area)shape;
740             else
741                 aa = new Area(shape);
742             //task2.finish();
743             //task2 = ThreadLogger.getInstance().begin("union area " + aa);
744             a.add(aa);
745             //task2.finish();
746         }
747         //task.finish();
748         return a;
749     }
750
751     public static Shape mergeShapes(Collection<Shape> shapes)
752     {
753         if (shapes.isEmpty()) return null;
754         if (shapes.size()==1) return shapes.iterator().next();
755         Area a = new Area();
756         for (Shape s : shapes)
757             a.add(new Area(s));
758         return a;
759     }
760
761     public static boolean pickInElement(IElement e, ICanvasContext ctx, PickRequest req)
762     {
763         Rectangle2D elementBounds = getElementBounds(e);
764
765         // Pick with pick handler(s)
766         List<Pick> pickHandlers = e.getElementClass().getItemsByClass(Pick.class);
767         if (!pickHandlers.isEmpty())
768         {
769             // Rough filtering with bounds
770             if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;
771
772             // Convert pick shape to element coordinates
773             for (Pick p : pickHandlers)
774             {
775                 if (p.pickTest(e, req.pickArea, req.pickPolicy))
776                     return true;
777             }
778             return false;
779         }
780
781         // Pick with shape handler(s)
782         List<Outline> shapeHandlers = e.getElementClass().getItemsByClass(Outline.class);
783         if (!shapeHandlers.isEmpty())
784         {
785             // Rough filtering with bounds
786             if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;
787
788             // Intersection with one shape is enough
789             if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
790             {
791                 for (Outline es : shapeHandlers)
792                 {
793                     Shape elementShape = es.getElementShape(e);
794                     if (GeometryUtils.intersects(req.pickArea, elementShape))
795                         return true;
796                 }
797                 return false;
798             }
799
800             // Contains of all shapes is required
801             if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
802             {
803                 for (Outline es : shapeHandlers)
804                 {
805                     Shape elementShape = es.getElementShape(e);
806                     if (!GeometryUtils.contains(req.pickArea, elementShape))
807                         return false;
808                 }
809                 return true;
810             }
811             return false;
812         }
813
814         // Pick by rectangle
815         if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
816         {
817             if (GeometryUtils.intersects(req.pickArea, elementBounds))
818                 return true;
819         }
820
821         else
822
823             if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
824             {
825                 if (GeometryUtils.contains(req.pickArea, elementBounds))
826                     return true;
827             }
828         return false;
829     }
830
831     /**
832      * Get bends of an edge
833      * 
834      * @param e edge
835      * @param bends the handles of each bend point
836      * @param points collection to be filled with the bends points in the same
837      *        order as the bend handle objects
838      */
839     public static void getBends(IElement e, List<Bend> bends, List<Point2D> points)
840     {
841         BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
842         bh.getBends(e, bends);
843         for (Bend b : bends)
844         {
845             Point2D pos = new Point2D.Double();
846             bh.getBendPosition(e, b, pos);
847             points.add(pos);
848         }
849     }
850
851     /**
852      * Get bends of an edge
853      * @param e edge
854      * @param points collection to be filled with the bends points
855      */
856     public static void getBends(IElement e, List<Point2D> points)
857     {
858         BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
859         int bendCount = bh.getBendsCount(e);
860         ArrayList<Bend> bends = new ArrayList<Bend>(bendCount);
861         getBends(e, bends, points);
862     }
863
864
865     public static void resizeElement(IElement e, double x, double y, double w, double h) {
866         Move m = e.getElementClass().getSingleItem(Move.class);
867         m.moveTo(e, x, y);
868         Resize s = e.getElementClass().getSingleItem(Resize.class);
869         s.resize(e, new Rectangle2D.Double(0,0,w,h));
870     }
871
872     public static <T> T getHintOrDefault(IHintContext e, Key key, T defaultValue) {
873         T t = e.getHint(key);
874         assert key.isValueAccepted(defaultValue);
875         return t == null ? defaultValue : t;
876     }
877
878     public static void setOrRemoveHint(IHintContext e, Key key, Object value) {
879         if (value == null) {
880             e.removeHint(key);
881         } else {
882             assert key.isValueAccepted(value);
883             e.setHint(key, value);
884         }
885     }
886
887     public static boolean elementEquals(IElement e1, IElement e2) {
888         Object o1 = getObject(e1);
889         Object o2 = getObject(e2);
890         if (o1 == null && o2 == null)
891             return ObjectUtils.objectEquals(e1, e2);
892         return ObjectUtils.objectEquals(o1, o2);
893     }
894
895     public static IElement getDiagramMappedElement(IElement e) {
896         IDiagram d = e.peekDiagram();
897         if (d == null)
898             return e;
899         DataElementMap map = d.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
900         if (map == null)
901             return e;
902         Object o = map.getData(d, e);
903         if (o == null)
904             return e;
905         IElement mapped = map.getElement(d, o);
906         return mapped != null ? mapped : e;
907     }
908
909     /**
910      * Calculates the center of the bounding box containing all the specified
911      * elements.
912      * 
913      * @param the elements for which to calculate the center of a containing
914      *        bounding box
915      * @param pivotPoint a Point2D for writing the result of the calculation or
916      *        <code>null</code> to allocate a new Point2D if necessary
917      * @return the center of the containing bounding box or <code>null</code> if
918      *         there are no elements
919      */
920     public static Point2D getElementBoundsCenter(Collection<IElement> elements, Point2D result) {
921         Shape b = getElementBoundsOnDiagram(elements);
922         if (b == null)
923             return null;
924         Rectangle2D bounds = b.getBounds2D();
925         if (result == null)
926             result = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
927         else
928             result.setLocation(bounds.getCenterX(), bounds.getCenterY());
929         return result;
930     }
931
932     /**
933      * A utility for retrieving the containg diagram of an element. The element
934      * does not have to be directly in a diagram, the utility will also look for
935      * through the element parents for a diagram too.
936      * 
937      * @param e
938      * @return
939      */
940     public static IDiagram getDiagram(IElement e) {
941         if (e == null)
942             throw new IllegalArgumentException("null element");
943         IDiagram d = peekDiagram(e);
944         if (d == null)
945             throw new IllegalStateException("element " + e + " is not part of a diagram");
946         return d;
947     }
948
949     /**
950      * A utility for retrieving the containg diagram of an element. The element
951      * does not have to be directly in a diagram, the utility will also look for
952      * through the element parents for a diagram too.
953      * 
954      * @param e
955      * @return <code>null</code> if the element is not on a diagram nor is the
956      *         element transitively a child of any element that is on a diagram
957      */
958     public static IDiagram peekDiagram(IElement e) {
959         while (e != null) {
960             IDiagram d = e.peekDiagram();
961             if (d != null)
962                 return d;
963             e = getParent(e);
964         }
965         return null;
966     }
967
968     /**
969      * Retrieves a possible parent element of an element.
970      * 
971      * @param e the element to get a parent for
972      * @return the parent element or <code>null</code> if the element does not
973      *         have a parent element
974      */
975     public static IElement getParent(IElement e) {
976         Parent p = e.getElementClass().getAtMostOneItemOfClass(Parent.class);
977         if (p == null)
978             return null;
979         return p.getParent(e);
980     }
981
982     /**
983      * Retrieves a possible parent element of an element.
984      * 
985      * @param e the element to get a parent for
986      * @return the parent element or <code>null</code> if the element does not
987      *         have a parent element
988      */
989     public static Collection<IElement> getParents(IElement e) {
990         List<IElement> result = new ArrayList<IElement>(3);
991         return getParents(e, result);
992     }
993
994     /**
995      * Retrieves a possible parent element of an element.
996      * 
997      * @param e the element to get a parent for
998      * @param result a collection wherein to store the possible parent elements
999      * @return the specified result collection
1000      */
1001     public static Collection<IElement> getParents(IElement e, Collection<IElement> result) {
1002         IElement p = e;
1003         while (true) {
1004             Parent ph = p.getElementClass().getAtMostOneItemOfClass(Parent.class);
1005             if (ph == null)
1006                 return result;
1007             p = ph.getParent(p);
1008             if (p == null)
1009                 return result;
1010             result.add(p);
1011         }
1012     }
1013
1014     /**
1015      * @param e
1016      * @return
1017      */
1018     public static String generateNodeId(IElement e) {
1019
1020         String prefix = "";
1021         String sgName = e.getHint(ElementHints.KEY_SG_NAME);
1022         if (sgName != null) prefix = sgName + " ";
1023
1024         Object object = e.getHint(ElementHints.KEY_OBJECT);
1025         if (object != null) {
1026             return prefix + object.toString();
1027         }
1028         // Warning: this can be hazardous for scene graph consistency!
1029         // Duplicate nodes may be introduced when elements are updated through
1030         // the Image interface.
1031         return String.valueOf(e.hashCode());
1032     }
1033
1034     /**
1035      * 
1036      * @param <T> the adaption target class
1037      * @param e the element to adapt
1038      * @param toClass the object class to adapt the element to
1039      * @return adapter result or <code>null</code> if adaptation failed
1040      */
1041     public static <T> T adaptElement(IElement e, Class<T> toClass) {
1042         for (ElementAdapter adapter : e.getElementClass().getItemsByClass(ElementAdapter.class)){
1043             T t = adapter.adapt(e, toClass);
1044             if (t != null)
1045                 return t;
1046         }
1047         return null;
1048     }
1049
1050     /**
1051      * Tries to adapt an {@link ElementClass} into a requested class through
1052      * {@link Adapter} handlers. Implements the IElement adaptation logic
1053      * described in {@link Adapter}.
1054      * 
1055      * @param <T>
1056      *            the adaption target class
1057      * @param e
1058      *            the element to adapt
1059      * @param toClass
1060      *            the object class to adapt the element to
1061      * @return adapter result or <code>null</code> if adaptation failed
1062      */
1063     public static <T> T adapt(ElementClass ec, Class<T> toClass) {
1064         if (ec == null)
1065             throw new IllegalArgumentException("null element class");
1066         if (toClass == null)
1067             throw new IllegalArgumentException("null target class");
1068
1069         for (Adapter adapter : ec.getItemsByClass(Adapter.class)){
1070             T t = adapter.adapt(toClass);
1071             if (t != null)
1072                 return t;
1073         }
1074         return null;
1075     }
1076
1077     /**
1078      * Otherwise the same as {@link #adapt(ElementClass, Class)} but will throw
1079      * {@link UnsupportedOperationException} if the adaption fails.
1080      * 
1081      * @param <T>
1082      *            the adaption target class
1083      * @param e
1084      *            the element to adapt
1085      * @param toClass
1086      *            the object class to adapt the element to
1087      * @return adapter result or <code>null</code> if adaptation failed
1088      * @throws UnsupportedOperationException
1089      */
1090     public static <T> T checkedAdapt(ElementClass ec, Class<T> toClass) {
1091         T t = adapt(ec, toClass);
1092         if (t != null)
1093             return t;
1094         throw new UnsupportedOperationException("cannot adapt " + ec + " to " + toClass);
1095     }
1096
1097     /**
1098      * Looks for a scene graph node from the specified element with the
1099      * specified node key. If the node does not exist, a new node is created
1100      * using the specified node class.
1101      * 
1102      * If a previous node exists, its class is verified to match the requested
1103      * node class and returned as such upon success. If the classes do not
1104      * match, an exception is raised since this is most likely a bug that needs
1105      * to be fixed elsewhere.
1106      * 
1107      * @param <T>
1108      * @param forElement
1109      * @param withParentNode
1110      * @param withNodeKey
1111      * @param nodeClass
1112      * @return
1113      */
1114     public static <T extends INode> T getOrCreateNode(IElement forElement, ParentNode<?> withParentNode, Key withNodeKey, Class<T> nodeClass) {
1115         return getOrCreateNode(forElement, withParentNode, withNodeKey, null, nodeClass);
1116     }
1117
1118     /**
1119      * Looks for a scene graph node from the specified element with the
1120      * specified node key. If the node does not exist, a new node is created
1121      * using the specified node class.
1122      * 
1123      * If a previous node exists, its class is verified to match the requested
1124      * node class and returned as such upon success. If the classes do not
1125      * match, an exception is raised since this is most likely a bug that needs
1126      * to be fixed elsewhere.
1127      * 
1128      * @param <T>
1129      * @param forElement
1130      * @param withParentNode
1131      * @param withNodeKey
1132      * @param nodeId
1133      * @param nodeClass
1134      * @return
1135      */
1136     public static <T extends INode> T getOrCreateNode(IElement forElement, ParentNode<?> withParentNode, Key withNodeKey, String nodeId, Class<T> nodeClass) {
1137         return getOrCreateNode(forElement, withParentNode, withNodeKey, nodeId, nodeClass, null);
1138     }
1139
1140     /**
1141      * Looks for a scene graph node from the specified element with the
1142      * specified node key. If the node does not exist, a new node is created
1143      * using the specified node class.
1144      * 
1145      * If a previous node exists, its class is verified to match the requested
1146      * node class and returned as such upon success. If the classes do not
1147      * match, an exception is raised since this is most likely a bug that needs
1148      * to be fixed elsewhere.
1149      * 
1150      * @param <T>
1151      * @param forElement
1152      * @param withParentNode
1153      * @param withNodeKey
1154      * @param nodeId
1155      * @param nodeClass
1156      * @param nodeCreationCallback a callback that is invoked with the node
1157      *        instance if a new node was created by this method
1158      * @return
1159      */
1160     public static <T extends INode> T getOrCreateNode(IElement forElement, ParentNode<?> withParentNode, Key withNodeKey, String nodeId, Class<T> nodeClass, Consumer<T> nodeCreationCallback) {
1161         if (!(withNodeKey instanceof SceneGraphNodeKey))
1162             System.out.println("ElementUtils.getOrCreateNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + withNodeKey);
1163
1164         @SuppressWarnings("unchecked")
1165         T node = (T) forElement.getHint(withNodeKey);
1166         if (node == null) {
1167             node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass);
1168             forElement.setHint(withNodeKey, node);
1169             if (nodeCreationCallback != null)
1170                 nodeCreationCallback.accept(node);
1171         } else {
1172             if (!nodeClass.isAssignableFrom(node.getClass())) {
1173                 throw new ElementSceneGraphException("ElementUtils.getOrCreateNode: WARNING: existing node class (" + node.getClass() + ") does not match requested node class (" + nodeClass + ") for element " + forElement + " with parent node " + withParentNode + " and node key " + withNodeKey);
1174             }
1175             // If the previously available node is not a parent of the specified
1176             // node create a new node under the specified parent and set that
1177             // as the node of the specified element.
1178             if (!withParentNode.equals(node.getParent())) {
1179                 node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass);
1180                 forElement.setHint(withNodeKey, node);
1181                 if (nodeCreationCallback != null)
1182                     nodeCreationCallback.accept(node);
1183             }
1184         }
1185         return node;
1186     }
1187
1188     /**
1189      * @param element
1190      * @param nodeKey
1191      * @return
1192      */
1193     public static INode removePossibleNode(IElement element, Key nodeKey) {
1194         if (!(nodeKey instanceof SceneGraphNodeKey))
1195             System.out.println("ElementUtils.removePossibleNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + nodeKey);
1196
1197         INode node = element.getHint(nodeKey);
1198         if (node != null)
1199             node.remove();
1200         return node;
1201     }
1202
1203     /**
1204      * 
1205      * @param element
1206      * @return
1207      */
1208     public static Font getTextFont(IElement element) {
1209         TextFont tf = element.getElementClass().getSingleItem(TextFont.class);
1210         return tf.getFont(element);
1211     }
1212
1213     public static void setTextFont(IElement element, Font font) {
1214         TextFont tf = element.getElementClass().getSingleItem(TextFont.class);
1215         tf.setFont(element, font);
1216     }
1217
1218     public static <T> void addToCollectionHint(IElement element, Key key, T item) {
1219         Collection<T> collection = element.getHint(key);
1220         if (collection == null) {
1221             collection = new ArrayList<T>();
1222             element.setHint(key, collection);
1223         }
1224         collection.add(item);
1225     }
1226
1227     public static <T> void removeFromCollectionHint(IElement element, Key key, T item) {
1228         Collection<T> collection = element.getHint(key);
1229         if (collection != null) {
1230             collection = new ArrayList<T>();
1231             collection.remove(item);
1232             if (collection.isEmpty())
1233                 element.removeHint(key);
1234         }
1235     }
1236
1237     public static void setHover(IElement e, boolean hover)
1238     {
1239         Hover h = e.getElementClass().getSingleItem(Hover.class);
1240         h.setHover(e, hover);
1241     }
1242
1243     public static boolean isHovering(IElement e)
1244     {
1245         Hover h = e.getElementClass().getSingleItem(Hover.class);
1246         return h.isHovering(e);
1247     }
1248
1249 }