1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.utils;
14 import java.awt.AWTEvent;
15 import java.awt.Container;
16 import java.awt.event.MouseEvent;
17 import java.awt.event.MouseWheelEvent;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.NoninvertibleTransformException;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.io.PrintStream;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.locks.Condition;
33 import java.util.concurrent.locks.Lock;
34 import java.util.concurrent.locks.ReentrantLock;
35 import java.util.function.Function;
37 import org.simantics.scenegraph.IDynamicSelectionPainterNode;
38 import org.simantics.scenegraph.ILookupService;
39 import org.simantics.scenegraph.INode;
40 import org.simantics.scenegraph.INode.PropertySetter;
41 import org.simantics.scenegraph.ISelectionPainterNode;
42 import org.simantics.scenegraph.ParentNode;
43 import org.simantics.scenegraph.g2d.G2DParentNode;
44 import org.simantics.scenegraph.g2d.G2DSceneGraph;
45 import org.simantics.scenegraph.g2d.IG2DNode;
46 import org.simantics.scenegraph.g2d.events.EventDelegator;
47 import org.simantics.scenegraph.g2d.events.NodeEventHandler;
48 import org.simantics.scenegraph.g2d.events.SGMouseEvent;
49 import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;
50 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
51 import org.simantics.scenegraph.g2d.nodes.FlagNode;
52 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
53 import org.simantics.scl.runtime.function.Function1;
54 import org.simantics.scl.runtime.function.FunctionImpl1;
55 import org.simantics.utils.datastructures.Pair;
56 import org.simantics.utils.threads.IThreadWorkQueue;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * Utilities for debugging/printing the contents of a scenegraph.
63 * @author Tuukka Lehtonen
65 public final class NodeUtil {
67 private static final Logger LOGGER = LoggerFactory.getLogger(NodeUtil.class);
71 public static interface Filter<T> {
72 public boolean accept(T t);
75 public static class PrefixFilter implements Filter<String> {
76 private final String prefix;
77 public PrefixFilter(String prefix) {
81 public boolean accept(String t) {
82 return t.startsWith(prefix);
87 * The name of the sibling-node that is used to represent that a node is selected.
89 public static final String SELECTION_NODE_NAME = "selection";
91 public static INode getNearestParentOfType(INode node, Class<?> clazz) {
92 ParentNode<?> parent = null;
94 parent = node.getParent();
98 if (clazz.isInstance(node))
103 public static INode getPossibleNearestParentOfType(INode node, Class<?> clazz) {
104 ParentNode<?> parent = null;
106 parent = node.getParent();
110 if (clazz.isInstance(node))
115 public static INode getRootNode(INode node) {
116 ParentNode<?> parent = null;
118 parent = node.getParent();
125 public static G2DSceneGraph getRootNode(IG2DNode node) {
126 INode root = getRootNode((INode) node);
127 return (G2DSceneGraph) root;
130 public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {
131 INode root = getRootNode((INode) node);
132 return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;
136 * Method for seeking node from scenegraph by class
143 public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {
144 return getNearestChildByClass(parent.getNodes(), clazz);
148 * Breadth-first-search implementation to be used by getNearestChildByClass method
155 @SuppressWarnings("unchecked")
156 public static <T> T getNearestChildByClass(Collection<IG2DNode> nodes, Class<T> clazz) {
157 Collection<IG2DNode> list = null;
158 for (IG2DNode n : nodes) {
159 if (clazz.isInstance(n)) {
161 } else if (n instanceof G2DParentNode) {
163 list = new ArrayList<IG2DNode>();
164 list.addAll(((G2DParentNode)n).getNodes());
167 if (list == null || list.isEmpty()) return null;
168 return getNearestChildByClass(list, clazz);
172 * Tries to look for a child node from the specified node with the specified
173 * ID. Returns <code>null</code> if the specified node is a not a
174 * {@link ParentNode}.
180 public static INode getChildById(INode node, String id) {
181 if (node instanceof ParentNode<?>) {
182 return ((ParentNode<?>) node).getNode(id);
188 * Looks for the first child node of the specified node based on 2D Z-order.
189 * The specified node must be a {@link G2DParentNode} for the method to
192 * @param node the node to get for the first child from
193 * @return <code>null</code> if the specified node is not a
194 * {@link G2DParentNode} or has no children.
196 public static INode getFirstChild(INode node) {
197 if (node instanceof G2DParentNode) {
198 G2DParentNode pn = (G2DParentNode) node;
199 IG2DNode[] sorted = pn.getSortedNodes();
200 if (sorted.length > 0)
207 * Returns a single child node of the specified node or <code>null</code> if
208 * there are more than one or zero children.
210 * @param node the node to get a possible single child from
211 * @return single child node or <code>null</code> if specified node has more
212 * than one or zero children
214 public static INode getPossibleChild(INode node) {
215 if (node instanceof ParentNode<?>) {
216 ParentNode<?> pn = (ParentNode<?>) node;
217 if (pn.getNodeCount() == 1)
218 return pn.getNodes().iterator().next();
224 * Counts the depth of the specified node in its scene graph node tree.
225 * Depth 1 equals root level.
227 * @param node the node for which to count a depth
228 * @return the depth of the node
230 public static int getDepth(INode node) {
232 ParentNode<?> parent = null;
234 parent = node.getParent();
242 private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {
243 for (int i = 0; i < indentLevel; ++i)
245 stream.print(node.getSimpleClassName());
247 String lookupId = tryLookupId(node);
248 if (lookupId != null) {
249 stream.print(" {" + id + ", lookupId = "+lookupId+"}");
251 stream.print(" {" + id + "}");
254 stream.println(node);
255 if (node instanceof G2DParentNode) {
256 G2DParentNode parentNode = (G2DParentNode) node;
257 for (String cid : parentNode.getSortedNodesById()) {
258 Object child = parentNode.getNode(cid);
259 if (child instanceof INode)
260 printSceneGraph(stream, indentLevel + 1, (INode) child, cid);
262 } else if (node instanceof ParentNode<?>) {
263 ParentNode<? extends INode> parentNode = (ParentNode<?>) node;
264 for (String cid : parentNode.getNodeIds()) {
265 INode child = parentNode.getNode(cid);
266 printSceneGraph(stream, indentLevel + 1, (INode) child);
271 public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {
273 ParentNode<?> parent = node.getParent();
274 if (parent != null) {
275 Collection<String> ids = parent.getNodeIds();
276 for (String i : ids) {
277 INode n = parent.getNode(i);
284 printSceneGraph(stream, indentLevel, node, id);
287 public static final void printSceneGraph(int indentLevel, INode node) {
288 printSceneGraph(System.out, indentLevel, node);
291 public static final void printSceneGraph(INode node) {
292 printSceneGraph(System.out, 0, node);
296 public static interface NodeProcedure<T> {
297 T execute(INode node, String id);
301 * @param node the node to iterate possible children for
302 * @param procedure invoked for each child node, if returns a
303 * <code>non-null</code> value, the return value is collected into the result
305 * @return the list of collected children
307 public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {
308 return forChildren(node, procedure, new ArrayList<T>());
312 * @param node the node to iterate possible children for
313 * @param procedure invoked for each child node, if returns a
314 * <code>non-null</code> value, the node is collected into the result
316 * @param result the result list into which selected children are collected
317 * or <code>null</code> to not collect
318 * @return the list of collected children or null if provided result list
319 * was <code>null</code>
321 public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure, List<T> result) {
322 if (node instanceof ParentNode<?>) {
323 ParentNode<?> pn = (ParentNode<?>) node;
324 if (node instanceof G2DParentNode) {
325 G2DParentNode g2dpn = (G2DParentNode) node;
326 for (String id : g2dpn.getSortedNodesById()) {
327 INode n = pn.getNode(id);
328 T t = procedure.execute(n, id);
329 if (t != null && result != null)
333 for (String id : pn.getNodeIds()) {
334 INode n = pn.getNode(id);
335 T t = procedure.execute(n, id);
336 if (t != null && result != null)
345 * Recursively iterates through all child nodes of the specified node and
346 * for those nodes that are of class <code>ofClass</code>, invokes
347 * <code>consumer</code>.
353 @SuppressWarnings("unchecked")
354 public static <T extends INode> INode forChildrenDeep(INode node, Class<T> ofClass, Function<T, INode> func) {
355 return forChildrenDeep(node, n -> ofClass.isInstance(n) ? func.apply((T) n) : null);
358 public static <T extends INode> INode forChildrenDeep(INode node, Function<INode, INode> func) {
359 INode ret = func.apply(node);
363 if (node instanceof ParentNode<?>) {
364 if (node instanceof G2DParentNode) {
365 G2DParentNode g2dpn = (G2DParentNode) node;
366 for (IG2DNode n : g2dpn.getSortedNodes()) {
367 INode r = forChildrenDeep(n, func);
373 for (INode n : ((ParentNode<?>) node).getNodes()) {
374 INode r = forChildrenDeep(n, func);
385 public static final int countTreeNodes(INode node) {
387 if (node instanceof ParentNode<?>) {
388 ParentNode<? extends INode> pn = (ParentNode<?>) node;
389 Collection<? extends INode> ns = pn.getNodes();
391 result += countTreeNodes(n);
397 public static final StringBuilder printTreeNodes(INode node, StringBuilder builder) {
398 printTreeNodes(node, 0, builder);
402 public static final StringBuilder printTreeNodes(INode node, int indent, StringBuilder builder) {
403 for (int i = 0; i < indent; i++)
405 builder.append(node.toString() + "\n");
406 if (node instanceof ParentNode<?>) {
407 ParentNode<? extends INode> pn = (ParentNode<?>) node;
408 Collection<? extends INode> ns = pn.getNodes();
410 printTreeNodes(n, indent+2, builder);
416 public static final <T extends INode> Set<T> collectNodes(INode node, Class<T> clazz) {
417 Set<T> result = new HashSet<T>();
418 collectNodes(node, clazz, result);
422 @SuppressWarnings("unchecked")
423 public static final <T extends INode> void collectNodes(INode node, Class<T> clazz, Set<T> result) {
424 if (clazz.isInstance(node))
425 result.add((T) node);
426 if (node instanceof ParentNode<?>) {
427 ParentNode<? extends INode> pn = (ParentNode<?>) node;
428 Collection<? extends INode> ns = pn.getNodes();
430 collectNodes(n, clazz, result);
435 public static <T extends INode> T getSingleNode(INode node, Class<T> clazz) {
436 Set<T> all = collectNodes(node, clazz);
437 if(all.size() != 1) throw new RuntimeException("Expected exactly 1 instance of class " + clazz.getCanonicalName() + ", got " + all.size());
438 return (T)all.iterator().next();
441 public static final boolean hasChildren(INode node) {
442 if (node instanceof ParentNode<?>) {
443 ParentNode<?> pn = (ParentNode<?>) node;
444 return !pn.getNodes().isEmpty();
450 * Look for a single scene graph node by its ID in a path under a specified
453 * @param parent the parent node under which to start looking
454 * @param idPath the node ID path
455 * @return <code>null</code> if node was not found
456 * @throws ClassCastException if the found node was not of the expected type
459 @SuppressWarnings("unchecked")
460 public static <T extends INode> T findNodeById(INode parent, String... idPath) {
462 for (int i = 0;; ++i) {
463 if (i >= idPath.length)
465 if (n instanceof ParentNode<?>) {
466 n = ((ParentNode<?>) n).getNode(idPath[i]);
474 * Tries to find out whether a node is selected or not.
476 * DISCLAIMER: this is a hack for the current
477 * org.simantics.g2d.diagram.participant.ElementPainter implementation that
478 * will stop working if the implementation changes.
481 * @param ascendLimit the max. amount of steps towards parent nodes to take
482 * while looking for selection information
483 * @return <code>true</code> if evidence of selection is found
485 public static boolean isSelected(INode node, int ascendLimit) {
487 ParentNode<?> pn = null;
488 if (node instanceof ParentNode<?>) {
489 pn = (ParentNode<?>) node;
491 pn = node.getParent();
494 for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {
495 INode child = pn.getNode(SELECTION_NODE_NAME);
502 public static Container findRootPane(INode node) {
503 G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);
506 return ((G2DSceneGraph) parent).getRootPane();
509 private static boolean isSelectionPainter(INode node) {
510 if (node instanceof ISelectionPainterNode) {
511 if (node instanceof IDynamicSelectionPainterNode)
512 return ((IDynamicSelectionPainterNode)node).showsSelection();
522 public static boolean needSelectionPaint(INode elementNode) {
523 // FIXME: there should be a cleaner way to implement this.
525 if (isSelectionPainter(elementNode)) {
526 // System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
530 if (elementNode instanceof ConnectionNode) {
531 // System.out.println("connectionNode");
532 for (IG2DNode child : ((ConnectionNode) elementNode).getNodes()) {
533 // System.out.println(" child " + child);
534 if (isSelectionPainter(child)) {
535 // System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
538 if (child instanceof SingleElementNode) {
539 for(IG2DNode child2 : ((SingleElementNode) child).getNodes()) {
540 // System.out.println(" child2 " + child2);
541 if (isSelectionPainter(child2)) {
542 // System.out.println("skipped selection painting for edge ISelectionPainterNode");
548 } else if (elementNode instanceof SingleElementNode) {
549 for (INode child : ((SingleElementNode) elementNode).getNodes()) {
550 if (isSelectionPainter(child))
558 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
560 public static Method getSetterForProperty(String property, INode node) {
561 assert(node != null);
562 Class<?> cl = node.getClass();
565 boolean isEnhanced = false;
566 for(Method method : cl.getMethods()) {
567 if(method.isAnnotationPresent(PropertySetter.class)) {
568 PropertySetter ann = method.getAnnotation(PropertySetter.class);
569 if(ann.value().equals(property)) {
572 } else if(method.getName().equals(SET_THREAD_CALLBACKS_NAME) && method.getGenericParameterTypes().length == 1) {
573 // The class seems to be enhanced by cglib, hence annotations are not present. Check superclass for annotations..
575 cl = cl.getSuperclass();
576 break; // We are not going to find any annotations, stop loop and try with the parent class
579 if(!isEnhanced || cl == null) break;
585 * TODO: log exceptions for debug purposes
587 * @param property name of the property
588 * @param value can be null..
592 public static boolean setPropertyIfSupported(String property, Object value, INode node) {
593 Method setter = getSetterForProperty(property, node);
595 Class<?> pc[] = setter.getParameterTypes();
596 if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {
598 setter.invoke(node, value);
600 } catch (IllegalArgumentException e) {
601 // TODO Auto-generated catch block
603 } catch (IllegalAccessException e) {
604 // TODO Auto-generated catch block
606 } catch (InvocationTargetException e) {
607 // TODO Auto-generated catch block
608 e.getCause().printStackTrace();
613 LOGGER.warn("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");
622 public static INode findChildById(ParentNode<?> parent, String key) {
623 INode result = parent.getNode(key);
627 for (String entry : parent.getNodeIds()) {
628 if (entry.startsWith(key))
629 return parent.getNode(key);
632 for (INode node : parent.getNodes()) {
633 if (node instanceof ParentNode) {
634 result = findChildById((ParentNode<?>) node, key);
644 private static int getSegmentEnd(String suffix) {
646 for(pos=1;pos<suffix.length();++pos) {
647 char c = suffix.charAt(pos);
648 if(c == '/' || c == '#')
654 public static String decodeString(String string) {
658 public static INode browsePossible(INode node, String suffix) {
661 switch(suffix.charAt(0)) {
663 INode parent = node.getParent();
666 return browsePossible(parent, suffix.substring(1));
669 /*int segmentEnd = getSegmentEnd(suffix);
670 Variable property = getPossibleProperty(graph,
671 decodeString(suffix.substring(1, segmentEnd)));
674 return property.browsePossible(graph, suffix.substring(segmentEnd));*/
678 int segmentEnd = getSegmentEnd(suffix);
679 INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
682 return browsePossible(child, suffix.substring(segmentEnd));
689 public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {
691 throw new RuntimeException("Did not find a reference.");
692 switch(suffix.charAt(0)) {
694 INode parent = node.getParent();
697 return browsePossibleReference(parent, suffix.substring(1));
700 return Pair.make(node, suffix.substring(1));
703 int segmentEnd = getSegmentEnd(suffix);
704 INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
707 return browsePossibleReference(child, suffix.substring(segmentEnd));
714 public static INode findChildByPrefix(G2DParentNode parent, String prefix) {
715 INode result = parent.getNode(prefix);
719 for (String entry : parent.getNodeIds()) {
720 if (entry.startsWith(prefix))
721 return parent.getNode(entry);
724 for (IG2DNode node : parent.getNodes()) {
725 if (node instanceof G2DParentNode) {
726 result = findChildByPrefix((G2DParentNode) node, prefix);
740 public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {
741 return filterDirectChildIds(parent, new PrefixFilter(prefix));
749 public static Collection<String> filterDirectChildIds(ParentNode<?> parent, Filter<String> childFilter) {
750 Collection<String> childIds = parent.getNodeIds();
751 ArrayList<String> result = new ArrayList<String>(childIds.size());
753 for (String id : childIds)
754 if (childFilter.accept(id))
765 public static Collection<INode> filterDirectChildren(ParentNode<?> parent, Filter<String> childFilter) {
766 Collection<String> childIds = parent.getNodeIds();
767 ArrayList<INode> result = new ArrayList<INode>(childIds.size());
769 for (String id : childIds)
770 if (childFilter.accept(id))
771 result.add( parent.getNode(id) );
778 * @return the lookup service for the specified node
779 * @throws UnsupportedOperationException if ILookupService is not available
781 public static ILookupService getLookupService(INode node) {
782 ParentNode<?> root = node.getRootNode();
783 if (!(root instanceof ILookupService))
784 throw new UnsupportedOperationException("ILookupService not supported by root node " + root + " attained from " + node);
785 return (ILookupService) root;
790 * @return <code>null</code> if lookup service is not available
792 public static ILookupService tryGetLookupService(INode node) {
793 ParentNode<?> root = node.getRootNode();
794 return root instanceof ILookupService ? (ILookupService) root : null;
800 * @return <code>null</code> if lookup failed, i.e. mapping does not exist
801 * @throws UnsupportedOperationException if lookup is not supported
802 * @see #getLookupService(INode)
803 * @see ILookupService
805 public static INode lookup(INode node, String id) {
806 ILookupService lookup = getLookupService(node);
807 return lookup.lookupNode(id);
813 * @return <code>null</code> if lookup not supported or lookup failed
814 * @see #tryGetLookupService(INode)
815 * @see ILookupService
817 public static INode tryLookup(INode node, String id) {
818 ILookupService lookup = tryGetLookupService(node);
819 return lookup != null ? lookup.lookupNode(id) : null;
826 * @return <code>null</code> if lookup failed, i.e. mapping does not exist
827 * @throws UnsupportedOperationException if lookup is not supported
828 * @throws ClassCastException if the found node cannot be cast to the
830 * @see #getLookupService(INode)
831 * @see ILookupService
833 public static <T> T lookup(INode node, String id, Class<T> clazz) {
834 ILookupService lookup = getLookupService(node);
835 INode found = lookup.lookupNode(id);
836 return found != null ? clazz.cast(found) : null;
842 * @return <code>null</code> if lookup not supported or lookup failed
843 * @see #tryGetLookupService(INode)
844 * @see ILookupService
846 public static <T> T tryLookup(INode node, String id, Class<T> clazz) {
847 ILookupService lookup = tryGetLookupService(node);
850 INode found = lookup.lookupNode(id);
851 return found != null ? clazz.cast(found) : null;
857 * @return <code>null</code> if lookup failed, i.e. mapping does not exist
858 * @throws UnsupportedOperationException if lookup is not supported
859 * @see #getLookupService(INode)
860 * @see ILookupService
862 public static String lookupId(INode node) {
863 ILookupService lookup = getLookupService(node);
864 return lookup.lookupId(node);
870 * @return <code>null</code> if lookup not supported or lookup failed
871 * @see #tryGetLookupService(INode)
872 * @see ILookupService
874 public static String tryLookupId(INode node) {
875 ILookupService lookup = tryGetLookupService(node);
876 return lookup != null ? lookup.lookupId(node) : null;
880 * Map the specified node to the specified ID in the {@link ILookupService}
881 * provided by the root node of the specified node.
885 * @throws UnsupportedOperationException if {@link ILookupService} is not
886 * available for the specified node
887 * @see #getLookupService(INode)
888 * @see ILookupService
890 public static void map(INode node, String id) {
891 getLookupService(node).map(id, node);
895 * Remove possible ILookupService mapping for the specified node.
897 * @param node the node to try to remove mappings for
898 * @return mapped ID or <code>null</code> if no mapping existed
899 * @throws UnsupportedOperationException if {@link ILookupService} is not
900 * available for the specified node
901 * @see ILookupService
902 * @see #getLookupService(INode)
904 public static String unmap(INode node) {
905 return getLookupService(node).unmap(node);
909 * Try to remove possible ILookupService mapping for the specified node.
911 * @param node the node to try to remove mappings for
912 * @return mapped ID or <code>null</code> if {@link ILookupService} is not
913 * supported or no mapping existed
914 * @see ILookupService
915 * @see #tryGetLookupService(INode)
917 public static String tryUnmap(INode node) {
918 ILookupService lookup = tryGetLookupService(node);
919 return lookup != null ? lookup.unmap(node) : null;
922 public static EventDelegator getEventDelegator(INode node) {
923 ParentNode<?> n = node.getRootNode();
924 if (n instanceof G2DSceneGraph) {
925 return ((G2DSceneGraph) n).getEventDelegator();
930 public static NodeEventHandler getNodeEventHandler(INode node) {
931 ParentNode<?> n = node.getRootNode();
932 return (n instanceof G2DSceneGraph) ? ((G2DSceneGraph) n).getEventHandler() : null;
933 // INodeEventHandlerProvider provider = findNearestParentNode(node, INodeEventHandlerProvider.class);
934 // return provider != null ? provider.getEventHandler() : null;
937 public static AWTEvent transformEvent(AWTEvent event, IG2DNode node) {
938 if (event instanceof MouseEvent) {
939 // Find node transform..
940 AffineTransform transform = getGlobalToLocalTransform(node, null);
941 if (transform == null) {
942 LOGGER.warn("WARNING: Non-invertible transform for node: " + node);
945 MouseEvent me = (MouseEvent)event;
946 // Use double coordinates if available
947 Point2D p = new Point2D.Double((double)me.getX(), (double)me.getY());
948 transform.transform(p, p);
951 // Giving event.getSource() as a parameter for the new events will cause major delay for the event instantiation, hence dummy component is used
952 if (event instanceof MouseWheelEvent) {
953 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);
955 e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);
962 private static final boolean DEBUG_BOUNDS = false;
964 private static Rectangle2D getLocalBoundsImpl(INode node, Function1<INode, Boolean> filter, int indent) {
965 if (node instanceof IG2DNode) {
966 if (node instanceof G2DParentNode) {
967 G2DParentNode pNode = (G2DParentNode)node;
968 Iterator<IG2DNode> it = pNode.getNodes().iterator();
971 Rectangle2D bounds = null;
972 while (it.hasNext()) {
973 IG2DNode next = it.next();
974 if (filter != null && !filter.apply(next))
977 Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);
980 for(int i=0;i<indent;i++) System.err.print(" ");
981 LOGGER.warn("+getLocalBoundsImpl " + next + " => " + bl);
986 bounds = next.localToParent(bl.getFrame());
988 bounds.add(next.localToParent(bl));
994 for(int i=0;i<indent;i++) System.err.print(" ");
995 LOGGER.warn("=getLocalBoundsImpl " + node + " => " + bounds);
1000 Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);
1001 if(result != null) {
1003 for(int i=0;i<indent;i++) System.err.print(" ");
1004 LOGGER.warn("=getLocalBoundsImpl " + node + " => " + result);
1013 public static Rectangle2D getLocalBounds(INode node) {
1014 return getLocalBoundsImpl(node, null, 0);
1017 public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {
1018 return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
1021 public Boolean apply(INode node) {
1022 return !excluding.contains(node);
1027 public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {
1028 return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
1031 public Boolean apply(INode node) {
1032 return !excluding.isInstance(node);
1037 public static Rectangle2D getLocalElementBounds(INode node) {
1038 if(node instanceof ConnectionNode) {
1039 return getLocalBounds(node);
1040 } else if(node instanceof SingleElementNode) {
1041 // For normal symbols
1042 INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image");
1044 // For generic text nodes
1045 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");
1047 // For I/O table diagram flags (value of org.simantics.diagram.flag.FlagSceneGraph.VISUAL_ROOT)
1048 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "visual");
1050 image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);
1052 return getLocalElementBounds(image);
1054 return getLocalBounds(node);
1056 return getLocalBounds(node);
1060 public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {
1061 ParentNode<?> parent = null;
1063 parent = node.getParent();
1066 if (ofClass.isInstance(parent))
1067 return ofClass.cast(parent);
1072 private static class PendingTester implements Runnable {
1074 private boolean pending = true;
1075 private final G2DSceneGraph sg;
1077 private final Lock pendingLock = new ReentrantLock();
1078 private final Condition pendingSet = pendingLock.newCondition();
1080 public PendingTester(G2DSceneGraph sg) {
1088 pending = sg.isPending();
1089 pendingSet.signalAll();
1091 pendingLock.unlock();
1095 public boolean isPending() {
1099 public void await() {
1103 pendingSet.await(10, TimeUnit.MILLISECONDS);
1104 } catch (InterruptedException e) {
1107 pendingLock.unlock();
1113 public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {
1114 // Wait for 30s by default
1115 waitPending(thread, sg, 30000);
1119 * Synchronously waits until the the specified scene graph is no longer in
1122 * @param thread the thread to schedule pending checks into
1123 * @param sg the scene graph to wait upon
1125 public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg, int timeoutMs) {
1126 PendingTester tester = new PendingTester(sg);
1127 long start = System.currentTimeMillis();
1128 while (tester.isPending()) {
1129 thread.asyncExec(tester);
1130 if (tester.isPending())
1132 long duration = System.currentTimeMillis() - start;
1133 if(duration > timeoutMs)
1134 throw new IllegalStateException("Timeout in resolving pending nodes.");
1138 public static void increasePending(INode node) {
1139 G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
1141 sg.increasePending(node);
1144 public static void decreasePending(INode node) {
1145 G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
1147 sg.decreasePending(node);
1152 public static AffineTransform getLocalToGlobalTransform(IG2DNode node, AffineTransform result) {
1153 result.setToIdentity();
1154 ParentNode<?> parent = node.getParent();
1155 while (parent != null) {
1156 result.preConcatenate(((IG2DNode) parent).getTransform());
1157 parent = parent.getParent();
1162 public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {
1163 return getLocalToGlobalTransform(node, new AffineTransform());
1166 public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {
1167 AffineTransform transform = getLocalToGlobalTransform(node);
1172 public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {
1173 AffineTransform transform = getLocalToGlobalTransform(node);
1177 } catch (NoninvertibleTransformException e) {
1178 return returnIfNonInvertible;
1182 public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {
1183 AffineTransform at = getGlobalToLocalTransform(local, null);
1185 pt2.setLocation(pt);
1188 return at.transform(pt, pt2);
1191 public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {
1192 AffineTransform at = getLocalToGlobalTransform(local);
1193 return at.transform(pt, pt2);
1196 public static String getNodeName(INode nn) {
1197 INode node = nn.getParent();
1198 ParentNode<?> pn = (ParentNode<?>) node;
1199 if (node instanceof G2DParentNode) {
1200 G2DParentNode g2dpn = (G2DParentNode) node;
1201 for (String id : g2dpn.getSortedNodesById())
1203 INode n = pn.getNode(id);
1213 * For asking whether specified parent node really is a parent of the
1214 * specified child node.
1219 * alleged child of parent
1220 * @return <code>true</code> if parent really is a parent of child
1222 public static boolean isParentOf(INode parent, INode child) {
1224 if (parent == child)
1226 child = child.getParent();