/******************************************************************************* * 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utilities for debugging/printing the contents of a scenegraph. * * @author Tuukka Lehtonen */ public final class NodeUtil { private static final Logger LOGGER = LoggerFactory.getLogger(NodeUtil.class); /** * @param */ public static interface Filter { public boolean accept(T t); } public static class PrefixFilter implements Filter { 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 getPossibleNearestParentOfType(INode node, Class clazz) { ParentNode parent = null; while (true) { parent = node.getParent(); if (parent == null) return null; 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 * @param parent * @param clazz * @return */ public static T getNearestChildByClass(G2DParentNode parent, Class clazz) { return getNearestChildByClass(parent.getNodes(), clazz); } /** * Breadth-first-search implementation to be used by getNearestChildByClass method * * @param * @param nodes * @param clazz * @return */ @SuppressWarnings("unchecked") public static T getNearestChildByClass(Collection nodes, Class clazz) { Collection list = null; for (IG2DNode n : nodes) { if (clazz.isInstance(n)) { return (T) n; } else if (n instanceof G2DParentNode) { if (list == null) list = new ArrayList(); 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 null 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 null 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 null 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 null 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 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 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 execute(INode node, String id); } /** * @param node the node to iterate possible children for * @param procedure invoked for each child node, if returns a * non-null value, the return value is collected into the result * list * @return the list of collected children */ public static final List forChildren(INode node, NodeProcedure procedure) { return forChildren(node, procedure, new ArrayList()); } /** * @param node the node to iterate possible children for * @param procedure invoked for each child node, if returns a * non-null value, the node is collected into the result * list * @param result the result list into which selected children are collected * or null to not collect * @return the list of collected children or null if provided result list * was null */ public static final List forChildren(INode node, NodeProcedure procedure, List 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 ofClass, invokes * consumer. * * @param node * @param ofClass * @param consumer */ @SuppressWarnings("unchecked") public static INode forChildrenDeep(INode node, Class ofClass, Function func) { return forChildrenDeep(node, n -> ofClass.isInstance(n) ? func.apply((T) n) : null); } public static INode forChildrenDeep(INode node, Function 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 pn = (ParentNode) node; Collection 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 pn = (ParentNode) node; Collection ns = pn.getNodes(); for (INode n : ns) { printTreeNodes(n, indent+2, builder); } } return builder; } public static final Set collectNodes(INode node, Class clazz) { Set result = new HashSet(); collectNodes(node, clazz, result); return result; } @SuppressWarnings("unchecked") public static final void collectNodes(INode node, Class clazz, Set result) { if (clazz.isInstance(node)) result.add((T) node); if (node instanceof ParentNode) { ParentNode pn = (ParentNode) node; Collection ns = pn.getNodes(); for (INode n : ns) { collectNodes(n, clazz, result); } } } public static T getSingleNode(INode node, Class clazz) { Set 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 null 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 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 true 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) { LOGGER.warn("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)node, decodeString(suffix.substring(1, segmentEnd))); if(child == null) return null; return browsePossible(child, suffix.substring(segmentEnd)); } default: return null; } } public static Pair 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 filterDirectChildIds(ParentNode parent, String prefix) { return filterDirectChildIds(parent, new PrefixFilter(prefix)); } /** * @param parent * @param prefix * @return */ public static Collection filterDirectChildIds(ParentNode parent, Filter childFilter) { Collection childIds = parent.getNodeIds(); ArrayList result = new ArrayList(childIds.size()); for (String id : childIds) if (childFilter.accept(id)) result.add(id); return result; } /** * @param parent * @param prefix * @return */ public static Collection filterDirectChildren(ParentNode parent, Filter childFilter) { Collection childIds = parent.getNodeIds(); ArrayList result = new ArrayList(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 null 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 null 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 null 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 null 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 lookup(INode node, String id, Class clazz) { ILookupService lookup = getLookupService(node); INode found = lookup.lookupNode(id); return found != null ? clazz.cast(found) : null; } /** * @param node * @param id * @return null if lookup not supported or lookup failed * @see #tryGetLookupService(INode) * @see ILookupService */ public static T tryLookup(INode node, String id, Class 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 null 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 null 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 null 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 null 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) { LOGGER.warn("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 filter, int indent) { if (node instanceof IG2DNode) { if (node instanceof G2DParentNode) { G2DParentNode pNode = (G2DParentNode)node; Iterator 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 " + 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 " + bounds); } return bounds; } else { Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true); if(result != null) { if(DEBUG_BOUNDS) { for(int i=0;i " + result); } return result; } } } return null; } public static Rectangle2D getLocalBounds(INode node) { return getLocalBoundsImpl(node, null, 0); } public static Rectangle2D getLocalBounds(INode node, final Set excluding) { return getLocalBoundsImpl(node, new FunctionImpl1() { @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() { @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 findNearestParentNode(INode node, Class 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 true 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; } } }