1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.utils;
\r
14 import java.awt.AWTEvent;
\r
15 import java.awt.Container;
\r
16 import java.awt.event.MouseEvent;
\r
17 import java.awt.event.MouseWheelEvent;
\r
18 import java.awt.geom.AffineTransform;
\r
19 import java.awt.geom.NoninvertibleTransformException;
\r
20 import java.awt.geom.Point2D;
\r
21 import java.awt.geom.Rectangle2D;
\r
22 import java.io.PrintStream;
\r
23 import java.lang.reflect.InvocationTargetException;
\r
24 import java.lang.reflect.Method;
\r
25 import java.util.ArrayList;
\r
26 import java.util.Collection;
\r
27 import java.util.HashSet;
\r
28 import java.util.Iterator;
\r
29 import java.util.List;
\r
30 import java.util.Set;
\r
31 import java.util.concurrent.TimeUnit;
\r
32 import java.util.concurrent.locks.Condition;
\r
33 import java.util.concurrent.locks.Lock;
\r
34 import java.util.concurrent.locks.ReentrantLock;
\r
36 import org.simantics.scenegraph.IDynamicSelectionPainterNode;
\r
37 import org.simantics.scenegraph.ILookupService;
\r
38 import org.simantics.scenegraph.INode;
\r
39 import org.simantics.scenegraph.INode.PropertySetter;
\r
40 import org.simantics.scenegraph.ISelectionPainterNode;
\r
41 import org.simantics.scenegraph.ParentNode;
\r
42 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
43 import org.simantics.scenegraph.g2d.G2DSceneGraph;
\r
44 import org.simantics.scenegraph.g2d.IG2DNode;
\r
45 import org.simantics.scenegraph.g2d.events.EventDelegator;
\r
46 import org.simantics.scenegraph.g2d.events.NodeEventHandler;
\r
47 import org.simantics.scenegraph.g2d.events.SGMouseEvent;
\r
48 import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;
\r
49 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
\r
50 import org.simantics.scenegraph.g2d.nodes.FlagNode;
\r
51 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
52 import org.simantics.scl.runtime.function.Function1;
\r
53 import org.simantics.scl.runtime.function.FunctionImpl1;
\r
54 import org.simantics.utils.datastructures.Pair;
\r
55 import org.simantics.utils.threads.IThreadWorkQueue;
\r
58 * Utilities for debugging/printing the contents of a scenegraph.
\r
60 * @author Tuukka Lehtonen
\r
62 public final class NodeUtil {
\r
67 public static interface Filter<T> {
\r
68 public boolean accept(T t);
\r
71 public static class PrefixFilter implements Filter<String> {
\r
72 private final String prefix;
\r
73 public PrefixFilter(String prefix) {
\r
74 this.prefix = prefix;
\r
77 public boolean accept(String t) {
\r
78 return t.startsWith(prefix);
\r
83 * The name of the sibling-node that is used to represent that a node is selected.
\r
85 public static final String SELECTION_NODE_NAME = "selection";
\r
87 public static INode getNearestParentOfType(INode node, Class<?> clazz) {
\r
88 ParentNode<?> parent = null;
\r
90 parent = node.getParent();
\r
94 if (clazz.isInstance(node))
\r
99 public static INode getRootNode(INode node) {
\r
100 ParentNode<?> parent = null;
\r
102 parent = node.getParent();
\r
103 if (parent == null)
\r
109 public static G2DSceneGraph getRootNode(IG2DNode node) {
\r
110 INode root = getRootNode((INode) node);
\r
111 return (G2DSceneGraph) root;
\r
114 public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {
\r
115 INode root = getRootNode((INode) node);
\r
116 return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;
\r
120 * Method for seeking node from scenegraph by class
\r
127 public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {
\r
128 return getNearestChildByClass(parent.getNodes(), clazz);
\r
132 * Breadth-first-search implementation to be used by getNearestChildByClass method
\r
139 @SuppressWarnings("unchecked")
\r
140 public static <T> T getNearestChildByClass(Collection<IG2DNode> nodes, Class<T> clazz) {
\r
141 Collection<IG2DNode> list = null;
\r
142 for (IG2DNode n : nodes) {
\r
143 if (clazz.isInstance(n)) {
\r
145 } else if (n instanceof G2DParentNode) {
\r
147 list = new ArrayList<IG2DNode>();
\r
148 list.addAll(((G2DParentNode)n).getNodes());
\r
151 if (list == null || list.isEmpty()) return null;
\r
152 return getNearestChildByClass(list, clazz);
\r
156 * Tries to look for a child node from the specified node with the specified
\r
157 * ID. Returns <code>null</code> if the specified node is a not a
\r
158 * {@link ParentNode}.
\r
164 public static INode getChildById(INode node, String id) {
\r
165 if (node instanceof ParentNode<?>) {
\r
166 return ((ParentNode<?>) node).getNode(id);
\r
172 * Looks for the first child node of the specified node based on 2D Z-order.
\r
173 * The specified node must be a {@link G2DParentNode} for the method to
\r
176 * @param node the node to get for the first child from
\r
177 * @return <code>null</code> if the specified node is not a
\r
178 * {@link G2DParentNode} or has no children.
\r
180 public static INode getFirstChild(INode node) {
\r
181 if (node instanceof G2DParentNode) {
\r
182 G2DParentNode pn = (G2DParentNode) node;
\r
183 IG2DNode[] sorted = pn.getSortedNodes();
\r
184 if (sorted.length > 0)
\r
191 * Returns a single child node of the specified node or <code>null</code> if
\r
192 * there are more than one or zero children.
\r
194 * @param node the node to get a possible single child from
\r
195 * @return single child node or <code>null</code> if specified node has more
\r
196 * than one or zero children
\r
198 public static INode getPossibleChild(INode node) {
\r
199 if (node instanceof ParentNode<?>) {
\r
200 ParentNode<?> pn = (ParentNode<?>) node;
\r
201 if (pn.getNodeCount() == 1)
\r
202 return pn.getNodes().iterator().next();
\r
208 * Counts the depth of the specified node in its scene graph node tree.
\r
209 * Depth 1 equals root level.
\r
211 * @param node the node for which to count a depth
\r
212 * @return the depth of the node
\r
214 public static int getDepth(INode node) {
\r
216 ParentNode<?> parent = null;
\r
218 parent = node.getParent();
\r
219 if (parent == null)
\r
226 private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {
\r
227 for (int i = 0; i < indentLevel; ++i)
\r
228 stream.print("\t");
\r
229 stream.print(node.getSimpleClassName());
\r
231 String lookupId = tryLookupId(node);
\r
232 if (lookupId != null) {
\r
233 stream.print(" {" + id + ", lookupId = "+lookupId+"}");
\r
235 stream.print(" {" + id + "}");
\r
238 stream.println(node);
\r
239 if (node instanceof G2DParentNode) {
\r
240 G2DParentNode parentNode = (G2DParentNode) node;
\r
241 for (String cid : parentNode.getSortedNodesById()) {
\r
242 Object child = parentNode.getNode(cid);
\r
243 if (child instanceof INode)
\r
244 printSceneGraph(stream, indentLevel + 1, (INode) child, cid);
\r
246 } else if (node instanceof ParentNode<?>) {
\r
247 ParentNode<? extends INode> parentNode = (ParentNode<?>) node;
\r
248 for (String cid : parentNode.getNodeIds()) {
\r
249 INode child = parentNode.getNode(cid);
\r
250 printSceneGraph(stream, indentLevel + 1, (INode) child);
\r
255 public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {
\r
257 ParentNode<?> parent = node.getParent();
\r
258 if (parent != null) {
\r
259 Collection<String> ids = parent.getNodeIds();
\r
260 for (String i : ids) {
\r
261 INode n = parent.getNode(i);
\r
268 printSceneGraph(stream, indentLevel, node, id);
\r
271 public static final void printSceneGraph(int indentLevel, INode node) {
\r
272 printSceneGraph(System.out, indentLevel, node);
\r
275 public static final void printSceneGraph(INode node) {
\r
276 printSceneGraph(System.out, 0, node);
\r
279 public static interface NodeProcedure<T> {
\r
280 T execute(INode node, String id);
\r
284 * @param node the node to iterate possible children for
\r
285 * @param procedure invoked for each child node, if returns a
\r
286 * <code>non-null</code> value, the return value is collected into the result
\r
288 * @return the list of collected children
\r
290 public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {
\r
291 return forChildren(node, procedure, new ArrayList<T>());
\r
295 * @param node the node to iterate possible children for
\r
296 * @param procedure invoked for each child node, if returns a
\r
297 * <code>non-null</code> value, the node is collected into the result
\r
299 * @param result the result list into which selected children are collected
\r
300 * or <code>null</code> to not collect
\r
301 * @return the list of collected children or null if provided result list
\r
302 * was <code>null</code>
\r
304 public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure, List<T> result) {
\r
305 if (node instanceof ParentNode<?>) {
\r
306 ParentNode<?> pn = (ParentNode<?>) node;
\r
307 if (node instanceof G2DParentNode) {
\r
308 G2DParentNode g2dpn = (G2DParentNode) node;
\r
309 for (String id : g2dpn.getSortedNodesById()) {
\r
310 INode n = pn.getNode(id);
\r
311 T t = procedure.execute(n, id);
\r
312 if (t != null && result != null)
\r
316 for (String id : pn.getNodeIds()) {
\r
317 INode n = pn.getNode(id);
\r
318 T t = procedure.execute(n, id);
\r
319 if (t != null && result != null)
\r
327 public static final int countTreeNodes(INode node) {
\r
329 if (node instanceof ParentNode<?>) {
\r
330 ParentNode<? extends INode> pn = (ParentNode<?>) node;
\r
331 Collection<? extends INode> ns = pn.getNodes();
\r
332 for (INode n : ns) {
\r
333 result += countTreeNodes(n);
\r
339 public static final void printTreeNodes(INode node, StringBuilder builder) {
\r
340 printTreeNodes(node, 0, builder);
\r
343 public static final void printTreeNodes(INode node, int indent, StringBuilder builder) {
\r
344 for (int i = 0; i < indent; i++)
\r
345 builder.append(" ");
\r
346 builder.append(node.toString() + "\n");
\r
347 if (node instanceof ParentNode<?>) {
\r
348 ParentNode<? extends INode> pn = (ParentNode<?>) node;
\r
349 Collection<? extends INode> ns = pn.getNodes();
\r
350 for (INode n : ns) {
\r
351 printTreeNodes(n, indent+2, builder);
\r
356 public static final <T extends INode> Set<T> collectNodes(INode node, Class<T> clazz) {
\r
357 Set<T> result = new HashSet<T>();
\r
358 collectNodes(node, clazz, result);
\r
362 @SuppressWarnings("unchecked")
\r
363 public static final <T extends INode> void collectNodes(INode node, Class<T> clazz, Set<T> result) {
\r
364 if (clazz.isInstance(node))
\r
365 result.add((T) node);
\r
366 if (node instanceof ParentNode<?>) {
\r
367 ParentNode<? extends INode> pn = (ParentNode<?>) node;
\r
368 Collection<? extends INode> ns = pn.getNodes();
\r
369 for (INode n : ns) {
\r
370 collectNodes(n, clazz, result);
\r
375 public static <T extends INode> T getSingleNode(INode node, Class<T> clazz) {
\r
376 Set<T> all = collectNodes(node, clazz);
\r
377 if(all.size() != 1) throw new RuntimeException("Expected exactly 1 instance of class " + clazz.getCanonicalName() + ", got " + all.size());
\r
378 return (T)all.iterator().next();
\r
381 public static final boolean hasChildren(INode node) {
\r
382 if (node instanceof ParentNode<?>) {
\r
383 ParentNode<?> pn = (ParentNode<?>) node;
\r
384 return !pn.getNodes().isEmpty();
\r
390 * Look for a single scene graph node by its ID in a path under a specified
\r
393 * @param parent the parent node under which to start looking
\r
394 * @param idPath the node ID path
\r
395 * @return <code>null</code> if node was not found
\r
396 * @throws ClassCastException if the found node was not of the expected type
\r
397 * T extending INode
\r
399 @SuppressWarnings("unchecked")
\r
400 public static <T extends INode> T findNodeById(INode parent, String... idPath) {
\r
402 for (int i = 0;; ++i) {
\r
403 if (i >= idPath.length)
\r
405 if (n instanceof ParentNode<?>) {
\r
406 n = ((ParentNode<?>) n).getNode(idPath[i]);
\r
414 * Tries to find out whether a node is selected or not.
\r
416 * DISCLAIMER: this is a hack for the current
\r
417 * org.simantics.g2d.diagram.participant.ElementPainter implementation that
\r
418 * will stop working if the implementation changes.
\r
421 * @param ascendLimit the max. amount of steps towards parent nodes to take
\r
422 * while looking for selection information
\r
423 * @return <code>true</code> if evidence of selection is found
\r
425 public static boolean isSelected(INode node, int ascendLimit) {
\r
427 ParentNode<?> pn = null;
\r
428 if (node instanceof ParentNode<?>) {
\r
429 pn = (ParentNode<?>) node;
\r
431 pn = node.getParent();
\r
434 for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {
\r
435 INode child = pn.getNode(SELECTION_NODE_NAME);
\r
442 public static Container findRootPane(INode node) {
\r
443 G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);
\r
444 if (parent == null)
\r
446 return ((G2DSceneGraph) parent).getRootPane();
\r
449 private static boolean isSelectionPainter(INode node) {
\r
450 if (node instanceof ISelectionPainterNode) {
\r
451 if (node instanceof IDynamicSelectionPainterNode)
\r
452 return ((IDynamicSelectionPainterNode)node).showsSelection();
\r
459 * @param elementNode
\r
462 public static boolean needSelectionPaint(INode elementNode) {
\r
463 // FIXME: there should be a cleaner way to implement this.
\r
465 if (isSelectionPainter(elementNode)) {
\r
466 // System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
\r
470 if (elementNode instanceof ConnectionNode) {
\r
471 // System.out.println("connectionNode");
\r
472 for (IG2DNode child : ((ConnectionNode) elementNode).getNodes()) {
\r
473 // System.out.println(" child " + child);
\r
474 if (isSelectionPainter(child)) {
\r
475 // System.out.println("skipped selection painting for connection node child ISelectionPainterNode");
\r
478 if (child instanceof SingleElementNode) {
\r
479 for(IG2DNode child2 : ((SingleElementNode) child).getNodes()) {
\r
480 // System.out.println(" child2 " + child2);
\r
481 if (isSelectionPainter(child2)) {
\r
482 // System.out.println("skipped selection painting for edge ISelectionPainterNode");
\r
488 } else if (elementNode instanceof SingleElementNode) {
\r
489 for (INode child : ((SingleElementNode) elementNode).getNodes()) {
\r
490 if (isSelectionPainter(child))
\r
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
\r
500 public static Method getSetterForProperty(String property, INode node) {
\r
501 assert(node != null);
\r
502 Class<?> cl = node.getClass();
\r
505 boolean isEnhanced = false;
\r
506 for(Method method : cl.getMethods()) {
\r
507 if(method.isAnnotationPresent(PropertySetter.class)) {
\r
508 PropertySetter ann = method.getAnnotation(PropertySetter.class);
\r
509 if(ann.value().equals(property)) {
\r
512 } else if(method.getName().equals(SET_THREAD_CALLBACKS_NAME) && method.getGenericParameterTypes().length == 1) {
\r
513 // The class seems to be enhanced by cglib, hence annotations are not present. Check superclass for annotations..
\r
515 cl = cl.getSuperclass();
\r
516 break; // We are not going to find any annotations, stop loop and try with the parent class
\r
519 if(!isEnhanced || cl == null) break;
\r
525 * TODO: log exceptions for debug purposes
\r
527 * @param property name of the property
\r
528 * @param value can be null..
\r
532 public static boolean setPropertyIfSupported(String property, Object value, INode node) {
\r
533 Method setter = getSetterForProperty(property, node);
\r
534 if(setter != null) {
\r
535 Class<?> pc[] = setter.getParameterTypes();
\r
536 if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {
\r
538 setter.invoke(node, value);
\r
540 } catch (IllegalArgumentException e) {
\r
541 // TODO Auto-generated catch block
\r
542 e.printStackTrace();
\r
543 } catch (IllegalAccessException e) {
\r
544 // TODO Auto-generated catch block
\r
545 e.printStackTrace();
\r
546 } catch (InvocationTargetException e) {
\r
547 // TODO Auto-generated catch block
\r
548 e.getCause().printStackTrace();
\r
552 if(pc.length > 0) {
\r
553 System.err.println("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");
\r
562 public static INode findChildById(ParentNode<?> parent, String key) {
\r
563 INode result = parent.getNode(key);
\r
564 if (result != null)
\r
567 for (String entry : parent.getNodeIds()) {
\r
568 if (entry.startsWith(key))
\r
569 return parent.getNode(key);
\r
572 for (INode node : parent.getNodes()) {
\r
573 if (node instanceof ParentNode) {
\r
574 result = findChildById((ParentNode<?>) node, key);
\r
575 if (result != null)
\r
584 private static int getSegmentEnd(String suffix) {
\r
586 for(pos=1;pos<suffix.length();++pos) {
\r
587 char c = suffix.charAt(pos);
\r
588 if(c == '/' || c == '#')
\r
594 public static String decodeString(String string) {
\r
598 public static INode browsePossible(INode node, String suffix) {
\r
599 if(suffix.isEmpty())
\r
601 switch(suffix.charAt(0)) {
\r
603 INode parent = node.getParent();
\r
606 return browsePossible(parent, suffix.substring(1));
\r
609 /*int segmentEnd = getSegmentEnd(suffix);
\r
610 Variable property = getPossibleProperty(graph,
\r
611 decodeString(suffix.substring(1, segmentEnd)));
\r
612 if(property == null)
\r
614 return property.browsePossible(graph, suffix.substring(segmentEnd));*/
\r
618 int segmentEnd = getSegmentEnd(suffix);
\r
619 INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
\r
622 return browsePossible(child, suffix.substring(segmentEnd));
\r
629 public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {
\r
630 if(suffix.isEmpty())
\r
631 throw new RuntimeException("Did not find a reference.");
\r
632 switch(suffix.charAt(0)) {
\r
634 INode parent = node.getParent();
\r
637 return browsePossibleReference(parent, suffix.substring(1));
\r
640 return Pair.make(node, suffix.substring(1));
\r
643 int segmentEnd = getSegmentEnd(suffix);
\r
644 INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));
\r
647 return browsePossibleReference(child, suffix.substring(segmentEnd));
\r
654 public static INode findChildByPrefix(G2DParentNode parent, String prefix) {
\r
655 INode result = parent.getNode(prefix);
\r
656 if (result != null)
\r
659 for (String entry : parent.getNodeIds()) {
\r
660 if (entry.startsWith(prefix))
\r
661 return parent.getNode(entry);
\r
664 for (IG2DNode node : parent.getNodes()) {
\r
665 if (node instanceof G2DParentNode) {
\r
666 result = findChildByPrefix((G2DParentNode) node, prefix);
\r
667 if (result != null)
\r
680 public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {
\r
681 return filterDirectChildIds(parent, new PrefixFilter(prefix));
\r
689 public static Collection<String> filterDirectChildIds(ParentNode<?> parent, Filter<String> childFilter) {
\r
690 Collection<String> childIds = parent.getNodeIds();
\r
691 ArrayList<String> result = new ArrayList<String>(childIds.size());
\r
693 for (String id : childIds)
\r
694 if (childFilter.accept(id))
\r
705 public static Collection<INode> filterDirectChildren(ParentNode<?> parent, Filter<String> childFilter) {
\r
706 Collection<String> childIds = parent.getNodeIds();
\r
707 ArrayList<INode> result = new ArrayList<INode>(childIds.size());
\r
709 for (String id : childIds)
\r
710 if (childFilter.accept(id))
\r
711 result.add( parent.getNode(id) );
\r
718 * @return the lookup service for the specified node
\r
719 * @throws UnsupportedOperationException if ILookupService is not available
\r
721 public static ILookupService getLookupService(INode node) {
\r
722 ParentNode<?> root = node.getRootNode();
\r
723 if (!(root instanceof ILookupService))
\r
724 throw new UnsupportedOperationException("ILookupService not supported by root node " + root + " attained from " + node);
\r
725 return (ILookupService) root;
\r
730 * @return <code>null</code> if lookup service is not available
\r
732 public static ILookupService tryGetLookupService(INode node) {
\r
733 ParentNode<?> root = node.getRootNode();
\r
734 return root instanceof ILookupService ? (ILookupService) root : null;
\r
740 * @return <code>null</code> if lookup failed, i.e. mapping does not exist
\r
741 * @throws UnsupportedOperationException if lookup is not supported
\r
742 * @see #getLookupService(INode)
\r
743 * @see ILookupService
\r
745 public static INode lookup(INode node, String id) {
\r
746 ILookupService lookup = getLookupService(node);
\r
747 return lookup.lookupNode(id);
\r
753 * @return <code>null</code> if lookup not supported or lookup failed
\r
754 * @see #tryGetLookupService(INode)
\r
755 * @see ILookupService
\r
757 public static INode tryLookup(INode node, String id) {
\r
758 ILookupService lookup = tryGetLookupService(node);
\r
759 return lookup != null ? lookup.lookupNode(id) : null;
\r
766 * @return <code>null</code> if lookup failed, i.e. mapping does not exist
\r
767 * @throws UnsupportedOperationException if lookup is not supported
\r
768 * @throws ClassCastException if the found node cannot be cast to the
\r
770 * @see #getLookupService(INode)
\r
771 * @see ILookupService
\r
773 public static <T> T lookup(INode node, String id, Class<T> clazz) {
\r
774 ILookupService lookup = getLookupService(node);
\r
775 INode found = lookup.lookupNode(id);
\r
776 return found != null ? clazz.cast(found) : null;
\r
782 * @return <code>null</code> if lookup not supported or lookup failed
\r
783 * @see #tryGetLookupService(INode)
\r
784 * @see ILookupService
\r
786 public static <T> T tryLookup(INode node, String id, Class<T> clazz) {
\r
787 ILookupService lookup = tryGetLookupService(node);
\r
788 if (lookup == null)
\r
790 INode found = lookup.lookupNode(id);
\r
791 return found != null ? clazz.cast(found) : null;
\r
797 * @return <code>null</code> if lookup failed, i.e. mapping does not exist
\r
798 * @throws UnsupportedOperationException if lookup is not supported
\r
799 * @see #getLookupService(INode)
\r
800 * @see ILookupService
\r
802 public static String lookupId(INode node) {
\r
803 ILookupService lookup = getLookupService(node);
\r
804 return lookup.lookupId(node);
\r
810 * @return <code>null</code> if lookup not supported or lookup failed
\r
811 * @see #tryGetLookupService(INode)
\r
812 * @see ILookupService
\r
814 public static String tryLookupId(INode node) {
\r
815 ILookupService lookup = tryGetLookupService(node);
\r
816 return lookup != null ? lookup.lookupId(node) : null;
\r
820 * Map the specified node to the specified ID in the {@link ILookupService}
\r
821 * provided by the root node of the specified node.
\r
825 * @throws UnsupportedOperationException if {@link ILookupService} is not
\r
826 * available for the specified node
\r
827 * @see #getLookupService(INode)
\r
828 * @see ILookupService
\r
830 public static void map(INode node, String id) {
\r
831 getLookupService(node).map(id, node);
\r
835 * Remove possible ILookupService mapping for the specified node.
\r
837 * @param node the node to try to remove mappings for
\r
838 * @return mapped ID or <code>null</code> if no mapping existed
\r
839 * @throws UnsupportedOperationException if {@link ILookupService} is not
\r
840 * available for the specified node
\r
841 * @see ILookupService
\r
842 * @see #getLookupService(INode)
\r
844 public static String unmap(INode node) {
\r
845 return getLookupService(node).unmap(node);
\r
849 * Try to remove possible ILookupService mapping for the specified node.
\r
851 * @param node the node to try to remove mappings for
\r
852 * @return mapped ID or <code>null</code> if {@link ILookupService} is not
\r
853 * supported or no mapping existed
\r
854 * @see ILookupService
\r
855 * @see #tryGetLookupService(INode)
\r
857 public static String tryUnmap(INode node) {
\r
858 ILookupService lookup = tryGetLookupService(node);
\r
859 return lookup != null ? lookup.unmap(node) : null;
\r
862 public static EventDelegator getEventDelegator(INode node) {
\r
863 ParentNode<?> n = node.getRootNode();
\r
864 if (n instanceof G2DSceneGraph) {
\r
865 return ((G2DSceneGraph) n).getEventDelegator();
\r
870 public static NodeEventHandler getNodeEventHandler(INode node) {
\r
871 ParentNode<?> n = node.getRootNode();
\r
872 return (n instanceof G2DSceneGraph) ? ((G2DSceneGraph) n).getEventHandler() : null;
\r
873 // INodeEventHandlerProvider provider = findNearestParentNode(node, INodeEventHandlerProvider.class);
\r
874 // return provider != null ? provider.getEventHandler() : null;
\r
877 public static AWTEvent transformEvent(AWTEvent event, IG2DNode node) {
\r
878 if (event instanceof MouseEvent) {
\r
879 // Find node transform..
\r
880 AffineTransform transform = getGlobalToLocalTransform(node, null);
\r
881 if (transform == null) {
\r
882 System.err.println("WARNING: Non-invertible transform for node: " + node);
\r
885 MouseEvent me = (MouseEvent)event;
\r
886 // Use double coordinates if available
\r
887 Point2D p = new Point2D.Double((double)me.getX(), (double)me.getY());
\r
888 transform.transform(p, p);
\r
890 MouseEvent e = null;
\r
891 // Giving event.getSource() as a parameter for the new events will cause major delay for the event instantiation, hence dummy component is used
\r
892 if (event instanceof MouseWheelEvent) {
\r
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);
\r
895 e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);
\r
902 private static final boolean DEBUG_BOUNDS = false;
\r
904 private static Rectangle2D getLocalBoundsImpl(INode node, Function1<INode, Boolean> filter, int indent) {
\r
905 if (node instanceof IG2DNode) {
\r
906 if (node instanceof G2DParentNode) {
\r
907 G2DParentNode pNode = (G2DParentNode)node;
\r
908 Iterator<IG2DNode> it = pNode.getNodes().iterator();
\r
911 Rectangle2D bounds = null;
\r
912 while (it.hasNext()) {
\r
913 IG2DNode next = it.next();
\r
914 if (filter != null && !filter.apply(next))
\r
917 Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);
\r
920 for(int i=0;i<indent;i++) System.err.print(" ");
\r
921 System.err.println("+getLocalBoundsImpl " + next + " => " + bl);
\r
925 if(bounds == null) {
\r
926 bounds = next.localToParent(bl.getFrame());
\r
928 bounds.add(next.localToParent(bl));
\r
934 for(int i=0;i<indent;i++) System.err.print(" ");
\r
935 System.err.println("=getLocalBoundsImpl " + node + " => " + bounds);
\r
940 Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);
\r
941 if(result != null) {
\r
943 for(int i=0;i<indent;i++) System.err.print(" ");
\r
944 System.err.println("=getLocalBoundsImpl " + node + " => " + result);
\r
953 public static Rectangle2D getLocalBounds(INode node) {
\r
954 return getLocalBoundsImpl(node, null, 0);
\r
957 public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {
\r
958 return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
\r
961 public Boolean apply(INode node) {
\r
962 return !excluding.contains(node);
\r
967 public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {
\r
968 return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {
\r
971 public Boolean apply(INode node) {
\r
972 return !excluding.isInstance(node);
\r
977 public static Rectangle2D getLocalElementBounds(INode node) {
\r
978 if(node instanceof ConnectionNode) {
\r
979 return getLocalBounds(node);
\r
980 } else if(node instanceof SingleElementNode) {
\r
981 // For normal symbols
\r
982 INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image");
\r
984 // For generic text nodes
\r
985 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");
\r
987 // For I/O table diagram flags (value of org.simantics.diagram.flag.FlagSceneGraph.VISUAL_ROOT)
\r
988 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "visual");
\r
990 image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);
\r
992 return getLocalElementBounds(image);
\r
994 return getLocalBounds(node);
\r
996 return getLocalBounds(node);
\r
1000 public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {
\r
1001 ParentNode<?> parent = null;
\r
1003 parent = node.getParent();
\r
1004 if (parent == null)
\r
1006 if (ofClass.isInstance(parent))
\r
1007 return ofClass.cast(parent);
\r
1012 private static class PendingTester implements Runnable {
\r
1014 private boolean pending = true;
\r
1015 private final G2DSceneGraph sg;
\r
1017 private final Lock pendingLock = new ReentrantLock();
\r
1018 private final Condition pendingSet = pendingLock.newCondition();
\r
1020 public PendingTester(G2DSceneGraph sg) {
\r
1025 public void run() {
\r
1026 pendingLock.lock();
\r
1028 pending = sg.isPending();
\r
1029 pendingSet.signalAll();
\r
1031 pendingLock.unlock();
\r
1035 public boolean isPending() {
\r
1039 public void await() {
\r
1040 pendingLock.lock();
\r
1043 pendingSet.await(10, TimeUnit.MILLISECONDS);
\r
1044 } catch (InterruptedException e) {
\r
1047 pendingLock.unlock();
\r
1053 public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {
\r
1054 // Wait for 30s by default
\r
1055 waitPending(thread, sg, 30000);
\r
1059 * Synchronously waits until the the specified scene graph is no longer in
\r
1062 * @param thread the thread to schedule pending checks into
\r
1063 * @param sg the scene graph to wait upon
\r
1065 public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg, int timeoutMs) {
\r
1066 PendingTester tester = new PendingTester(sg);
\r
1067 long start = System.currentTimeMillis();
\r
1068 while (tester.isPending()) {
\r
1069 thread.asyncExec(tester);
\r
1070 if (tester.isPending())
\r
1072 long duration = System.currentTimeMillis() - start;
\r
1073 if(duration > timeoutMs)
\r
1074 throw new IllegalStateException("Timeout in resolving pending nodes.");
\r
1078 public static void increasePending(INode node) {
\r
1079 G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
\r
1081 sg.increasePending(node);
\r
1084 public static void decreasePending(INode node) {
\r
1085 G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();
\r
1087 sg.decreasePending(node);
\r
1090 // TRANSFORMATIONS
\r
1092 public static AffineTransform getLocalToGlobalTransform(IG2DNode node, AffineTransform result) {
\r
1093 result.setToIdentity();
\r
1094 ParentNode<?> parent = node.getParent();
\r
1095 while (parent != null) {
\r
1096 result.preConcatenate(((IG2DNode) parent).getTransform());
\r
1097 parent = parent.getParent();
\r
1102 public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {
\r
1103 return getLocalToGlobalTransform(node, new AffineTransform());
\r
1106 public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {
\r
1107 AffineTransform transform = getLocalToGlobalTransform(node);
\r
1108 transform.invert();
\r
1112 public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {
\r
1113 AffineTransform transform = getLocalToGlobalTransform(node);
\r
1115 transform.invert();
\r
1117 } catch (NoninvertibleTransformException e) {
\r
1118 return returnIfNonInvertible;
\r
1122 public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {
\r
1123 AffineTransform at = getGlobalToLocalTransform(local, null);
\r
1125 pt2.setLocation(pt);
\r
1128 return at.transform(pt, pt2);
\r
1131 public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {
\r
1132 AffineTransform at = getLocalToGlobalTransform(local);
\r
1133 return at.transform(pt, pt2);
\r
1136 public static String getNodeName(INode nn) {
\r
1137 INode node = nn.getParent();
\r
1138 ParentNode<?> pn = (ParentNode<?>) node;
\r
1139 if (node instanceof G2DParentNode) {
\r
1140 G2DParentNode g2dpn = (G2DParentNode) node;
\r
1141 for (String id : g2dpn.getSortedNodesById())
\r
1143 INode n = pn.getNode(id);
\r
1153 * For asking whether specified parent node really is a parent of the
\r
1154 * specified child node.
\r
1159 * alleged child of parent
\r
1160 * @return <code>true</code> if parent really is a parent of child
\r
1162 public static boolean isParentOf(INode parent, INode child) {
\r
1164 if (parent == child)
\r
1166 child = child.getParent();
\r
1167 if (child == null)
\r