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