]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / NodeUtil.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.utils;
13
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;
30 import java.util.Set;
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;
36
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;
59
60 /**
61  * Utilities for debugging/printing the contents of a scenegraph.
62  * 
63  * @author Tuukka Lehtonen
64  */
65 public final class NodeUtil {
66
67     private static final Logger LOGGER = LoggerFactory.getLogger(NodeUtil.class);
68     /**
69      * @param <T>
70      */
71     public static interface Filter<T> {
72         public boolean accept(T t);
73     }
74
75     public static class PrefixFilter implements Filter<String> {
76         private final String prefix;
77         public PrefixFilter(String prefix) {
78             this.prefix = prefix;
79         }
80         @Override
81         public boolean accept(String t) {
82             return t.startsWith(prefix);
83         }
84     }
85
86     /**
87      * The name of the sibling-node that is used to represent that a node is selected.
88      */
89     public static final String SELECTION_NODE_NAME = "selection";
90
91     public static INode getNearestParentOfType(INode node, Class<?> clazz) {
92         ParentNode<?> parent = null;
93         while (true) {
94             parent = node.getParent();
95             if (parent == null)
96                 return node;
97             node = parent;
98             if (clazz.isInstance(node))
99                 return node;
100         }
101     }
102
103     public static INode getPossibleNearestParentOfType(INode node, Class<?> clazz) {
104         ParentNode<?> parent = null;
105         while (true) {
106             parent = node.getParent();
107             if (parent == null)
108                 return null;
109             node = parent;
110             if (clazz.isInstance(node))
111                 return node;
112         }
113     }
114
115     public static INode getRootNode(INode node) {
116         ParentNode<?> parent = null;
117         while (true) {
118             parent = node.getParent();
119             if (parent == null)
120                 return node;
121             node = parent;
122         }
123     }
124
125     public static G2DSceneGraph getRootNode(IG2DNode node) {
126         INode root = getRootNode((INode) node);
127         return (G2DSceneGraph) root;
128     }
129
130     public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {
131         INode root = getRootNode((INode) node);
132         return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;
133     }
134
135     /**
136      * Method for seeking node from scenegraph by class
137      * 
138      * @param <T>
139      * @param parent
140      * @param clazz
141      * @return
142      */
143     public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {
144         return getNearestChildByClass(parent.getNodes(), clazz);
145     }
146
147     /**
148      * Breadth-first-search implementation to be used by getNearestChildByClass method
149      * 
150      * @param <T>
151      * @param nodes
152      * @param clazz
153      * @return
154      */
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)) {
160                 return (T) n;
161             } else if (n instanceof G2DParentNode) {
162                 if (list == null)
163                     list = new ArrayList<IG2DNode>();
164                 list.addAll(((G2DParentNode)n).getNodes());
165             }
166         }
167         if (list == null || list.isEmpty()) return null;
168         return getNearestChildByClass(list, clazz);
169     }
170
171     /**
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}.
175      * 
176      * @param node
177      * @param id
178      * @return
179      */
180     public static INode getChildById(INode node, String id) {
181         if (node instanceof ParentNode<?>) {
182             return ((ParentNode<?>) node).getNode(id);
183         }
184         return null;
185     }
186
187     /**
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
190      * succeed.
191      * 
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.
195      */
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)
201                 return sorted[0];
202         }
203         return null;
204     }
205
206     /**
207      * Returns a single child node of the specified node or <code>null</code> if
208      * there are more than one or zero children.
209      * 
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
213      */
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();
219         }
220         return null;
221     }
222
223     /**
224      * Counts the depth of the specified node in its scene graph node tree.
225      * Depth 1 equals root level.
226      * 
227      * @param node the node for which to count a depth
228      * @return the depth of the node
229      */
230     public static int getDepth(INode node) {
231         int result = 1;
232         ParentNode<?> parent = null;
233         while (true) {
234             parent = node.getParent();
235             if (parent == null)
236                 return result;
237             node = parent;
238             ++result;
239         }
240     }
241
242     private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {
243         for (int i = 0; i < indentLevel; ++i)
244             stream.print("\t");
245         stream.print(node.getSimpleClassName());
246         if (id != null) {               
247                 String lookupId = tryLookupId(node);
248                 if (lookupId != null) {
249                         stream.print(" {" + id + ", lookupId = "+lookupId+"}");
250                 } else {
251                         stream.print(" {" + id + "}");
252                 }
253         }
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);
261             }
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);
267             }
268         }
269     }
270
271     public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {
272         String id = null;
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);
278                 if (n == node) {
279                     id = i;
280                     break;
281                 }
282             }
283         }
284         printSceneGraph(stream, indentLevel, node, id);
285     }
286
287     public static final void printSceneGraph(int indentLevel, INode node) {
288         printSceneGraph(System.out, indentLevel, node);
289     }
290
291     public static final void printSceneGraph(INode node) {
292         printSceneGraph(System.out, 0, node);
293     }
294
295     @FunctionalInterface
296     public static interface NodeProcedure<T> {
297         T execute(INode node, String id);
298     }
299
300     /**
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
304      *        list
305      * @return the list of collected children
306      */
307     public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {
308         return forChildren(node, procedure, new ArrayList<T>());
309     }
310
311     /**
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
315      *        list
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>
320      */
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)
330                         result.add(t);
331                 }
332             } else {
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)
337                         result.add(t);
338                 }
339             }
340         }
341         return result;
342     }
343
344     /**
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>.
348      * 
349      * @param node
350      * @param ofClass
351      * @param consumer
352      */
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);
356     }
357
358     public static <T extends INode> INode forChildrenDeep(INode node, Function<INode, INode> func) {
359         INode ret = func.apply(node);
360         if (ret != null)
361             return ret;
362
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);
368                     if (r != null) {
369                         return r;
370                     }
371                 }
372             } else {
373                 for (INode n : ((ParentNode<?>) node).getNodes()) {
374                     INode r = forChildrenDeep(n, func);
375                     if (r != null) {
376                         return r;
377                     }
378                 }
379             }
380         }
381
382         return null;
383     }
384
385     public static final int countTreeNodes(INode node) {
386         int result = 1;
387         if (node instanceof ParentNode<?>) {
388             ParentNode<? extends INode> pn = (ParentNode<?>) node;
389             Collection<? extends INode> ns = pn.getNodes();
390             for (INode n : ns) {
391                 result += countTreeNodes(n);
392             }
393         }
394         return result;
395     }
396
397     public static final StringBuilder printTreeNodes(INode node, StringBuilder builder) {
398         printTreeNodes(node, 0, builder);
399         return builder;
400     }
401
402     public static final StringBuilder printTreeNodes(INode node, int indent, StringBuilder builder) {
403         for (int i = 0; i < indent; i++)
404             builder.append(" ");
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();
409             for (INode n : ns) {
410                 printTreeNodes(n, indent+2, builder);
411             }
412         }
413         return builder;
414     }
415
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);
419         return result;
420     }
421
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();
429             for (INode n : ns) {
430                 collectNodes(n, clazz, result);
431             }
432         }
433     }
434     
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();
439     }
440     
441     public static final boolean hasChildren(INode node) {
442         if (node instanceof ParentNode<?>) {
443             ParentNode<?> pn = (ParentNode<?>) node;
444             return !pn.getNodes().isEmpty();
445         }
446         return false;
447     }
448
449     /**
450      * Look for a single scene graph node by its ID in a path under a specified
451      * node.
452      * 
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
457      *         T extending INode
458      */
459     @SuppressWarnings("unchecked")
460     public static <T extends INode> T findNodeById(INode parent, String... idPath) {
461         INode n = parent;
462         for (int i = 0;; ++i) {
463             if (i >= idPath.length)
464                 return (T) n;
465             if (n instanceof ParentNode<?>) {
466                 n = ((ParentNode<?>) n).getNode(idPath[i]);
467             } else {
468                 return null;
469             }
470         }
471     }
472
473     /**
474      * Tries to find out whether a node is selected or not.
475      * 
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.
479      * 
480      * @param node
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
484      */
485     public static boolean isSelected(INode node, int ascendLimit) {
486         int steps = 0;
487         ParentNode<?> pn = null;
488         if (node instanceof ParentNode<?>) {
489             pn = (ParentNode<?>) node;
490         } else {
491             pn = node.getParent();
492             ++steps;
493         }
494         for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {
495             INode child = pn.getNode(SELECTION_NODE_NAME);
496             if (child != null)
497                 return true;
498         }
499         return false;
500     }
501
502     public static Container findRootPane(INode node) {
503         G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);
504         if (parent == null)
505             return null;
506         return ((G2DSceneGraph) parent).getRootPane();
507     }
508
509     private static boolean isSelectionPainter(INode node) {
510         if (node instanceof ISelectionPainterNode) {
511             if (node instanceof IDynamicSelectionPainterNode)
512                 return ((IDynamicSelectionPainterNode)node).showsSelection();
513             return true;
514         }
515         return false;
516     }
517
518     /**
519      * @param elementNode
520      * @return
521      */
522     public static boolean needSelectionPaint(INode elementNode) {
523         // FIXME: there should be a cleaner way to implement this.
524
525         if (isSelectionPainter(elementNode)) {
526 //          System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
527             return false;
528         }
529
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");
536                     return false;
537                 }
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");
543                             return false;
544                         }
545                     }
546                 }
547             }
548         } else if (elementNode instanceof SingleElementNode) {
549             for (INode child : ((SingleElementNode) elementNode).getNodes()) {
550                 if (isSelectionPainter(child))
551                     return false;
552             }
553         }
554
555         return true;
556     }
557
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
559
560     public static Method getSetterForProperty(String property, INode node) {
561         assert(node != null);
562         Class<?> cl = node.getClass();
563
564         while(true) {
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)) {
570                         return method;
571                     }
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..
574                     isEnhanced = true;
575                     cl = cl.getSuperclass();
576                     break; // We are not going to find any annotations, stop loop and try with the parent class
577                 }
578             }
579             if(!isEnhanced || cl == null) break;
580         }
581         return null;
582     }
583
584     /**
585      * TODO: log exceptions for debug purposes
586      * 
587      * @param property name of the property
588      * @param value    can be null..
589      * @param node
590      * @return
591      */
592     public static boolean setPropertyIfSupported(String property, Object value, INode node) {
593         Method setter = getSetterForProperty(property, node);
594         if(setter != null) {
595             Class<?> pc[] = setter.getParameterTypes();
596             if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {
597                 try {
598                     setter.invoke(node, value);
599                     return true;
600                 } catch (IllegalArgumentException e) {
601                     // TODO Auto-generated catch block
602                     e.printStackTrace();
603                 } catch (IllegalAccessException e) {
604                     // TODO Auto-generated catch block
605                     e.printStackTrace();
606                 } catch (InvocationTargetException e) {
607                     // TODO Auto-generated catch block
608                     e.getCause().printStackTrace();
609                 }
610             } else {
611
612                 if(pc.length > 0) {
613                     LOGGER.warn("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");
614                 }
615
616             }
617         }
618
619         return false;
620     }
621
622     public static INode findChildById(ParentNode<?> parent, String key) {
623         INode result = parent.getNode(key);
624         if (result != null)
625             return result;
626
627         for (String entry : parent.getNodeIds()) {
628             if (entry.startsWith(key))
629                 return parent.getNode(key);
630         }
631
632         for (INode node : parent.getNodes()) {
633             if (node instanceof ParentNode) {
634                 result = findChildById((ParentNode<?>) node, key);
635                 if (result != null)
636                     return result;
637             }
638         }
639
640         return null;
641     }
642
643     
644     private static int getSegmentEnd(String suffix) {
645         int pos;
646         for(pos=1;pos<suffix.length();++pos) {
647             char c = suffix.charAt(pos);
648             if(c == '/' || c == '#')
649                 break;
650         }
651         return pos;
652     }
653     
654     public static String decodeString(String string) {
655         return string;
656     }
657     
658     public static INode browsePossible(INode node, String suffix) {
659         if(suffix.isEmpty()) 
660             return node;        
661         switch(suffix.charAt(0)) {
662         case '.': {
663                 INode parent = node.getParent();
664             if(parent == null)
665                 return null;
666             return browsePossible(parent, suffix.substring(1));
667         }
668         case '#': {
669             /*int segmentEnd = getSegmentEnd(suffix);
670             Variable property = getPossibleProperty(graph, 
671                     decodeString(suffix.substring(1, segmentEnd)));
672             if(property == null) 
673                 return null;
674             return property.browsePossible(graph, suffix.substring(segmentEnd));*/
675                 return node;
676         }
677         case '/': {
678             int segmentEnd = getSegmentEnd(suffix);
679             INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
680             if(child == null) 
681                 return null;
682             return browsePossible(child, suffix.substring(segmentEnd));
683         }
684         default:
685             return null;
686         }
687     }    
688     
689     public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {
690         if(suffix.isEmpty()) 
691             throw new RuntimeException("Did not find a reference.");        
692         switch(suffix.charAt(0)) {
693         case '.': {
694                 INode parent = node.getParent();
695             if(parent == null)
696                 return null;
697             return browsePossibleReference(parent, suffix.substring(1));
698         }
699         case '#': {
700                 return Pair.make(node, suffix.substring(1));
701         }
702         case '/': {
703             int segmentEnd = getSegmentEnd(suffix);
704             INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
705             if(child == null) 
706                 return null;
707             return browsePossibleReference(child, suffix.substring(segmentEnd));
708         }
709         default:
710             return null;
711         }
712     }    
713
714     public static INode findChildByPrefix(G2DParentNode parent, String prefix) {
715         INode result = parent.getNode(prefix);
716         if (result != null)
717             return result;
718
719         for (String entry : parent.getNodeIds()) {
720             if (entry.startsWith(prefix))
721                 return parent.getNode(entry);
722         }
723
724         for (IG2DNode node : parent.getNodes()) {
725             if (node instanceof G2DParentNode) {
726                 result = findChildByPrefix((G2DParentNode) node, prefix);
727                 if (result != null)
728                     return result;
729             }
730         }
731
732         return null;
733     }
734
735     /**
736      * @param parent
737      * @param prefix
738      * @return
739      */
740     public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {
741         return filterDirectChildIds(parent, new PrefixFilter(prefix));
742     }
743
744     /**
745      * @param parent
746      * @param prefix
747      * @return
748      */
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());
752
753         for (String id : childIds)
754             if (childFilter.accept(id))
755                 result.add(id);
756
757         return result;
758     }
759
760     /**
761      * @param parent
762      * @param prefix
763      * @return
764      */
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());
768
769         for (String id : childIds)
770             if (childFilter.accept(id))
771                 result.add( parent.getNode(id) );
772
773         return result;
774     }
775
776     /**
777      * @param node
778      * @return the lookup service for the specified node
779      * @throws UnsupportedOperationException if ILookupService is not available
780      */
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;
786     }
787
788     /**
789      * @param node
790      * @return <code>null</code> if lookup service is not available
791      */
792     public static ILookupService tryGetLookupService(INode node) {
793         ParentNode<?> root = node.getRootNode();
794         return root instanceof ILookupService ? (ILookupService) root : null;
795     }
796
797     /**
798      * @param node
799      * @param id
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
804      */
805     public static INode lookup(INode node, String id) {
806         ILookupService lookup = getLookupService(node);
807         return lookup.lookupNode(id);
808     }
809
810     /**
811      * @param node
812      * @param id
813      * @return <code>null</code> if lookup not supported or lookup failed
814      * @see #tryGetLookupService(INode)
815      * @see ILookupService
816      */
817     public static INode tryLookup(INode node, String id) {
818         ILookupService lookup = tryGetLookupService(node);
819         return lookup != null ? lookup.lookupNode(id) : null;
820     }
821
822     /**
823      * @param node
824      * @param id
825      * @param clazz
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
829      *         specified class
830      * @see #getLookupService(INode)
831      * @see ILookupService
832      */
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;
837     }
838
839     /**
840      * @param node
841      * @param id
842      * @return <code>null</code> if lookup not supported or lookup failed
843      * @see #tryGetLookupService(INode)
844      * @see ILookupService
845      */
846     public static <T> T tryLookup(INode node, String id, Class<T> clazz) {
847         ILookupService lookup = tryGetLookupService(node);
848         if (lookup == null)
849             return null;
850         INode found = lookup.lookupNode(id);
851         return found != null ? clazz.cast(found) : null;
852     }
853
854     /**
855      * @param node
856      * @param id
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
861      */
862     public static String lookupId(INode node) {
863         ILookupService lookup = getLookupService(node);
864         return lookup.lookupId(node);
865     }
866
867     /**
868      * @param node
869      * @param id
870      * @return <code>null</code> if lookup not supported or lookup failed
871      * @see #tryGetLookupService(INode)
872      * @see ILookupService
873      */
874     public static String tryLookupId(INode node) {
875         ILookupService lookup = tryGetLookupService(node);
876         return lookup != null ? lookup.lookupId(node) : null;
877     }
878
879     /**
880      * Map the specified node to the specified ID in the {@link ILookupService}
881      * provided by the root node of the specified node.
882      * 
883      * @param node
884      * @param id
885      * @throws UnsupportedOperationException if {@link ILookupService} is not
886      *         available for the specified node
887      * @see #getLookupService(INode)
888      * @see ILookupService
889      */
890     public static void map(INode node, String id) {
891         getLookupService(node).map(id, node);
892     }
893
894     /**
895      * Remove possible ILookupService mapping for the specified node.
896      * 
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)
903      */
904     public static String unmap(INode node) {
905         return getLookupService(node).unmap(node);
906     }
907
908     /**
909      * Try to remove possible ILookupService mapping for the specified node.
910      * 
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)
916      */
917     public static String tryUnmap(INode node) {
918         ILookupService lookup = tryGetLookupService(node);
919         return lookup != null ? lookup.unmap(node) : null;
920     }
921
922     public static EventDelegator getEventDelegator(INode node) {
923         ParentNode<?> n = node.getRootNode();
924         if (n instanceof G2DSceneGraph) {
925             return ((G2DSceneGraph) n).getEventDelegator();
926         }
927         return null;
928     }
929
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;
935     }
936
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);
943                 return event;
944             }
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);
949
950             MouseEvent e = null;
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);
954             } else {
955                 e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);
956             }
957             return e;
958         }
959         return event;
960     }
961
962     private static final boolean DEBUG_BOUNDS = false;
963
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();
969                 if (!it.hasNext())
970                     return null;
971                 Rectangle2D bounds = null;
972                 while (it.hasNext()) {
973                     IG2DNode next = it.next();
974                     if (filter != null && !filter.apply(next))
975                         continue;
976
977                     Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);
978
979                     if(DEBUG_BOUNDS) {
980                         for(int i=0;i<indent;i++) System.err.print(" ");
981                         LOGGER.warn("+getLocalBoundsImpl " + next  + " => " + bl);
982                     }
983
984                     if(bl != null) {
985                         if(bounds == null) {
986                             bounds = next.localToParent(bl.getFrame());
987                         } else {
988                             bounds.add(next.localToParent(bl));
989                         }
990                     }
991                 }
992
993                 if(DEBUG_BOUNDS) {
994                     for(int i=0;i<indent;i++) System.err.print(" ");
995                     LOGGER.warn("=getLocalBoundsImpl " + node  + " => " + bounds);
996                 }
997
998                 return bounds;
999             } else {
1000                 Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);
1001                 if(result != null) {
1002                     if(DEBUG_BOUNDS) {
1003                         for(int i=0;i<indent;i++) System.err.print(" ");
1004                         LOGGER.warn("=getLocalBoundsImpl " + node  + " => " + result);
1005                     }
1006                     return result;
1007                 }
1008             }
1009         }
1010         return null;
1011     }
1012
1013     public static Rectangle2D getLocalBounds(INode node) {
1014         return getLocalBoundsImpl(node, null, 0);
1015     }
1016
1017     public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {
1018         return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
1019
1020                         @Override
1021                         public Boolean apply(INode node) {
1022                                 return !excluding.contains(node);
1023                         }
1024         }, 0);
1025     }
1026
1027     public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {
1028         return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
1029
1030                         @Override
1031                         public Boolean apply(INode node) {
1032                                 return !excluding.isInstance(node);
1033                         }
1034         }, 0);
1035     }
1036
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");
1043             if (image == null)
1044                 // For generic text nodes
1045                 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");
1046             if (image == null)
1047                 // For I/O table diagram flags (value of org.simantics.diagram.flag.FlagSceneGraph.VISUAL_ROOT)
1048                 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "visual");
1049             if (image == null)
1050                 image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);
1051             if (image != null)
1052                 return getLocalElementBounds(image);
1053             else
1054                 return getLocalBounds(node);
1055         } else {
1056             return getLocalBounds(node);
1057         }
1058     }
1059
1060     public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {
1061         ParentNode<?> parent = null;
1062         while (true) {
1063             parent = node.getParent();
1064             if (parent == null)
1065                 return null;
1066             if (ofClass.isInstance(parent))
1067                 return ofClass.cast(parent);
1068             node = parent;
1069         }
1070     }
1071  
1072     private static class PendingTester implements Runnable {
1073
1074         private boolean             pending     = true;
1075         private final G2DSceneGraph sg;
1076
1077         private final Lock          pendingLock = new ReentrantLock();
1078         private final Condition     pendingSet  = pendingLock.newCondition();
1079
1080         public PendingTester(G2DSceneGraph sg) {
1081             this.sg = sg;
1082         }
1083
1084         @Override
1085         public void run() {
1086             pendingLock.lock();
1087             try {
1088                 pending = sg.isPending();
1089                 pendingSet.signalAll();
1090             } finally {
1091                 pendingLock.unlock();
1092             }
1093         }
1094
1095         public boolean isPending() {
1096             return pending;
1097         }
1098
1099         public void await() {
1100             pendingLock.lock();
1101             try {
1102                 if (pending)
1103                     pendingSet.await(10, TimeUnit.MILLISECONDS);
1104             } catch (InterruptedException e) {
1105                 // Ignore.
1106             } finally {
1107                 pendingLock.unlock();
1108             }
1109         }
1110
1111     }
1112
1113     public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {
1114         // Wait for 30s by default
1115         waitPending(thread, sg, 30000);
1116     }
1117
1118     /**
1119      * Synchronously waits until the the specified scene graph is no longer in
1120      * pending state.
1121      * 
1122      * @param thread the thread to schedule pending checks into
1123      * @param sg the scene graph to wait upon
1124      */
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())
1131                 tester.await();
1132             long duration = System.currentTimeMillis() - start;
1133             if(duration > timeoutMs)
1134                 throw new IllegalStateException("Timeout in resolving pending nodes.");
1135         }
1136     }
1137
1138     public static void increasePending(INode node) {
1139         G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
1140         if(sg != null)
1141                 sg.increasePending(node);
1142     }
1143
1144     public static void decreasePending(INode node) {
1145         G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
1146         if(sg != null)
1147                 sg.decreasePending(node);
1148     }
1149
1150     // TRANSFORMATIONS
1151
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();
1158         }
1159         return result;
1160     }
1161
1162     public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {
1163         return getLocalToGlobalTransform(node, new AffineTransform());
1164     }
1165
1166     public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {
1167         AffineTransform transform = getLocalToGlobalTransform(node);
1168         transform.invert();
1169         return transform;
1170     }
1171
1172     public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {
1173         AffineTransform transform = getLocalToGlobalTransform(node);
1174         try {
1175             transform.invert();
1176             return transform;
1177         } catch (NoninvertibleTransformException e) {
1178             return returnIfNonInvertible;
1179         }
1180     }
1181
1182     public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {
1183         AffineTransform at = getGlobalToLocalTransform(local, null);
1184         if (at == null) {
1185                 pt2.setLocation(pt);
1186             return pt2;
1187         }
1188         return at.transform(pt, pt2);
1189     }
1190
1191     public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {
1192         AffineTransform at = getLocalToGlobalTransform(local);
1193         return at.transform(pt, pt2);
1194     }
1195
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()) 
1202             {
1203                 INode n = pn.getNode(id);
1204                 if ( nn == n ) {
1205                         return id;
1206                 }
1207             }
1208         }
1209         return null;
1210     }
1211
1212     /**
1213      * For asking whether specified parent node really is a parent of the
1214      * specified child node.
1215      * 
1216      * @param parent
1217      *            the parent
1218      * @param child
1219      *            alleged child of parent
1220      * @return <code>true</code> if parent really is a parent of child
1221      */
1222     public static boolean isParentOf(INode parent, INode child) {
1223         while (true) {
1224             if (parent == child)
1225                 return true;
1226             child = child.getParent();
1227             if (child == null)
1228                 return false;
1229         }
1230     }
1231
1232 }