Propagate ignoreNulls parameter recursively in bounds calculation
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / nodes / LinkNode.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.g2d.nodes;
13
14 import java.awt.Graphics2D;
15 import java.awt.geom.AffineTransform;
16 import java.awt.geom.Rectangle2D;
17
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;
23
24 /**
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.
29  * 
30  * <p>
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.
39  * 
40  * <p>
41  * <b>CAVEAT:</b> Nodes with internal state that is updated during rendering.
42  * Such nodes should not be used with {@link LinkNode}.
43  * 
44  * @author Tuukka Lehtonen
45  * 
46  * @see ILookupService
47  */
48 public class LinkNode extends StateMaskNode {
49
50     private static final long   serialVersionUID = -7465071303188585400L;
51
52     /**
53      * The ID of delegate node.
54      */
55     protected String delegateId;
56
57     /**
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
60      * default.
61      */
62     protected boolean ignoreDelegate = false;
63
64     /**
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
67      * <code>false</code>.
68      */
69     protected boolean lookupIdOwner = false;
70
71     @SyncField("delegateId")
72     public void setDelegateId(String delegateId) {
73         this.delegateId = delegateId;
74     }
75
76     @SyncField({"delegateId", "lookupIdOwner"})
77     public void setDelegateId(String delegateId, boolean owner) {
78         this.delegateId = delegateId;
79         this.lookupIdOwner = owner;
80     }
81
82     @SyncField("ignoreDelegate")
83     public void setIgnoreDelegate(boolean ignore) {
84         this.ignoreDelegate = ignore;
85     }
86
87     @SyncField("lookupIdOwner")
88     public void setLookupIdOwner(boolean idOwner) {
89         this.lookupIdOwner = idOwner;
90     }
91
92     @Override
93     public void cleanup() {
94         if (lookupIdOwner)
95             removeDelegateMapping();
96         super.cleanup();
97     }
98
99     @Override
100     public void render(Graphics2D g2d) {
101         // Safety against cyclic cases.
102         if (hasFlags(IN_RENDER))
103             return;
104
105         IG2DNode n = getDelegate2D();
106         if (n == null)
107             return;
108         if (ignoreDelegate && !(n instanceof G2DParentNode))
109             return;
110
111         setFlags(IN_RENDER);
112         AffineTransform oldTransform = null;
113         if (transform != null && !transform.isIdentity()) {
114             g2d.transform(transform);
115             oldTransform = g2d.getTransform();
116         }
117         try {
118             if (!ignoreDelegate) {
119                 n.render(g2d);
120             } else {
121                 G2DParentNode parent = (G2DParentNode) n;
122                 for (IG2DNode child : parent.getSortedNodes())
123                     child.render(g2d);
124             }
125         } finally {
126             if (oldTransform != null)
127                 g2d.setTransform(oldTransform);
128             clearFlags(IN_RENDER);
129         }
130     }
131
132     @Override
133     public Rectangle2D getBoundsInLocal() {
134         return getBoundsInLocal(false);
135     }
136     
137     @Override
138     public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {
139         // Safety against cyclic cases.
140         if (hasFlags(IN_GET_BOUNDS))
141             return new Rectangle2D.Double();
142
143         IG2DNode n = getDelegate2D();
144         if (n == null)
145             return new Rectangle2D.Double();
146
147         setFlags(IN_GET_BOUNDS);
148         try {
149             Rectangle2D bounds = n.getBoundsInLocal(ignoreNulls);
150             if (transform != null && !transform.isIdentity())
151                 bounds = transform.createTransformedShape(bounds).getBounds2D();
152             return bounds;
153         } finally {
154             clearFlags(IN_GET_BOUNDS);
155         }
156     }
157
158     protected IG2DNode getDelegate2D() {
159         INode node = NodeUtil.lookup(this, delegateId);
160         if (node instanceof IG2DNode) {
161             return (IG2DNode) node;
162         }
163         return null;
164     }
165
166     public INode getDelegate() {
167         INode node = NodeUtil.lookup(this, delegateId);
168         if (node instanceof IG2DNode) {
169             return (IG2DNode) node;
170         }
171         return null;
172     }
173
174     protected void removeDelegateMapping() {
175         ILookupService lookup = NodeUtil.getLookupService(this);
176         lookup.unmap(delegateId);
177     }
178
179     @Override
180     public String toString() {
181         return super.toString() + "[delegateId=" + delegateId + "]";
182     }
183
184 }