]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java
Make Write-interfaces as @FunctionalInterface for lambdas
[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
36 import org.simantics.scenegraph.IDynamicSelectionPainterNode;
37 import org.simantics.scenegraph.ILookupService;
38 import org.simantics.scenegraph.INode;
39 import org.simantics.scenegraph.INode.PropertySetter;
40 import org.simantics.scenegraph.ISelectionPainterNode;
41 import org.simantics.scenegraph.ParentNode;
42 import org.simantics.scenegraph.g2d.G2DParentNode;
43 import org.simantics.scenegraph.g2d.G2DSceneGraph;
44 import org.simantics.scenegraph.g2d.IG2DNode;
45 import org.simantics.scenegraph.g2d.events.EventDelegator;
46 import org.simantics.scenegraph.g2d.events.NodeEventHandler;
47 import org.simantics.scenegraph.g2d.events.SGMouseEvent;
48 import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;
49 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
50 import org.simantics.scenegraph.g2d.nodes.FlagNode;
51 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
52 import org.simantics.scl.runtime.function.Function1;
53 import org.simantics.scl.runtime.function.FunctionImpl1;
54 import org.simantics.utils.datastructures.Pair;
55 import org.simantics.utils.threads.IThreadWorkQueue;
56
57 /**
58  * Utilities for debugging/printing the contents of a scenegraph.
59  * 
60  * @author Tuukka Lehtonen
61  */
62 public final class NodeUtil {
63
64     /**
65      * @param <T>
66      */
67     public static interface Filter<T> {
68         public boolean accept(T t);
69     }
70
71     public static class PrefixFilter implements Filter<String> {
72         private final String prefix;
73         public PrefixFilter(String prefix) {
74             this.prefix = prefix;
75         }
76         @Override
77         public boolean accept(String t) {
78             return t.startsWith(prefix);
79         }
80     }
81
82     /**
83      * The name of the sibling-node that is used to represent that a node is selected.
84      */
85     public static final String SELECTION_NODE_NAME = "selection";
86
87     public static INode getNearestParentOfType(INode node, Class<?> clazz) {
88         ParentNode<?> parent = null;
89         while (true) {
90             parent = node.getParent();
91             if (parent == null)
92                 return node;
93             node = parent;
94             if (clazz.isInstance(node))
95                 return node;
96         }
97     }
98
99     public static INode getRootNode(INode node) {
100         ParentNode<?> parent = null;
101         while (true) {
102             parent = node.getParent();
103             if (parent == null)
104                 return node;
105             node = parent;
106         }
107     }
108
109     public static G2DSceneGraph getRootNode(IG2DNode node) {
110         INode root = getRootNode((INode) node);
111         return (G2DSceneGraph) root;
112     }
113
114     public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {
115         INode root = getRootNode((INode) node);
116         return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;
117     }
118
119     /**
120      * Method for seeking node from scenegraph by class
121      * 
122      * @param <T>
123      * @param parent
124      * @param clazz
125      * @return
126      */
127     public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {
128         return getNearestChildByClass(parent.getNodes(), clazz);
129     }
130
131     /**
132      * Breadth-first-search implementation to be used by getNearestChildByClass method
133      * 
134      * @param <T>
135      * @param nodes
136      * @param clazz
137      * @return
138      */
139     @SuppressWarnings("unchecked")
140     public static <T> T getNearestChildByClass(Collection<IG2DNode> nodes, Class<T> clazz) {
141         Collection<IG2DNode> list = null;
142         for (IG2DNode n : nodes) {
143             if (clazz.isInstance(n)) {
144                 return (T) n;
145             } else if (n instanceof G2DParentNode) {
146                 if (list == null)
147                     list = new ArrayList<IG2DNode>();
148                 list.addAll(((G2DParentNode)n).getNodes());
149             }
150         }
151         if (list == null || list.isEmpty()) return null;
152         return getNearestChildByClass(list, clazz);
153     }
154
155     /**
156      * Tries to look for a child node from the specified node with the specified
157      * ID. Returns <code>null</code> if the specified node is a not a
158      * {@link ParentNode}.
159      * 
160      * @param node
161      * @param id
162      * @return
163      */
164     public static INode getChildById(INode node, String id) {
165         if (node instanceof ParentNode<?>) {
166             return ((ParentNode<?>) node).getNode(id);
167         }
168         return null;
169     }
170
171     /**
172      * Looks for the first child node of the specified node based on 2D Z-order.
173      * The specified node must be a {@link G2DParentNode} for the method to
174      * succeed.
175      * 
176      * @param node the node to get for the first child from
177      * @return <code>null</code> if the specified node is not a
178      *         {@link G2DParentNode} or has no children.
179      */
180     public static INode getFirstChild(INode node) {
181         if (node instanceof G2DParentNode) {
182             G2DParentNode pn = (G2DParentNode) node;
183             IG2DNode[] sorted = pn.getSortedNodes();
184             if (sorted.length > 0)
185                 return sorted[0];
186         }
187         return null;
188     }
189
190     /**
191      * Returns a single child node of the specified node or <code>null</code> if
192      * there are more than one or zero children.
193      * 
194      * @param node the node to get a possible single child from
195      * @return single child node or <code>null</code> if specified node has more
196      *         than one or zero children
197      */
198     public static INode getPossibleChild(INode node) {
199         if (node instanceof ParentNode<?>) {
200             ParentNode<?> pn = (ParentNode<?>) node;
201             if (pn.getNodeCount() == 1)
202                 return pn.getNodes().iterator().next();
203         }
204         return null;
205     }
206
207     /**
208      * Counts the depth of the specified node in its scene graph node tree.
209      * Depth 1 equals root level.
210      * 
211      * @param node the node for which to count a depth
212      * @return the depth of the node
213      */
214     public static int getDepth(INode node) {
215         int result = 1;
216         ParentNode<?> parent = null;
217         while (true) {
218             parent = node.getParent();
219             if (parent == null)
220                 return result;
221             node = parent;
222             ++result;
223         }
224     }
225
226     private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {
227         for (int i = 0; i < indentLevel; ++i)
228             stream.print("\t");
229         stream.print(node.getSimpleClassName());
230         if (id != null) {               
231                 String lookupId = tryLookupId(node);
232                 if (lookupId != null) {
233                         stream.print(" {" + id + ", lookupId = "+lookupId+"}");
234                 } else {
235                         stream.print(" {" + id + "}");
236                 }
237         }
238         stream.println(node);
239         if (node instanceof G2DParentNode) {
240             G2DParentNode parentNode = (G2DParentNode) node;
241             for (String cid : parentNode.getSortedNodesById()) {
242                 Object child = parentNode.getNode(cid);
243                 if (child instanceof INode)
244                     printSceneGraph(stream, indentLevel + 1, (INode) child, cid);
245             }
246         } else if (node instanceof ParentNode<?>) {
247             ParentNode<? extends INode> parentNode = (ParentNode<?>) node;
248             for (String cid : parentNode.getNodeIds()) {
249                 INode child = parentNode.getNode(cid);
250                 printSceneGraph(stream, indentLevel + 1, (INode) child);
251             }
252         }
253     }
254
255     public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {
256         String id = null;
257         ParentNode<?> parent = node.getParent();
258         if (parent != null) {
259             Collection<String> ids = parent.getNodeIds();
260             for (String i : ids) {
261                 INode n = parent.getNode(i);
262                 if (n == node) {
263                     id = i;
264                     break;
265                 }
266             }
267         }
268         printSceneGraph(stream, indentLevel, node, id);
269     }
270
271     public static final void printSceneGraph(int indentLevel, INode node) {
272         printSceneGraph(System.out, indentLevel, node);
273     }
274
275     public static final void printSceneGraph(INode node) {
276         printSceneGraph(System.out, 0, node);
277     }
278
279     public static interface NodeProcedure<T> {
280         T execute(INode node, String id);
281     }
282
283     /**
284      * @param node the node to iterate possible children for
285      * @param procedure invoked for each child node, if returns a
286      *        <code>non-null</code> value, the return value is collected into the result
287      *        list
288      * @return the list of collected children
289      */
290     public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {
291         return forChildren(node, procedure, new ArrayList<T>());
292     }
293
294     /**
295      * @param node the node to iterate possible children for
296      * @param procedure invoked for each child node, if returns a
297      *        <code>non-null</code> value, the node is collected into the result
298      *        list
299      * @param result the result list into which selected children are collected
300      *        or <code>null</code> to not collect
301      * @return the list of collected children or null if provided result list
302      *         was <code>null</code>
303      */
304     public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure, List<T> result) {
305         if (node instanceof ParentNode<?>) {
306             ParentNode<?> pn = (ParentNode<?>) node;
307             if (node instanceof G2DParentNode) {
308                 G2DParentNode g2dpn = (G2DParentNode) node;
309                 for (String id : g2dpn.getSortedNodesById()) {
310                     INode n = pn.getNode(id);
311                     T t = procedure.execute(n, id);
312                     if (t != null && result != null)
313                         result.add(t);
314                 }
315             } else {
316                 for (String id : pn.getNodeIds()) {
317                     INode n = pn.getNode(id);
318                     T t = procedure.execute(n, id);
319                     if (t != null && result != null)
320                         result.add(t);
321                 }
322             }
323         }
324         return result;
325     }
326
327     public static final int countTreeNodes(INode node) {
328         int result = 1;
329         if (node instanceof ParentNode<?>) {
330             ParentNode<? extends INode> pn = (ParentNode<?>) node;
331             Collection<? extends INode> ns = pn.getNodes();
332             for (INode n : ns) {
333                 result += countTreeNodes(n);
334             }
335         }
336         return result;
337     }
338
339     public static final void printTreeNodes(INode node, StringBuilder builder) {
340         printTreeNodes(node, 0, builder);
341     }
342
343     public static final void printTreeNodes(INode node, int indent, StringBuilder builder) {
344         for (int i = 0; i < indent; i++)
345             builder.append(" ");
346         builder.append(node.toString() + "\n");
347         if (node instanceof ParentNode<?>) {
348             ParentNode<? extends INode> pn = (ParentNode<?>) node;
349             Collection<? extends INode> ns = pn.getNodes();
350             for (INode n : ns) {
351                 printTreeNodes(n, indent+2, builder);
352             }
353         }
354     }
355
356     public static final <T extends INode> Set<T> collectNodes(INode node, Class<T> clazz) {
357         Set<T> result = new HashSet<T>();
358         collectNodes(node, clazz, result);
359         return result;
360     }
361
362     @SuppressWarnings("unchecked")
363     public static final <T extends INode> void collectNodes(INode node, Class<T> clazz, Set<T> result) {
364         if (clazz.isInstance(node))
365             result.add((T) node);
366         if (node instanceof ParentNode<?>) {
367             ParentNode<? extends INode> pn = (ParentNode<?>) node;
368             Collection<? extends INode> ns = pn.getNodes();
369             for (INode n : ns) {
370                 collectNodes(n, clazz, result);
371             }
372         }
373     }
374     
375     public static <T extends INode> T getSingleNode(INode node, Class<T> clazz) {
376         Set<T> all = collectNodes(node, clazz);
377         if(all.size() != 1) throw new RuntimeException("Expected exactly 1 instance of class " + clazz.getCanonicalName() + ", got " + all.size());
378         return (T)all.iterator().next();
379     }
380     
381     public static final boolean hasChildren(INode node) {
382         if (node instanceof ParentNode<?>) {
383             ParentNode<?> pn = (ParentNode<?>) node;
384             return !pn.getNodes().isEmpty();
385         }
386         return false;
387     }
388
389     /**
390      * Look for a single scene graph node by its ID in a path under a specified
391      * node.
392      * 
393      * @param parent the parent node under which to start looking
394      * @param idPath the node ID path
395      * @return <code>null</code> if node was not found
396      * @throws ClassCastException if the found node was not of the expected type
397      *         T extending INode
398      */
399     @SuppressWarnings("unchecked")
400     public static <T extends INode> T findNodeById(INode parent, String... idPath) {
401         INode n = parent;
402         for (int i = 0;; ++i) {
403             if (i >= idPath.length)
404                 return (T) n;
405             if (n instanceof ParentNode<?>) {
406                 n = ((ParentNode<?>) n).getNode(idPath[i]);
407             } else {
408                 return null;
409             }
410         }
411     }
412
413     /**
414      * Tries to find out whether a node is selected or not.
415      * 
416      * DISCLAIMER: this is a hack for the current
417      * org.simantics.g2d.diagram.participant.ElementPainter implementation that
418      * will stop working if the implementation changes.
419      * 
420      * @param node
421      * @param ascendLimit the max. amount of steps towards parent nodes to take
422      *        while looking for selection information
423      * @return <code>true</code> if evidence of selection is found
424      */
425     public static boolean isSelected(INode node, int ascendLimit) {
426         int steps = 0;
427         ParentNode<?> pn = null;
428         if (node instanceof ParentNode<?>) {
429             pn = (ParentNode<?>) node;
430         } else {
431             pn = node.getParent();
432             ++steps;
433         }
434         for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {
435             INode child = pn.getNode(SELECTION_NODE_NAME);
436             if (child != null)
437                 return true;
438         }
439         return false;
440     }
441
442     public static Container findRootPane(INode node) {
443         G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);
444         if (parent == null)
445             return null;
446         return ((G2DSceneGraph) parent).getRootPane();
447     }
448
449     private static boolean isSelectionPainter(INode node) {
450         if (node instanceof ISelectionPainterNode) {
451             if (node instanceof IDynamicSelectionPainterNode)
452                 return ((IDynamicSelectionPainterNode)node).showsSelection();
453             return true;
454         }
455         return false;
456     }
457
458     /**
459      * @param elementNode
460      * @return
461      */
462     public static boolean needSelectionPaint(INode elementNode) {
463         // FIXME: there should be a cleaner way to implement this.
464
465         if (isSelectionPainter(elementNode)) {
466 //          System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
467             return false;
468         }
469
470         if (elementNode instanceof ConnectionNode) {
471 //          System.out.println("connectionNode");
472             for (IG2DNode child : ((ConnectionNode) elementNode).getNodes()) {
473 //              System.out.println(" child " + child);
474                 if (isSelectionPainter(child)) {
475 //                  System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
476                     return false;
477                 }
478                 if (child instanceof SingleElementNode) {
479                     for(IG2DNode child2 : ((SingleElementNode) child).getNodes()) {
480 //                      System.out.println(" child2 " + child2);
481                         if (isSelectionPainter(child2)) {
482 //                          System.out.println("skipped selection painting for edge ISelectionPainterNode");
483                             return false;
484                         }
485                     }
486                 }
487             }
488         } else if (elementNode instanceof SingleElementNode) {
489             for (INode child : ((SingleElementNode) elementNode).getNodes()) {
490                 if (isSelectionPainter(child))
491                     return false;
492             }
493         }
494
495         return true;
496     }
497
498     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
499
500     public static Method getSetterForProperty(String property, INode node) {
501         assert(node != null);
502         Class<?> cl = node.getClass();
503
504         while(true) {
505             boolean isEnhanced = false;
506             for(Method method : cl.getMethods()) {
507                 if(method.isAnnotationPresent(PropertySetter.class)) {
508                     PropertySetter ann = method.getAnnotation(PropertySetter.class);
509                     if(ann.value().equals(property)) {
510                         return method;
511                     }
512                 } else if(method.getName().equals(SET_THREAD_CALLBACKS_NAME) && method.getGenericParameterTypes().length == 1) {
513                     // The class seems to be enhanced by cglib, hence annotations are not present. Check superclass for annotations..
514                     isEnhanced = true;
515                     cl = cl.getSuperclass();
516                     break; // We are not going to find any annotations, stop loop and try with the parent class
517                 }
518             }
519             if(!isEnhanced || cl == null) break;
520         }
521         return null;
522     }
523
524     /**
525      * TODO: log exceptions for debug purposes
526      * 
527      * @param property name of the property
528      * @param value    can be null..
529      * @param node
530      * @return
531      */
532     public static boolean setPropertyIfSupported(String property, Object value, INode node) {
533         Method setter = getSetterForProperty(property, node);
534         if(setter != null) {
535             Class<?> pc[] = setter.getParameterTypes();
536             if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {
537                 try {
538                     setter.invoke(node, value);
539                     return true;
540                 } catch (IllegalArgumentException e) {
541                     // TODO Auto-generated catch block
542                     e.printStackTrace();
543                 } catch (IllegalAccessException e) {
544                     // TODO Auto-generated catch block
545                     e.printStackTrace();
546                 } catch (InvocationTargetException e) {
547                     // TODO Auto-generated catch block
548                     e.getCause().printStackTrace();
549                 }
550             } else {
551
552                 if(pc.length > 0) {
553                     System.err.println("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");
554                 }
555
556             }
557         }
558
559         return false;
560     }
561
562     public static INode findChildById(ParentNode<?> parent, String key) {
563         INode result = parent.getNode(key);
564         if (result != null)
565             return result;
566
567         for (String entry : parent.getNodeIds()) {
568             if (entry.startsWith(key))
569                 return parent.getNode(key);
570         }
571
572         for (INode node : parent.getNodes()) {
573             if (node instanceof ParentNode) {
574                 result = findChildById((ParentNode<?>) node, key);
575                 if (result != null)
576                     return result;
577             }
578         }
579
580         return null;
581     }
582
583     
584     private static int getSegmentEnd(String suffix) {
585         int pos;
586         for(pos=1;pos<suffix.length();++pos) {
587             char c = suffix.charAt(pos);
588             if(c == '/' || c == '#')
589                 break;
590         }
591         return pos;
592     }
593     
594     public static String decodeString(String string) {
595         return string;
596     }
597     
598     public static INode browsePossible(INode node, String suffix) {
599         if(suffix.isEmpty()) 
600             return node;        
601         switch(suffix.charAt(0)) {
602         case '.': {
603                 INode parent = node.getParent();
604             if(parent == null)
605                 return null;
606             return browsePossible(parent, suffix.substring(1));
607         }
608         case '#': {
609             /*int segmentEnd = getSegmentEnd(suffix);
610             Variable property = getPossibleProperty(graph, 
611                     decodeString(suffix.substring(1, segmentEnd)));
612             if(property == null) 
613                 return null;
614             return property.browsePossible(graph, suffix.substring(segmentEnd));*/
615                 return node;
616         }
617         case '/': {
618             int segmentEnd = getSegmentEnd(suffix);
619             INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
620             if(child == null) 
621                 return null;
622             return browsePossible(child, suffix.substring(segmentEnd));
623         }
624         default:
625             return null;
626         }
627     }    
628     
629     public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {
630         if(suffix.isEmpty()) 
631             throw new RuntimeException("Did not find a reference.");        
632         switch(suffix.charAt(0)) {
633         case '.': {
634                 INode parent = node.getParent();
635             if(parent == null)
636                 return null;
637             return browsePossibleReference(parent, suffix.substring(1));
638         }
639         case '#': {
640                 return Pair.make(node, suffix.substring(1));
641         }
642         case '/': {
643             int segmentEnd = getSegmentEnd(suffix);
644             INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
645             if(child == null) 
646                 return null;
647             return browsePossibleReference(child, suffix.substring(segmentEnd));
648         }
649         default:
650             return null;
651         }
652     }    
653
654     public static INode findChildByPrefix(G2DParentNode parent, String prefix) {
655         INode result = parent.getNode(prefix);
656         if (result != null)
657             return result;
658
659         for (String entry : parent.getNodeIds()) {
660             if (entry.startsWith(prefix))
661                 return parent.getNode(entry);
662         }
663
664         for (IG2DNode node : parent.getNodes()) {
665             if (node instanceof G2DParentNode) {
666                 result = findChildByPrefix((G2DParentNode) node, prefix);
667                 if (result != null)
668                     return result;
669             }
670         }
671
672         return null;
673     }
674
675     /**
676      * @param parent
677      * @param prefix
678      * @return
679      */
680     public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {
681         return filterDirectChildIds(parent, new PrefixFilter(prefix));
682     }
683
684     /**
685      * @param parent
686      * @param prefix
687      * @return
688      */
689     public static Collection<String> filterDirectChildIds(ParentNode<?> parent, Filter<String> childFilter) {
690         Collection<String> childIds = parent.getNodeIds();
691         ArrayList<String> result = new ArrayList<String>(childIds.size());
692
693         for (String id : childIds)
694             if (childFilter.accept(id))
695                 result.add(id);
696
697         return result;
698     }
699
700     /**
701      * @param parent
702      * @param prefix
703      * @return
704      */
705     public static Collection<INode> filterDirectChildren(ParentNode<?> parent, Filter<String> childFilter) {
706         Collection<String> childIds = parent.getNodeIds();
707         ArrayList<INode> result = new ArrayList<INode>(childIds.size());
708
709         for (String id : childIds)
710             if (childFilter.accept(id))
711                 result.add( parent.getNode(id) );
712
713         return result;
714     }
715
716     /**
717      * @param node
718      * @return the lookup service for the specified node
719      * @throws UnsupportedOperationException if ILookupService is not available
720      */
721     public static ILookupService getLookupService(INode node) {
722         ParentNode<?> root = node.getRootNode();
723         if (!(root instanceof ILookupService))
724             throw new UnsupportedOperationException("ILookupService not supported by root node " + root + " attained from " + node);
725         return (ILookupService) root;
726     }
727
728     /**
729      * @param node
730      * @return <code>null</code> if lookup service is not available
731      */
732     public static ILookupService tryGetLookupService(INode node) {
733         ParentNode<?> root = node.getRootNode();
734         return root instanceof ILookupService ? (ILookupService) root : null;
735     }
736
737     /**
738      * @param node
739      * @param id
740      * @return <code>null</code> if lookup failed, i.e. mapping does not exist
741      * @throws UnsupportedOperationException if lookup is not supported
742      * @see #getLookupService(INode)
743      * @see ILookupService
744      */
745     public static INode lookup(INode node, String id) {
746         ILookupService lookup = getLookupService(node);
747         return lookup.lookupNode(id);
748     }
749
750     /**
751      * @param node
752      * @param id
753      * @return <code>null</code> if lookup not supported or lookup failed
754      * @see #tryGetLookupService(INode)
755      * @see ILookupService
756      */
757     public static INode tryLookup(INode node, String id) {
758         ILookupService lookup = tryGetLookupService(node);
759         return lookup != null ? lookup.lookupNode(id) : null;
760     }
761
762     /**
763      * @param node
764      * @param id
765      * @param clazz
766      * @return <code>null</code> if lookup failed, i.e. mapping does not exist
767      * @throws UnsupportedOperationException if lookup is not supported
768      * @throws ClassCastException if the found node cannot be cast to the
769      *         specified class
770      * @see #getLookupService(INode)
771      * @see ILookupService
772      */
773     public static <T> T lookup(INode node, String id, Class<T> clazz) {
774         ILookupService lookup = getLookupService(node);
775         INode found = lookup.lookupNode(id);
776         return found != null ? clazz.cast(found) : null;
777     }
778
779     /**
780      * @param node
781      * @param id
782      * @return <code>null</code> if lookup not supported or lookup failed
783      * @see #tryGetLookupService(INode)
784      * @see ILookupService
785      */
786     public static <T> T tryLookup(INode node, String id, Class<T> clazz) {
787         ILookupService lookup = tryGetLookupService(node);
788         if (lookup == null)
789             return null;
790         INode found = lookup.lookupNode(id);
791         return found != null ? clazz.cast(found) : null;
792     }
793
794     /**
795      * @param node
796      * @param id
797      * @return <code>null</code> if lookup failed, i.e. mapping does not exist
798      * @throws UnsupportedOperationException if lookup is not supported
799      * @see #getLookupService(INode)
800      * @see ILookupService
801      */
802     public static String lookupId(INode node) {
803         ILookupService lookup = getLookupService(node);
804         return lookup.lookupId(node);
805     }
806
807     /**
808      * @param node
809      * @param id
810      * @return <code>null</code> if lookup not supported or lookup failed
811      * @see #tryGetLookupService(INode)
812      * @see ILookupService
813      */
814     public static String tryLookupId(INode node) {
815         ILookupService lookup = tryGetLookupService(node);
816         return lookup != null ? lookup.lookupId(node) : null;
817     }
818
819     /**
820      * Map the specified node to the specified ID in the {@link ILookupService}
821      * provided by the root node of the specified node.
822      * 
823      * @param node
824      * @param id
825      * @throws UnsupportedOperationException if {@link ILookupService} is not
826      *         available for the specified node
827      * @see #getLookupService(INode)
828      * @see ILookupService
829      */
830     public static void map(INode node, String id) {
831         getLookupService(node).map(id, node);
832     }
833
834     /**
835      * Remove possible ILookupService mapping for the specified node.
836      * 
837      * @param node the node to try to remove mappings for
838      * @return mapped ID or <code>null</code> if no mapping existed
839      * @throws UnsupportedOperationException if {@link ILookupService} is not
840      *         available for the specified node
841      * @see ILookupService
842      * @see #getLookupService(INode)
843      */
844     public static String unmap(INode node) {
845         return getLookupService(node).unmap(node);
846     }
847
848     /**
849      * Try to remove possible ILookupService mapping for the specified node.
850      * 
851      * @param node the node to try to remove mappings for
852      * @return mapped ID or <code>null</code> if {@link ILookupService} is not
853      *         supported or no mapping existed
854      * @see ILookupService
855      * @see #tryGetLookupService(INode)
856      */
857     public static String tryUnmap(INode node) {
858         ILookupService lookup = tryGetLookupService(node);
859         return lookup != null ? lookup.unmap(node) : null;
860     }
861
862     public static EventDelegator getEventDelegator(INode node) {
863         ParentNode<?> n = node.getRootNode();
864         if (n instanceof G2DSceneGraph) {
865             return ((G2DSceneGraph) n).getEventDelegator();
866         }
867         return null;
868     }
869
870     public static NodeEventHandler getNodeEventHandler(INode node) {
871         ParentNode<?> n = node.getRootNode();
872         return (n instanceof G2DSceneGraph) ? ((G2DSceneGraph) n).getEventHandler() : null;
873 //        INodeEventHandlerProvider provider = findNearestParentNode(node, INodeEventHandlerProvider.class);
874 //        return provider != null ? provider.getEventHandler() : null;
875     }
876
877     public static AWTEvent transformEvent(AWTEvent event, IG2DNode node) {
878         if (event instanceof MouseEvent) {
879             // Find node transform..
880             AffineTransform transform = getGlobalToLocalTransform(node, null);
881             if (transform == null) {
882                 System.err.println("WARNING: Non-invertible transform for node: " + node);
883                 return event;
884             }
885             MouseEvent me = (MouseEvent)event;
886             // Use double coordinates if available
887             Point2D p = new Point2D.Double((double)me.getX(), (double)me.getY());
888             transform.transform(p, p);
889
890             MouseEvent e = null;
891             // Giving event.getSource() as a parameter for the new events will cause major delay for the event instantiation, hence dummy component is used
892             if (event instanceof MouseWheelEvent) {
893                 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);
894             } else {
895                 e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);
896             }
897             return e;
898         }
899         return event;
900     }
901
902     private static final boolean DEBUG_BOUNDS = false;
903
904     private static Rectangle2D getLocalBoundsImpl(INode node, Function1<INode, Boolean> filter, int indent) {
905         if (node instanceof IG2DNode) {
906             if (node instanceof G2DParentNode) {
907                 G2DParentNode pNode = (G2DParentNode)node;
908                 Iterator<IG2DNode> it = pNode.getNodes().iterator();
909                 if (!it.hasNext())
910                     return null;
911                 Rectangle2D bounds = null;
912                 while (it.hasNext()) {
913                     IG2DNode next = it.next();
914                     if (filter != null && !filter.apply(next))
915                         continue;
916
917                     Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);
918
919                     if(DEBUG_BOUNDS) {
920                         for(int i=0;i<indent;i++) System.err.print(" ");
921                         System.err.println("+getLocalBoundsImpl " + next  + " => " + bl);
922                     }
923
924                     if(bl != null) {
925                         if(bounds == null) {
926                             bounds = next.localToParent(bl.getFrame());
927                         } else {
928                             bounds.add(next.localToParent(bl));
929                         }
930                     }
931                 }
932
933                 if(DEBUG_BOUNDS) {
934                     for(int i=0;i<indent;i++) System.err.print(" ");
935                     System.err.println("=getLocalBoundsImpl " + node  + " => " + bounds);
936                 }
937
938                 return bounds;
939             } else {
940                 Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);
941                 if(result != null) {
942                     if(DEBUG_BOUNDS) {
943                         for(int i=0;i<indent;i++) System.err.print(" ");
944                         System.err.println("=getLocalBoundsImpl " + node  + " => " + result);
945                     }
946                     return result;
947                 }
948             }
949         }
950         return null;
951     }
952
953     public static Rectangle2D getLocalBounds(INode node) {
954         return getLocalBoundsImpl(node, null, 0);
955     }
956
957     public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {
958         return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
959
960                         @Override
961                         public Boolean apply(INode node) {
962                                 return !excluding.contains(node);
963                         }
964         }, 0);
965     }
966
967     public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {
968         return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
969
970                         @Override
971                         public Boolean apply(INode node) {
972                                 return !excluding.isInstance(node);
973                         }
974         }, 0);
975     }
976
977     public static Rectangle2D getLocalElementBounds(INode node) {
978         if(node instanceof ConnectionNode) {
979             return getLocalBounds(node);
980         } else if(node instanceof SingleElementNode) {
981             // For normal symbols
982             INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image");
983             if (image == null)
984                 // For generic text nodes
985                 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");
986             if (image == null)
987                 // For I/O table diagram flags (value of org.simantics.diagram.flag.FlagSceneGraph.VISUAL_ROOT)
988                 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "visual");
989             if (image == null)
990                 image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);
991             if (image != null)
992                 return getLocalElementBounds(image);
993             else
994                 return getLocalBounds(node);
995         } else {
996             return getLocalBounds(node);
997         }
998     }
999
1000     public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {
1001         ParentNode<?> parent = null;
1002         while (true) {
1003             parent = node.getParent();
1004             if (parent == null)
1005                 return null;
1006             if (ofClass.isInstance(parent))
1007                 return ofClass.cast(parent);
1008             node = parent;
1009         }
1010     }
1011  
1012     private static class PendingTester implements Runnable {
1013
1014         private boolean             pending     = true;
1015         private final G2DSceneGraph sg;
1016
1017         private final Lock          pendingLock = new ReentrantLock();
1018         private final Condition     pendingSet  = pendingLock.newCondition();
1019
1020         public PendingTester(G2DSceneGraph sg) {
1021             this.sg = sg;
1022         }
1023
1024         @Override
1025         public void run() {
1026             pendingLock.lock();
1027             try {
1028                 pending = sg.isPending();
1029                 pendingSet.signalAll();
1030             } finally {
1031                 pendingLock.unlock();
1032             }
1033         }
1034
1035         public boolean isPending() {
1036             return pending;
1037         }
1038
1039         public void await() {
1040             pendingLock.lock();
1041             try {
1042                 if (pending)
1043                     pendingSet.await(10, TimeUnit.MILLISECONDS);
1044             } catch (InterruptedException e) {
1045                 // Ignore.
1046             } finally {
1047                 pendingLock.unlock();
1048             }
1049         }
1050
1051     }
1052
1053     public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {
1054         // Wait for 30s by default
1055         waitPending(thread, sg, 30000);
1056     }
1057
1058     /**
1059      * Synchronously waits until the the specified scene graph is no longer in
1060      * pending state.
1061      * 
1062      * @param thread the thread to schedule pending checks into
1063      * @param sg the scene graph to wait upon
1064      */
1065     public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg, int timeoutMs) {
1066         PendingTester tester = new PendingTester(sg);
1067         long start = System.currentTimeMillis();
1068         while (tester.isPending()) {
1069             thread.asyncExec(tester);
1070             if (tester.isPending())
1071                 tester.await();
1072             long duration = System.currentTimeMillis() - start;
1073             if(duration > timeoutMs)
1074                 throw new IllegalStateException("Timeout in resolving pending nodes.");
1075         }
1076     }
1077
1078     public static void increasePending(INode node) {
1079         G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
1080         if(sg != null)
1081                 sg.increasePending(node);
1082     }
1083
1084     public static void decreasePending(INode node) {
1085         G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
1086         if(sg != null)
1087                 sg.decreasePending(node);
1088     }
1089
1090     // TRANSFORMATIONS
1091
1092     public static AffineTransform getLocalToGlobalTransform(IG2DNode node, AffineTransform result) {
1093         result.setToIdentity();
1094         ParentNode<?> parent = node.getParent();
1095         while (parent != null) {
1096             result.preConcatenate(((IG2DNode) parent).getTransform());
1097             parent = parent.getParent();
1098         }
1099         return result;
1100     }
1101
1102     public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {
1103         return getLocalToGlobalTransform(node, new AffineTransform());
1104     }
1105
1106     public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {
1107         AffineTransform transform = getLocalToGlobalTransform(node);
1108         transform.invert();
1109         return transform;
1110     }
1111
1112     public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {
1113         AffineTransform transform = getLocalToGlobalTransform(node);
1114         try {
1115             transform.invert();
1116             return transform;
1117         } catch (NoninvertibleTransformException e) {
1118             return returnIfNonInvertible;
1119         }
1120     }
1121
1122     public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {
1123         AffineTransform at = getGlobalToLocalTransform(local, null);
1124         if (at == null) {
1125                 pt2.setLocation(pt);
1126             return pt2;
1127         }
1128         return at.transform(pt, pt2);
1129     }
1130
1131     public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {
1132         AffineTransform at = getLocalToGlobalTransform(local);
1133         return at.transform(pt, pt2);
1134     }
1135
1136     public static String getNodeName(INode nn) {
1137         INode node = nn.getParent();
1138         ParentNode<?> pn = (ParentNode<?>) node;
1139         if (node instanceof G2DParentNode) {
1140             G2DParentNode g2dpn = (G2DParentNode) node;
1141             for (String id : g2dpn.getSortedNodesById()) 
1142             {
1143                 INode n = pn.getNode(id);
1144                 if ( nn == n ) {
1145                         return id;
1146                 }
1147             }
1148         }
1149         return null;
1150     }
1151
1152     /**
1153      * For asking whether specified parent node really is a parent of the
1154      * specified child node.
1155      * 
1156      * @param parent
1157      *            the parent
1158      * @param child
1159      *            alleged child of parent
1160      * @return <code>true</code> if parent really is a parent of child
1161      */
1162     public static boolean isParentOf(INode parent, INode child) {
1163         while (true) {
1164             if (parent == child)
1165                 return true;
1166             child = child.getParent();
1167             if (child == null)
1168                 return false;
1169         }
1170     }
1171
1172 }