/******************************************************************************* * 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 + "]"; } }