X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2Futils%2FNodeUtil.java;h=9439369a5927a89b9cfb714f15a296ef1c0ed678;hp=fb2f485f6b5935cc9166a59d9728fbaafd244458;hb=19fa38f6cd07e0917e5f25e009a90617ce431488;hpb=8ada31c956ee02aef38627ba4deaaae3eecb623a diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java index fb2f485f6..9439369a5 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java @@ -1,1167 +1,1229 @@ -/******************************************************************************* - * 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 - */ - 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 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); - } - - 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; - } - - 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 void printTreeNodes(INode node, StringBuilder builder) { - printTreeNodes(node, 0, builder); - } - - public static final void 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); - } - } - } - - 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) { - 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)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) { - 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 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) { - INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image"); - if (image == null) - image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text"); - 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; - } - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.scenegraph.utils; + +import java.awt.AWTEvent; +import java.awt.Container; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +import org.simantics.scenegraph.IDynamicSelectionPainterNode; +import org.simantics.scenegraph.ILookupService; +import org.simantics.scenegraph.INode; +import org.simantics.scenegraph.INode.PropertySetter; +import org.simantics.scenegraph.ISelectionPainterNode; +import org.simantics.scenegraph.ParentNode; +import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.scenegraph.g2d.G2DSceneGraph; +import org.simantics.scenegraph.g2d.IG2DNode; +import org.simantics.scenegraph.g2d.events.EventDelegator; +import org.simantics.scenegraph.g2d.events.NodeEventHandler; +import org.simantics.scenegraph.g2d.events.SGMouseEvent; +import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent; +import org.simantics.scenegraph.g2d.nodes.ConnectionNode; +import org.simantics.scenegraph.g2d.nodes.FlagNode; +import org.simantics.scenegraph.g2d.nodes.SingleElementNode; +import org.simantics.scl.runtime.function.Function1; +import org.simantics.scl.runtime.function.FunctionImpl1; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.threads.IThreadWorkQueue; + +/** + * Utilities for debugging/printing the contents of a scenegraph. + * + * @author Tuukka Lehtonen + */ +public final class NodeUtil { + + /** + * @param + */ + 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) { + 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)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) { + 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 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; + } + } + +}