/*******************************************************************************
* Copyright (c) 2007, 2010 Association for Decentralized Information Management
* in Industry THTH ry.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.scenegraph.g2d.nodes;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import org.simantics.scenegraph.ILookupService;
import org.simantics.scenegraph.INode;
import org.simantics.scenegraph.g2d.G2DParentNode;
import org.simantics.scenegraph.g2d.IG2DNode;
import org.simantics.scenegraph.utils.NodeUtil;
/**
* A node that only contains a String ID to link to another node in the scene
* graph through {@link ILookupService}. In {@link #render(Graphics2D)},
* LinkNode
will try to lookup the node identified by the ID and
* delegate the important method invocations to it.
*
*
* CAUTION: this node must be used with care! It can be used to
* generate cyclic scene graphs which may cause rendering to crash due to
* infinite recursion and in any case rendering will not work as intended. E.g.
* a scene graph could have a {@link NavigationNode} under its root node and
* under which a LinkNode
could link back to the navigation node,
* which would cause everything to be rendered twice and with double
* transformations. As a safety measure against cyclic cases, this node contains
* state that prevents it from being invoked recursively.
*
*
* CAVEAT: Nodes with internal state that is updated during rendering.
* Such nodes should not be used with {@link LinkNode}.
*
* @author Tuukka Lehtonen
*
* @see ILookupService
*/
public class LinkNode extends StateMaskNode {
private static final long serialVersionUID = -7465071303188585400L;
/**
* The ID of delegate node.
*/
protected String delegateId;
/**
* true
to only process the children of the delegate node and
* not the node itself. false
for normal behavior which is the
* default.
*/
protected boolean ignoreDelegate = false;
/**
* true
to make this node responsible for removing the lookup
* id mapping after this node is disposed of. The default value is
* false
.
*/
protected boolean lookupIdOwner = false;
@SyncField("delegateId")
public void setDelegateId(String delegateId) {
this.delegateId = delegateId;
}
@SyncField({"delegateId", "lookupIdOwner"})
public void setDelegateId(String delegateId, boolean owner) {
this.delegateId = delegateId;
this.lookupIdOwner = owner;
}
@SyncField("ignoreDelegate")
public void setIgnoreDelegate(boolean ignore) {
this.ignoreDelegate = ignore;
}
@SyncField("lookupIdOwner")
public void setLookupIdOwner(boolean idOwner) {
this.lookupIdOwner = idOwner;
}
@Override
public void cleanup() {
if (lookupIdOwner)
removeDelegateMapping();
super.cleanup();
}
@Override
public void render(Graphics2D g2d) {
// Safety against cyclic cases.
if (hasFlags(IN_RENDER))
return;
IG2DNode n = getDelegate2D();
if (n == null)
return;
if (ignoreDelegate && !(n instanceof G2DParentNode))
return;
setFlags(IN_RENDER);
AffineTransform oldTransform = null;
if (transform != null && !transform.isIdentity()) {
g2d.transform(transform);
oldTransform = g2d.getTransform();
}
try {
if (!ignoreDelegate) {
n.render(g2d);
} else {
G2DParentNode parent = (G2DParentNode) n;
for (IG2DNode child : parent.getSortedNodes())
child.render(g2d);
}
} finally {
if (oldTransform != null)
g2d.setTransform(oldTransform);
clearFlags(IN_RENDER);
}
}
@Override
public Rectangle2D getBoundsInLocal() {
return getBoundsInLocal(false);
}
@Override
public Rectangle2D getBoundsInLocal(boolean ignoreNulls) {
// Safety against cyclic cases.
if (hasFlags(IN_GET_BOUNDS))
return new Rectangle2D.Double();
IG2DNode n = getDelegate2D();
if (n == null)
return new Rectangle2D.Double();
setFlags(IN_GET_BOUNDS);
try {
Rectangle2D bounds = n.getBoundsInLocal(ignoreNulls);
if (transform != null && !transform.isIdentity())
bounds = transform.createTransformedShape(bounds).getBounds2D();
return bounds;
} finally {
clearFlags(IN_GET_BOUNDS);
}
}
protected IG2DNode getDelegate2D() {
INode node = NodeUtil.lookup(this, delegateId);
if (node instanceof IG2DNode) {
return (IG2DNode) node;
}
return null;
}
public INode getDelegate() {
INode node = NodeUtil.lookup(this, delegateId);
if (node instanceof IG2DNode) {
return (IG2DNode) node;
}
return null;
}
protected void removeDelegateMapping() {
ILookupService lookup = NodeUtil.getLookupService(this);
lookup.unmap(delegateId);
}
@Override
public String toString() {
return super.toString() + "[delegateId=" + delegateId + "]";
}
}