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