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