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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.element;
\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
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
74 * Utils for element users.
\r
76 * @See {@link TerminalUtil}
\r
77 * @See {@link ElementHandlerUtils} Utils for element handler (coders)
\r
78 * @author Toni Kalajainen
\r
80 public class ElementUtils {
\r
82 @SuppressWarnings("unchecked")
\r
83 public static <T> T getObject(IElement e) {
\r
84 return (T) e.getHint(ElementHints.KEY_OBJECT);
\r
87 public static void disable(IElement e)
\r
89 Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);
\r
90 enabled.setEnabled(e, false);
\r
93 public static void enable(IElement e)
\r
95 Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);
\r
96 enabled.setEnabled(e, true);
\r
103 public static boolean isHidden(IElement e) {
\r
104 return e.containsHint(ElementHints.KEY_HIDDEN);
\r
109 * @param state <code>null</code> to remove hidden state
\r
112 public static void setHidden(IElement e, boolean hidden) {
\r
114 e.setHint(ElementHints.KEY_HIDDEN, HideState.COMPLETELY_HIDDEN);
\r
116 e.removeHint(ElementHints.KEY_HIDDEN);
\r
119 public static void setText(IElement e, String text)
\r
121 Text t = e.getElementClass().getSingleItem(Text.class);
\r
122 t.setText(e, text);
\r
125 public static String getText(IElement e)
\r
127 Text t = e.getElementClass().getSingleItem(Text.class);
\r
128 return t.getText(e);
\r
132 * Resizes or scales an element to fit it into a rectangle.
\r
135 * @param rect rectangle on diagram
\r
137 public static void fitToRectangle(IElement e, Rectangle2D rect)
\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
146 Resize rs = ec.getAtMostOneItemOfClass(Resize.class);
\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
154 Double requiredAspectRatio = rs==null?null:rs.getFixedAspectRatio(e);
\r
155 if (requiredAspectRatio!=null)
\r
157 if (aspectRatio>requiredAspectRatio)
\r
158 width = height*requiredAspectRatio;
\r
160 height = width / requiredAspectRatio;
\r
165 m.moveTo(e, rect.getX(), rect.getY());
\r
167 width /= scale.getX();
\r
168 height /= scale.getY();
\r
170 Rectangle2D r = new Rectangle2D.Double(0, 0, width, height);
\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
185 public static void addClickListener(IElement e, ICanvasContext ctx, ClickListener listener)
\r
187 Clickable clickable = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);
\r
188 clickable.addListener(e, ctx, ctx.getThreadAccess(), listener);
\r
191 public static Point2D getPos(IElement e)
\r
193 Move m = e.getElementClass().getSingleItem(Move.class);
\r
194 return m.getPosition(e);
\r
197 public static Point2D getPos(IElement e, Point2D result)
\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
207 public static Point2D getAbsolutePos(IElement e)
\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
214 public static Point2D getAbsolutePos(IElement e, Point2D result)
\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
224 public static void setPos(IElement e, Point2D newPosition)
\r
226 Move m = e.getElementClass().getSingleItem(Move.class);
\r
227 m.moveTo(e, newPosition.getX(), newPosition.getY());
\r
230 public static void setPos(IElement e, double x, double y)
\r
232 Move m = e.getElementClass().getSingleItem(Move.class);
\r
236 public static IElement getByData(IDiagram d, Object data)
\r
238 DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);
\r
239 return map.getElement(d, data);
\r
242 public static Object getData(IDiagram d, IElement element)
\r
244 DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);
\r
245 return map.getData(d, element);
\r
249 * Get all terminals of an 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
257 public static Collection<Terminal> getTerminals(IElement e, Collection<Terminal> result, boolean clearResult) {
\r
260 TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);
\r
261 tt.getTerminals(e, result);
\r
266 * Get a terminal of an element assuming there is only a single terminal.
\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
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
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
287 * @param t terminal
\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
300 * Get allowed outward directions of a terminal
\r
302 * @param t terminal
\r
305 public static DirectionSet getTerminalDirection(IElement e, Terminal t)
\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
315 public static AffineTransform getTransform(IElement e)
\r
317 return e.getElementClass().getSingleItem(Transform.class).getTransform(e);
\r
320 public static AffineTransform getTransform(IElement e, AffineTransform result)
\r
324 AffineTransform tr = e.getElementClass().getSingleItem(Transform.class).getTransform(e);
\r
325 result.setTransform(tr);
\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
338 public static AffineTransform getLocalTransform(IElement e, AffineTransform result)
\r
340 AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);
\r
341 if (result == null)
\r
342 result = new AffineTransform();
\r
344 result.setTransform(at);
\r
348 public static void setTransform(IElement e, AffineTransform at)
\r
350 e.getElementClass().getSingleItem(Transform.class).setTransform(e, at);
\r
353 public static AffineTransform getInvTransform(IElement e)
\r
356 return e.getElementClass().getSingleItem(Transform.class).getTransform(e).createInverse();
\r
357 } catch (NoninvertibleTransformException e1) {
\r
358 throw new RuntimeException(e1);
\r
364 * Element to canvas coordinates
\r
366 * @param elementPoint
\r
367 * @param canvasPoint
\r
370 public static Point2D elementToCanvasCoordinate(IElement e, Point2D elementPoint, Point2D canvasPoint)
\r
372 Transform t = e.getElementClass().getSingleItem(Transform.class);
\r
373 AffineTransform at = t.getTransform(e);
\r
374 return at.transform(elementPoint, canvasPoint);
\r
378 * Element to control coordinates
\r
381 * @param elementPoint
\r
382 * @param controlPoint
\r
385 public static Point2D elementToControlCoordinate(IElement e, ICanvasContext ctx, Point2D elementPoint, Point2D controlPoint)
\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
393 public static Point2D controlToElementCoordinate(IElement e, ICanvasContext ctx, Point2D controlPoint, Point2D elementPoint)
\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
401 at.inverseTransform(canvasPoint, elementPoint);
\r
402 return elementPoint;
\r
403 } catch (NoninvertibleTransformException e1) {
\r
404 throw new RuntimeException(e1);
\r
408 public static Point2D controlToCanvasCoordinate(ICanvasContext ctx, Point2D controlPoint, Point2D canvasPoint)
\r
410 TransformUtil tu = ctx.getSingleItem(TransformUtil.class);
\r
411 return tu.controlToCanvas(controlPoint, canvasPoint);
\r
415 public static PressStatus getPressStatus(IElement e, ICanvasContext ctx)
\r
417 Clickable c = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);
\r
418 if (c==null) return null;
\r
419 return c.getPressStatus(e, ctx);
\r
422 public static Color getBorderColor(IElement e)
\r
424 return getBorderColor(e, null);
\r
427 public static Color getFillColor(IElement e)
\r
429 return getFillColor(e, null);
\r
432 public static Color getAdditionalColor(IElement e)
\r
434 return getAdditionalColor(e, null);
\r
437 public static Color getTextColor(IElement e)
\r
439 return getTextColor(e, null);
\r
443 * Get border color of element of return defaultValue if border color is not
\r
447 * @param defaultValue
\r
450 public static Color getBorderColor(IElement e, Color defaultValue)
\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
459 * Get fill color of element of return defaultValue if fill color is not
\r
463 * @param defaultValue
\r
466 public static Color getFillColor(IElement e, Color defaultValue)
\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
475 * Get additional color of element of return defaultValue if additional
\r
476 * color is not available.
\r
479 * @param defaultValue
\r
482 public static Color getAdditionalColor(IElement e, Color defaultValue)
\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
491 * Get text color of element of return defaultValue if text color is not
\r
495 * @param defaultValue
\r
498 public static Color getTextColor(IElement e, Color defaultValue)
\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
506 public static TextEditor getTextEditor(IElement e)
\r
508 TextEditor ed = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);
\r
512 public static void setBorderColor(IElement e, Color color)
\r
514 BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class);
\r
515 if (bc==null) return;
\r
516 bc.setBorderColor(e, color);
\r
519 public static void setFillColor(IElement e, Color color)
\r
521 FillColor bc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class);
\r
522 if (bc==null) return;
\r
523 bc.setFillColor(e, color);
\r
526 public static void setAdditionalColor(IElement e, Color color)
\r
528 AdditionalColor bc = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class);
\r
529 if (bc==null) return;
\r
530 bc.setAdditionalColor(e, color);
\r
533 public static void setTextColor(IElement e, Color color)
\r
535 TextColor bc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class);
\r
536 if (bc==null) return;
\r
537 bc.setTextColor(e, color);
\r
540 public static void setEdgeStroke(IElement e, Stroke s)
\r
542 EdgeVisuals ev = e.getElementClass().getSingleItem(EdgeVisuals.class);
\r
543 ev.setStroke(e, s);
\r
547 * Fill given map with element bounds (the bounds on diagram)
\r
550 * @param rects structure to be filled or null (instantates new)
\r
551 * @return rects or newly instantiated structure
\r
553 public static Map<IElement, Rectangle2D> getElementBoundsOnDiagram(Collection<IElement> elements, Map<IElement, Rectangle2D> rects)
\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
564 * get element bounds
\r
566 * @return element bounds in element coordinates
\r
568 public static Rectangle2D getElementBounds(IElement e)
\r
570 InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);
\r
571 return b.getBounds(e, new Rectangle2D.Double());
\r
575 * get element bounds
\r
577 * @param result a rectangle for storing the result
\r
578 * @return the specified result rectangle
\r
580 public static Rectangle2D getElementBounds(IElement e, Rectangle2D result)
\r
582 InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);
\r
583 return b.getBounds(e, result);
\r
587 * Get rough estimation of outer bounds of an element
\r
589 * @return bounds on a diagram
\r
591 public static Shape getElementBoundsOnDiagram(IElement e)
\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
600 * Get rough estimation of outer bounds of an element
\r
602 * @param result a rectangle for storing the result
\r
603 * @return bounds on a diagram
\r
605 public static Rectangle2D getElementBoundsOnDiagram(IElement e, Rectangle2D result)
\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
616 * Get union of outer bounds of a set of elements
\r
618 * @return Union of element bounds (on diagram) or null
\r
620 public static Shape getElementBoundsOnDiagram(Collection<IElement> elements)
\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
634 * Get union of outer bounds of a set of elements
\r
636 * @return Union of element bounds (on diagram) or null
\r
638 public static Rectangle2D getSurroundingElementBoundsOnDiagram(Collection<IElement> elements)
\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
650 return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY);
\r
654 * Get as accurate shape if available
\r
657 * @return accurate shape of an element or <code>null</code> if shape is not available
\r
659 public static Shape getElementShape(IElement e)
\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
667 Shape shape = es.getElementShape(e);
\r
668 Area ae = shape instanceof Area ? (Area) shape : new Area(shape);
\r
674 public static Shape getElementShapeOnDiagram(IElement e)
\r
676 Shape shape = getElementShape(e);
\r
679 Transform t = e.getElementClass().getSingleItem(Transform.class);
\r
680 AffineTransform canvasToElement = t.getTransform(e);
\r
681 return GeometryUtils.transformShape(shape, canvasToElement);
\r
685 * Get element shape is one exists otherwise its bounds
\r
687 * @return shape or bounds
\r
689 public static Shape getElementShapeOrBounds(IElement e)
\r
691 Shape shape = getElementShape(e);
\r
692 if (shape!=null) return shape;
\r
693 return getElementBounds(e);
\r
697 * Get element shape is one exists otherwise its bounds
\r
699 * @return shape or bounds
\r
701 public static Shape getElementShapeOrBoundsOnDiagram(IElement e)
\r
703 Shape shape = getElementShapeOnDiagram(e);
\r
704 if (shape!=null) return shape;
\r
705 return getElementBoundsOnDiagram(e);
\r
708 public static Shape getElementShapesOnDiagram(Collection<IElement> elements)
\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
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
723 //task2 = ThreadLogger.getInstance().begin("construct area from " + shape);
\r
725 if (shape instanceof Area)
\r
728 aa = new Area(shape);
\r
730 //task2 = ThreadLogger.getInstance().begin("union area " + aa);
\r
738 public static Shape mergeShapes(Collection<Shape> shapes)
\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
748 public static boolean pickInElement(IElement e, ICanvasContext ctx, PickRequest req)
\r
750 Rectangle2D elementBounds = getElementBounds(e);
\r
752 // Pick with pick handler(s)
\r
753 List<Pick> pickHandlers = e.getElementClass().getItemsByClass(Pick.class);
\r
754 if (!pickHandlers.isEmpty())
\r
756 // Rough filtering with bounds
\r
757 if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;
\r
759 // Convert pick shape to element coordinates
\r
760 for (Pick p : pickHandlers)
\r
762 if (p.pickTest(e, req.pickArea, req.pickPolicy))
\r
768 // Pick with shape handler(s)
\r
769 List<Outline> shapeHandlers = e.getElementClass().getItemsByClass(Outline.class);
\r
770 if (!shapeHandlers.isEmpty())
\r
772 // Rough filtering with bounds
\r
773 if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;
\r
775 // Intersection with one shape is enough
\r
776 if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
\r
778 for (Outline es : shapeHandlers)
\r
780 Shape elementShape = es.getElementShape(e);
\r
781 if (GeometryUtils.intersects(req.pickArea, elementShape))
\r
787 // Contains of all shapes is required
\r
788 if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
\r
790 for (Outline es : shapeHandlers)
\r
792 Shape elementShape = es.getElementShape(e);
\r
793 if (!GeometryUtils.contains(req.pickArea, elementShape))
\r
801 // Pick by rectangle
\r
802 if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)
\r
804 if (GeometryUtils.intersects(req.pickArea, elementBounds))
\r
810 if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)
\r
812 if (GeometryUtils.contains(req.pickArea, elementBounds))
\r
819 * Get bends of an 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
826 public static void getBends(IElement e, List<Bend> bends, List<Point2D> points)
\r
828 BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
\r
829 bh.getBends(e, bends);
\r
830 for (Bend b : bends)
\r
832 Point2D pos = new Point2D.Double();
\r
833 bh.getBendPosition(e, b, pos);
\r
839 * Get bends of an edge
\r
841 * @param points collection to be filled with the bends points
\r
843 public static void getBends(IElement e, List<Point2D> points)
\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
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
855 Resize s = e.getElementClass().getSingleItem(Resize.class);
\r
856 s.resize(e, new Rectangle2D.Double(0,0,w,h));
\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
865 public static void setOrRemoveHint(IHintContext e, Key key, Object value) {
\r
866 if (value == null) {
\r
869 assert key.isValueAccepted(value);
\r
870 e.setHint(key, value);
\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
882 public static IElement getDiagramMappedElement(IElement e) {
\r
883 IDiagram d = e.peekDiagram();
\r
886 DataElementMap map = d.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
\r
889 Object o = map.getData(d, e);
\r
892 IElement mapped = map.getElement(d, o);
\r
893 return mapped != null ? mapped : e;
\r
897 * Calculates the center of the bounding box containing all the specified
\r
900 * @param the elements for which to calculate the center of a containing
\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
907 public static Point2D getElementBoundsCenter(Collection<IElement> elements, Point2D result) {
\r
908 Shape b = getElementBoundsOnDiagram(elements);
\r
911 Rectangle2D bounds = b.getBounds2D();
\r
912 if (result == null)
\r
913 result = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
\r
915 result.setLocation(bounds.getCenterX(), bounds.getCenterY());
\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
927 public static IDiagram getDiagram(IElement e) {
\r
929 throw new IllegalArgumentException("null element");
\r
930 IDiagram d = peekDiagram(e);
\r
932 throw new IllegalStateException("element " + e + " is not part of a diagram");
\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
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
945 public static IDiagram peekDiagram(IElement e) {
\r
946 while (e != null) {
\r
947 IDiagram d = e.peekDiagram();
\r
956 * Retrieves a possible parent element of an element.
\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
962 public static IElement getParent(IElement e) {
\r
963 Parent p = e.getElementClass().getAtMostOneItemOfClass(Parent.class);
\r
966 return p.getParent(e);
\r
970 * Retrieves a possible parent element of an element.
\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
976 public static Collection<IElement> getParents(IElement e) {
\r
977 List<IElement> result = new ArrayList<IElement>(3);
\r
978 return getParents(e, result);
\r
982 * Retrieves a possible parent element of an element.
\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
988 public static Collection<IElement> getParents(IElement e, Collection<IElement> result) {
\r
991 Parent ph = p.getElementClass().getAtMostOneItemOfClass(Parent.class);
\r
994 p = ph.getParent(p);
\r
1005 public static String generateNodeId(IElement e) {
\r
1007 String prefix = "";
\r
1008 String sgName = e.getHint(ElementHints.KEY_SG_NAME);
\r
1009 if (sgName != null) prefix = sgName + " ";
\r
1011 Object object = e.getHint(ElementHints.KEY_OBJECT);
\r
1012 if (object != null) {
\r
1013 return prefix + object.toString();
\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
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
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
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
1043 * the adaption target class
\r
1045 * the element to adapt
\r
1047 * the object class to adapt the element to
\r
1048 * @return adapter result or <code>null</code> if adaptation failed
\r
1050 public static <T> T adapt(ElementClass ec, Class<T> toClass) {
\r
1052 throw new IllegalArgumentException("null element class");
\r
1053 if (toClass == null)
\r
1054 throw new IllegalArgumentException("null target class");
\r
1056 for (Adapter adapter : ec.getItemsByClass(Adapter.class)){
\r
1057 T t = adapter.adapt(toClass);
\r
1065 * Otherwise the same as {@link #adapt(ElementClass, Class)} but will throw
\r
1066 * {@link UnsupportedOperationException} if the adaption fails.
\r
1069 * the adaption target class
\r
1071 * the element to adapt
\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
1077 public static <T> T checkedAdapt(ElementClass ec, Class<T> toClass) {
\r
1078 T t = adapt(ec, toClass);
\r
1081 throw new UnsupportedOperationException("cannot adapt " + ec + " to " + toClass);
\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
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
1095 * @param forElement
\r
1096 * @param withParentNode
\r
1097 * @param withNodeKey
\r
1098 * @param nodeClass
\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
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
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
1116 * @param forElement
\r
1117 * @param withParentNode
\r
1118 * @param withNodeKey
\r
1120 * @param nodeClass
\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
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
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
1138 * @param forElement
\r
1139 * @param withParentNode
\r
1140 * @param withNodeKey
\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
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
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
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
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
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
1184 INode node = element.getHint(nodeKey);
\r
1195 public static Font getTextFont(IElement element) {
\r
1196 TextFont tf = element.getElementClass().getSingleItem(TextFont.class);
\r
1197 return tf.getFont(element);
\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
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
1211 collection.add(item);
\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
1224 public static void setHover(IElement e, boolean hover)
\r
1226 Hover h = e.getElementClass().getSingleItem(Hover.class);
\r
1227 h.setHover(e, hover);
\r
1230 public static boolean isHovering(IElement e)
\r
1232 Hover h = e.getElementClass().getSingleItem(Hover.class);
\r
1233 return h.isHovering(e);
\r