001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.perseus.model;
027:
028: import com.sun.perseus.j2d.RenderGraphics;
029:
030: import com.sun.perseus.j2d.Box;
031: import com.sun.perseus.j2d.Tile;
032: import com.sun.perseus.j2d.Transform;
033:
034: /**
035: * A <code>ElementNodeProxy</code> delegates its rendering to a
036: * proxied <code>ElementNode</code> object. This class is used to
037: * model expanded content. SVG markup defines content that needs to
038: * be expanded before it is rendered. For example, the <code><use></code>
039: * element is expanded with <code>ElementNodeProxy</code> children
040: * to represent the expansion expressed by the element.
041: *
042: * @version $Id: ElementNodeProxy.java,v 1.14 2006/06/29 10:47:30 ln156897 Exp $
043: */
044: public class ElementNodeProxy extends ModelNode {
045: /**
046: * The proxied ModelNode
047: */
048: protected ElementNode proxied;
049:
050: /**
051: * Controls whether content is expanded or not
052: */
053: protected boolean expanded;
054:
055: /**
056: * The next proxy, if any.
057: */
058: protected ElementNodeProxy nextProxy;
059:
060: /**
061: * The previous proxy, if any.
062: */
063: protected ElementNodeProxy prevProxy;
064:
065: /**
066: * The first expanded child, if content has been expanded.
067: */
068: protected ModelNode firstExpandedChild;
069:
070: /**
071: * The last expanded child, if content has been expanded
072: */
073: protected ModelNode lastExpandedChild;
074:
075: /**
076: * @param proxiedNode <tt>ElementNode</tt> to proxy
077: */
078: protected ElementNodeProxy(final ElementNode proxiedNode) {
079: super ();
080: this .proxied = proxiedNode;
081: proxiedNode.addProxy(this );
082: ownerDocument = proxiedNode.ownerDocument;
083: }
084:
085: /**
086: * When a CompositeNode is hooked into the document tree, by default,
087: * it notifies its children and calls its own nodeHookedInDocumentTree
088: * method.
089: */
090: final void onHookedInDocumentTree() {
091: super .onHookedInDocumentTree();
092:
093: ModelNode c = firstExpandedChild;
094: while (c != null) {
095: c.onHookedInDocumentTree();
096: c = c.nextSibling;
097: }
098: }
099:
100: /**
101: * Clears the text layouts, if any exist. This is typically
102: * called when the font selection has changed and nodes such
103: * as <code>Text</code> should recompute their layouts.
104: * This should recursively call clearLayouts on children
105: * node or expanded content, if any.
106: */
107: protected void clearLayouts() {
108: clearLayouts(firstExpandedChild);
109: }
110:
111: /**
112: * @return a reference to the node's first child, or null if there
113: * are no children.
114: */
115: public ModelNode getFirstChildNode() {
116: return null;
117: }
118:
119: /**
120: * @return a reference to the node's last child, or null if there
121: * are no children.
122: */
123: public ModelNode getLastChildNode() {
124: return null;
125: }
126:
127: /**
128: * Does nothing, as there are no children.
129: */
130: protected void unhookChildrenQuiet() {
131: }
132:
133: /**
134: * Some node types (such as <code>ElementNodeProxy</code>) have
135: * expanded children that they compute in some specific
136: * way depending on the implementation.
137: *
138: * @return a reference to the node's first expanded child, or null if there
139: * are no expanded children. This forces the computation of expanded
140: * content if needed.
141: */
142: public ModelNode getFirstExpandedChild() {
143: expand();
144: return firstExpandedChild;
145: }
146:
147: /**
148: * Some node types (such as <code>ElementNodeProxy</code>) have
149: * expanded children that they compute in some specific
150: * way depending on the implementation.
151: *
152: * @return a reference to the node's first expanded child, or null if there
153: * are no expanded children.
154: */
155: public ModelNode getFirstComputedExpandedChild() {
156: return firstExpandedChild;
157: }
158:
159: /**
160: * Some node types (such as <code>ElementNodeProxy</code>) have
161: * expanded children that they compute in some specific
162: * way depending on the implementation.
163: *
164: * @return a reference to the node's last expanded child, or null if there
165: * are no expanded children. This forces the computation of expanded
166: * content if needed.
167: */
168: public ModelNode getLastExpandedChild() {
169: expand();
170: return lastExpandedChild;
171: }
172:
173: /**
174: * Utility method. Unhooks the expanded content.
175: */
176: protected void unhookExpandedQuiet() {
177: unhookQuiet(firstExpandedChild);
178: firstExpandedChild = null;
179: lastExpandedChild = null;
180: expanded = false;
181: }
182:
183: /**
184: * @return true if the ElementNodeProxy has children or if it has
185: * expanded content.
186: */
187: public boolean hasDescendants() {
188: if (super .hasDescendants()) {
189: return true;
190: } else {
191: expand();
192: return firstExpandedChild != null;
193: }
194:
195: }
196:
197: /**
198: * Returns the <code>ModelNode</code>, if any, hit by the
199: * point at coordinate x/y.
200: *
201: * @param pt the x/y coordinate. Should never be null and be
202: * of size two. If not, the behavior is unspecified.
203: * The coordinates are in viewport space.
204: * @return the <tt>ModelNode</tt> hit at the given point or null
205: * if none was hit.
206: */
207: public ModelNode nodeHitAt(final float[] pt) {
208: return proxied.proxyNodeHitAt(pt, this );
209: }
210:
211: /**
212: * Appends the proxied node's transform, if there is a proxied node.
213: *
214: * @param tx the <code>Transform</code> to apply additional node
215: * transforms to. This may be null.
216: * @param workTx a <code>Transform</code> which can be re-used if a
217: * new <code>Transform</code> needs to be created and workTx
218: * is not the same instance as tx.
219: * @return a transform with this node's transform added.
220: */
221: protected Transform appendTransform(Transform tx,
222: final Transform workTx) {
223: if (proxied != null) {
224: return proxied.appendTransform(tx, workTx);
225: }
226:
227: return tx;
228: }
229:
230: /**
231: * @return a reference to the proxied <code>ModelNode</code>
232: */
233: public ElementNode getProxied() {
234: return proxied;
235: }
236:
237: /**
238: * Modifies the node proxied by this proxy.
239: *
240: * @param newProxied this node's new proxied node
241: */
242: protected void setProxied(final ElementNode newProxied) {
243: if (this .proxied == newProxied) {
244: return;
245: }
246:
247: // We call modifyingNode because this node's rendering
248: // may change as a result of changing the proxy.
249: modifyingNode();
250:
251: if (this .proxied != null) {
252: this .proxied.removeProxy(this );
253: }
254:
255: unhookQuiet(firstExpandedChild);
256:
257: this .proxied = newProxied;
258: firstExpandedChild = null;
259: lastExpandedChild = null;
260: expanded = false;
261:
262: if (newProxied != null) {
263: newProxied.addProxy(this );
264: }
265:
266: // Initialize the requiredFeatures/requiredExtensions/systemLanguage
267: // bits to the same value as the proxied node.
268: int oldCanRenderState = canRenderState;
269: canRenderState &= CAN_RENDER_REQUIRED_FEATURES_MASK;
270: canRenderState &= CAN_RENDER_REQUIRED_EXTENSIONS_MASK;
271: canRenderState &= CAN_RENDER_SYSTEM_LANGUAGE_MASK;
272:
273: if (newProxied != null) {
274: canRenderState |= (newProxied.canRenderState & CAN_RENDER_REQUIRED_FEATURES_BIT);
275: canRenderState |= (newProxied.canRenderState & CAN_RENDER_REQUIRED_EXTENSIONS_BIT);
276: canRenderState |= (newProxied.canRenderState & CAN_RENDER_SYSTEM_LANGUAGE_BIT);
277: }
278:
279: propagateCanRenderState(oldCanRenderState, canRenderState);
280:
281: // We call modifiedNode because this node's rendering
282: // may have changed as a result of changing the proxy.
283: modifiedNode();
284: }
285:
286: /**
287: * Expand the content. This is done lazilly
288: */
289: protected void expand() {
290: if (expanded) {
291: return;
292: }
293:
294: // Expand proxy tree.
295: //
296: // NOTE: This implements the SVGElementInstance tree structure,
297: // as described in the SVG 1.1 specification, section 5.17.
298: //
299: if (proxied != null) {
300: firstExpandedChild = computeProxiesChain((ElementNode) proxied
301: .getFirstChildNode());
302:
303: if (firstExpandedChild != null) {
304: lastExpandedChild = firstExpandedChild.prevSibling;
305:
306: // The prevSibling was set as a way to return both the first
307: // and last element of the chain. We need to break the circular
308: // reference.
309: firstExpandedChild.prevSibling = null;
310: } else {
311: firstExpandedChild = null;
312: }
313: }
314:
315: expanded = true;
316: }
317:
318: /**
319: * Proxied nodes should call this method when they are being modified.
320: */
321: public void modifyingProxied() {
322: modifyingNode();
323: }
324:
325: /**
326: * Proxied nodes should call this method when they have been modified.
327: */
328: public void modifiedProxied() {
329: modifiedNode();
330: }
331:
332: /**
333: * Proxied nodes should call this method they got a new added child.
334: * This is an optimization of the more generic insertion
335: * case. Appending a child is a recursive process which avoids
336: * recomputing all the proxies recursively (a proxy referencing a proxy
337: * referencing a proxy .... referencing a composite on which nodes are
338: * appended). It might be advantageous to consider doing a generic
339: * optimized insertion into the children list.
340: *
341: * @param child the <code>ElementNode</code> which was just added under
342: * the proxied node.
343: * @see #proxiedExpandedChildAdded
344: */
345: public void proxiedChildAdded(final ElementNode child) {
346: // If this node is not expanded at all, expand it now
347: if (!expanded) {
348: expand();
349: } else {
350: ElementNodeProxy newChildProxy = child.buildProxy();
351:
352: if (firstExpandedChild == null) {
353: firstExpandedChild = newChildProxy;
354: lastExpandedChild = newChildProxy;
355: newChildProxy.nextSibling = null;
356: newChildProxy.prevSibling = null;
357: } else {
358: lastExpandedChild.nextSibling = newChildProxy;
359: newChildProxy.nextSibling = null;
360: newChildProxy.prevSibling = lastExpandedChild;
361: lastExpandedChild = newChildProxy;
362: }
363:
364: newChildProxy.setParentQuiet(this );
365: newChildProxy.expand();
366: nodeInserted(newChildProxy);
367: }
368: }
369:
370: /**
371: * Implementation helper: computes the set of chained proxies
372: * for the input node and return the head of the chain.
373: *
374: * @param proxiedChild the <code>ElementNode</code> for which the chain of
375: * proxies should be computed.
376: *
377: * @return the head of the proxies chaing. <b>NOTE</b> that the prevSibling
378: * is set on the head to point to the last element of the chain,
379: * creating a circular list for the 'prevSibling' reference. This
380: * circular reference should be broken by the code using this
381: * method.
382: */
383: protected ElementNodeProxy computeProxiesChain(
384: ElementNode proxiedChild) {
385: ElementNodeProxy firstProxy = null;
386: ElementNodeProxy proxy = null;
387: ElementNodeProxy previousProxy = null;
388: while (proxiedChild != null) {
389: proxy = proxiedChild.buildProxy();
390: proxy.setParentQuiet(this );
391: proxy.expand();
392: if (previousProxy == null) {
393: firstProxy = proxy;
394: } else {
395: previousProxy.nextSibling = proxy;
396: proxy.prevSibling = previousProxy;
397: }
398: firstProxy.prevSibling = proxy;
399: previousProxy = proxy;
400: proxiedChild = (ElementNode) proxiedChild.nextSibling;
401: }
402: return firstProxy;
403: }
404:
405: }
|