1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.element;
14 import java.awt.Color;
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;
28 import java.util.function.Consumer;
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;
75 * Utils for element users.
77 * @See {@link TerminalUtil}
78 * @See {@link ElementHandlerUtils} Utils for element handler (coders)
79 * @author Toni Kalajainen
81 public class ElementUtils {
83 @SuppressWarnings("unchecked")
84 public static <T> T getObject(IElement e) {
85 return (T) e.getHint(ElementHints.KEY_OBJECT);
88 public static void disable(IElement e)
90 Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);
91 enabled.setEnabled(e, false);
94 public static void enable(IElement e)
96 Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);
97 enabled.setEnabled(e, true);
104 public static boolean isHidden(IElement e) {
105 return e.containsHint(ElementHints.KEY_HIDDEN);
110 * @param state <code>null</code> to remove hidden state
113 public static void setHidden(IElement e, boolean hidden) {
115 e.setHint(ElementHints.KEY_HIDDEN, HideState.COMPLETELY_HIDDEN);
117 e.removeHint(ElementHints.KEY_HIDDEN);
120 public static void setText(IElement e, String text)
122 Text t = e.getElementClass().getSingleItem(Text.class);
126 public static String getText(IElement e)
128 Text t = e.getElementClass().getSingleItem(Text.class);
133 * Resizes or scales an element to fit it into a rectangle.
136 * @param rect rectangle on diagram
138 public static void fitToRectangle(IElement e, Rectangle2D rect)
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)
147 Resize rs = ec.getAtMostOneItemOfClass(Resize.class);
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;
155 Double requiredAspectRatio = rs==null?null:rs.getFixedAspectRatio(e);
156 if (requiredAspectRatio!=null)
158 if (aspectRatio>requiredAspectRatio)
159 width = height*requiredAspectRatio;
161 height = width / requiredAspectRatio;
166 m.moveTo(e, rect.getX(), rect.getY());
168 width /= scale.getX();
169 height /= scale.getY();
171 Rectangle2D r = new Rectangle2D.Double(0, 0, width, height);
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;
181 scale.setLocation(sx, sy);
182 s.setScale(e, scale);
186 public static void addClickListener(IElement e, ICanvasContext ctx, ClickListener listener)
188 Clickable clickable = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);
189 clickable.addListener(e, ctx, ctx.getThreadAccess(), listener);
192 public static Point2D getPos(IElement e)
194 Move m = e.getElementClass().getSingleItem(Move.class);
195 return m.getPosition(e);
198 public static Point2D getPos(IElement e, Point2D result)
200 Move m = e.getElementClass().getSingleItem(Move.class);
202 result = new Point2D.Double();
203 Point2D p = m.getPosition(e);
204 result.setLocation(p);
208 public static Point2D getAbsolutePos(IElement e)
210 Transform tr = e.getElementClass().getSingleItem(Transform.class);
211 AffineTransform at = tr.getTransform(e);
212 return new Point2D.Double(at.getTranslateX(), at.getTranslateY());
215 public static Point2D getAbsolutePos(IElement e, Point2D result)
217 Transform tr = e.getElementClass().getSingleItem(Transform.class);
218 AffineTransform at = tr.getTransform(e);
220 result = new Point2D.Double();
221 result.setLocation(at.getTranslateX(), at.getTranslateY());
225 public static void setPos(IElement e, Point2D newPosition)
227 Move m = e.getElementClass().getSingleItem(Move.class);
228 m.moveTo(e, newPosition.getX(), newPosition.getY());
231 public static void setPos(IElement e, double x, double y)
233 Move m = e.getElementClass().getSingleItem(Move.class);
237 public static IElement getByData(IDiagram d, Object data)
239 DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);
240 return map.getElement(d, data);
243 public static Object getData(IDiagram d, IElement element)
245 DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);
246 return map.getData(d, element);
250 * Get all terminals of an element.
253 * @param result a store for the terminals
254 * @param clearResult <code>true</code> to clear the result collection
256 * @return the specified result collection
258 public static Collection<Terminal> getTerminals(IElement e, Collection<Terminal> result, boolean clearResult) {
261 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
263 tt.getTerminals(e, result);
269 * Get a terminal of an element assuming there is only a single terminal.
273 * @return the only terminal of element e
274 * @throws IllegalArgumentException if there are zero or multiple terminals
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);
281 throw new IllegalArgumentException("expected 1 terminal, element e has " + ts.size() + " terminals: " + ts);
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.
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);
303 * Get allowed outward directions of a terminal
308 public static DirectionSet getTerminalDirection(IElement e, Terminal t)
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);
318 public static AffineTransform getTransform(IElement e)
320 return e.getElementClass().getSingleItem(Transform.class).getTransform(e);
323 public static AffineTransform getTransform(IElement e, AffineTransform result)
327 AffineTransform tr = e.getElementClass().getSingleItem(Transform.class).getTransform(e);
328 result.setTransform(tr);
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
341 public static AffineTransform getLocalTransform(IElement e, AffineTransform result)
343 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
345 result = new AffineTransform();
347 result.setTransform(at);
351 public static void setTransform(IElement e, AffineTransform at)
353 e.getElementClass().getSingleItem(Transform.class).setTransform(e, at);
356 public static void setParameters(IElement e, Map<String,Object> parameters)
358 Parameters ps = e.getElementClass().getSingleItem(Parameters.class);
359 if(ps != null) ps.setParameters(e, parameters);
362 public static Map<String,Object> getParameters(IElement e)
364 Parameters ps = e.getElementClass().getAtMostOneItemOfClass(Parameters.class);
365 return ps != null ? ps.getParameters(e) : null;
368 public static AffineTransform getInvTransform(IElement e)
371 return e.getElementClass().getSingleItem(Transform.class).getTransform(e).createInverse();
372 } catch (NoninvertibleTransformException e1) {
373 throw new RuntimeException(e1);
379 * Element to canvas coordinates
381 * @param elementPoint
385 public static Point2D elementToCanvasCoordinate(IElement e, Point2D elementPoint, Point2D canvasPoint)
387 Transform t = e.getElementClass().getSingleItem(Transform.class);
388 AffineTransform at = t.getTransform(e);
389 return at.transform(elementPoint, canvasPoint);
393 * Element to control coordinates
396 * @param elementPoint
397 * @param controlPoint
400 public static Point2D elementToControlCoordinate(IElement e, ICanvasContext ctx, Point2D elementPoint, Point2D controlPoint)
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);
408 public static Point2D controlToElementCoordinate(IElement e, ICanvasContext ctx, Point2D controlPoint, Point2D elementPoint)
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();
416 at.inverseTransform(canvasPoint, elementPoint);
418 } catch (NoninvertibleTransformException e1) {
419 throw new RuntimeException(e1);
423 public static Point2D controlToCanvasCoordinate(ICanvasContext ctx, Point2D controlPoint, Point2D canvasPoint)
425 TransformUtil tu = ctx.getSingleItem(TransformUtil.class);
426 return tu.controlToCanvas(controlPoint, canvasPoint);
430 public static PressStatus getPressStatus(IElement e, ICanvasContext ctx)
432 Clickable c = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);
433 if (c==null) return null;
434 return c.getPressStatus(e, ctx);
437 public static Color getBorderColor(IElement e)
439 return getBorderColor(e, null);
442 public static Color getFillColor(IElement e)
444 return getFillColor(e, null);
447 public static Color getAdditionalColor(IElement e)
449 return getAdditionalColor(e, null);
452 public static Color getTextColor(IElement e)
454 return getTextColor(e, null);
458 * Get border color of element of return defaultValue if border color is not
462 * @param defaultValue
465 public static Color getBorderColor(IElement e, Color defaultValue)
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;
474 * Get fill color of element of return defaultValue if fill color is not
478 * @param defaultValue
481 public static Color getFillColor(IElement e, Color defaultValue)
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;
490 * Get additional color of element of return defaultValue if additional
491 * color is not available.
494 * @param defaultValue
497 public static Color getAdditionalColor(IElement e, Color defaultValue)
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;
506 * Get text color of element of return defaultValue if text color is not
510 * @param defaultValue
513 public static Color getTextColor(IElement e, Color defaultValue)
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;
521 public static TextEditor getTextEditor(IElement e)
523 TextEditor ed = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);
527 public static void setBorderColor(IElement e, Color color)
529 BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class);
530 if (bc==null) return;
531 bc.setBorderColor(e, color);
534 public static void setFillColor(IElement e, Color color)
536 FillColor bc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class);
537 if (bc==null) return;
538 bc.setFillColor(e, color);
541 public static void setAdditionalColor(IElement e, Color color)
543 AdditionalColor bc = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class);
544 if (bc==null) return;
545 bc.setAdditionalColor(e, color);
548 public static void setTextColor(IElement e, Color color)
550 TextColor bc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class);
551 if (bc==null) return;
552 bc.setTextColor(e, color);
555 public static void setEdgeStroke(IElement e, Stroke s)
557 EdgeVisuals ev = e.getElementClass().getSingleItem(EdgeVisuals.class);
562 * Fill given map with element bounds (the bounds on diagram)
565 * @param rects structure to be filled or null (instantates new)
566 * @return rects or newly instantiated structure
568 public static Map<IElement, Rectangle2D> getElementBoundsOnDiagram(Collection<IElement> elements, Map<IElement, Rectangle2D> rects)
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());
581 * @return element bounds in element coordinates
583 public static Rectangle2D getElementBounds(IElement e)
585 InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);
586 return b.getBounds(e, new Rectangle2D.Double());
592 * @param result a rectangle for storing the result
593 * @return the specified result rectangle
595 public static Rectangle2D getElementBounds(IElement e, Rectangle2D result)
597 InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);
598 return b.getBounds(e, result);
602 * Get rough estimation of outer bounds of an element
604 * @return bounds on a diagram
606 public static Shape getElementBoundsOnDiagram(IElement e)
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);
615 * Get rough estimation of outer bounds of an element
617 * @param result a rectangle for storing the result
618 * @return bounds on a diagram
620 public static Rectangle2D getElementBoundsOnDiagram(IElement e, Rectangle2D result)
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());
631 * Get union of outer bounds of a set of elements
633 * @return Union of element bounds (on diagram) or null
635 public static Shape getElementBoundsOnDiagram(Collection<IElement> elements)
637 if (elements.size()==0) return null;
638 if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next());
640 for (IElement e : elements) {
641 Shape bounds = getElementBoundsOnDiagram(e);
642 Area ae = bounds instanceof Area ? (Area) bounds : new Area(bounds);
649 * Get union of outer bounds of a set of elements
651 * @return Union of element bounds (on diagram) or null
653 public static Rectangle2D getSurroundingElementBoundsOnDiagram(Collection<IElement> elements)
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();
665 return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY);
669 * Get as accurate shape if available
672 * @return accurate shape of an element or <code>null</code> if shape is not available
674 public static Shape getElementShape(IElement e)
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);
680 for (Outline es : shapeProviders)
682 Shape shape = es.getElementShape(e);
683 Area ae = shape instanceof Area ? (Area) shape : new Area(shape);
689 public static Shape getElementShapeOnDiagram(IElement e)
691 Shape shape = getElementShape(e);
694 Transform t = e.getElementClass().getSingleItem(Transform.class);
695 AffineTransform canvasToElement = t.getTransform(e);
696 return GeometryUtils.transformShape(shape, canvasToElement);
700 * Get element shape is one exists otherwise its bounds
702 * @return shape or bounds
704 public static Shape getElementShapeOrBounds(IElement e)
706 Shape shape = getElementShape(e);
707 if (shape!=null) return shape;
708 return getElementBounds(e);
712 * Get element shape is one exists otherwise its bounds
714 * @return shape or bounds
716 public static Shape getElementShapeOrBoundsOnDiagram(IElement e)
718 Shape shape = getElementShapeOnDiagram(e);
719 if (shape!=null) return shape;
720 return getElementBoundsOnDiagram(e);
723 public static Shape getElementShapesOnDiagram(Collection<IElement> elements)
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());
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);
738 //task2 = ThreadLogger.getInstance().begin("construct area from " + shape);
740 if (shape instanceof Area)
743 aa = new Area(shape);
745 //task2 = ThreadLogger.getInstance().begin("union area " + aa);
753 public static Shape mergeShapes(Collection<Shape> shapes)
755 if (shapes.isEmpty()) return null;
756 if (shapes.size()==1) return shapes.iterator().next();
758 for (Shape s : shapes)
763 public static boolean pickInElement(IElement e, ICanvasContext ctx, PickRequest req)
765 Rectangle2D elementBounds = getElementBounds(e);
767 // Pick with pick handler(s)
768 List<Pick> pickHandlers = e.getElementClass().getItemsByClass(Pick.class);
769 if (!pickHandlers.isEmpty())
771 // Rough filtering with bounds
772 if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;
774 // Convert pick shape to element coordinates
775 for (Pick p : pickHandlers)
777 if (p.pickTest(e, req.pickArea, req.pickPolicy))
783 // Pick with shape handler(s)
784 List<Outline> shapeHandlers = e.getElementClass().getItemsByClass(Outline.class);
785 if (!shapeHandlers.isEmpty())
787 // Rough filtering with bounds
788 if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;
790 // Intersection with one shape is enough
791 if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
793 for (Outline es : shapeHandlers)
795 Shape elementShape = es.getElementShape(e);
796 if (GeometryUtils.intersects(req.pickArea, elementShape))
802 // Contains of all shapes is required
803 if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
805 for (Outline es : shapeHandlers)
807 Shape elementShape = es.getElementShape(e);
808 if (!GeometryUtils.contains(req.pickArea, elementShape))
817 if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
819 if (GeometryUtils.intersects(req.pickArea, elementBounds))
825 if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
827 if (GeometryUtils.contains(req.pickArea, elementBounds))
834 * Get bends of an 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
841 public static void getBends(IElement e, List<Bend> bends, List<Point2D> points)
843 BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
844 bh.getBends(e, bends);
847 Point2D pos = new Point2D.Double();
848 bh.getBendPosition(e, b, pos);
854 * Get bends of an edge
856 * @param points collection to be filled with the bends points
858 public static void getBends(IElement e, List<Point2D> points)
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);
867 public static void resizeElement(IElement e, double x, double y, double w, double h) {
868 Move m = e.getElementClass().getSingleItem(Move.class);
870 Resize s = e.getElementClass().getSingleItem(Resize.class);
871 s.resize(e, new Rectangle2D.Double(0,0,w,h));
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;
880 public static void setOrRemoveHint(IHintContext e, Key key, Object value) {
884 assert key.isValueAccepted(value);
885 e.setHint(key, value);
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);
897 public static IElement getDiagramMappedElement(IElement e) {
898 IDiagram d = e.peekDiagram();
901 DataElementMap map = d.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
904 Object o = map.getData(d, e);
907 IElement mapped = map.getElement(d, o);
908 return mapped != null ? mapped : e;
912 * Calculates the center of the bounding box containing all the specified
915 * @param the elements for which to calculate the center of a containing
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
922 public static Point2D getElementBoundsCenter(Collection<IElement> elements, Point2D result) {
923 Shape b = getElementBoundsOnDiagram(elements);
926 Rectangle2D bounds = b.getBounds2D();
928 result = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
930 result.setLocation(bounds.getCenterX(), bounds.getCenterY());
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.
942 public static IDiagram getDiagram(IElement e) {
944 throw new IllegalArgumentException("null element");
945 IDiagram d = peekDiagram(e);
947 throw new IllegalStateException("element " + e + " is not part of a diagram");
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.
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
960 public static IDiagram peekDiagram(IElement e) {
962 IDiagram d = e.peekDiagram();
971 * Retrieves a possible parent element of an element.
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
977 public static IElement getParent(IElement e) {
978 Parent p = e.getElementClass().getAtMostOneItemOfClass(Parent.class);
981 return p.getParent(e);
985 * Retrieves a possible parent element of an element.
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
991 public static Collection<IElement> getParents(IElement e) {
992 List<IElement> result = new ArrayList<IElement>(3);
993 return getParents(e, result);
997 * Retrieves a possible parent element of an element.
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
1003 public static Collection<IElement> getParents(IElement e, Collection<IElement> result) {
1006 Parent ph = p.getElementClass().getAtMostOneItemOfClass(Parent.class);
1009 p = ph.getParent(p);
1020 public static String generateNodeId(IElement e) {
1023 String sgName = e.getHint(ElementHints.KEY_SG_NAME);
1024 if (sgName != null) prefix = sgName + " ";
1026 Object object = e.getHint(ElementHints.KEY_OBJECT);
1027 if (object != null) {
1028 return prefix + object.toString();
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());
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
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);
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}.
1058 * the adaption target class
1060 * the element to adapt
1062 * the object class to adapt the element to
1063 * @return adapter result or <code>null</code> if adaptation failed
1065 public static <T> T adapt(ElementClass ec, Class<T> toClass) {
1067 throw new IllegalArgumentException("null element class");
1068 if (toClass == null)
1069 throw new IllegalArgumentException("null target class");
1071 for (Adapter adapter : ec.getItemsByClass(Adapter.class)){
1072 T t = adapter.adapt(toClass);
1080 * Otherwise the same as {@link #adapt(ElementClass, Class)} but will throw
1081 * {@link UnsupportedOperationException} if the adaption fails.
1084 * the adaption target class
1086 * the element to adapt
1088 * the object class to adapt the element to
1089 * @return adapter result or <code>null</code> if adaptation failed
1090 * @throws UnsupportedOperationException
1092 public static <T> T checkedAdapt(ElementClass ec, Class<T> toClass) {
1093 T t = adapt(ec, toClass);
1096 throw new UnsupportedOperationException("cannot adapt " + ec + " to " + toClass);
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.
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.
1111 * @param withParentNode
1112 * @param withNodeKey
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);
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.
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.
1132 * @param withParentNode
1133 * @param withNodeKey
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);
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.
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.
1154 * @param withParentNode
1155 * @param withNodeKey
1158 * @param nodeCreationCallback a callback that is invoked with the node
1159 * instance if a new node was created by this method
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);
1166 @SuppressWarnings("unchecked")
1167 T node = (T) forElement.getHint(withNodeKey);
1169 node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass);
1170 forElement.setHint(withNodeKey, node);
1171 if (nodeCreationCallback != null)
1172 nodeCreationCallback.accept(node);
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);
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);
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);
1199 INode node = element.getHint(nodeKey);
1210 public static Font getTextFont(IElement element) {
1211 TextFont tf = element.getElementClass().getSingleItem(TextFont.class);
1212 return tf.getFont(element);
1215 public static void setTextFont(IElement element, Font font) {
1216 TextFont tf = element.getElementClass().getSingleItem(TextFont.class);
1217 tf.setFont(element, font);
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);
1226 collection.add(item);
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);
1239 public static void setHover(IElement e, boolean hover)
1241 Hover h = e.getElementClass().getSingleItem(Hover.class);
1242 h.setHover(e, hover);
1245 public static boolean isHovering(IElement e)
1247 Hover h = e.getElementClass().getSingleItem(Hover.class);
1248 return h.isHovering(e);