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.g2d.nodes;
\r
14 import java.awt.Graphics2D;
\r
15 import java.awt.geom.AffineTransform;
\r
16 import java.awt.geom.Rectangle2D;
\r
18 import org.simantics.scenegraph.ILookupService;
\r
19 import org.simantics.scenegraph.INode;
\r
20 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
21 import org.simantics.scenegraph.g2d.IG2DNode;
\r
22 import org.simantics.scenegraph.utils.NodeUtil;
\r
25 * A node that only contains a String ID to link to another node in the scene
\r
26 * graph through {@link ILookupService}. In {@link #render(Graphics2D)},
\r
27 * <code>LinkNode</code> will try to lookup the node identified by the ID and
\r
28 * delegate the important method invocations to it.
\r
31 * <b>CAUTION:</b> <em>this node must be used with care</em>! It can be used to
\r
32 * generate cyclic scene graphs which may cause rendering to crash due to
\r
33 * infinite recursion and in any case rendering will not work as intended. E.g.
\r
34 * a scene graph could have a {@link NavigationNode} under its root node and
\r
35 * under which a <code>LinkNode</code> could link back to the navigation node,
\r
36 * which would cause everything to be rendered twice and with double
\r
37 * transformations. As a safety measure against cyclic cases, this node contains
\r
38 * state that prevents it from being invoked recursively.
\r
41 * <b>CAVEAT:</b> Nodes with internal state that is updated during rendering.
\r
42 * Such nodes should not be used with {@link LinkNode}.
\r
44 * @author Tuukka Lehtonen
\r
46 * @see ILookupService
\r
48 public class LinkNode extends StateMaskNode {
\r
50 private static final long serialVersionUID = -7465071303188585400L;
\r
53 * The ID of delegate node.
\r
55 protected String delegateId;
\r
58 * <code>true</code> to only process the children of the delegate node and
\r
59 * not the node itself. <code>false</code> for normal behavior which is the
\r
62 protected boolean ignoreDelegate = false;
\r
65 * <code>true</code> to make this node responsible for removing the lookup
\r
66 * id mapping after this node is disposed of. The default value is
\r
67 * <code>false</code>.
\r
69 protected boolean lookupIdOwner = false;
\r
71 @SyncField("delegateId")
\r
72 public void setDelegateId(String delegateId) {
\r
73 this.delegateId = delegateId;
\r
76 @SyncField({"delegateId", "lookupIdOwner"})
\r
77 public void setDelegateId(String delegateId, boolean owner) {
\r
78 this.delegateId = delegateId;
\r
79 this.lookupIdOwner = owner;
\r
82 @SyncField("ignoreDelegate")
\r
83 public void setIgnoreDelegate(boolean ignore) {
\r
84 this.ignoreDelegate = ignore;
\r
87 @SyncField("lookupIdOwner")
\r
88 public void setLookupIdOwner(boolean idOwner) {
\r
89 this.lookupIdOwner = idOwner;
\r
93 public void cleanup() {
\r
95 removeDelegateMapping();
\r
100 public void render(Graphics2D g2d) {
\r
101 // Safety against cyclic cases.
\r
102 if (hasFlags(IN_RENDER))
\r
105 IG2DNode n = getDelegate2D();
\r
108 if (ignoreDelegate && !(n instanceof G2DParentNode))
\r
111 setFlags(IN_RENDER);
\r
112 AffineTransform oldTransform = null;
\r
113 if (transform != null && !transform.isIdentity()) {
\r
114 g2d.transform(transform);
\r
115 oldTransform = g2d.getTransform();
\r
118 if (!ignoreDelegate) {
\r
121 G2DParentNode parent = (G2DParentNode) n;
\r
122 for (IG2DNode child : parent.getSortedNodes())
\r
126 if (oldTransform != null)
\r
127 g2d.setTransform(oldTransform);
\r
128 clearFlags(IN_RENDER);
\r
133 public Rectangle2D getBoundsInLocal() {
\r
134 // Safety against cyclic cases.
\r
135 if (hasFlags(IN_GET_BOUNDS))
\r
136 return new Rectangle2D.Double();
\r
138 IG2DNode n = getDelegate2D();
\r
140 return new Rectangle2D.Double();
\r
142 setFlags(IN_GET_BOUNDS);
\r
144 Rectangle2D bounds = n.getBoundsInLocal();
\r
145 if (transform != null && !transform.isIdentity())
\r
146 bounds = transform.createTransformedShape(bounds).getBounds2D();
\r
149 clearFlags(IN_GET_BOUNDS);
\r
153 protected IG2DNode getDelegate2D() {
\r
154 INode node = NodeUtil.lookup(this, delegateId);
\r
155 if (node instanceof IG2DNode) {
\r
156 return (IG2DNode) node;
\r
161 public INode getDelegate() {
\r
162 INode node = NodeUtil.lookup(this, delegateId);
\r
163 if (node instanceof IG2DNode) {
\r
164 return (IG2DNode) node;
\r
169 protected void removeDelegateMapping() {
\r
170 ILookupService lookup = NodeUtil.getLookupService(this);
\r
171 lookup.unmap(delegateId);
\r
175 public String toString() {
\r
176 return super.toString() + "[delegateId=" + delegateId + "]";
\r