--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.element;\r
+\r
+import java.awt.Color;\r
+import java.awt.Font;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Area;\r
+import java.awt.geom.NoninvertibleTransformException;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.function.Consumer;\r
+\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.DataElementMap;\r
+import org.simantics.g2d.diagram.handler.PickRequest;\r
+import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
+import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
+import org.simantics.g2d.element.handler.Adapter;\r
+import org.simantics.g2d.element.handler.AdditionalColor;\r
+import org.simantics.g2d.element.handler.BendsHandler;\r
+import org.simantics.g2d.element.handler.BendsHandler.Bend;\r
+import org.simantics.g2d.element.handler.BorderColor;\r
+import org.simantics.g2d.element.handler.Clickable;\r
+import org.simantics.g2d.element.handler.Clickable.ClickListener;\r
+import org.simantics.g2d.element.handler.Clickable.PressStatus;\r
+import org.simantics.g2d.element.handler.EdgeVisuals;\r
+import org.simantics.g2d.element.handler.ElementAdapter;\r
+import org.simantics.g2d.element.handler.FillColor;\r
+import org.simantics.g2d.element.handler.Hover;\r
+import org.simantics.g2d.element.handler.InternalSize;\r
+import org.simantics.g2d.element.handler.Move;\r
+import org.simantics.g2d.element.handler.Outline;\r
+import org.simantics.g2d.element.handler.Parent;\r
+import org.simantics.g2d.element.handler.Pick;\r
+import org.simantics.g2d.element.handler.Resize;\r
+import org.simantics.g2d.element.handler.Scale;\r
+import org.simantics.g2d.element.handler.Stateful;\r
+import org.simantics.g2d.element.handler.TerminalLayout;\r
+import org.simantics.g2d.element.handler.TerminalTopology;\r
+import org.simantics.g2d.element.handler.Text;\r
+import org.simantics.g2d.element.handler.TextColor;\r
+import org.simantics.g2d.element.handler.TextEditor;\r
+import org.simantics.g2d.element.handler.TextFont;\r
+import org.simantics.g2d.element.handler.Transform;\r
+import org.simantics.g2d.participant.TransformUtil;\r
+import org.simantics.g2d.utils.GeometryUtils;\r
+import org.simantics.g2d.utils.geom.DirectionSet;\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.ParentNode;\r
+import org.simantics.utils.ObjectUtils;\r
+import org.simantics.utils.datastructures.hints.IHintContext;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+\r
+/**\r
+ * Utils for element users.\r
+ * \r
+ * @See {@link TerminalUtil}\r
+ * @See {@link ElementHandlerUtils} Utils for element handler (coders)\r
+ * @author Toni Kalajainen\r
+ */\r
+public class ElementUtils {\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public static <T> T getObject(IElement e) {\r
+ return (T) e.getHint(ElementHints.KEY_OBJECT);\r
+ }\r
+\r
+ public static void disable(IElement e)\r
+ {\r
+ Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);\r
+ enabled.setEnabled(e, false);\r
+ }\r
+\r
+ public static void enable(IElement e)\r
+ {\r
+ Stateful enabled = e.getElementClass().getSingleItem(Stateful.class);\r
+ enabled.setEnabled(e, true);\r
+ }\r
+\r
+ /**\r
+ * @param e\r
+ * @return\r
+ */\r
+ public static boolean isHidden(IElement e) {\r
+ return e.containsHint(ElementHints.KEY_HIDDEN);\r
+ }\r
+\r
+ /**\r
+ * @param e\r
+ * @param state <code>null</code> to remove hidden state\r
+ * @return\r
+ */\r
+ public static void setHidden(IElement e, boolean hidden) {\r
+ if (hidden)\r
+ e.setHint(ElementHints.KEY_HIDDEN, HideState.COMPLETELY_HIDDEN);\r
+ else\r
+ e.removeHint(ElementHints.KEY_HIDDEN);\r
+ }\r
+\r
+ public static void setText(IElement e, String text)\r
+ {\r
+ Text t = e.getElementClass().getSingleItem(Text.class);\r
+ t.setText(e, text);\r
+ }\r
+\r
+ public static String getText(IElement e)\r
+ {\r
+ Text t = e.getElementClass().getSingleItem(Text.class);\r
+ return t.getText(e);\r
+ }\r
+\r
+ /**\r
+ * Resizes or scales an element to fit it into a rectangle.\r
+ * \r
+ * @param e element\r
+ * @param rect rectangle on diagram\r
+ */\r
+ public static void fitToRectangle(IElement e, Rectangle2D rect)\r
+ {\r
+ ElementClass ec = e.getElementClass();\r
+ Move m = ec.getSingleItem(Move.class);\r
+ InternalSize b = ec.getSingleItem(InternalSize.class);\r
+ Rectangle2D internalSize = b.getBounds(e, null);\r
+ if (internalSize == null)\r
+ return;\r
+\r
+ Resize rs = ec.getAtMostOneItemOfClass(Resize.class);\r
+\r
+ Scale s = ec.getAtMostOneItemOfClass(Scale.class);\r
+ Point2D scale = s==null?new Point2D.Double(1.0,1.0):s.getScale(e);\r
+ double width = rect.getWidth();\r
+ double height = rect.getHeight();\r
+ double aspectRatio = width/height;\r
+\r
+ Double requiredAspectRatio = rs==null?null:rs.getFixedAspectRatio(e);\r
+ if (requiredAspectRatio!=null)\r
+ {\r
+ if (aspectRatio>requiredAspectRatio)\r
+ width = height*requiredAspectRatio;\r
+ else\r
+ height = width / requiredAspectRatio;\r
+ }\r
+\r
+ // Resize it\r
+ if (rs!=null) {\r
+ m.moveTo(e, rect.getX(), rect.getY());\r
+ if (scale!=null) {\r
+ width /= scale.getX();\r
+ height /= scale.getY();\r
+ }\r
+ Rectangle2D r = new Rectangle2D.Double(0, 0, width, height);\r
+ rs.resize(e, r);\r
+ } else\r
+ // Scale it\r
+ if (s!=null) {\r
+ double sx = rect.getWidth() / internalSize.getWidth();\r
+ double sy = rect.getHeight() / internalSize.getHeight();\r
+ double px = rect.getX() - internalSize.getX()*sx;\r
+ double py = rect.getY() - internalSize.getY()*sy;\r
+ m.moveTo(e, px, py);\r
+ scale.setLocation(sx, sy);\r
+ s.setScale(e, scale);\r
+ }\r
+ }\r
+\r
+ public static void addClickListener(IElement e, ICanvasContext ctx, ClickListener listener)\r
+ {\r
+ Clickable clickable = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);\r
+ clickable.addListener(e, ctx, ctx.getThreadAccess(), listener);\r
+ }\r
+\r
+ public static Point2D getPos(IElement e)\r
+ {\r
+ Move m = e.getElementClass().getSingleItem(Move.class);\r
+ return m.getPosition(e);\r
+ }\r
+\r
+ public static Point2D getPos(IElement e, Point2D result)\r
+ {\r
+ Move m = e.getElementClass().getSingleItem(Move.class);\r
+ if (result == null)\r
+ result = new Point2D.Double();\r
+ Point2D p = m.getPosition(e);\r
+ result.setLocation(p);\r
+ return result;\r
+ }\r
+\r
+ public static Point2D getAbsolutePos(IElement e)\r
+ {\r
+ Transform tr = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform at = tr.getTransform(e);\r
+ return new Point2D.Double(at.getTranslateX(), at.getTranslateY());\r
+ }\r
+\r
+ public static Point2D getAbsolutePos(IElement e, Point2D result)\r
+ {\r
+ Transform tr = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform at = tr.getTransform(e);\r
+ if (result == null)\r
+ result = new Point2D.Double();\r
+ result.setLocation(at.getTranslateX(), at.getTranslateY());\r
+ return result;\r
+ }\r
+\r
+ public static void setPos(IElement e, Point2D newPosition)\r
+ {\r
+ Move m = e.getElementClass().getSingleItem(Move.class);\r
+ m.moveTo(e, newPosition.getX(), newPosition.getY());\r
+ }\r
+\r
+ public static void setPos(IElement e, double x, double y)\r
+ {\r
+ Move m = e.getElementClass().getSingleItem(Move.class);\r
+ m.moveTo(e,x, y);\r
+ }\r
+\r
+ public static IElement getByData(IDiagram d, Object data)\r
+ {\r
+ DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
+ return map.getElement(d, data);\r
+ }\r
+\r
+ public static Object getData(IDiagram d, IElement element)\r
+ {\r
+ DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class);\r
+ return map.getData(d, element);\r
+ }\r
+\r
+ /**\r
+ * Get all terminals of an element.\r
+ * \r
+ * @param e element\r
+ * @param result a store for the terminals\r
+ * @param clearResult <code>true</code> to clear the result collection\r
+ * before filling it\r
+ * @return the specified result collection\r
+ */\r
+ public static Collection<Terminal> getTerminals(IElement e, Collection<Terminal> result, boolean clearResult) {\r
+ if (clearResult)\r
+ result.clear();\r
+ TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);\r
+ tt.getTerminals(e, result);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * Get a terminal of an element assuming there is only a single terminal.\r
+ * \r
+ * @param e element\r
+ * @param t terminal\r
+ * @return the only terminal of element e\r
+ * @throws IllegalArgumentException if there are zero or multiple terminals\r
+ */\r
+ public static Terminal getSingleTerminal(IElement e) {\r
+ ArrayList<Terminal> ts = new ArrayList<Terminal>(4);\r
+ TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);\r
+ tt.getTerminals(e, ts);\r
+ if (ts.size() != 1)\r
+ throw new IllegalArgumentException("expected 1 terminal, element e has " + ts.size() + " terminals: " + ts);\r
+ return ts.get(0);\r
+ }\r
+\r
+ /**\r
+ * Get a terminal of an element assuming there is only a single terminal.\r
+ * If there are no or multiple terminals, <code>null</code> is returned.\r
+ * \r
+ * @param e element\r
+ * @param t terminal\r
+ * @return\r
+ */\r
+ public static Terminal peekSingleTerminal(IElement e) {\r
+ ArrayList<Terminal> ts = new ArrayList<Terminal>(4);\r
+ TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);\r
+ tt.getTerminals(e, ts);\r
+ if (ts.size() != 1)\r
+ return null;\r
+ return ts.get(0);\r
+ }\r
+\r
+ /**\r
+ * Get allowed outward directions of a terminal\r
+ * @param e element\r
+ * @param t terminal\r
+ * @return\r
+ */\r
+ public static DirectionSet getTerminalDirection(IElement e, Terminal t)\r
+ {\r
+ List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
+ DirectionSet result = new DirectionSet();\r
+ for (TerminalLayout tl : tls) {\r
+ tl.getTerminalDirection(e, t, result);\r
+ }\r
+ return result;\r
+ }\r
+\r
+ public static AffineTransform getTransform(IElement e)\r
+ {\r
+ return e.getElementClass().getSingleItem(Transform.class).getTransform(e);\r
+ }\r
+\r
+ public static AffineTransform getTransform(IElement e, AffineTransform result)\r
+ {\r
+ if (e == null)\r
+ return result;\r
+ AffineTransform tr = e.getElementClass().getSingleItem(Transform.class).getTransform(e);\r
+ result.setTransform(tr);\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * @param e the element to get the local transform from\r
+ * @param result the transform to set to the local transform value or\r
+ * <code>null</code> to allocate a new transform if the element\r
+ * doesn't provide one. By providing a result transform one can make\r
+ * sure that no internal state of the element is returned.\r
+ * @return the provided result transform or a new transform instance\r
+ * depending on the arguments\r
+ */\r
+ public static AffineTransform getLocalTransform(IElement e, AffineTransform result)\r
+ {\r
+ AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM);\r
+ if (result == null)\r
+ result = new AffineTransform();\r
+ if (at != null)\r
+ result.setTransform(at);\r
+ return result;\r
+ }\r
+\r
+ public static void setTransform(IElement e, AffineTransform at)\r
+ {\r
+ e.getElementClass().getSingleItem(Transform.class).setTransform(e, at);\r
+ }\r
+\r
+ public static AffineTransform getInvTransform(IElement e)\r
+ {\r
+ try {\r
+ return e.getElementClass().getSingleItem(Transform.class).getTransform(e).createInverse();\r
+ } catch (NoninvertibleTransformException e1) {\r
+ throw new RuntimeException(e1);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Element to canvas coordinates\r
+ * @param e\r
+ * @param elementPoint\r
+ * @param canvasPoint\r
+ * @return\r
+ */\r
+ public static Point2D elementToCanvasCoordinate(IElement e, Point2D elementPoint, Point2D canvasPoint)\r
+ {\r
+ Transform t = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform at = t.getTransform(e);\r
+ return at.transform(elementPoint, canvasPoint);\r
+ }\r
+\r
+ /**\r
+ * Element to control coordinates\r
+ * @param e\r
+ * @param ctx\r
+ * @param elementPoint\r
+ * @param controlPoint\r
+ * @return\r
+ */\r
+ public static Point2D elementToControlCoordinate(IElement e, ICanvasContext ctx, Point2D elementPoint, Point2D controlPoint)\r
+ {\r
+ Transform t = e.getElementClass().getSingleItem(Transform.class);\r
+ TransformUtil util = ctx.getSingleItem(TransformUtil.class);\r
+ Point2D canvasPoint = t.getTransform(e).transform(elementPoint, null);\r
+ return util.getTransform().transform(elementPoint, canvasPoint);\r
+ }\r
+\r
+ public static Point2D controlToElementCoordinate(IElement e, ICanvasContext ctx, Point2D controlPoint, Point2D elementPoint)\r
+ {\r
+ Transform t = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform at = t.getTransform(e);\r
+ TransformUtil util = ctx.getSingleItem(TransformUtil.class);\r
+ Point2D canvasPoint = util.controlToCanvas(controlPoint, new Point2D.Double());\r
+ if (elementPoint==null) elementPoint = new Point2D.Double();\r
+ try {\r
+ at.inverseTransform(canvasPoint, elementPoint);\r
+ return elementPoint;\r
+ } catch (NoninvertibleTransformException e1) {\r
+ throw new RuntimeException(e1);\r
+ }\r
+ }\r
+\r
+ public static Point2D controlToCanvasCoordinate(ICanvasContext ctx, Point2D controlPoint, Point2D canvasPoint)\r
+ {\r
+ TransformUtil tu = ctx.getSingleItem(TransformUtil.class);\r
+ return tu.controlToCanvas(controlPoint, canvasPoint);\r
+ }\r
+\r
+\r
+ public static PressStatus getPressStatus(IElement e, ICanvasContext ctx)\r
+ {\r
+ Clickable c = e.getElementClass().getAtMostOneItemOfClass(Clickable.class);\r
+ if (c==null) return null;\r
+ return c.getPressStatus(e, ctx);\r
+ }\r
+\r
+ public static Color getBorderColor(IElement e)\r
+ {\r
+ return getBorderColor(e, null);\r
+ }\r
+\r
+ public static Color getFillColor(IElement e)\r
+ {\r
+ return getFillColor(e, null);\r
+ }\r
+\r
+ public static Color getAdditionalColor(IElement e)\r
+ {\r
+ return getAdditionalColor(e, null);\r
+ }\r
+\r
+ public static Color getTextColor(IElement e)\r
+ {\r
+ return getTextColor(e, null);\r
+ }\r
+\r
+ /**\r
+ * Get border color of element of return defaultValue if border color is not\r
+ * available.\r
+ * \r
+ * @param e\r
+ * @param defaultValue\r
+ * @return\r
+ */\r
+ public static Color getBorderColor(IElement e, Color defaultValue)\r
+ {\r
+ BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class);\r
+ if (bc==null) return defaultValue;\r
+ Color c = bc.getBorderColor(e);\r
+ return c != null ? c : defaultValue;\r
+ }\r
+\r
+ /**\r
+ * Get fill color of element of return defaultValue if fill color is not\r
+ * available.\r
+ * \r
+ * @param e\r
+ * @param defaultValue\r
+ * @return\r
+ */\r
+ public static Color getFillColor(IElement e, Color defaultValue)\r
+ {\r
+ FillColor fc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class);\r
+ if (fc==null) return defaultValue;\r
+ Color c = fc.getFillColor(e);\r
+ return c != null ? c : defaultValue;\r
+ }\r
+\r
+ /**\r
+ * Get additional color of element of return defaultValue if additional\r
+ * color is not available.\r
+ * \r
+ * @param e\r
+ * @param defaultValue\r
+ * @return\r
+ */\r
+ public static Color getAdditionalColor(IElement e, Color defaultValue)\r
+ {\r
+ AdditionalColor ac = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class);\r
+ if (ac==null) return null;\r
+ Color c = ac.getAdditionalColor(e);\r
+ return c != null ? c : defaultValue;\r
+ }\r
+\r
+ /**\r
+ * Get text color of element of return defaultValue if text color is not\r
+ * available.\r
+ * \r
+ * @param e\r
+ * @param defaultValue\r
+ * @return\r
+ */\r
+ public static Color getTextColor(IElement e, Color defaultValue)\r
+ {\r
+ TextColor tc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class);\r
+ if (tc==null) return defaultValue;\r
+ Color c = tc.getTextColor(e);\r
+ return c != null ? c : defaultValue;\r
+ }\r
+\r
+ public static TextEditor getTextEditor(IElement e)\r
+ {\r
+ TextEditor ed = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class);\r
+ return ed;\r
+ }\r
+\r
+ public static void setBorderColor(IElement e, Color color)\r
+ {\r
+ BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class);\r
+ if (bc==null) return;\r
+ bc.setBorderColor(e, color);\r
+ }\r
+\r
+ public static void setFillColor(IElement e, Color color)\r
+ {\r
+ FillColor bc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class);\r
+ if (bc==null) return;\r
+ bc.setFillColor(e, color);\r
+ }\r
+\r
+ public static void setAdditionalColor(IElement e, Color color)\r
+ {\r
+ AdditionalColor bc = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class);\r
+ if (bc==null) return;\r
+ bc.setAdditionalColor(e, color);\r
+ }\r
+\r
+ public static void setTextColor(IElement e, Color color)\r
+ {\r
+ TextColor bc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class);\r
+ if (bc==null) return;\r
+ bc.setTextColor(e, color);\r
+ }\r
+\r
+ public static void setEdgeStroke(IElement e, Stroke s)\r
+ {\r
+ EdgeVisuals ev = e.getElementClass().getSingleItem(EdgeVisuals.class);\r
+ ev.setStroke(e, s);\r
+ }\r
+\r
+ /**\r
+ * Fill given map with element bounds (the bounds on diagram)\r
+ * \r
+ * @param elements\r
+ * @param rects structure to be filled or null (instantates new)\r
+ * @return rects or newly instantiated structure\r
+ */\r
+ public static Map<IElement, Rectangle2D> getElementBoundsOnDiagram(Collection<IElement> elements, Map<IElement, Rectangle2D> rects)\r
+ {\r
+ if (rects == null) rects = new HashMap<IElement, Rectangle2D>();\r
+ for (IElement e : elements) {\r
+ Shape shape = getElementBoundsOnDiagram(e);\r
+ rects.put(e, shape.getBounds2D());\r
+ }\r
+ return rects;\r
+ }\r
+\r
+ /**\r
+ * get element bounds\r
+ * @param e element\r
+ * @return element bounds in element coordinates\r
+ */\r
+ public static Rectangle2D getElementBounds(IElement e)\r
+ {\r
+ InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);\r
+ return b.getBounds(e, new Rectangle2D.Double());\r
+ }\r
+\r
+ /**\r
+ * get element bounds\r
+ * @param e element\r
+ * @param result a rectangle for storing the result\r
+ * @return the specified result rectangle\r
+ */\r
+ public static Rectangle2D getElementBounds(IElement e, Rectangle2D result)\r
+ {\r
+ InternalSize b = e.getElementClass().getSingleItem(InternalSize.class);\r
+ return b.getBounds(e, result);\r
+ }\r
+\r
+ /**\r
+ * Get rough estimation of outer bounds of an element\r
+ * @param e element\r
+ * @return bounds on a diagram\r
+ */\r
+ public static Shape getElementBoundsOnDiagram(IElement e)\r
+ {\r
+ Rectangle2D elementBounds = getElementBounds(e);\r
+ Transform t = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform canvasToElement = t.getTransform(e);\r
+ return GeometryUtils.transformShape(elementBounds, canvasToElement);\r
+ }\r
+\r
+ /**\r
+ * Get rough estimation of outer bounds of an element\r
+ * @param e element\r
+ * @param result a rectangle for storing the result\r
+ * @return bounds on a diagram\r
+ */\r
+ public static Rectangle2D getElementBoundsOnDiagram(IElement e, Rectangle2D result)\r
+ {\r
+ result = getElementBounds(e, result);\r
+ Transform t = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform canvasToElement = t.getTransform(e);\r
+ Shape shp = GeometryUtils.transformShape(result, canvasToElement);\r
+ result.setFrame(shp.getBounds2D());\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * Get union of outer bounds of a set of elements\r
+ * @param elements\r
+ * @return Union of element bounds (on diagram) or null\r
+ */\r
+ public static Shape getElementBoundsOnDiagram(Collection<IElement> elements)\r
+ {\r
+ if (elements.size()==0) return null;\r
+ if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next());\r
+ Area a = new Area();\r
+ for (IElement e : elements) {\r
+ Shape bounds = getElementBoundsOnDiagram(e);\r
+ Area ae = bounds instanceof Area ? (Area) bounds : new Area(bounds);\r
+ a.add(ae);\r
+ }\r
+ return a;\r
+ }\r
+\r
+ /**\r
+ * Get union of outer bounds of a set of elements\r
+ * @param elements\r
+ * @return Union of element bounds (on diagram) or null\r
+ */\r
+ public static Rectangle2D getSurroundingElementBoundsOnDiagram(Collection<IElement> elements)\r
+ {\r
+ if (elements.size()==0) return null;\r
+ if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next()).getBounds2D();\r
+ double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, maxX = -Double.MAX_VALUE, maxY = -Double.MAX_VALUE;\r
+ for (IElement e : elements) {\r
+ Rectangle2D bounds = getElementBoundsOnDiagram(e).getBounds2D();\r
+ if (bounds.getMinX() < minX) minX = bounds.getMinX();\r
+ if (bounds.getMinY() < minY) minY = bounds.getMinY();\r
+ if (bounds.getMaxX() > maxX) maxX = bounds.getMaxX();\r
+ if (bounds.getMaxY() > maxY) maxY = bounds.getMaxY();\r
+ }\r
+ return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY);\r
+ }\r
+\r
+ /**\r
+ * Get as accurate shape if available\r
+ * \r
+ * @param e\r
+ * @return accurate shape of an element or <code>null</code> if shape is not available\r
+ */\r
+ public static Shape getElementShape(IElement e)\r
+ {\r
+ List<Outline> shapeProviders = e.getElementClass().getItemsByClass(Outline.class);\r
+ if (shapeProviders.isEmpty()) return null;\r
+ if (shapeProviders.size()==1) return shapeProviders.iterator().next().getElementShape(e);\r
+ Area a = new Area();\r
+ for (Outline es : shapeProviders)\r
+ {\r
+ Shape shape = es.getElementShape(e);\r
+ Area ae = shape instanceof Area ? (Area) shape : new Area(shape);\r
+ a.add(ae);\r
+ }\r
+ return a;\r
+ }\r
+\r
+ public static Shape getElementShapeOnDiagram(IElement e)\r
+ {\r
+ Shape shape = getElementShape(e);\r
+ if (shape == null)\r
+ return null;\r
+ Transform t = e.getElementClass().getSingleItem(Transform.class);\r
+ AffineTransform canvasToElement = t.getTransform(e);\r
+ return GeometryUtils.transformShape(shape, canvasToElement);\r
+ }\r
+\r
+ /**\r
+ * Get element shape is one exists otherwise its bounds\r
+ * @param e\r
+ * @return shape or bounds\r
+ */\r
+ public static Shape getElementShapeOrBounds(IElement e)\r
+ {\r
+ Shape shape = getElementShape(e);\r
+ if (shape!=null) return shape;\r
+ return getElementBounds(e);\r
+ }\r
+\r
+ /**\r
+ * Get element shape is one exists otherwise its bounds\r
+ * @param e\r
+ * @return shape or bounds\r
+ */\r
+ public static Shape getElementShapeOrBoundsOnDiagram(IElement e)\r
+ {\r
+ Shape shape = getElementShapeOnDiagram(e);\r
+ if (shape!=null) return shape;\r
+ return getElementBoundsOnDiagram(e);\r
+ }\r
+\r
+ public static Shape getElementShapesOnDiagram(Collection<IElement> elements)\r
+ {\r
+ if (elements.isEmpty()) return null;\r
+ if (elements.size()==1) {\r
+ //ITask task = ThreadLogger.getInstance().begin("single element shape: " + elements.iterator().next() + ")");\r
+ Shape shp = getElementShapeOrBoundsOnDiagram(elements.iterator().next());\r
+ //task.finish();\r
+ return shp;\r
+ }\r
+ Area a = new Area();\r
+ //ITask task = ThreadLogger.getInstance().begin("union of " + elements.size() + " element shapes");\r
+ for (IElement e : elements) {\r
+ //ITask task2 = ThreadLogger.getInstance().begin("calculate area of " + e);\r
+ Shape shape = getElementShapeOrBoundsOnDiagram(e);\r
+ //task2.finish();\r
+ //task2 = ThreadLogger.getInstance().begin("construct area from " + shape);\r
+ Area aa = null;\r
+ if (shape instanceof Area)\r
+ aa = (Area)shape;\r
+ else\r
+ aa = new Area(shape);\r
+ //task2.finish();\r
+ //task2 = ThreadLogger.getInstance().begin("union area " + aa);\r
+ a.add(aa);\r
+ //task2.finish();\r
+ }\r
+ //task.finish();\r
+ return a;\r
+ }\r
+\r
+ public static Shape mergeShapes(Collection<Shape> shapes)\r
+ {\r
+ if (shapes.isEmpty()) return null;\r
+ if (shapes.size()==1) return shapes.iterator().next();\r
+ Area a = new Area();\r
+ for (Shape s : shapes)\r
+ a.add(new Area(s));\r
+ return a;\r
+ }\r
+\r
+ public static boolean pickInElement(IElement e, ICanvasContext ctx, PickRequest req)\r
+ {\r
+ Rectangle2D elementBounds = getElementBounds(e);\r
+\r
+ // Pick with pick handler(s)\r
+ List<Pick> pickHandlers = e.getElementClass().getItemsByClass(Pick.class);\r
+ if (!pickHandlers.isEmpty())\r
+ {\r
+ // Rough filtering with bounds\r
+ if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;\r
+\r
+ // Convert pick shape to element coordinates\r
+ for (Pick p : pickHandlers)\r
+ {\r
+ if (p.pickTest(e, req.pickArea, req.pickPolicy))\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ // Pick with shape handler(s)\r
+ List<Outline> shapeHandlers = e.getElementClass().getItemsByClass(Outline.class);\r
+ if (!shapeHandlers.isEmpty())\r
+ {\r
+ // Rough filtering with bounds\r
+ if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false;\r
+\r
+ // Intersection with one shape is enough\r
+ if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)\r
+ {\r
+ for (Outline es : shapeHandlers)\r
+ {\r
+ Shape elementShape = es.getElementShape(e);\r
+ if (GeometryUtils.intersects(req.pickArea, elementShape))\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ // Contains of all shapes is required\r
+ if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)\r
+ {\r
+ for (Outline es : shapeHandlers)\r
+ {\r
+ Shape elementShape = es.getElementShape(e);\r
+ if (!GeometryUtils.contains(req.pickArea, elementShape))\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ // Pick by rectangle\r
+ if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS)\r
+ {\r
+ if (GeometryUtils.intersects(req.pickArea, elementBounds))\r
+ return true;\r
+ }\r
+\r
+ else\r
+\r
+ if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS)\r
+ {\r
+ if (GeometryUtils.contains(req.pickArea, elementBounds))\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Get bends of an edge\r
+ * \r
+ * @param e edge\r
+ * @param bends the handles of each bend point\r
+ * @param points collection to be filled with the bends points in the same\r
+ * order as the bend handle objects\r
+ */\r
+ public static void getBends(IElement e, List<Bend> bends, List<Point2D> points)\r
+ {\r
+ BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
+ bh.getBends(e, bends);\r
+ for (Bend b : bends)\r
+ {\r
+ Point2D pos = new Point2D.Double();\r
+ bh.getBendPosition(e, b, pos);\r
+ points.add(pos);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get bends of an edge\r
+ * @param e edge\r
+ * @param points collection to be filled with the bends points\r
+ */\r
+ public static void getBends(IElement e, List<Point2D> points)\r
+ {\r
+ BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
+ int bendCount = bh.getBendsCount(e);\r
+ ArrayList<Bend> bends = new ArrayList<Bend>(bendCount);\r
+ getBends(e, bends, points);\r
+ }\r
+\r
+\r
+ public static void resizeElement(IElement e, double x, double y, double w, double h) {\r
+ Move m = e.getElementClass().getSingleItem(Move.class);\r
+ m.moveTo(e, x, y);\r
+ Resize s = e.getElementClass().getSingleItem(Resize.class);\r
+ s.resize(e, new Rectangle2D.Double(0,0,w,h));\r
+ }\r
+\r
+ public static <T> T getHintOrDefault(IHintContext e, Key key, T defaultValue) {\r
+ T t = e.getHint(key);\r
+ assert key.isValueAccepted(defaultValue);\r
+ return t == null ? defaultValue : t;\r
+ }\r
+\r
+ public static void setOrRemoveHint(IHintContext e, Key key, Object value) {\r
+ if (value == null) {\r
+ e.removeHint(key);\r
+ } else {\r
+ assert key.isValueAccepted(value);\r
+ e.setHint(key, value);\r
+ }\r
+ }\r
+\r
+ public static boolean elementEquals(IElement e1, IElement e2) {\r
+ Object o1 = getObject(e1);\r
+ Object o2 = getObject(e2);\r
+ if (o1 == null && o2 == null)\r
+ return ObjectUtils.objectEquals(e1, e2);\r
+ return ObjectUtils.objectEquals(o1, o2);\r
+ }\r
+\r
+ public static IElement getDiagramMappedElement(IElement e) {\r
+ IDiagram d = e.peekDiagram();\r
+ if (d == null)\r
+ return e;\r
+ DataElementMap map = d.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);\r
+ if (map == null)\r
+ return e;\r
+ Object o = map.getData(d, e);\r
+ if (o == null)\r
+ return e;\r
+ IElement mapped = map.getElement(d, o);\r
+ return mapped != null ? mapped : e;\r
+ }\r
+\r
+ /**\r
+ * Calculates the center of the bounding box containing all the specified\r
+ * elements.\r
+ * \r
+ * @param the elements for which to calculate the center of a containing\r
+ * bounding box\r
+ * @param pivotPoint a Point2D for writing the result of the calculation or\r
+ * <code>null</code> to allocate a new Point2D if necessary\r
+ * @return the center of the containing bounding box or <code>null</code> if\r
+ * there are no elements\r
+ */\r
+ public static Point2D getElementBoundsCenter(Collection<IElement> elements, Point2D result) {\r
+ Shape b = getElementBoundsOnDiagram(elements);\r
+ if (b == null)\r
+ return null;\r
+ Rectangle2D bounds = b.getBounds2D();\r
+ if (result == null)\r
+ result = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());\r
+ else\r
+ result.setLocation(bounds.getCenterX(), bounds.getCenterY());\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * A utility for retrieving the containg diagram of an element. The element\r
+ * does not have to be directly in a diagram, the utility will also look for\r
+ * through the element parents for a diagram too.\r
+ * \r
+ * @param e\r
+ * @return\r
+ */\r
+ public static IDiagram getDiagram(IElement e) {\r
+ if (e == null)\r
+ throw new IllegalArgumentException("null element");\r
+ IDiagram d = peekDiagram(e);\r
+ if (d == null)\r
+ throw new IllegalStateException("element " + e + " is not part of a diagram");\r
+ return d;\r
+ }\r
+\r
+ /**\r
+ * A utility for retrieving the containg diagram of an element. The element\r
+ * does not have to be directly in a diagram, the utility will also look for\r
+ * through the element parents for a diagram too.\r
+ * \r
+ * @param e\r
+ * @return <code>null</code> if the element is not on a diagram nor is the\r
+ * element transitively a child of any element that is on a diagram\r
+ */\r
+ public static IDiagram peekDiagram(IElement e) {\r
+ while (e != null) {\r
+ IDiagram d = e.peekDiagram();\r
+ if (d != null)\r
+ return d;\r
+ e = getParent(e);\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Retrieves a possible parent element of an element.\r
+ * \r
+ * @param e the element to get a parent for\r
+ * @return the parent element or <code>null</code> if the element does not\r
+ * have a parent element\r
+ */\r
+ public static IElement getParent(IElement e) {\r
+ Parent p = e.getElementClass().getAtMostOneItemOfClass(Parent.class);\r
+ if (p == null)\r
+ return null;\r
+ return p.getParent(e);\r
+ }\r
+\r
+ /**\r
+ * Retrieves a possible parent element of an element.\r
+ * \r
+ * @param e the element to get a parent for\r
+ * @return the parent element or <code>null</code> if the element does not\r
+ * have a parent element\r
+ */\r
+ public static Collection<IElement> getParents(IElement e) {\r
+ List<IElement> result = new ArrayList<IElement>(3);\r
+ return getParents(e, result);\r
+ }\r
+\r
+ /**\r
+ * Retrieves a possible parent element of an element.\r
+ * \r
+ * @param e the element to get a parent for\r
+ * @param result a collection wherein to store the possible parent elements\r
+ * @return the specified result collection\r
+ */\r
+ public static Collection<IElement> getParents(IElement e, Collection<IElement> result) {\r
+ IElement p = e;\r
+ while (true) {\r
+ Parent ph = p.getElementClass().getAtMostOneItemOfClass(Parent.class);\r
+ if (ph == null)\r
+ return result;\r
+ p = ph.getParent(p);\r
+ if (p == null)\r
+ return result;\r
+ result.add(p);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @param e\r
+ * @return\r
+ */\r
+ public static String generateNodeId(IElement e) {\r
+\r
+ String prefix = "";\r
+ String sgName = e.getHint(ElementHints.KEY_SG_NAME);\r
+ if (sgName != null) prefix = sgName + " ";\r
+\r
+ Object object = e.getHint(ElementHints.KEY_OBJECT);\r
+ if (object != null) {\r
+ return prefix + object.toString();\r
+ }\r
+ // Warning: this can be hazardous for scene graph consistency!\r
+ // Duplicate nodes may be introduced when elements are updated through\r
+ // the Image interface.\r
+ return String.valueOf(e.hashCode());\r
+ }\r
+\r
+ /**\r
+ * \r
+ * @param <T> the adaption target class\r
+ * @param e the element to adapt\r
+ * @param toClass the object class to adapt the element to\r
+ * @return adapter result or <code>null</code> if adaptation failed\r
+ */\r
+ public static <T> T adaptElement(IElement e, Class<T> toClass) {\r
+ for (ElementAdapter adapter : e.getElementClass().getItemsByClass(ElementAdapter.class)){\r
+ T t = adapter.adapt(e, toClass);\r
+ if (t != null)\r
+ return t;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Tries to adapt an {@link ElementClass} into a requested class through\r
+ * {@link Adapter} handlers. Implements the IElement adaptation logic\r
+ * described in {@link Adapter}.\r
+ * \r
+ * @param <T>\r
+ * the adaption target class\r
+ * @param e\r
+ * the element to adapt\r
+ * @param toClass\r
+ * the object class to adapt the element to\r
+ * @return adapter result or <code>null</code> if adaptation failed\r
+ */\r
+ public static <T> T adapt(ElementClass ec, Class<T> toClass) {\r
+ if (ec == null)\r
+ throw new IllegalArgumentException("null element class");\r
+ if (toClass == null)\r
+ throw new IllegalArgumentException("null target class");\r
+\r
+ for (Adapter adapter : ec.getItemsByClass(Adapter.class)){\r
+ T t = adapter.adapt(toClass);\r
+ if (t != null)\r
+ return t;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Otherwise the same as {@link #adapt(ElementClass, Class)} but will throw\r
+ * {@link UnsupportedOperationException} if the adaption fails.\r
+ * \r
+ * @param <T>\r
+ * the adaption target class\r
+ * @param e\r
+ * the element to adapt\r
+ * @param toClass\r
+ * the object class to adapt the element to\r
+ * @return adapter result or <code>null</code> if adaptation failed\r
+ * @throws UnsupportedOperationException\r
+ */\r
+ public static <T> T checkedAdapt(ElementClass ec, Class<T> toClass) {\r
+ T t = adapt(ec, toClass);\r
+ if (t != null)\r
+ return t;\r
+ throw new UnsupportedOperationException("cannot adapt " + ec + " to " + toClass);\r
+ }\r
+\r
+ /**\r
+ * Looks for a scene graph node from the specified element with the\r
+ * specified node key. If the node does not exist, a new node is created\r
+ * using the specified node class.\r
+ * \r
+ * If a previous node exists, its class is verified to match the requested\r
+ * node class and returned as such upon success. If the classes do not\r
+ * match, an exception is raised since this is most likely a bug that needs\r
+ * to be fixed elsewhere.\r
+ * \r
+ * @param <T>\r
+ * @param forElement\r
+ * @param withParentNode\r
+ * @param withNodeKey\r
+ * @param nodeClass\r
+ * @return\r
+ */\r
+ public static <T extends INode> T getOrCreateNode(IElement forElement, ParentNode<?> withParentNode, Key withNodeKey, Class<T> nodeClass) {\r
+ return getOrCreateNode(forElement, withParentNode, withNodeKey, null, nodeClass);\r
+ }\r
+\r
+ /**\r
+ * Looks for a scene graph node from the specified element with the\r
+ * specified node key. If the node does not exist, a new node is created\r
+ * using the specified node class.\r
+ * \r
+ * If a previous node exists, its class is verified to match the requested\r
+ * node class and returned as such upon success. If the classes do not\r
+ * match, an exception is raised since this is most likely a bug that needs\r
+ * to be fixed elsewhere.\r
+ * \r
+ * @param <T>\r
+ * @param forElement\r
+ * @param withParentNode\r
+ * @param withNodeKey\r
+ * @param nodeId\r
+ * @param nodeClass\r
+ * @return\r
+ */\r
+ public static <T extends INode> T getOrCreateNode(IElement forElement, ParentNode<?> withParentNode, Key withNodeKey, String nodeId, Class<T> nodeClass) {\r
+ return getOrCreateNode(forElement, withParentNode, withNodeKey, nodeId, nodeClass, null);\r
+ }\r
+\r
+ /**\r
+ * Looks for a scene graph node from the specified element with the\r
+ * specified node key. If the node does not exist, a new node is created\r
+ * using the specified node class.\r
+ * \r
+ * If a previous node exists, its class is verified to match the requested\r
+ * node class and returned as such upon success. If the classes do not\r
+ * match, an exception is raised since this is most likely a bug that needs\r
+ * to be fixed elsewhere.\r
+ * \r
+ * @param <T>\r
+ * @param forElement\r
+ * @param withParentNode\r
+ * @param withNodeKey\r
+ * @param nodeId\r
+ * @param nodeClass\r
+ * @param nodeCreationCallback a callback that is invoked with the node\r
+ * instance if a new node was created by this method\r
+ * @return\r
+ */\r
+ public static <T extends INode> T getOrCreateNode(IElement forElement, ParentNode<?> withParentNode, Key withNodeKey, String nodeId, Class<T> nodeClass, Consumer<T> nodeCreationCallback) {\r
+ if (!(withNodeKey instanceof SceneGraphNodeKey))\r
+ System.out.println("ElementUtils.getOrCreateNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + withNodeKey);\r
+\r
+ @SuppressWarnings("unchecked")\r
+ T node = (T) forElement.getHint(withNodeKey);\r
+ if (node == null) {\r
+ node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass);\r
+ forElement.setHint(withNodeKey, node);\r
+ if (nodeCreationCallback != null)\r
+ nodeCreationCallback.accept(node);\r
+ } else {\r
+ if (!nodeClass.isAssignableFrom(node.getClass())) {\r
+ 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
+ }\r
+ // If the previously available node is not a parent of the specified\r
+ // node create a new node under the specified parent and set that\r
+ // as the node of the specified element.\r
+ if (!node.getParent().equals(withParentNode)) {\r
+ node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass);\r
+ forElement.setHint(withNodeKey, node);\r
+ if (nodeCreationCallback != null)\r
+ nodeCreationCallback.accept(node);\r
+ }\r
+ }\r
+ return node;\r
+ }\r
+\r
+ /**\r
+ * @param element\r
+ * @param nodeKey\r
+ * @return\r
+ */\r
+ public static INode removePossibleNode(IElement element, Key nodeKey) {\r
+ if (!(nodeKey instanceof SceneGraphNodeKey))\r
+ System.out.println("ElementUtils.removePossibleNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + nodeKey);\r
+\r
+ INode node = element.getHint(nodeKey);\r
+ if (node != null)\r
+ node.remove();\r
+ return node;\r
+ }\r
+\r
+ /**\r
+ * \r
+ * @param element\r
+ * @return\r
+ */\r
+ public static Font getTextFont(IElement element) {\r
+ TextFont tf = element.getElementClass().getSingleItem(TextFont.class);\r
+ return tf.getFont(element);\r
+ }\r
+\r
+ public static void setTextFont(IElement element, Font font) {\r
+ TextFont tf = element.getElementClass().getSingleItem(TextFont.class);\r
+ tf.setFont(element, font);\r
+ }\r
+\r
+ public static <T> void addToCollectionHint(IElement element, Key key, T item) {\r
+ Collection<T> collection = element.getHint(key);\r
+ if (collection == null) {\r
+ collection = new ArrayList<T>();\r
+ element.setHint(key, collection);\r
+ }\r
+ collection.add(item);\r
+ }\r
+\r
+ public static <T> void removeFromCollectionHint(IElement element, Key key, T item) {\r
+ Collection<T> collection = element.getHint(key);\r
+ if (collection != null) {\r
+ collection = new ArrayList<T>();\r
+ collection.remove(item);\r
+ if (collection.isEmpty())\r
+ element.removeHint(key);\r
+ }\r
+ }\r
+\r
+ public static void setHover(IElement e, boolean hover)\r
+ {\r
+ Hover h = e.getElementClass().getSingleItem(Hover.class);\r
+ h.setHover(e, hover);\r
+ }\r
+\r
+ public static boolean isHovering(IElement e)\r
+ {\r
+ Hover h = e.getElementClass().getSingleItem(Hover.class);\r
+ return h.isHovering(e);\r
+ }\r
+\r
+}
\ No newline at end of file