]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java
Merge branch 'feature/funcwrite'
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / NodeUtil.java
index 7f777d8bbd4af0f9bf84767fecd102ac2f6ee1f2..615899a59eef25676ff17b0cbdcb69ca0d62d1b8 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.scenegraph.utils;\r
-\r
-import java.awt.AWTEvent;\r
-import java.awt.Container;\r
-import java.awt.event.MouseEvent;\r
-import java.awt.event.MouseWheelEvent;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.NoninvertibleTransformException;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.io.PrintStream;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Set;\r
-import java.util.concurrent.TimeUnit;\r
-import java.util.concurrent.locks.Condition;\r
-import java.util.concurrent.locks.Lock;\r
-import java.util.concurrent.locks.ReentrantLock;\r
-\r
-import org.simantics.scenegraph.IDynamicSelectionPainterNode;\r
-import org.simantics.scenegraph.ILookupService;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.INode.PropertySetter;\r
-import org.simantics.scenegraph.ISelectionPainterNode;\r
-import org.simantics.scenegraph.ParentNode;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.events.EventDelegator;\r
-import org.simantics.scenegraph.g2d.events.NodeEventHandler;\r
-import org.simantics.scenegraph.g2d.events.SGMouseEvent;\r
-import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;\r
-import org.simantics.scenegraph.g2d.nodes.ConnectionNode;\r
-import org.simantics.scenegraph.g2d.nodes.FlagNode;\r
-import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
-import org.simantics.scl.runtime.function.Function1;\r
-import org.simantics.scl.runtime.function.FunctionImpl1;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.simantics.utils.threads.IThreadWorkQueue;\r
-\r
-/**\r
- * Utilities for debugging/printing the contents of a scenegraph.\r
- * \r
- * @author Tuukka Lehtonen\r
- */\r
-public final class NodeUtil {\r
-\r
-    /**\r
-     * @param <T>\r
-     */\r
-    public static interface Filter<T> {\r
-        public boolean accept(T t);\r
-    }\r
-\r
-    public static class PrefixFilter implements Filter<String> {\r
-        private final String prefix;\r
-        public PrefixFilter(String prefix) {\r
-            this.prefix = prefix;\r
-        }\r
-        @Override\r
-        public boolean accept(String t) {\r
-            return t.startsWith(prefix);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * The name of the sibling-node that is used to represent that a node is selected.\r
-     */\r
-    public static final String SELECTION_NODE_NAME = "selection";\r
-\r
-    public static INode getNearestParentOfType(INode node, Class<?> clazz) {\r
-        ParentNode<?> parent = null;\r
-        while (true) {\r
-            parent = node.getParent();\r
-            if (parent == null)\r
-                return node;\r
-            node = parent;\r
-            if (clazz.isInstance(node))\r
-                return node;\r
-        }\r
-    }\r
-\r
-    public static INode getRootNode(INode node) {\r
-        ParentNode<?> parent = null;\r
-        while (true) {\r
-            parent = node.getParent();\r
-            if (parent == null)\r
-                return node;\r
-            node = parent;\r
-        }\r
-    }\r
-\r
-    public static G2DSceneGraph getRootNode(IG2DNode node) {\r
-        INode root = getRootNode((INode) node);\r
-        return (G2DSceneGraph) root;\r
-    }\r
-\r
-    public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {\r
-        INode root = getRootNode((INode) node);\r
-        return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;\r
-    }\r
-\r
-    /**\r
-     * Method for seeking node from scenegraph by class\r
-     * \r
-     * @param <T>\r
-     * @param parent\r
-     * @param clazz\r
-     * @return\r
-     */\r
-    public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {\r
-        return getNearestChildByClass(parent.getNodes(), clazz);\r
-    }\r
-\r
-    /**\r
-     * Breadth-first-search implementation to be used by getNearestChildByClass method\r
-     * \r
-     * @param <T>\r
-     * @param nodes\r
-     * @param clazz\r
-     * @return\r
-     */\r
-    @SuppressWarnings("unchecked")\r
-    public static <T> T getNearestChildByClass(Collection<IG2DNode> nodes, Class<T> clazz) {\r
-        Collection<IG2DNode> list = null;\r
-        for (IG2DNode n : nodes) {\r
-            if (clazz.isInstance(n)) {\r
-                return (T) n;\r
-            } else if (n instanceof G2DParentNode) {\r
-                if (list == null)\r
-                    list = new ArrayList<IG2DNode>();\r
-                list.addAll(((G2DParentNode)n).getNodes());\r
-            }\r
-        }\r
-        if (list == null || list.isEmpty()) return null;\r
-        return getNearestChildByClass(list, clazz);\r
-    }\r
-\r
-    /**\r
-     * Tries to look for a child node from the specified node with the specified\r
-     * ID. Returns <code>null</code> if the specified node is a not a\r
-     * {@link ParentNode}.\r
-     * \r
-     * @param node\r
-     * @param id\r
-     * @return\r
-     */\r
-    public static INode getChildById(INode node, String id) {\r
-        if (node instanceof ParentNode<?>) {\r
-            return ((ParentNode<?>) node).getNode(id);\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Looks for the first child node of the specified node based on 2D Z-order.\r
-     * The specified node must be a {@link G2DParentNode} for the method to\r
-     * succeed.\r
-     * \r
-     * @param node the node to get for the first child from\r
-     * @return <code>null</code> if the specified node is not a\r
-     *         {@link G2DParentNode} or has no children.\r
-     */\r
-    public static INode getFirstChild(INode node) {\r
-        if (node instanceof G2DParentNode) {\r
-            G2DParentNode pn = (G2DParentNode) node;\r
-            IG2DNode[] sorted = pn.getSortedNodes();\r
-            if (sorted.length > 0)\r
-                return sorted[0];\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Returns a single child node of the specified node or <code>null</code> if\r
-     * there are more than one or zero children.\r
-     * \r
-     * @param node the node to get a possible single child from\r
-     * @return single child node or <code>null</code> if specified node has more\r
-     *         than one or zero children\r
-     */\r
-    public static INode getPossibleChild(INode node) {\r
-        if (node instanceof ParentNode<?>) {\r
-            ParentNode<?> pn = (ParentNode<?>) node;\r
-            if (pn.getNodeCount() == 1)\r
-                return pn.getNodes().iterator().next();\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * Counts the depth of the specified node in its scene graph node tree.\r
-     * Depth 1 equals root level.\r
-     * \r
-     * @param node the node for which to count a depth\r
-     * @return the depth of the node\r
-     */\r
-    public static int getDepth(INode node) {\r
-        int result = 1;\r
-        ParentNode<?> parent = null;\r
-        while (true) {\r
-            parent = node.getParent();\r
-            if (parent == null)\r
-                return result;\r
-            node = parent;\r
-            ++result;\r
-        }\r
-    }\r
-\r
-    private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {\r
-        for (int i = 0; i < indentLevel; ++i)\r
-            stream.print("\t");\r
-        stream.print(node.getSimpleClassName());\r
-        if (id != null) {              \r
-               String lookupId = tryLookupId(node);\r
-               if (lookupId != null) {\r
-                       stream.print(" {" + id + ", lookupId = "+lookupId+"}");\r
-               } else {\r
-                       stream.print(" {" + id + "}");\r
-               }\r
-        }\r
-        stream.println(node);\r
-        if (node instanceof G2DParentNode) {\r
-            G2DParentNode parentNode = (G2DParentNode) node;\r
-            for (String cid : parentNode.getSortedNodesById()) {\r
-                Object child = parentNode.getNode(cid);\r
-                if (child instanceof INode)\r
-                    printSceneGraph(stream, indentLevel + 1, (INode) child, cid);\r
-            }\r
-        } else if (node instanceof ParentNode<?>) {\r
-            ParentNode<? extends INode> parentNode = (ParentNode<?>) node;\r
-            for (String cid : parentNode.getNodeIds()) {\r
-                INode child = parentNode.getNode(cid);\r
-                printSceneGraph(stream, indentLevel + 1, (INode) child);\r
-            }\r
-        }\r
-    }\r
-\r
-    public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {\r
-        String id = null;\r
-        ParentNode<?> parent = node.getParent();\r
-        if (parent != null) {\r
-            Collection<String> ids = parent.getNodeIds();\r
-            for (String i : ids) {\r
-                INode n = parent.getNode(i);\r
-                if (n == node) {\r
-                    id = i;\r
-                    break;\r
-                }\r
-            }\r
-        }\r
-        printSceneGraph(stream, indentLevel, node, id);\r
-    }\r
-\r
-    public static final void printSceneGraph(int indentLevel, INode node) {\r
-        printSceneGraph(System.out, indentLevel, node);\r
-    }\r
-\r
-    public static final void printSceneGraph(INode node) {\r
-        printSceneGraph(System.out, 0, node);\r
-    }\r
-\r
-    public static interface NodeProcedure<T> {\r
-        T execute(INode node, String id);\r
-    }\r
-\r
-    /**\r
-     * @param node the node to iterate possible children for\r
-     * @param procedure invoked for each child node, if returns a\r
-     *        <code>non-null</code> value, the return value is collected into the result\r
-     *        list\r
-     * @return the list of collected children\r
-     */\r
-    public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {\r
-        return forChildren(node, procedure, new ArrayList<T>());\r
-    }\r
-\r
-    /**\r
-     * @param node the node to iterate possible children for\r
-     * @param procedure invoked for each child node, if returns a\r
-     *        <code>non-null</code> value, the node is collected into the result\r
-     *        list\r
-     * @param result the result list into which selected children are collected\r
-     *        or <code>null</code> to not collect\r
-     * @return the list of collected children or null if provided result list\r
-     *         was <code>null</code>\r
-     */\r
-    public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure, List<T> result) {\r
-        if (node instanceof ParentNode<?>) {\r
-            ParentNode<?> pn = (ParentNode<?>) node;\r
-            if (node instanceof G2DParentNode) {\r
-                G2DParentNode g2dpn = (G2DParentNode) node;\r
-                for (String id : g2dpn.getSortedNodesById()) {\r
-                    INode n = pn.getNode(id);\r
-                    T t = procedure.execute(n, id);\r
-                    if (t != null && result != null)\r
-                        result.add(t);\r
-                }\r
-            } else {\r
-                for (String id : pn.getNodeIds()) {\r
-                    INode n = pn.getNode(id);\r
-                    T t = procedure.execute(n, id);\r
-                    if (t != null && result != null)\r
-                        result.add(t);\r
-                }\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public static final int countTreeNodes(INode node) {\r
-        int result = 1;\r
-        if (node instanceof ParentNode<?>) {\r
-            ParentNode<? extends INode> pn = (ParentNode<?>) node;\r
-            Collection<? extends INode> ns = pn.getNodes();\r
-            for (INode n : ns) {\r
-                result += countTreeNodes(n);\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public static final void printTreeNodes(INode node, StringBuilder builder) {\r
-        printTreeNodes(node, 0, builder);\r
-    }\r
-\r
-    public static final void printTreeNodes(INode node, int indent, StringBuilder builder) {\r
-        for (int i = 0; i < indent; i++)\r
-            builder.append(" ");\r
-        builder.append(node.toString() + "\n");\r
-        if (node instanceof ParentNode<?>) {\r
-            ParentNode<? extends INode> pn = (ParentNode<?>) node;\r
-            Collection<? extends INode> ns = pn.getNodes();\r
-            for (INode n : ns) {\r
-                printTreeNodes(n, indent+2, builder);\r
-            }\r
-        }\r
-    }\r
-\r
-    public static final <T extends INode> Set<T> collectNodes(INode node, Class<T> clazz) {\r
-        Set<T> result = new HashSet<T>();\r
-        collectNodes(node, clazz, result);\r
-        return result;\r
-    }\r
-\r
-    @SuppressWarnings("unchecked")\r
-    public static final <T extends INode> void collectNodes(INode node, Class<T> clazz, Set<T> result) {\r
-        if (clazz.isInstance(node))\r
-            result.add((T) node);\r
-        if (node instanceof ParentNode<?>) {\r
-            ParentNode<? extends INode> pn = (ParentNode<?>) node;\r
-            Collection<? extends INode> ns = pn.getNodes();\r
-            for (INode n : ns) {\r
-                collectNodes(n, clazz, result);\r
-            }\r
-        }\r
-    }\r
-    \r
-    public static <T extends INode> T getSingleNode(INode node, Class<T> clazz) {\r
-        Set<T> all = collectNodes(node, clazz);\r
-        if(all.size() != 1) throw new RuntimeException("Expected exactly 1 instance of class " + clazz.getCanonicalName() + ", got " + all.size());\r
-        return (T)all.iterator().next();\r
-    }\r
-    \r
-    public static final boolean hasChildren(INode node) {\r
-        if (node instanceof ParentNode<?>) {\r
-            ParentNode<?> pn = (ParentNode<?>) node;\r
-            return !pn.getNodes().isEmpty();\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * Look for a single scene graph node by its ID in a path under a specified\r
-     * node.\r
-     * \r
-     * @param parent the parent node under which to start looking\r
-     * @param idPath the node ID path\r
-     * @return <code>null</code> if node was not found\r
-     * @throws ClassCastException if the found node was not of the expected type\r
-     *         T extending INode\r
-     */\r
-    @SuppressWarnings("unchecked")\r
-    public static <T extends INode> T findNodeById(INode parent, String... idPath) {\r
-        INode n = parent;\r
-        for (int i = 0;; ++i) {\r
-            if (i >= idPath.length)\r
-                return (T) n;\r
-            if (n instanceof ParentNode<?>) {\r
-                n = ((ParentNode<?>) n).getNode(idPath[i]);\r
-            } else {\r
-                return null;\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Tries to find out whether a node is selected or not.\r
-     * \r
-     * DISCLAIMER: this is a hack for the current\r
-     * org.simantics.g2d.diagram.participant.ElementPainter implementation that\r
-     * will stop working if the implementation changes.\r
-     * \r
-     * @param node\r
-     * @param ascendLimit the max. amount of steps towards parent nodes to take\r
-     *        while looking for selection information\r
-     * @return <code>true</code> if evidence of selection is found\r
-     */\r
-    public static boolean isSelected(INode node, int ascendLimit) {\r
-        int steps = 0;\r
-        ParentNode<?> pn = null;\r
-        if (node instanceof ParentNode<?>) {\r
-            pn = (ParentNode<?>) node;\r
-        } else {\r
-            pn = node.getParent();\r
-            ++steps;\r
-        }\r
-        for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {\r
-            INode child = pn.getNode(SELECTION_NODE_NAME);\r
-            if (child != null)\r
-                return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    public static Container findRootPane(INode node) {\r
-        G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);\r
-        if (parent == null)\r
-            return null;\r
-        return ((G2DSceneGraph) parent).getRootPane();\r
-    }\r
-\r
-    private static boolean isSelectionPainter(INode node) {\r
-        if (node instanceof ISelectionPainterNode) {\r
-            if (node instanceof IDynamicSelectionPainterNode)\r
-                return ((IDynamicSelectionPainterNode)node).showsSelection();\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * @param elementNode\r
-     * @return\r
-     */\r
-    public static boolean needSelectionPaint(INode elementNode) {\r
-        // FIXME: there should be a cleaner way to implement this.\r
-\r
-        if (isSelectionPainter(elementNode)) {\r
-//          System.out.println("skipped selection painting for connection node child ISelectionPainterNode");\r
-            return false;\r
-        }\r
-\r
-        if (elementNode instanceof ConnectionNode) {\r
-//          System.out.println("connectionNode");\r
-            for (IG2DNode child : ((ConnectionNode) elementNode).getNodes()) {\r
-//              System.out.println(" child " + child);\r
-                if (isSelectionPainter(child)) {\r
-//                  System.out.println("skipped selection painting for connection node child ISelectionPainterNode");\r
-                    return false;\r
-                }\r
-                if (child instanceof SingleElementNode) {\r
-                    for(IG2DNode child2 : ((SingleElementNode) child).getNodes()) {\r
-//                      System.out.println(" child2 " + child2);\r
-                        if (isSelectionPainter(child2)) {\r
-//                          System.out.println("skipped selection painting for edge ISelectionPainterNode");\r
-                            return false;\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-        } else if (elementNode instanceof SingleElementNode) {\r
-            for (INode child : ((SingleElementNode) elementNode).getNodes()) {\r
-                if (isSelectionPainter(child))\r
-                    return false;\r
-            }\r
-        }\r
-\r
-        return true;\r
-    }\r
-\r
-    private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS"; // If this method is found, the class is enhanced by cglib, thus does not contain annotations\r
-\r
-    public static Method getSetterForProperty(String property, INode node) {\r
-        assert(node != null);\r
-        Class<?> cl = node.getClass();\r
-\r
-        while(true) {\r
-            boolean isEnhanced = false;\r
-            for(Method method : cl.getMethods()) {\r
-                if(method.isAnnotationPresent(PropertySetter.class)) {\r
-                    PropertySetter ann = method.getAnnotation(PropertySetter.class);\r
-                    if(ann.value().equals(property)) {\r
-                        return method;\r
-                    }\r
-                } else if(method.getName().equals(SET_THREAD_CALLBACKS_NAME) && method.getGenericParameterTypes().length == 1) {\r
-                    // The class seems to be enhanced by cglib, hence annotations are not present. Check superclass for annotations..\r
-                    isEnhanced = true;\r
-                    cl = cl.getSuperclass();\r
-                    break; // We are not going to find any annotations, stop loop and try with the parent class\r
-                }\r
-            }\r
-            if(!isEnhanced || cl == null) break;\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * TODO: log exceptions for debug purposes\r
-     * \r
-     * @param property name of the property\r
-     * @param value    can be null..\r
-     * @param node\r
-     * @return\r
-     */\r
-    public static boolean setPropertyIfSupported(String property, Object value, INode node) {\r
-        Method setter = getSetterForProperty(property, node);\r
-        if(setter != null) {\r
-            Class<?> pc[] = setter.getParameterTypes();\r
-            if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {\r
-                try {\r
-                    setter.invoke(node, value);\r
-                    return true;\r
-                } catch (IllegalArgumentException e) {\r
-                    // TODO Auto-generated catch block\r
-                    e.printStackTrace();\r
-                } catch (IllegalAccessException e) {\r
-                    // TODO Auto-generated catch block\r
-                    e.printStackTrace();\r
-                } catch (InvocationTargetException e) {\r
-                    // TODO Auto-generated catch block\r
-                    e.printStackTrace();\r
-                }\r
-            } else {\r
-\r
-                if(pc.length > 0) {\r
-                    System.err.println("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");\r
-                }\r
-\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    public static INode findChildById(ParentNode<?> parent, String key) {\r
-        INode result = parent.getNode(key);\r
-        if (result != null)\r
-            return result;\r
-\r
-        for (String entry : parent.getNodeIds()) {\r
-            if (entry.startsWith(key))\r
-                return parent.getNode(key);\r
-        }\r
-\r
-        for (INode node : parent.getNodes()) {\r
-            if (node instanceof ParentNode) {\r
-                result = findChildById((ParentNode<?>) node, key);\r
-                if (result != null)\r
-                    return result;\r
-            }\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    \r
-    private static int getSegmentEnd(String suffix) {\r
-        int pos;\r
-        for(pos=1;pos<suffix.length();++pos) {\r
-            char c = suffix.charAt(pos);\r
-            if(c == '/' || c == '#')\r
-                break;\r
-        }\r
-        return pos;\r
-    }\r
-    \r
-    public static String decodeString(String string) {\r
-       return string;\r
-    }\r
-    \r
-    public static INode browsePossible(INode node, String suffix) {\r
-        if(suffix.isEmpty()) \r
-            return node;        \r
-        switch(suffix.charAt(0)) {\r
-        case '.': {\r
-               INode parent = node.getParent();\r
-            if(parent == null)\r
-                return null;\r
-            return browsePossible(parent, suffix.substring(1));\r
-        }\r
-        case '#': {\r
-            /*int segmentEnd = getSegmentEnd(suffix);\r
-            Variable property = getPossibleProperty(graph, \r
-                    decodeString(suffix.substring(1, segmentEnd)));\r
-            if(property == null) \r
-                return null;\r
-            return property.browsePossible(graph, suffix.substring(segmentEnd));*/\r
-               return node;\r
-        }\r
-        case '/': {\r
-            int segmentEnd = getSegmentEnd(suffix);\r
-            INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));\r
-            if(child == null) \r
-                return null;\r
-            return browsePossible(child, suffix.substring(segmentEnd));\r
-        }\r
-        default:\r
-            return null;\r
-        }\r
-    }    \r
-    \r
-    public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {\r
-        if(suffix.isEmpty()) \r
-            throw new RuntimeException("Did not find a reference.");        \r
-        switch(suffix.charAt(0)) {\r
-        case '.': {\r
-               INode parent = node.getParent();\r
-            if(parent == null)\r
-                return null;\r
-            return browsePossibleReference(parent, suffix.substring(1));\r
-        }\r
-        case '#': {\r
-               return Pair.make(node, suffix.substring(1));\r
-        }\r
-        case '/': {\r
-            int segmentEnd = getSegmentEnd(suffix);\r
-            INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));\r
-            if(child == null) \r
-                return null;\r
-            return browsePossibleReference(child, suffix.substring(segmentEnd));\r
-        }\r
-        default:\r
-            return null;\r
-        }\r
-    }    \r
-\r
-    public static INode findChildByPrefix(G2DParentNode parent, String prefix) {\r
-        INode result = parent.getNode(prefix);\r
-        if (result != null)\r
-            return result;\r
-\r
-        for (String entry : parent.getNodeIds()) {\r
-            if (entry.startsWith(prefix))\r
-                return parent.getNode(entry);\r
-        }\r
-\r
-        for (IG2DNode node : parent.getNodes()) {\r
-            if (node instanceof G2DParentNode) {\r
-                result = findChildByPrefix((G2DParentNode) node, prefix);\r
-                if (result != null)\r
-                    return result;\r
-            }\r
-        }\r
-\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * @param parent\r
-     * @param prefix\r
-     * @return\r
-     */\r
-    public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {\r
-        return filterDirectChildIds(parent, new PrefixFilter(prefix));\r
-    }\r
-\r
-    /**\r
-     * @param parent\r
-     * @param prefix\r
-     * @return\r
-     */\r
-    public static Collection<String> filterDirectChildIds(ParentNode<?> parent, Filter<String> childFilter) {\r
-        Collection<String> childIds = parent.getNodeIds();\r
-        ArrayList<String> result = new ArrayList<String>(childIds.size());\r
-\r
-        for (String id : childIds)\r
-            if (childFilter.accept(id))\r
-                result.add(id);\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * @param parent\r
-     * @param prefix\r
-     * @return\r
-     */\r
-    public static Collection<INode> filterDirectChildren(ParentNode<?> parent, Filter<String> childFilter) {\r
-        Collection<String> childIds = parent.getNodeIds();\r
-        ArrayList<INode> result = new ArrayList<INode>(childIds.size());\r
-\r
-        for (String id : childIds)\r
-            if (childFilter.accept(id))\r
-                result.add( parent.getNode(id) );\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @return the lookup service for the specified node\r
-     * @throws UnsupportedOperationException if ILookupService is not available\r
-     */\r
-    public static ILookupService getLookupService(INode node) {\r
-        ParentNode<?> root = node.getRootNode();\r
-        if (!(root instanceof ILookupService))\r
-            throw new UnsupportedOperationException("ILookupService not supported by root node " + root + " attained from " + node);\r
-        return (ILookupService) root;\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @return <code>null</code> if lookup service is not available\r
-     */\r
-    public static ILookupService tryGetLookupService(INode node) {\r
-        ParentNode<?> root = node.getRootNode();\r
-        return root instanceof ILookupService ? (ILookupService) root : null;\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @param id\r
-     * @return <code>null</code> if lookup failed, i.e. mapping does not exist\r
-     * @throws UnsupportedOperationException if lookup is not supported\r
-     * @see #getLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static INode lookup(INode node, String id) {\r
-        ILookupService lookup = getLookupService(node);\r
-        return lookup.lookupNode(id);\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @param id\r
-     * @return <code>null</code> if lookup not supported or lookup failed\r
-     * @see #tryGetLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static INode tryLookup(INode node, String id) {\r
-        ILookupService lookup = tryGetLookupService(node);\r
-        return lookup != null ? lookup.lookupNode(id) : null;\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @param id\r
-     * @param clazz\r
-     * @return <code>null</code> if lookup failed, i.e. mapping does not exist\r
-     * @throws UnsupportedOperationException if lookup is not supported\r
-     * @throws ClassCastException if the found node cannot be cast to the\r
-     *         specified class\r
-     * @see #getLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static <T> T lookup(INode node, String id, Class<T> clazz) {\r
-        ILookupService lookup = getLookupService(node);\r
-        INode found = lookup.lookupNode(id);\r
-        return found != null ? clazz.cast(found) : null;\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @param id\r
-     * @return <code>null</code> if lookup not supported or lookup failed\r
-     * @see #tryGetLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static <T> T tryLookup(INode node, String id, Class<T> clazz) {\r
-        ILookupService lookup = tryGetLookupService(node);\r
-        if (lookup == null)\r
-            return null;\r
-        INode found = lookup.lookupNode(id);\r
-        return found != null ? clazz.cast(found) : null;\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @param id\r
-     * @return <code>null</code> if lookup failed, i.e. mapping does not exist\r
-     * @throws UnsupportedOperationException if lookup is not supported\r
-     * @see #getLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static String lookupId(INode node) {\r
-        ILookupService lookup = getLookupService(node);\r
-        return lookup.lookupId(node);\r
-    }\r
-\r
-    /**\r
-     * @param node\r
-     * @param id\r
-     * @return <code>null</code> if lookup not supported or lookup failed\r
-     * @see #tryGetLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static String tryLookupId(INode node) {\r
-        ILookupService lookup = tryGetLookupService(node);\r
-        return lookup != null ? lookup.lookupId(node) : null;\r
-    }\r
-\r
-    /**\r
-     * Map the specified node to the specified ID in the {@link ILookupService}\r
-     * provided by the root node of the specified node.\r
-     * \r
-     * @param node\r
-     * @param id\r
-     * @throws UnsupportedOperationException if {@link ILookupService} is not\r
-     *         available for the specified node\r
-     * @see #getLookupService(INode)\r
-     * @see ILookupService\r
-     */\r
-    public static void map(INode node, String id) {\r
-        getLookupService(node).map(id, node);\r
-    }\r
-\r
-    /**\r
-     * Remove possible ILookupService mapping for the specified node.\r
-     * \r
-     * @param node the node to try to remove mappings for\r
-     * @return mapped ID or <code>null</code> if no mapping existed\r
-     * @throws UnsupportedOperationException if {@link ILookupService} is not\r
-     *         available for the specified node\r
-     * @see ILookupService\r
-     * @see #getLookupService(INode)\r
-     */\r
-    public static String unmap(INode node) {\r
-        return getLookupService(node).unmap(node);\r
-    }\r
-\r
-    /**\r
-     * Try to remove possible ILookupService mapping for the specified node.\r
-     * \r
-     * @param node the node to try to remove mappings for\r
-     * @return mapped ID or <code>null</code> if {@link ILookupService} is not\r
-     *         supported or no mapping existed\r
-     * @see ILookupService\r
-     * @see #tryGetLookupService(INode)\r
-     */\r
-    public static String tryUnmap(INode node) {\r
-        ILookupService lookup = tryGetLookupService(node);\r
-        return lookup != null ? lookup.unmap(node) : null;\r
-    }\r
-\r
-    public static EventDelegator getEventDelegator(INode node) {\r
-        ParentNode<?> n = node.getRootNode();\r
-        if (n instanceof G2DSceneGraph) {\r
-            return ((G2DSceneGraph) n).getEventDelegator();\r
-        }\r
-        return null;\r
-    }\r
-\r
-    public static NodeEventHandler getNodeEventHandler(INode node) {\r
-        ParentNode<?> n = node.getRootNode();\r
-        return (n instanceof G2DSceneGraph) ? ((G2DSceneGraph) n).getEventHandler() : null;\r
-//        INodeEventHandlerProvider provider = findNearestParentNode(node, INodeEventHandlerProvider.class);\r
-//        return provider != null ? provider.getEventHandler() : null;\r
-    }\r
-\r
-    public static AWTEvent transformEvent(AWTEvent event, IG2DNode node) {\r
-        if (event instanceof MouseEvent) {\r
-            // Find node transform..\r
-            AffineTransform transform = getGlobalToLocalTransform(node, null);\r
-            if (transform == null) {\r
-                System.err.println("WARNING: Non-invertible transform for node: " + node);\r
-                return event;\r
-            }\r
-            MouseEvent me = (MouseEvent)event;\r
-            // Use double coordinates if available\r
-            Point2D p = new Point2D.Double((double)me.getX(), (double)me.getY());\r
-            transform.transform(p, p);\r
-\r
-            MouseEvent e = null;\r
-            // Giving event.getSource() as a parameter for the new events will cause major delay for the event instantiation, hence dummy component is used\r
-            if (event instanceof MouseWheelEvent) {\r
-                e = new SGMouseWheelEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), ((MouseWheelEvent)me).getScrollType(), ((MouseWheelEvent)me).getScrollAmount(), ((MouseWheelEvent)me).getWheelRotation(), me);\r
-            } else {\r
-                e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);\r
-            }\r
-            return e;\r
-        }\r
-        return event;\r
-    }\r
-\r
-    private static final boolean DEBUG_BOUNDS = false;\r
-\r
-    private static Rectangle2D getLocalBoundsImpl(INode node, Function1<INode, Boolean> filter, int indent) {\r
-        if (node instanceof IG2DNode) {\r
-            if (node instanceof G2DParentNode) {\r
-                G2DParentNode pNode = (G2DParentNode)node;\r
-                Iterator<IG2DNode> it = pNode.getNodes().iterator();\r
-                if (!it.hasNext())\r
-                    return null;\r
-                Rectangle2D bounds = null;\r
-                while (it.hasNext()) {\r
-                    IG2DNode next = it.next();\r
-                    if (filter != null && !filter.apply(next))\r
-                        continue;\r
-\r
-                    Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);\r
-\r
-                    if(DEBUG_BOUNDS) {\r
-                        for(int i=0;i<indent;i++) System.err.print(" ");\r
-                        System.err.println("+getLocalBoundsImpl " + next  + " => " + bl);\r
-                    }\r
-\r
-                    if(bl != null) {\r
-                        if(bounds == null) {\r
-                            bounds = next.localToParent(bl.getFrame());\r
-                        } else {\r
-                            bounds.add(next.localToParent(bl));\r
-                        }\r
-                    }\r
-                }\r
-\r
-                if(DEBUG_BOUNDS) {\r
-                    for(int i=0;i<indent;i++) System.err.print(" ");\r
-                    System.err.println("=getLocalBoundsImpl " + node  + " => " + bounds);\r
-                }\r
-\r
-                return bounds;\r
-            } else {\r
-                Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);\r
-                if(result != null) {\r
-                    if(DEBUG_BOUNDS) {\r
-                        for(int i=0;i<indent;i++) System.err.print(" ");\r
-                        System.err.println("=getLocalBoundsImpl " + node  + " => " + result);\r
-                    }\r
-                    return result;\r
-                }\r
-            }\r
-        }\r
-        return null;\r
-    }\r
-\r
-    public static Rectangle2D getLocalBounds(INode node) {\r
-        return getLocalBoundsImpl(node, null, 0);\r
-    }\r
-\r
-    public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {\r
-        return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {\r
-\r
-                       @Override\r
-                       public Boolean apply(INode node) {\r
-                               return !excluding.contains(node);\r
-                       }\r
-        }, 0);\r
-    }\r
-\r
-    public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {\r
-        return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {\r
-\r
-                       @Override\r
-                       public Boolean apply(INode node) {\r
-                               return !excluding.isInstance(node);\r
-                       }\r
-        }, 0);\r
-    }\r
-\r
-    public static Rectangle2D getLocalElementBounds(INode node) {\r
-        if(node instanceof ConnectionNode) {\r
-            return getLocalBounds(node);\r
-        } else if(node instanceof SingleElementNode) {\r
-            INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image");\r
-            if (image == null)\r
-                image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");\r
-            if (image == null)\r
-                image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);\r
-            if (image != null)\r
-                return getLocalElementBounds(image);\r
-            else\r
-                return getLocalBounds(node);\r
-        } else {\r
-            return getLocalBounds(node);\r
-        }\r
-    }\r
-\r
-    public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {\r
-        ParentNode<?> parent = null;\r
-        while (true) {\r
-            parent = node.getParent();\r
-            if (parent == null)\r
-                return null;\r
-            if (ofClass.isInstance(parent))\r
-                return ofClass.cast(parent);\r
-            node = parent;\r
-        }\r
-    }\r
\r
-    private static class PendingTester implements Runnable {\r
-\r
-        private boolean             pending     = true;\r
-        private final G2DSceneGraph sg;\r
-\r
-        private final Lock          pendingLock = new ReentrantLock();\r
-        private final Condition     pendingSet  = pendingLock.newCondition();\r
-\r
-        public PendingTester(G2DSceneGraph sg) {\r
-            this.sg = sg;\r
-        }\r
-\r
-        @Override\r
-        public void run() {\r
-            pendingLock.lock();\r
-            try {\r
-                pending = sg.isPending();\r
-                pendingSet.signalAll();\r
-            } finally {\r
-                pendingLock.unlock();\r
-            }\r
-        }\r
-\r
-        public boolean isPending() {\r
-            return pending;\r
-        }\r
-\r
-        public void await() {\r
-            pendingLock.lock();\r
-            try {\r
-                if (pending)\r
-                    pendingSet.await(10, TimeUnit.MILLISECONDS);\r
-            } catch (InterruptedException e) {\r
-                // Ignore.\r
-            } finally {\r
-                pendingLock.unlock();\r
-            }\r
-        }\r
-\r
-    }\r
-\r
-    public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {\r
-       // Wait for 30s by default\r
-       waitPending(thread, sg, 30000);\r
-    }\r
-\r
-    /**\r
-     * Synchronously waits until the the specified scene graph is no longer in\r
-     * pending state.\r
-     * \r
-     * @param thread the thread to schedule pending checks into\r
-     * @param sg the scene graph to wait upon\r
-     */\r
-    public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg, int timeoutMs) {\r
-        PendingTester tester = new PendingTester(sg);\r
-        long start = System.currentTimeMillis();\r
-        while (tester.isPending()) {\r
-            thread.asyncExec(tester);\r
-            if (tester.isPending())\r
-                tester.await();\r
-            long duration = System.currentTimeMillis() - start;\r
-            if(duration > timeoutMs)\r
-               throw new IllegalStateException("Timeout in resolving pending nodes.");\r
-        }\r
-    }\r
-\r
-    public static void increasePending(INode node) {\r
-        G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();\r
-        if(sg != null)\r
-               sg.increasePending(node);\r
-    }\r
-\r
-    public static void decreasePending(INode node) {\r
-        G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();\r
-        if(sg != null)\r
-               sg.decreasePending(node);\r
-    }\r
-\r
-    // TRANSFORMATIONS\r
-\r
-    public static AffineTransform getLocalToGlobalTransform(IG2DNode node, AffineTransform result) {\r
-        result.setToIdentity();\r
-        ParentNode<?> parent = node.getParent();\r
-        while (parent != null) {\r
-            result.preConcatenate(((IG2DNode) parent).getTransform());\r
-            parent = parent.getParent();\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {\r
-        return getLocalToGlobalTransform(node, new AffineTransform());\r
-    }\r
-\r
-    public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {\r
-        AffineTransform transform = getLocalToGlobalTransform(node);\r
-        transform.invert();\r
-        return transform;\r
-    }\r
-\r
-    public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {\r
-        AffineTransform transform = getLocalToGlobalTransform(node);\r
-        try {\r
-            transform.invert();\r
-            return transform;\r
-        } catch (NoninvertibleTransformException e) {\r
-            return returnIfNonInvertible;\r
-        }\r
-    }\r
-\r
-    public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {\r
-        AffineTransform at = getGlobalToLocalTransform(local, null);\r
-        if (at == null) {\r
-               pt2.setLocation(pt);\r
-            return pt2;\r
-        }\r
-        return at.transform(pt, pt2);\r
-    }\r
-\r
-    public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {\r
-        AffineTransform at = getLocalToGlobalTransform(local);\r
-        return at.transform(pt, pt2);\r
-    }\r
-\r
-    public static String getNodeName(INode nn) {\r
-       INode node = nn.getParent();\r
-        ParentNode<?> pn = (ParentNode<?>) node;\r
-        if (node instanceof G2DParentNode) {\r
-            G2DParentNode g2dpn = (G2DParentNode) node;\r
-            for (String id : g2dpn.getSortedNodesById()) \r
-            {\r
-                INode n = pn.getNode(id);\r
-               if ( nn == n ) {\r
-                               return id;\r
-               }\r
-            }\r
-        }\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * For asking whether specified parent node really is a parent of the\r
-     * specified child node.\r
-     * \r
-     * @param parent\r
-     *            the parent\r
-     * @param child\r
-     *            alleged child of parent\r
-     * @return <code>true</code> if parent really is a parent of child\r
-     */\r
-    public static boolean isParentOf(INode parent, INode child) {\r
-        while (true) {\r
-            if (parent == child)\r
-                return true;\r
-            child = child.getParent();\r
-            if (child == null)\r
-                return false;\r
-        }\r
-    }\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.scenegraph.utils;
+
+import java.awt.AWTEvent;
+import java.awt.Container;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+
+import org.simantics.scenegraph.IDynamicSelectionPainterNode;
+import org.simantics.scenegraph.ILookupService;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.INode.PropertySetter;
+import org.simantics.scenegraph.ISelectionPainterNode;
+import org.simantics.scenegraph.ParentNode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.G2DSceneGraph;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.events.EventDelegator;
+import org.simantics.scenegraph.g2d.events.NodeEventHandler;
+import org.simantics.scenegraph.g2d.events.SGMouseEvent;
+import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;
+import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
+import org.simantics.scenegraph.g2d.nodes.FlagNode;
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
+import org.simantics.scl.runtime.function.Function1;
+import org.simantics.scl.runtime.function.FunctionImpl1;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.threads.IThreadWorkQueue;
+
+/**
+ * Utilities for debugging/printing the contents of a scenegraph.
+ * 
+ * @author Tuukka Lehtonen
+ */
+public final class NodeUtil {
+
+    /**
+     * @param <T>
+     */
+    public static interface Filter<T> {
+        public boolean accept(T t);
+    }
+
+    public static class PrefixFilter implements Filter<String> {
+        private final String prefix;
+        public PrefixFilter(String prefix) {
+            this.prefix = prefix;
+        }
+        @Override
+        public boolean accept(String t) {
+            return t.startsWith(prefix);
+        }
+    }
+
+    /**
+     * The name of the sibling-node that is used to represent that a node is selected.
+     */
+    public static final String SELECTION_NODE_NAME = "selection";
+
+    public static INode getNearestParentOfType(INode node, Class<?> clazz) {
+        ParentNode<?> parent = null;
+        while (true) {
+            parent = node.getParent();
+            if (parent == null)
+                return node;
+            node = parent;
+            if (clazz.isInstance(node))
+                return node;
+        }
+    }
+
+    public static INode getRootNode(INode node) {
+        ParentNode<?> parent = null;
+        while (true) {
+            parent = node.getParent();
+            if (parent == null)
+                return node;
+            node = parent;
+        }
+    }
+
+    public static G2DSceneGraph getRootNode(IG2DNode node) {
+        INode root = getRootNode((INode) node);
+        return (G2DSceneGraph) root;
+    }
+
+    public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {
+        INode root = getRootNode((INode) node);
+        return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;
+    }
+
+    /**
+     * Method for seeking node from scenegraph by class
+     * 
+     * @param <T>
+     * @param parent
+     * @param clazz
+     * @return
+     */
+    public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {
+        return getNearestChildByClass(parent.getNodes(), clazz);
+    }
+
+    /**
+     * Breadth-first-search implementation to be used by getNearestChildByClass method
+     * 
+     * @param <T>
+     * @param nodes
+     * @param clazz
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getNearestChildByClass(Collection<IG2DNode> nodes, Class<T> clazz) {
+        Collection<IG2DNode> list = null;
+        for (IG2DNode n : nodes) {
+            if (clazz.isInstance(n)) {
+                return (T) n;
+            } else if (n instanceof G2DParentNode) {
+                if (list == null)
+                    list = new ArrayList<IG2DNode>();
+                list.addAll(((G2DParentNode)n).getNodes());
+            }
+        }
+        if (list == null || list.isEmpty()) return null;
+        return getNearestChildByClass(list, clazz);
+    }
+
+    /**
+     * Tries to look for a child node from the specified node with the specified
+     * ID. Returns <code>null</code> if the specified node is a not a
+     * {@link ParentNode}.
+     * 
+     * @param node
+     * @param id
+     * @return
+     */
+    public static INode getChildById(INode node, String id) {
+        if (node instanceof ParentNode<?>) {
+            return ((ParentNode<?>) node).getNode(id);
+        }
+        return null;
+    }
+
+    /**
+     * Looks for the first child node of the specified node based on 2D Z-order.
+     * The specified node must be a {@link G2DParentNode} for the method to
+     * succeed.
+     * 
+     * @param node the node to get for the first child from
+     * @return <code>null</code> if the specified node is not a
+     *         {@link G2DParentNode} or has no children.
+     */
+    public static INode getFirstChild(INode node) {
+        if (node instanceof G2DParentNode) {
+            G2DParentNode pn = (G2DParentNode) node;
+            IG2DNode[] sorted = pn.getSortedNodes();
+            if (sorted.length > 0)
+                return sorted[0];
+        }
+        return null;
+    }
+
+    /**
+     * Returns a single child node of the specified node or <code>null</code> if
+     * there are more than one or zero children.
+     * 
+     * @param node the node to get a possible single child from
+     * @return single child node or <code>null</code> if specified node has more
+     *         than one or zero children
+     */
+    public static INode getPossibleChild(INode node) {
+        if (node instanceof ParentNode<?>) {
+            ParentNode<?> pn = (ParentNode<?>) node;
+            if (pn.getNodeCount() == 1)
+                return pn.getNodes().iterator().next();
+        }
+        return null;
+    }
+
+    /**
+     * Counts the depth of the specified node in its scene graph node tree.
+     * Depth 1 equals root level.
+     * 
+     * @param node the node for which to count a depth
+     * @return the depth of the node
+     */
+    public static int getDepth(INode node) {
+        int result = 1;
+        ParentNode<?> parent = null;
+        while (true) {
+            parent = node.getParent();
+            if (parent == null)
+                return result;
+            node = parent;
+            ++result;
+        }
+    }
+
+    private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {
+        for (int i = 0; i < indentLevel; ++i)
+            stream.print("\t");
+        stream.print(node.getSimpleClassName());
+        if (id != null) {              
+               String lookupId = tryLookupId(node);
+               if (lookupId != null) {
+                       stream.print(" {" + id + ", lookupId = "+lookupId+"}");
+               } else {
+                       stream.print(" {" + id + "}");
+               }
+        }
+        stream.println(node);
+        if (node instanceof G2DParentNode) {
+            G2DParentNode parentNode = (G2DParentNode) node;
+            for (String cid : parentNode.getSortedNodesById()) {
+                Object child = parentNode.getNode(cid);
+                if (child instanceof INode)
+                    printSceneGraph(stream, indentLevel + 1, (INode) child, cid);
+            }
+        } else if (node instanceof ParentNode<?>) {
+            ParentNode<? extends INode> parentNode = (ParentNode<?>) node;
+            for (String cid : parentNode.getNodeIds()) {
+                INode child = parentNode.getNode(cid);
+                printSceneGraph(stream, indentLevel + 1, (INode) child);
+            }
+        }
+    }
+
+    public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {
+        String id = null;
+        ParentNode<?> parent = node.getParent();
+        if (parent != null) {
+            Collection<String> ids = parent.getNodeIds();
+            for (String i : ids) {
+                INode n = parent.getNode(i);
+                if (n == node) {
+                    id = i;
+                    break;
+                }
+            }
+        }
+        printSceneGraph(stream, indentLevel, node, id);
+    }
+
+    public static final void printSceneGraph(int indentLevel, INode node) {
+        printSceneGraph(System.out, indentLevel, node);
+    }
+
+    public static final void printSceneGraph(INode node) {
+        printSceneGraph(System.out, 0, node);
+    }
+
+    @FunctionalInterface
+    public static interface NodeProcedure<T> {
+        T execute(INode node, String id);
+    }
+
+    /**
+     * @param node the node to iterate possible children for
+     * @param procedure invoked for each child node, if returns a
+     *        <code>non-null</code> value, the return value is collected into the result
+     *        list
+     * @return the list of collected children
+     */
+    public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {
+        return forChildren(node, procedure, new ArrayList<T>());
+    }
+
+    /**
+     * @param node the node to iterate possible children for
+     * @param procedure invoked for each child node, if returns a
+     *        <code>non-null</code> value, the node is collected into the result
+     *        list
+     * @param result the result list into which selected children are collected
+     *        or <code>null</code> to not collect
+     * @return the list of collected children or null if provided result list
+     *         was <code>null</code>
+     */
+    public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure, List<T> result) {
+        if (node instanceof ParentNode<?>) {
+            ParentNode<?> pn = (ParentNode<?>) node;
+            if (node instanceof G2DParentNode) {
+                G2DParentNode g2dpn = (G2DParentNode) node;
+                for (String id : g2dpn.getSortedNodesById()) {
+                    INode n = pn.getNode(id);
+                    T t = procedure.execute(n, id);
+                    if (t != null && result != null)
+                        result.add(t);
+                }
+            } else {
+                for (String id : pn.getNodeIds()) {
+                    INode n = pn.getNode(id);
+                    T t = procedure.execute(n, id);
+                    if (t != null && result != null)
+                        result.add(t);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Recursively iterates through all child nodes of the specified node and
+     * for those nodes that are of class <code>ofClass</code>, invokes
+     * <code>consumer</code>.
+     * 
+     * @param node
+     * @param ofClass
+     * @param consumer
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends INode> INode forChildrenDeep(INode node, Class<T> ofClass, Function<T, INode> func) {
+        return forChildrenDeep(node, n -> ofClass.isInstance(n) ? func.apply((T) n) : null);
+    }
+
+    public static <T extends INode> INode forChildrenDeep(INode node, Function<INode, INode> func) {
+        INode ret = func.apply(node);
+        if (ret != null)
+            return ret;
+
+        if (node instanceof ParentNode<?>) {
+            if (node instanceof G2DParentNode) {
+                G2DParentNode g2dpn = (G2DParentNode) node;
+                for (IG2DNode n : g2dpn.getSortedNodes()) {
+                    INode r = forChildrenDeep(n, func);
+                    if (r != null) {
+                        return r;
+                    }
+                }
+            } else {
+                for (INode n : ((ParentNode<?>) node).getNodes()) {
+                    INode r = forChildrenDeep(n, func);
+                    if (r != null) {
+                        return r;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public static final int countTreeNodes(INode node) {
+        int result = 1;
+        if (node instanceof ParentNode<?>) {
+            ParentNode<? extends INode> pn = (ParentNode<?>) node;
+            Collection<? extends INode> ns = pn.getNodes();
+            for (INode n : ns) {
+                result += countTreeNodes(n);
+            }
+        }
+        return result;
+    }
+
+    public static final StringBuilder printTreeNodes(INode node, StringBuilder builder) {
+        printTreeNodes(node, 0, builder);
+        return builder;
+    }
+
+    public static final StringBuilder printTreeNodes(INode node, int indent, StringBuilder builder) {
+        for (int i = 0; i < indent; i++)
+            builder.append(" ");
+        builder.append(node.toString() + "\n");
+        if (node instanceof ParentNode<?>) {
+            ParentNode<? extends INode> pn = (ParentNode<?>) node;
+            Collection<? extends INode> ns = pn.getNodes();
+            for (INode n : ns) {
+                printTreeNodes(n, indent+2, builder);
+            }
+        }
+        return builder;
+    }
+
+    public static final <T extends INode> Set<T> collectNodes(INode node, Class<T> clazz) {
+        Set<T> result = new HashSet<T>();
+        collectNodes(node, clazz, result);
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static final <T extends INode> void collectNodes(INode node, Class<T> clazz, Set<T> result) {
+        if (clazz.isInstance(node))
+            result.add((T) node);
+        if (node instanceof ParentNode<?>) {
+            ParentNode<? extends INode> pn = (ParentNode<?>) node;
+            Collection<? extends INode> ns = pn.getNodes();
+            for (INode n : ns) {
+                collectNodes(n, clazz, result);
+            }
+        }
+    }
+    
+    public static <T extends INode> T getSingleNode(INode node, Class<T> clazz) {
+        Set<T> all = collectNodes(node, clazz);
+        if(all.size() != 1) throw new RuntimeException("Expected exactly 1 instance of class " + clazz.getCanonicalName() + ", got " + all.size());
+        return (T)all.iterator().next();
+    }
+    
+    public static final boolean hasChildren(INode node) {
+        if (node instanceof ParentNode<?>) {
+            ParentNode<?> pn = (ParentNode<?>) node;
+            return !pn.getNodes().isEmpty();
+        }
+        return false;
+    }
+
+    /**
+     * Look for a single scene graph node by its ID in a path under a specified
+     * node.
+     * 
+     * @param parent the parent node under which to start looking
+     * @param idPath the node ID path
+     * @return <code>null</code> if node was not found
+     * @throws ClassCastException if the found node was not of the expected type
+     *         T extending INode
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends INode> T findNodeById(INode parent, String... idPath) {
+        INode n = parent;
+        for (int i = 0;; ++i) {
+            if (i >= idPath.length)
+                return (T) n;
+            if (n instanceof ParentNode<?>) {
+                n = ((ParentNode<?>) n).getNode(idPath[i]);
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Tries to find out whether a node is selected or not.
+     * 
+     * DISCLAIMER: this is a hack for the current
+     * org.simantics.g2d.diagram.participant.ElementPainter implementation that
+     * will stop working if the implementation changes.
+     * 
+     * @param node
+     * @param ascendLimit the max. amount of steps towards parent nodes to take
+     *        while looking for selection information
+     * @return <code>true</code> if evidence of selection is found
+     */
+    public static boolean isSelected(INode node, int ascendLimit) {
+        int steps = 0;
+        ParentNode<?> pn = null;
+        if (node instanceof ParentNode<?>) {
+            pn = (ParentNode<?>) node;
+        } else {
+            pn = node.getParent();
+            ++steps;
+        }
+        for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {
+            INode child = pn.getNode(SELECTION_NODE_NAME);
+            if (child != null)
+                return true;
+        }
+        return false;
+    }
+
+    public static Container findRootPane(INode node) {
+        G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);
+        if (parent == null)
+            return null;
+        return ((G2DSceneGraph) parent).getRootPane();
+    }
+
+    private static boolean isSelectionPainter(INode node) {
+        if (node instanceof ISelectionPainterNode) {
+            if (node instanceof IDynamicSelectionPainterNode)
+                return ((IDynamicSelectionPainterNode)node).showsSelection();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @param elementNode
+     * @return
+     */
+    public static boolean needSelectionPaint(INode elementNode) {
+        // FIXME: there should be a cleaner way to implement this.
+
+        if (isSelectionPainter(elementNode)) {
+//          System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
+            return false;
+        }
+
+        if (elementNode instanceof ConnectionNode) {
+//          System.out.println("connectionNode");
+            for (IG2DNode child : ((ConnectionNode) elementNode).getNodes()) {
+//              System.out.println(" child " + child);
+                if (isSelectionPainter(child)) {
+//                  System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
+                    return false;
+                }
+                if (child instanceof SingleElementNode) {
+                    for(IG2DNode child2 : ((SingleElementNode) child).getNodes()) {
+//                      System.out.println(" child2 " + child2);
+                        if (isSelectionPainter(child2)) {
+//                          System.out.println("skipped selection painting for edge ISelectionPainterNode");
+                            return false;
+                        }
+                    }
+                }
+            }
+        } else if (elementNode instanceof SingleElementNode) {
+            for (INode child : ((SingleElementNode) elementNode).getNodes()) {
+                if (isSelectionPainter(child))
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS"; // If this method is found, the class is enhanced by cglib, thus does not contain annotations
+
+    public static Method getSetterForProperty(String property, INode node) {
+        assert(node != null);
+        Class<?> cl = node.getClass();
+
+        while(true) {
+            boolean isEnhanced = false;
+            for(Method method : cl.getMethods()) {
+                if(method.isAnnotationPresent(PropertySetter.class)) {
+                    PropertySetter ann = method.getAnnotation(PropertySetter.class);
+                    if(ann.value().equals(property)) {
+                        return method;
+                    }
+                } else if(method.getName().equals(SET_THREAD_CALLBACKS_NAME) && method.getGenericParameterTypes().length == 1) {
+                    // The class seems to be enhanced by cglib, hence annotations are not present. Check superclass for annotations..
+                    isEnhanced = true;
+                    cl = cl.getSuperclass();
+                    break; // We are not going to find any annotations, stop loop and try with the parent class
+                }
+            }
+            if(!isEnhanced || cl == null) break;
+        }
+        return null;
+    }
+
+    /**
+     * TODO: log exceptions for debug purposes
+     * 
+     * @param property name of the property
+     * @param value    can be null..
+     * @param node
+     * @return
+     */
+    public static boolean setPropertyIfSupported(String property, Object value, INode node) {
+        Method setter = getSetterForProperty(property, node);
+        if(setter != null) {
+            Class<?> pc[] = setter.getParameterTypes();
+            if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {
+                try {
+                    setter.invoke(node, value);
+                    return true;
+                } catch (IllegalArgumentException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (IllegalAccessException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } catch (InvocationTargetException e) {
+                    // TODO Auto-generated catch block
+                    e.getCause().printStackTrace();
+                }
+            } else {
+
+                if(pc.length > 0) {
+                    System.err.println("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");
+                }
+
+            }
+        }
+
+        return false;
+    }
+
+    public static INode findChildById(ParentNode<?> parent, String key) {
+        INode result = parent.getNode(key);
+        if (result != null)
+            return result;
+
+        for (String entry : parent.getNodeIds()) {
+            if (entry.startsWith(key))
+                return parent.getNode(key);
+        }
+
+        for (INode node : parent.getNodes()) {
+            if (node instanceof ParentNode) {
+                result = findChildById((ParentNode<?>) node, key);
+                if (result != null)
+                    return result;
+            }
+        }
+
+        return null;
+    }
+
+    
+    private static int getSegmentEnd(String suffix) {
+        int pos;
+        for(pos=1;pos<suffix.length();++pos) {
+            char c = suffix.charAt(pos);
+            if(c == '/' || c == '#')
+                break;
+        }
+        return pos;
+    }
+    
+    public static String decodeString(String string) {
+       return string;
+    }
+    
+    public static INode browsePossible(INode node, String suffix) {
+        if(suffix.isEmpty()) 
+            return node;        
+        switch(suffix.charAt(0)) {
+        case '.': {
+               INode parent = node.getParent();
+            if(parent == null)
+                return null;
+            return browsePossible(parent, suffix.substring(1));
+        }
+        case '#': {
+            /*int segmentEnd = getSegmentEnd(suffix);
+            Variable property = getPossibleProperty(graph, 
+                    decodeString(suffix.substring(1, segmentEnd)));
+            if(property == null) 
+                return null;
+            return property.browsePossible(graph, suffix.substring(segmentEnd));*/
+               return node;
+        }
+        case '/': {
+            int segmentEnd = getSegmentEnd(suffix);
+            INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
+            if(child == null) 
+                return null;
+            return browsePossible(child, suffix.substring(segmentEnd));
+        }
+        default:
+            return null;
+        }
+    }    
+    
+    public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {
+        if(suffix.isEmpty()) 
+            throw new RuntimeException("Did not find a reference.");        
+        switch(suffix.charAt(0)) {
+        case '.': {
+               INode parent = node.getParent();
+            if(parent == null)
+                return null;
+            return browsePossibleReference(parent, suffix.substring(1));
+        }
+        case '#': {
+               return Pair.make(node, suffix.substring(1));
+        }
+        case '/': {
+            int segmentEnd = getSegmentEnd(suffix);
+            INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
+            if(child == null) 
+                return null;
+            return browsePossibleReference(child, suffix.substring(segmentEnd));
+        }
+        default:
+            return null;
+        }
+    }    
+
+    public static INode findChildByPrefix(G2DParentNode parent, String prefix) {
+        INode result = parent.getNode(prefix);
+        if (result != null)
+            return result;
+
+        for (String entry : parent.getNodeIds()) {
+            if (entry.startsWith(prefix))
+                return parent.getNode(entry);
+        }
+
+        for (IG2DNode node : parent.getNodes()) {
+            if (node instanceof G2DParentNode) {
+                result = findChildByPrefix((G2DParentNode) node, prefix);
+                if (result != null)
+                    return result;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param parent
+     * @param prefix
+     * @return
+     */
+    public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {
+        return filterDirectChildIds(parent, new PrefixFilter(prefix));
+    }
+
+    /**
+     * @param parent
+     * @param prefix
+     * @return
+     */
+    public static Collection<String> filterDirectChildIds(ParentNode<?> parent, Filter<String> childFilter) {
+        Collection<String> childIds = parent.getNodeIds();
+        ArrayList<String> result = new ArrayList<String>(childIds.size());
+
+        for (String id : childIds)
+            if (childFilter.accept(id))
+                result.add(id);
+
+        return result;
+    }
+
+    /**
+     * @param parent
+     * @param prefix
+     * @return
+     */
+    public static Collection<INode> filterDirectChildren(ParentNode<?> parent, Filter<String> childFilter) {
+        Collection<String> childIds = parent.getNodeIds();
+        ArrayList<INode> result = new ArrayList<INode>(childIds.size());
+
+        for (String id : childIds)
+            if (childFilter.accept(id))
+                result.add( parent.getNode(id) );
+
+        return result;
+    }
+
+    /**
+     * @param node
+     * @return the lookup service for the specified node
+     * @throws UnsupportedOperationException if ILookupService is not available
+     */
+    public static ILookupService getLookupService(INode node) {
+        ParentNode<?> root = node.getRootNode();
+        if (!(root instanceof ILookupService))
+            throw new UnsupportedOperationException("ILookupService not supported by root node " + root + " attained from " + node);
+        return (ILookupService) root;
+    }
+
+    /**
+     * @param node
+     * @return <code>null</code> if lookup service is not available
+     */
+    public static ILookupService tryGetLookupService(INode node) {
+        ParentNode<?> root = node.getRootNode();
+        return root instanceof ILookupService ? (ILookupService) root : null;
+    }
+
+    /**
+     * @param node
+     * @param id
+     * @return <code>null</code> if lookup failed, i.e. mapping does not exist
+     * @throws UnsupportedOperationException if lookup is not supported
+     * @see #getLookupService(INode)
+     * @see ILookupService
+     */
+    public static INode lookup(INode node, String id) {
+        ILookupService lookup = getLookupService(node);
+        return lookup.lookupNode(id);
+    }
+
+    /**
+     * @param node
+     * @param id
+     * @return <code>null</code> if lookup not supported or lookup failed
+     * @see #tryGetLookupService(INode)
+     * @see ILookupService
+     */
+    public static INode tryLookup(INode node, String id) {
+        ILookupService lookup = tryGetLookupService(node);
+        return lookup != null ? lookup.lookupNode(id) : null;
+    }
+
+    /**
+     * @param node
+     * @param id
+     * @param clazz
+     * @return <code>null</code> if lookup failed, i.e. mapping does not exist
+     * @throws UnsupportedOperationException if lookup is not supported
+     * @throws ClassCastException if the found node cannot be cast to the
+     *         specified class
+     * @see #getLookupService(INode)
+     * @see ILookupService
+     */
+    public static <T> T lookup(INode node, String id, Class<T> clazz) {
+        ILookupService lookup = getLookupService(node);
+        INode found = lookup.lookupNode(id);
+        return found != null ? clazz.cast(found) : null;
+    }
+
+    /**
+     * @param node
+     * @param id
+     * @return <code>null</code> if lookup not supported or lookup failed
+     * @see #tryGetLookupService(INode)
+     * @see ILookupService
+     */
+    public static <T> T tryLookup(INode node, String id, Class<T> clazz) {
+        ILookupService lookup = tryGetLookupService(node);
+        if (lookup == null)
+            return null;
+        INode found = lookup.lookupNode(id);
+        return found != null ? clazz.cast(found) : null;
+    }
+
+    /**
+     * @param node
+     * @param id
+     * @return <code>null</code> if lookup failed, i.e. mapping does not exist
+     * @throws UnsupportedOperationException if lookup is not supported
+     * @see #getLookupService(INode)
+     * @see ILookupService
+     */
+    public static String lookupId(INode node) {
+        ILookupService lookup = getLookupService(node);
+        return lookup.lookupId(node);
+    }
+
+    /**
+     * @param node
+     * @param id
+     * @return <code>null</code> if lookup not supported or lookup failed
+     * @see #tryGetLookupService(INode)
+     * @see ILookupService
+     */
+    public static String tryLookupId(INode node) {
+        ILookupService lookup = tryGetLookupService(node);
+        return lookup != null ? lookup.lookupId(node) : null;
+    }
+
+    /**
+     * Map the specified node to the specified ID in the {@link ILookupService}
+     * provided by the root node of the specified node.
+     * 
+     * @param node
+     * @param id
+     * @throws UnsupportedOperationException if {@link ILookupService} is not
+     *         available for the specified node
+     * @see #getLookupService(INode)
+     * @see ILookupService
+     */
+    public static void map(INode node, String id) {
+        getLookupService(node).map(id, node);
+    }
+
+    /**
+     * Remove possible ILookupService mapping for the specified node.
+     * 
+     * @param node the node to try to remove mappings for
+     * @return mapped ID or <code>null</code> if no mapping existed
+     * @throws UnsupportedOperationException if {@link ILookupService} is not
+     *         available for the specified node
+     * @see ILookupService
+     * @see #getLookupService(INode)
+     */
+    public static String unmap(INode node) {
+        return getLookupService(node).unmap(node);
+    }
+
+    /**
+     * Try to remove possible ILookupService mapping for the specified node.
+     * 
+     * @param node the node to try to remove mappings for
+     * @return mapped ID or <code>null</code> if {@link ILookupService} is not
+     *         supported or no mapping existed
+     * @see ILookupService
+     * @see #tryGetLookupService(INode)
+     */
+    public static String tryUnmap(INode node) {
+        ILookupService lookup = tryGetLookupService(node);
+        return lookup != null ? lookup.unmap(node) : null;
+    }
+
+    public static EventDelegator getEventDelegator(INode node) {
+        ParentNode<?> n = node.getRootNode();
+        if (n instanceof G2DSceneGraph) {
+            return ((G2DSceneGraph) n).getEventDelegator();
+        }
+        return null;
+    }
+
+    public static NodeEventHandler getNodeEventHandler(INode node) {
+        ParentNode<?> n = node.getRootNode();
+        return (n instanceof G2DSceneGraph) ? ((G2DSceneGraph) n).getEventHandler() : null;
+//        INodeEventHandlerProvider provider = findNearestParentNode(node, INodeEventHandlerProvider.class);
+//        return provider != null ? provider.getEventHandler() : null;
+    }
+
+    public static AWTEvent transformEvent(AWTEvent event, IG2DNode node) {
+        if (event instanceof MouseEvent) {
+            // Find node transform..
+            AffineTransform transform = getGlobalToLocalTransform(node, null);
+            if (transform == null) {
+                System.err.println("WARNING: Non-invertible transform for node: " + node);
+                return event;
+            }
+            MouseEvent me = (MouseEvent)event;
+            // Use double coordinates if available
+            Point2D p = new Point2D.Double((double)me.getX(), (double)me.getY());
+            transform.transform(p, p);
+
+            MouseEvent e = null;
+            // Giving event.getSource() as a parameter for the new events will cause major delay for the event instantiation, hence dummy component is used
+            if (event instanceof MouseWheelEvent) {
+                e = new SGMouseWheelEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), ((MouseWheelEvent)me).getScrollType(), ((MouseWheelEvent)me).getScrollAmount(), ((MouseWheelEvent)me).getWheelRotation(), me);
+            } else {
+                e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);
+            }
+            return e;
+        }
+        return event;
+    }
+
+    private static final boolean DEBUG_BOUNDS = false;
+
+    private static Rectangle2D getLocalBoundsImpl(INode node, Function1<INode, Boolean> filter, int indent) {
+        if (node instanceof IG2DNode) {
+            if (node instanceof G2DParentNode) {
+                G2DParentNode pNode = (G2DParentNode)node;
+                Iterator<IG2DNode> it = pNode.getNodes().iterator();
+                if (!it.hasNext())
+                    return null;
+                Rectangle2D bounds = null;
+                while (it.hasNext()) {
+                    IG2DNode next = it.next();
+                    if (filter != null && !filter.apply(next))
+                        continue;
+
+                    Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);
+
+                    if(DEBUG_BOUNDS) {
+                        for(int i=0;i<indent;i++) System.err.print(" ");
+                        System.err.println("+getLocalBoundsImpl " + next  + " => " + bl);
+                    }
+
+                    if(bl != null) {
+                        if(bounds == null) {
+                            bounds = next.localToParent(bl.getFrame());
+                        } else {
+                            bounds.add(next.localToParent(bl));
+                        }
+                    }
+                }
+
+                if(DEBUG_BOUNDS) {
+                    for(int i=0;i<indent;i++) System.err.print(" ");
+                    System.err.println("=getLocalBoundsImpl " + node  + " => " + bounds);
+                }
+
+                return bounds;
+            } else {
+                Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);
+                if(result != null) {
+                    if(DEBUG_BOUNDS) {
+                        for(int i=0;i<indent;i++) System.err.print(" ");
+                        System.err.println("=getLocalBoundsImpl " + node  + " => " + result);
+                    }
+                    return result;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static Rectangle2D getLocalBounds(INode node) {
+        return getLocalBoundsImpl(node, null, 0);
+    }
+
+    public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {
+        return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
+
+                       @Override
+                       public Boolean apply(INode node) {
+                               return !excluding.contains(node);
+                       }
+        }, 0);
+    }
+
+    public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {
+        return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
+
+                       @Override
+                       public Boolean apply(INode node) {
+                               return !excluding.isInstance(node);
+                       }
+        }, 0);
+    }
+
+    public static Rectangle2D getLocalElementBounds(INode node) {
+        if(node instanceof ConnectionNode) {
+            return getLocalBounds(node);
+        } else if(node instanceof SingleElementNode) {
+            // For normal symbols
+            INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image");
+            if (image == null)
+                // For generic text nodes
+                image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");
+            if (image == null)
+                // For I/O table diagram flags (value of org.simantics.diagram.flag.FlagSceneGraph.VISUAL_ROOT)
+                image = NodeUtil.findChildByPrefix((SingleElementNode) node, "visual");
+            if (image == null)
+                image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);
+            if (image != null)
+                return getLocalElementBounds(image);
+            else
+                return getLocalBounds(node);
+        } else {
+            return getLocalBounds(node);
+        }
+    }
+
+    public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {
+        ParentNode<?> parent = null;
+        while (true) {
+            parent = node.getParent();
+            if (parent == null)
+                return null;
+            if (ofClass.isInstance(parent))
+                return ofClass.cast(parent);
+            node = parent;
+        }
+    }
+    private static class PendingTester implements Runnable {
+
+        private boolean             pending     = true;
+        private final G2DSceneGraph sg;
+
+        private final Lock          pendingLock = new ReentrantLock();
+        private final Condition     pendingSet  = pendingLock.newCondition();
+
+        public PendingTester(G2DSceneGraph sg) {
+            this.sg = sg;
+        }
+
+        @Override
+        public void run() {
+            pendingLock.lock();
+            try {
+                pending = sg.isPending();
+                pendingSet.signalAll();
+            } finally {
+                pendingLock.unlock();
+            }
+        }
+
+        public boolean isPending() {
+            return pending;
+        }
+
+        public void await() {
+            pendingLock.lock();
+            try {
+                if (pending)
+                    pendingSet.await(10, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // Ignore.
+            } finally {
+                pendingLock.unlock();
+            }
+        }
+
+    }
+
+    public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {
+       // Wait for 30s by default
+       waitPending(thread, sg, 30000);
+    }
+
+    /**
+     * Synchronously waits until the the specified scene graph is no longer in
+     * pending state.
+     * 
+     * @param thread the thread to schedule pending checks into
+     * @param sg the scene graph to wait upon
+     */
+    public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg, int timeoutMs) {
+        PendingTester tester = new PendingTester(sg);
+        long start = System.currentTimeMillis();
+        while (tester.isPending()) {
+            thread.asyncExec(tester);
+            if (tester.isPending())
+                tester.await();
+            long duration = System.currentTimeMillis() - start;
+            if(duration > timeoutMs)
+               throw new IllegalStateException("Timeout in resolving pending nodes.");
+        }
+    }
+
+    public static void increasePending(INode node) {
+        G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
+        if(sg != null)
+               sg.increasePending(node);
+    }
+
+    public static void decreasePending(INode node) {
+        G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
+        if(sg != null)
+               sg.decreasePending(node);
+    }
+
+    // TRANSFORMATIONS
+
+    public static AffineTransform getLocalToGlobalTransform(IG2DNode node, AffineTransform result) {
+        result.setToIdentity();
+        ParentNode<?> parent = node.getParent();
+        while (parent != null) {
+            result.preConcatenate(((IG2DNode) parent).getTransform());
+            parent = parent.getParent();
+        }
+        return result;
+    }
+
+    public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {
+        return getLocalToGlobalTransform(node, new AffineTransform());
+    }
+
+    public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {
+        AffineTransform transform = getLocalToGlobalTransform(node);
+        transform.invert();
+        return transform;
+    }
+
+    public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {
+        AffineTransform transform = getLocalToGlobalTransform(node);
+        try {
+            transform.invert();
+            return transform;
+        } catch (NoninvertibleTransformException e) {
+            return returnIfNonInvertible;
+        }
+    }
+
+    public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {
+        AffineTransform at = getGlobalToLocalTransform(local, null);
+        if (at == null) {
+               pt2.setLocation(pt);
+            return pt2;
+        }
+        return at.transform(pt, pt2);
+    }
+
+    public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {
+        AffineTransform at = getLocalToGlobalTransform(local);
+        return at.transform(pt, pt2);
+    }
+
+    public static String getNodeName(INode nn) {
+       INode node = nn.getParent();
+        ParentNode<?> pn = (ParentNode<?>) node;
+        if (node instanceof G2DParentNode) {
+            G2DParentNode g2dpn = (G2DParentNode) node;
+            for (String id : g2dpn.getSortedNodesById()) 
+            {
+                INode n = pn.getNode(id);
+               if ( nn == n ) {
+                               return id;
+               }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * For asking whether specified parent node really is a parent of the
+     * specified child node.
+     * 
+     * @param parent
+     *            the parent
+     * @param child
+     *            alleged child of parent
+     * @return <code>true</code> if parent really is a parent of child
+     */
+    public static boolean isParentOf(INode parent, INode child) {
+        while (true) {
+            if (parent == child)
+                return true;
+            child = child.getParent();
+            if (child == null)
+                return false;
+        }
+    }
+
+}