/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.perseus.model;
import com.sun.perseus.j2d.RenderGraphics;
import com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.Tile;
import com.sun.perseus.j2d.Transform;
/**
* A <code>ElementNodeProxy</code> delegates its rendering to a
* proxied <code>ElementNode</code> object. This class is used to
* model expanded content. SVG markup defines content that needs to
* be expanded before it is rendered. For example, the <code><use></code>
* element is expanded with <code>ElementNodeProxy</code> children
* to represent the expansion expressed by the element.
*
* @version $Id: ElementNodeProxy.java,v 1.14 2006/06/29 10:47:30 ln156897 Exp $
*/
public class ElementNodeProxy extends ModelNode {
/**
* The proxied ModelNode
*/
protected ElementNode proxied;
/**
* Controls whether content is expanded or not
*/
protected boolean expanded;
/**
* The next proxy, if any.
*/
protected ElementNodeProxy nextProxy;
/**
* The previous proxy, if any.
*/
protected ElementNodeProxy prevProxy;
/**
* The first expanded child, if content has been expanded.
*/
protected ModelNode firstExpandedChild;
/**
* The last expanded child, if content has been expanded
*/
protected ModelNode lastExpandedChild;
/**
* @param proxiedNode <tt>ElementNode</tt> to proxy
*/
protected ElementNodeProxy(final ElementNode proxiedNode) {
super();
this.proxied = proxiedNode;
proxiedNode.addProxy(this);
ownerDocument = proxiedNode.ownerDocument;
}
/**
* When a CompositeNode is hooked into the document tree, by default,
* it notifies its children and calls its own nodeHookedInDocumentTree
* method.
*/
final void onHookedInDocumentTree() {
super.onHookedInDocumentTree();
ModelNode c = firstExpandedChild;
while (c != null) {
c.onHookedInDocumentTree();
c = c.nextSibling;
}
}
/**
* Clears the text layouts, if any exist. This is typically
* called when the font selection has changed and nodes such
* as <code>Text</code> should recompute their layouts.
* This should recursively call clearLayouts on children
* node or expanded content, if any.
*/
protected void clearLayouts() {
clearLayouts(firstExpandedChild);
}
/**
* @return a reference to the node's first child, or null if there
* are no children.
*/
public ModelNode getFirstChildNode() {
return null;
}
/**
* @return a reference to the node's last child, or null if there
* are no children.
*/
public ModelNode getLastChildNode() {
return null;
}
/**
* Does nothing, as there are no children.
*/
protected void unhookChildrenQuiet() {
}
/**
* Some node types (such as <code>ElementNodeProxy</code>) have
* expanded children that they compute in some specific
* way depending on the implementation.
*
* @return a reference to the node's first expanded child, or null if there
* are no expanded children. This forces the computation of expanded
* content if needed.
*/
public ModelNode getFirstExpandedChild() {
expand();
return firstExpandedChild;
}
/**
* Some node types (such as <code>ElementNodeProxy</code>) have
* expanded children that they compute in some specific
* way depending on the implementation.
*
* @return a reference to the node's first expanded child, or null if there
* are no expanded children.
*/
public ModelNode getFirstComputedExpandedChild() {
return firstExpandedChild;
}
/**
* Some node types (such as <code>ElementNodeProxy</code>) have
* expanded children that they compute in some specific
* way depending on the implementation.
*
* @return a reference to the node's last expanded child, or null if there
* are no expanded children. This forces the computation of expanded
* content if needed.
*/
public ModelNode getLastExpandedChild() {
expand();
return lastExpandedChild;
}
/**
* Utility method. Unhooks the expanded content.
*/
protected void unhookExpandedQuiet() {
unhookQuiet(firstExpandedChild);
firstExpandedChild = null;
lastExpandedChild = null;
expanded = false;
}
/**
* @return true if the ElementNodeProxy has children or if it has
* expanded content.
*/
public boolean hasDescendants() {
if (super.hasDescendants()) {
return true;
} else {
expand();
return firstExpandedChild != null;
}
}
/**
* Returns the <code>ModelNode</code>, if any, hit by the
* point at coordinate x/y.
*
* @param pt the x/y coordinate. Should never be null and be
* of size two. If not, the behavior is unspecified.
* The coordinates are in viewport space.
* @return the <tt>ModelNode</tt> hit at the given point or null
* if none was hit.
*/
public ModelNode nodeHitAt(final float[] pt) {
return proxied.proxyNodeHitAt(pt, this);
}
/**
* Appends the proxied node's transform, if there is a proxied node.
*
* @param tx the <code>Transform</code> to apply additional node
* transforms to. This may be null.
* @param workTx a <code>Transform</code> which can be re-used if a
* new <code>Transform</code> needs to be created and workTx
* is not the same instance as tx.
* @return a transform with this node's transform added.
*/
protected Transform appendTransform(Transform tx,
final Transform workTx) {
if (proxied != null) {
return proxied.appendTransform(tx, workTx);
}
return tx;
}
/**
* @return a reference to the proxied <code>ModelNode</code>
*/
public ElementNode getProxied() {
return proxied;
}
/**
* Modifies the node proxied by this proxy.
*
* @param newProxied this node's new proxied node
*/
protected void setProxied(final ElementNode newProxied) {
if (this.proxied == newProxied) {
return;
}
// We call modifyingNode because this node's rendering
// may change as a result of changing the proxy.
modifyingNode();
if (this.proxied != null) {
this.proxied.removeProxy(this);
}
unhookQuiet(firstExpandedChild);
this.proxied = newProxied;
firstExpandedChild = null;
lastExpandedChild = null;
expanded = false;
if (newProxied != null) {
newProxied.addProxy(this);
}
// Initialize the requiredFeatures/requiredExtensions/systemLanguage
// bits to the same value as the proxied node.
int oldCanRenderState = canRenderState;
canRenderState &= CAN_RENDER_REQUIRED_FEATURES_MASK;
canRenderState &= CAN_RENDER_REQUIRED_EXTENSIONS_MASK;
canRenderState &= CAN_RENDER_SYSTEM_LANGUAGE_MASK;
if (newProxied != null) {
canRenderState |= (newProxied.canRenderState
& CAN_RENDER_REQUIRED_FEATURES_BIT);
canRenderState |= (newProxied.canRenderState
& CAN_RENDER_REQUIRED_EXTENSIONS_BIT);
canRenderState |= (newProxied.canRenderState
& CAN_RENDER_SYSTEM_LANGUAGE_BIT);
}
propagateCanRenderState(oldCanRenderState, canRenderState);
// We call modifiedNode because this node's rendering
// may have changed as a result of changing the proxy.
modifiedNode();
}
/**
* Expand the content. This is done lazilly
*/
protected void expand() {
if (expanded) {
return;
}
// Expand proxy tree.
//
// NOTE: This implements the SVGElementInstance tree structure,
// as described in the SVG 1.1 specification, section 5.17.
//
if (proxied != null) {
firstExpandedChild = computeProxiesChain(
(ElementNode) proxied.getFirstChildNode());
if (firstExpandedChild != null) {
lastExpandedChild
= firstExpandedChild.prevSibling;
// The prevSibling was set as a way to return both the first
// and last element of the chain. We need to break the circular
// reference.
firstExpandedChild.prevSibling = null;
} else {
firstExpandedChild = null;
}
}
expanded = true;
}
/**
* Proxied nodes should call this method when they are being modified.
*/
public void modifyingProxied() {
modifyingNode();
}
/**
* Proxied nodes should call this method when they have been modified.
*/
public void modifiedProxied() {
modifiedNode();
}
/**
* Proxied nodes should call this method they got a new added child.
* This is an optimization of the more generic insertion
* case. Appending a child is a recursive process which avoids
* recomputing all the proxies recursively (a proxy referencing a proxy
* referencing a proxy .... referencing a composite on which nodes are
* appended). It might be advantageous to consider doing a generic
* optimized insertion into the children list.
*
* @param child the <code>ElementNode</code> which was just added under
* the proxied node.
* @see #proxiedExpandedChildAdded
*/
public void proxiedChildAdded(final ElementNode child) {
// If this node is not expanded at all, expand it now
if (!expanded) {
expand();
} else {
ElementNodeProxy newChildProxy = child.buildProxy();
if (firstExpandedChild == null) {
firstExpandedChild = newChildProxy;
lastExpandedChild = newChildProxy;
newChildProxy.nextSibling = null;
newChildProxy.prevSibling = null;
} else {
lastExpandedChild.nextSibling = newChildProxy;
newChildProxy.nextSibling = null;
newChildProxy.prevSibling = lastExpandedChild;
lastExpandedChild = newChildProxy;
}
newChildProxy.setParentQuiet(this);
newChildProxy.expand();
nodeInserted(newChildProxy);
}
}
/**
* Implementation helper: computes the set of chained proxies
* for the input node and return the head of the chain.
*
* @param proxiedChild the <code>ElementNode</code> for which the chain of
* proxies should be computed.
*
* @return the head of the proxies chaing. <b>NOTE</b> that the prevSibling
* is set on the head to point to the last element of the chain,
* creating a circular list for the 'prevSibling' reference. This
* circular reference should be broken by the code using this
* method.
*/
protected ElementNodeProxy computeProxiesChain(ElementNode proxiedChild) {
ElementNodeProxy firstProxy = null;
ElementNodeProxy proxy = null;
ElementNodeProxy previousProxy = null;
while (proxiedChild != null) {
proxy = proxiedChild.buildProxy();
proxy.setParentQuiet(this);
proxy.expand();
if (previousProxy == null) {
firstProxy = proxy;
} else {
previousProxy.nextSibling = proxy;
proxy.prevSibling = previousProxy;
}
firstProxy.prevSibling = proxy;
previousProxy = proxy;
proxiedChild = (ElementNode) proxiedChild.nextSibling;
}
return firstProxy;
}
}
|