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:
027: package com.sun.perseus.model;
028:
029: import com.sun.perseus.j2d.RenderContext;
030: import com.sun.perseus.j2d.RenderGraphics;
031:
032: import java.io.InputStream;
033:
034: import com.sun.perseus.util.RunnableQueue;
035: import com.sun.perseus.util.RunnableQueue.RunnableHandler;
036:
037: import com.sun.perseus.j2d.RGB;
038: import com.sun.perseus.j2d.Transform;
039:
040: /**
041: * <p>The <code>CanvasManager</code> class is responsible for
042: * keeping the rendering of a <code>ModelNode</code> tree on a
043: * <code>RenderGraphics</code> current.</p>
044: *
045: * <p>Specifically, the <code>CanvasManager</code> listens to
046: * update events in a <code>ModelNode</code> tree and
047: * triggers repaint into the <code>RenderGraphics</code> when
048: * necessary.</p>
049: *
050: * <p>The <code>CanvasManager</code> optimizes rendering
051: * of the tree while the document is in loading phase.</p>
052: *
053: * @version $Id: CanvasManager.java,v 1.17 2006/07/13 00:55:57 st125089 Exp $
054: */
055: public class CanvasManager extends SimpleCanvasManager {
056: /**
057: * True while the component is processing a document
058: * which is in the loading phase, i.e., between
059: * the <code>UpdateListener</code>'s <code>loadStarting</code>
060: * and <code>loadComplete</code> calls.
061: */
062: protected boolean loading;
063:
064: /**
065: * Progressive painting is needed when a node has
066: * started loading and has been inserted into the tree.
067: * This is only used during the loading phase of
068: * a document when doing progressive rendering.
069: * The next node to paint progressively
070: */
071: protected ModelNode progressiveNode = null;
072:
073: /**
074: * Tracks the highest level node whose load completion
075: * is needed to proceed with progressive rendering.
076: * When loading this node completes, then the node
077: * is painted.
078: */
079: protected ModelNode needLoadNode = null;
080:
081: /**
082: * The associated SMILSampler, if animations are run.
083: */
084: protected SMILSample sampler = null;
085:
086: /**
087: * The rate for SMIL animation. The smilRate is the minimum time between
088: * SMIL samples.
089: */
090: protected long smilRate = 40;
091:
092: /**
093: * @param rg the <code>RenderGraphics</code> which this
094: * instance will keep up to date with the
095: * model changes.
096: * @param documentNode the <code>DocumentNode</code>, root of the
097: * tree that this <code>CanvasManager</code> will
098: * draw and keep current on the <code>RenderGraphics</code>
099: * @param canvasUpdateListener the <code>CanvasUpdateListener</code>
100: * which listens to completed updates on the associated
101: * <code>RenderGraphics</code>
102: *
103: * @throws IllegalArgumentException if rg, documentNode or listener is null.
104: */
105: public CanvasManager(final RenderGraphics rg,
106: final DocumentNode documentNode,
107: final CanvasUpdateListener canvasUpdateListener) {
108: super (rg, documentNode, canvasUpdateListener);
109: }
110:
111: /**
112: * Invoked when a node has been inserted into the tree
113: *
114: * @param node the newly inserted node
115: */
116: public void nodeInserted(final ModelNode node) {
117: if (loading) {
118: if (needLoadNode == null) {
119: // Progressive rendering is _not_ suspended
120:
121: // If this node's parent is already loaded,
122: // it means we are dealing with a node insertion
123: // resulting from reference resolution. We need
124: // to repaint the document in its current state.
125: if (node.parent.loaded) {
126: fullPaint();
127: } else {
128: // Check if this node suspends progressive
129: // rendering.
130: if (!node.getPaintNeedsLoad()) {
131: if (progressiveNode != null) {
132: needRepaint = true;
133: } else {
134: progressiveNode = node;
135: }
136: } else {
137: needLoadNode = node;
138: }
139: }
140: } else {
141: // Progressive rendering _is_ suspended
142:
143: // We are loading a document and progressive
144: // repaint is disabled. However, the newly
145: // inserted node might be a ElementNodeProxy
146: // child of a Use element which is referencing
147: // content under the current needLoadNode.
148: // In that situation, we need to do a repaint
149: // of the document up to, but not including
150: // the needLoadNode.
151: ModelNode parent = node;
152: while (parent != null) {
153: if (parent == needLoadNode) {
154: // We are under the disabled node, no
155: // problem
156: break;
157: }
158: parent = parent.parent;
159: }
160: if (parent == null) {
161: // Re-render the document up to the current
162: // needLoadNode
163: needRepaint = true;
164: }
165: }
166: } else {
167: needRepaint = true;
168: }
169: }
170:
171: /**
172: * @param node the node to test.
173: * @return true if <code>node</code> is
174: * a chid of the node currently holding up progressive
175: * rendering. The caller must make sure <code>needNodeLoad</code>
176: * is not null before calling this utility method. If called
177: * when <code>needLoadNode</code> is null, the method returns
178: * true.
179: */
180: boolean isNeedLoadNodeOrChild(final ModelNode node) {
181: ModelNode parent = node;
182:
183: while (parent != null) {
184: if (parent == needLoadNode) {
185: break;
186: }
187: parent = parent.parent;
188: }
189:
190: if (parent == null) {
191: return false;
192: }
193:
194: return true;
195: }
196:
197: /**
198: * Invoked when a node is about to be modified.
199: *
200: * @param node the node which is about to be modified
201: */
202: public void modifyingNode(final ModelNode node) {
203: if (!isNeedLoadNodeOrChild(node)
204: && ((node.hasNodeRendering() || node.hasDescendants()) && (node.canRenderState == 0))) {
205: needRepaint = true;
206: }
207: }
208:
209: /**
210: * Invoked when a node modification completed.
211: *
212: * @param node the node which was just modified.
213: */
214: public void modifiedNode(final ModelNode node) {
215: if (!loading) {
216: if (!needRepaint
217: && (node.hasNodeRendering() || node
218: .hasDescendants())) {
219: needRepaint = true;
220: }
221: } else {
222: // Ignore modifications on nodes which have no
223: // rendering and no descendants
224: if (!node.hasNodeRendering() && !node.hasDescendants()) {
225: return;
226: }
227:
228: // We are doing progressive rendering. Check if
229: // the modified node is the one currently suspended
230: // or one of its children.
231: // Modifications will be picked up when we
232: // paint the node after it has finished
233: // loading.
234: if (needLoadNode != null) {
235: if (node == needLoadNode) {
236: return;
237: } else {
238: ModelNode parent = node.parent;
239: while (parent != null) {
240: if (parent == needLoadNode) {
241: return;
242: }
243: parent = parent.parent;
244: }
245: needRepaint = true;
246: }
247: } else {
248: if (!needRepaint) {
249: // We modified a node which did not have node
250: // rendering.
251: if (progressiveNode != null
252: && progressiveNode != node) {
253: needRepaint = true;
254: }
255: progressiveNode = node;
256: }
257: }
258: }
259: }
260:
261: /**
262: * Invoked when the input node has finished loading.
263: *
264: * @param node the <code>node</code> for which loading
265: * is complete.
266: */
267: public void loadComplete(final ModelNode node) {
268: // System.err.println(">>>>>>>>>>>>>> loadComplete : " + node);
269: if (node instanceof DocumentNode) {
270: // We are finished with the loading phase.
271: // Progressive rendering can stop
272: loading = false;
273: canvasUpdateListener.initialLoadComplete(null);
274:
275: // At this point, we are ready to start the animation loop.
276: // We set the document's scheduled Runnable.
277:
278: // IMPL NOTE : We disable animations if there are no initial animations.
279: // We should really only sample when there are
280: // active animations, but animations can be added by scripts, so
281: // we will need a more sophisticated mechanism.
282: if (documentNode.updateQueue != null
283: && documentNode.timeContainerRootSupport.timedElementChildren
284: .size() > 0) {
285: SMILSample.DocumentWallClock clock = new SMILSample.DocumentWallClock(
286: documentNode);
287: sampler = new SMILSample(documentNode, clock);
288: documentNode.updateQueue.scheduleAtFixedRate(sampler,
289: this , smilRate);
290: documentNode.timeContainerRootSupport.initialize();
291: clock.start();
292: }
293: } else if (node == needLoadNode) {
294: // We loaded a node fully. We can now display that
295: // node and its children and proceed with progressive
296: // rendering
297: if (progressiveNode != null) {
298: throw new Error();
299: }
300: progressiveNode = node;
301: needLoadNode = null;
302: }
303: updateCanvas();
304: }
305:
306: /**
307: * Invoked when a document error happened before finishing loading.
308: *
309: * @param documentNode the <code>DocumentNode</code> for which loading
310: * has failed.
311: * @param error the exception which describes the reason why loading
312: * failed.
313: */
314: public void loadingFailed(final DocumentNode documentNode,
315: final Exception error) {
316: loading = false;
317: canvasUpdateListener.initialLoadComplete(error);
318: }
319:
320: /**
321: * Invoked when the document starts loading
322: *
323: * @param documentNode the <code>DocumentNode</code> for which loading
324: * is starting
325: * @param is the <code>InputStream</code> from which SVG content
326: * is loaded.
327: */
328: public void loadStarting(final DocumentNode documentNode,
329: final InputStream is) {
330: loading = true;
331: }
332:
333: /**
334: * Invoked when the input node has started loading
335: *
336: * @param node the <code>ModelNode</code> for which loading
337: * has started.
338: */
339: public void loadBegun(final ModelNode node) {
340: updateCanvas();
341: }
342:
343: /**
344: * Invoked when a string has been appended, during a load
345: * phase. This is only used when parsing a document and is
346: * used in support of progressive download, like the other
347: * loadXXX methods.
348: *
349: * @param node the <code>ModelNode</code> on which text has been
350: * inserted.
351: */
352: public void textInserted(final ModelNode node) {
353: }
354:
355: /**
356: * @return the associated SMILSampler, if animations are run.
357: */
358: public SMILSample getSampler() {
359: return sampler;
360: }
361:
362: /**
363: * Utility method used to update the canvas appropriately
364: * depending on what is needed.
365: *
366: * During the loading phase, while we do progressive
367: * rendering, the canvas will only redraw nodes in the
368: * progressiveNodes list, unless a repaint has been
369: * requested.
370: *
371: * Important Note: this method should only be called from
372: * the update thread, i.e., the thread that also manages
373: * the model node tree.
374: */
375: public void updateCanvas() {
376: if (!loading) {
377: if (needRepaint) {
378: if (canvasConsumed) {
379: fullPaint();
380: needRepaint = false;
381: } else {
382: // There is a request to update the canvas
383: // (likely after a Runnable was invoked),
384: // but the last update was not consumed.
385: // If there is a Runnable in the RunnableQueue,
386: // we just skip this rendering update. Otherwise,
387: // schedule a fake Runnable to force a later repaint.
388: if (documentNode.getUpdateQueue().getSize() == 0) {
389: documentNode.getUpdateQueue().preemptLater(
390: new Runnable() {
391: public void run() {
392: }
393: }, this );
394: }
395: }
396: }
397: } else {
398: if (needRepaint) {
399: // A full repaint was requested. If there is no
400: // suspended node, just do a full repaint.
401: // Otherwise, do a partial paint
402: if (needLoadNode == null) {
403: fullPaint();
404: } else {
405: partialPaint(documentNode);
406: canvasUpdateListener.updateComplete(this );
407: }
408: } else if (progressiveNode != null) {
409: progressivePaint(progressiveNode);
410: }
411: needRepaint = false;
412: progressiveNode = null;
413: }
414: }
415:
416: /**
417: * Utility method invoked when an incremental painting is needed
418: * on a node. This may be invoked when a node was just inserted
419: * into the tree or when a node which required full loading of
420: * its children has been completely loaded.
421: *
422: * @param node the node to paint incrementally on the canvas
423: */
424: protected void progressivePaint(final ModelNode node) {
425: // If this node already has children, we need to do a fullNodePaint.
426: // This happens for the <use> element when the <use> references
427: // an element which appeared before in the document.
428: if (node.hasDescendants()) {
429: fullNodePaint(node);
430: } else if (node.hasNodeRendering()
431: && (node.canRenderState == 0)) {
432: synchronized (lock) {
433: if (!canvasConsumed) {
434: try {
435: lock.wait();
436: } catch (InterruptedException ie) {
437: }
438: }
439: node.paint(rg);
440: canvasConsumed = false;
441: canvasUpdateListener.updateComplete(this );
442: }
443: }
444: }
445:
446: /**
447: * Utility method invoked when a node and its children need
448: * to be painted. This is used, for example, when a node
449: * which requires full loading before rendering is finally
450: * fully loaded.
451: *
452: * @param node the node to paint fully, i.e, including its
453: * children.
454: */
455: protected void fullNodePaint(final ModelNode node) {
456: if (node.canRenderState == 0) {
457: synchronized (lock) {
458: if (!canvasConsumed) {
459: try {
460: lock.wait();
461: } catch (InterruptedException ie) {
462: }
463: }
464: node.paint(rg);
465: canvasConsumed = false;
466: canvasUpdateListener.updateComplete(this );
467: }
468: }
469: }
470:
471: /**
472: * Utility method to paint the input tree up to, but not
473: * including the needLoadNode. This is a recursive method
474: * which should be called with the root of the tree to
475: * be painted.
476: *
477: * @param node the node to paint next.
478: */
479: protected void partialPaint(final ModelNode node) {
480: if (node == needLoadNode || (node.canRenderState != 0)) {
481: return;
482: }
483:
484: if (node.hasNodeRendering()) {
485: synchronized (lock) {
486: node.paint(rg);
487: }
488: } else {
489: ModelNode child = node.getFirstExpandedChild();
490: while (child != null) {
491: partialPaint(child);
492: child = child.nextSibling;
493: }
494:
495: child = node.getFirstChildNode();
496: while (child != null) {
497: partialPaint(child);
498: child = child.nextSibling;
499: }
500: }
501: }
502: }
|