-/*******************************************************************************\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.getCause().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 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);
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+}