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