1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.nodes;
14 import java.awt.Graphics2D;
15 import java.awt.geom.AffineTransform;
16 import java.awt.geom.Rectangle2D;
18 import org.simantics.scenegraph.ILookupService;
19 import org.simantics.scenegraph.INode;
20 import org.simantics.scenegraph.g2d.G2DParentNode;
21 import org.simantics.scenegraph.g2d.IG2DNode;
22 import org.simantics.scenegraph.utils.NodeUtil;
25 * A node that only contains a String ID to link to another node in the scene
26 * graph through {@link ILookupService}. In {@link #render(Graphics2D)},
27 * <code>LinkNode</code> will try to lookup the node identified by the ID and
28 * delegate the important method invocations to it.
31 * <b>CAUTION:</b> <em>this node must be used with care</em>! It can be used to
32 * generate cyclic scene graphs which may cause rendering to crash due to
33 * infinite recursion and in any case rendering will not work as intended. E.g.
34 * a scene graph could have a {@link NavigationNode} under its root node and
35 * under which a <code>LinkNode</code> could link back to the navigation node,
36 * which would cause everything to be rendered twice and with double
37 * transformations. As a safety measure against cyclic cases, this node contains
38 * state that prevents it from being invoked recursively.
41 * <b>CAVEAT:</b> Nodes with internal state that is updated during rendering.
42 * Such nodes should not be used with {@link LinkNode}.
44 * @author Tuukka Lehtonen
48 public class LinkNode extends StateMaskNode {
50 private static final long serialVersionUID = -7465071303188585400L;
53 * The ID of delegate node.
55 protected String delegateId;
58 * <code>true</code> to only process the children of the delegate node and
59 * not the node itself. <code>false</code> for normal behavior which is the
62 protected boolean ignoreDelegate = false;
65 * <code>true</code> to make this node responsible for removing the lookup
66 * id mapping after this node is disposed of. The default value is
69 protected boolean lookupIdOwner = false;
71 @SyncField("delegateId")
72 public void setDelegateId(String delegateId) {
73 this.delegateId = delegateId;
76 @SyncField({"delegateId", "lookupIdOwner"})
77 public void setDelegateId(String delegateId, boolean owner) {
78 this.delegateId = delegateId;
79 this.lookupIdOwner = owner;
82 @SyncField("ignoreDelegate")
83 public void setIgnoreDelegate(boolean ignore) {
84 this.ignoreDelegate = ignore;
87 @SyncField("lookupIdOwner")
88 public void setLookupIdOwner(boolean idOwner) {
89 this.lookupIdOwner = idOwner;
93 public void cleanup() {
95 removeDelegateMapping();
100 public void render(Graphics2D g2d) {
101 // Safety against cyclic cases.
102 if (hasFlags(IN_RENDER))
105 IG2DNode n = getDelegate2D();
108 if (ignoreDelegate && !(n instanceof G2DParentNode))
112 AffineTransform oldTransform = null;
113 if (transform != null && !transform.isIdentity()) {
114 g2d.transform(transform);
115 oldTransform = g2d.getTransform();
118 if (!ignoreDelegate) {
121 G2DParentNode parent = (G2DParentNode) n;
122 for (IG2DNode child : parent.getSortedNodes())
126 if (oldTransform != null)
127 g2d.setTransform(oldTransform);
128 clearFlags(IN_RENDER);
133 public Rectangle2D getBoundsInLocal() {
134 return getBoundsInLocal(false);
138 public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {
139 // Safety against cyclic cases.
140 if (hasFlags(IN_GET_BOUNDS))
141 return new Rectangle2D.Double();
143 IG2DNode n = getDelegate2D();
145 return new Rectangle2D.Double();
147 setFlags(IN_GET_BOUNDS);
149 Rectangle2D bounds = n.getBoundsInLocal(ignoreNulls);
150 if (transform != null && !transform.isIdentity())
151 bounds = transform.createTransformedShape(bounds).getBounds2D();
154 clearFlags(IN_GET_BOUNDS);
158 protected IG2DNode getDelegate2D() {
159 INode node = NodeUtil.lookup(this, delegateId);
160 if (node instanceof IG2DNode) {
161 return (IG2DNode) node;
166 public INode getDelegate() {
167 INode node = NodeUtil.lookup(this, delegateId);
168 if (node instanceof IG2DNode) {
169 return (IG2DNode) node;
174 protected void removeDelegateMapping() {
175 ILookupService lookup = NodeUtil.getLookupService(this);
176 lookup.unmap(delegateId);
180 public String toString() {
181 return super.toString() + "[delegateId=" + delegateId + "]";