/* * $RCSfile: CanvasManager.java,v $ * * Copyright 1990-2009 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.RenderContext; import com.sun.perseus.j2d.RenderGraphics; import java.io.InputStream; import com.sun.perseus.util.RunnableQueue; import com.sun.perseus.util.RunnableQueue.RunnableHandler; import com.sun.perseus.j2d.RGB; import com.sun.perseus.j2d.Transform; /** * <p>The <code>CanvasManager</code> class is responsible for * keeping the rendering of a <code>ModelNode</code> tree on a * <code>RenderGraphics</code> current.</p> * * <p>Specifically, the <code>CanvasManager</code> listens to * update events in a <code>ModelNode</code> tree and * triggers repaint into the <code>RenderGraphics</code> when * necessary.</p> * * <p>The <code>CanvasManager</code> optimizes rendering * of the tree while the document is in loading phase.</p> * * @version $Id: CanvasManager.java,v 1.17 2006/07/13 00:55:57 st125089 Exp $ */ public class CanvasManager extends SimpleCanvasManager { /** * True while the component is processing a document * which is in the loading phase, i.e., between * the <code>UpdateListener</code>'s <code>loadStarting</code> * and <code>loadComplete</code> calls. */ protected boolean loading; /** * Progressive painting is needed when a node has * started loading and has been inserted into the tree. * This is only used during the loading phase of * a document when doing progressive rendering. * The next node to paint progressively */ protected ModelNode progressiveNode = null; /** * Tracks the highest level node whose load completion * is needed to proceed with progressive rendering. * When loading this node completes, then the node * is painted. */ protected ModelNode needLoadNode = null; /** * The associated SMILSampler, if animations are run. */ protected SMILSample sampler = null; /** * The rate for SMIL animation. The smilRate is the minimum time between * SMIL samples. */ protected long smilRate = 40; /** * @param rg the <code>RenderGraphics</code> which this * instance will keep up to date with the * model changes. * @param documentNode the <code>DocumentNode</code>, root of the * tree that this <code>CanvasManager</code> will * draw and keep current on the <code>RenderGraphics</code> * @param canvasUpdateListener the <code>CanvasUpdateListener</code> * which listens to completed updates on the associated * <code>RenderGraphics</code> * * @throws IllegalArgumentException if rg, documentNode or listener is null. */ public CanvasManager(final RenderGraphics rg, final DocumentNode documentNode, final CanvasUpdateListener canvasUpdateListener) { super(rg, documentNode, canvasUpdateListener); } /** * Invoked when a node has been inserted into the tree * * @param node the newly inserted node */ public void nodeInserted(final ModelNode node) { if (loading) { if (needLoadNode == null) { // Progressive rendering is _not_ suspended // If this node's parent is already loaded, // it means we are dealing with a node insertion // resulting from reference resolution. We need // to repaint the document in its current state. if (node.parent.loaded) { fullPaint(); } else { // Check if this node suspends progressive // rendering. if (!node.getPaintNeedsLoad()) { if (progressiveNode != null) { needRepaint = true; } else { progressiveNode = node; } } else { needLoadNode = node; } } } else { // Progressive rendering _is_ suspended // We are loading a document and progressive // repaint is disabled. However, the newly // inserted node might be a ElementNodeProxy // child of a Use element which is referencing // content under the current needLoadNode. // In that situation, we need to do a repaint // of the document up to, but not including // the needLoadNode. ModelNode parent = node; while (parent != null) { if (parent == needLoadNode) { // We are under the disabled node, no // problem break; } parent = parent.parent; } if (parent == null) { // Re-render the document up to the current // needLoadNode needRepaint = true; } } } else { needRepaint = true; } } /** * @param node the node to test. * @return true if <code>node</code> is * a chid of the node currently holding up progressive * rendering. The caller must make sure <code>needNodeLoad</code> * is not null before calling this utility method. If called * when <code>needLoadNode</code> is null, the method returns * true. */ boolean isNeedLoadNodeOrChild(final ModelNode node) { ModelNode parent = node; while (parent != null) { if (parent == needLoadNode) { break; } parent = parent.parent; } if (parent == null) { return false; } return true; } /** * Invoked when a node is about to be modified. * * @param node the node which is about to be modified */ public void modifyingNode(final ModelNode node) { if (!isNeedLoadNodeOrChild(node) && ((node.hasNodeRendering() || node.hasDescendants()) && (node.canRenderState == 0))) { needRepaint = true; } } /** * Invoked when a node modification completed. * * @param node the node which was just modified. */ public void modifiedNode(final ModelNode node) { if (!loading) { if (!needRepaint && (node.hasNodeRendering() || node.hasDescendants())) { needRepaint = true; } } else { // Ignore modifications on nodes which have no // rendering and no descendants if (!node.hasNodeRendering() && !node.hasDescendants()) { return; } // We are doing progressive rendering. Check if // the modified node is the one currently suspended // or one of its children. // Modifications will be picked up when we // paint the node after it has finished // loading. if (needLoadNode != null) { if (node == needLoadNode) { return; } else { ModelNode parent = node.parent; while (parent != null) { if (parent == needLoadNode) { return; } parent = parent.parent; } needRepaint = true; } } else { if (!needRepaint) { // We modified a node which did not have node // rendering. if (progressiveNode != null && progressiveNode != node) { needRepaint = true; } progressiveNode = node; } } } } /** * Invoked when the input node has finished loading. * * @param node the <code>node</code> for which loading * is complete. */ public void loadComplete(final ModelNode node) { // System.err.println(">>>>>>>>>>>>>> loadComplete : " + node); if (node instanceof DocumentNode) { // We are finished with the loading phase. // Progressive rendering can stop loading = false; canvasUpdateListener.initialLoadComplete(null); // At this point, we are ready to start the animation loop. // We set the document's scheduled Runnable. // IMPL NOTE : We disable animations if there are no initial animations. // We should really only sample when there are // active animations, but animations can be added by scripts, so // we will need a more sophisticated mechanism. if (documentNode.updateQueue != null && documentNode.timeContainerRootSupport .timedElementChildren.size() > 0) { SMILSample.DocumentWallClock clock = new SMILSample.DocumentWallClock(documentNode); sampler = new SMILSample(documentNode, clock); documentNode.updateQueue.scheduleAtFixedRate(sampler, this, smilRate); documentNode.timeContainerRootSupport.initialize(); documentNode.updateQueue.preemptLater(new Runnable() { public void run() { documentNode.setPlaying(true); } }, this); clock.start(); } } else if (node == needLoadNode) { // We loaded a node fully. We can now display that // node and its children and proceed with progressive // rendering if (progressiveNode != null) { throw new Error(); } progressiveNode = node; needLoadNode = null; } updateCanvas(); } /** * Invoked when a document error happened before finishing loading. * * @param documentNode the <code>DocumentNode</code> for which loading * has failed. * @param error the exception which describes the reason why loading * failed. */ public void loadingFailed(final DocumentNode documentNode, final Exception error) { loading = false; canvasUpdateListener.initialLoadComplete(error); } /** * Invoked when the document starts loading * * @param documentNode the <code>DocumentNode</code> for which loading * is starting * @param is the <code>InputStream</code> from which SVG content * is loaded. */ public void loadStarting(final DocumentNode documentNode, final InputStream is) { loading = true; } /** * Invoked when the input node has started loading * * @param node the <code>ModelNode</code> for which loading * has started. */ public void loadBegun(final ModelNode node) { updateCanvas(); } /** * Invoked when a string has been appended, during a load * phase. This is only used when parsing a document and is * used in support of progressive download, like the other * loadXXX methods. * * @param node the <code>ModelNode</code> on which text has been * inserted. */ public void textInserted(final ModelNode node) { } /** * @return the associated SMILSampler, if animations are run. */ public SMILSample getSampler() { return sampler; } /** * Utility method used to update the canvas appropriately * depending on what is needed. * * During the loading phase, while we do progressive * rendering, the canvas will only redraw nodes in the * progressiveNodes list, unless a repaint has been * requested. * * Important Note: this method should only be called from * the update thread, i.e., the thread that also manages * the model node tree. */ public void updateCanvas() { if (!loading) { if (needRepaint) { if (canvasConsumed) { fullPaint(); needRepaint = false; } else { // There is a request to update the canvas // (likely after a Runnable was invoked), // but the last update was not consumed. // If there is a Runnable in the RunnableQueue, // we just skip this rendering update. Otherwise, // schedule a fake Runnable to force a later repaint. if (documentNode.getUpdateQueue().getSize() == 0) { documentNode.getUpdateQueue() .preemptLater(new Runnable() { public void run() { } }, this); } } } } else { if (needRepaint) { // A full repaint was requested. If there is no // suspended node, just do a full repaint. // Otherwise, do a partial paint if (needLoadNode == null) { fullPaint(); } else { partialPaint(documentNode); canvasUpdateListener.updateComplete(this); } } else if (progressiveNode != null) { progressivePaint(progressiveNode); } needRepaint = false; progressiveNode = null; } } /** * Utility method invoked when an incremental painting is needed * on a node. This may be invoked when a node was just inserted * into the tree or when a node which required full loading of * its children has been completely loaded. * * @param node the node to paint incrementally on the canvas */ protected void progressivePaint(final ModelNode node) { // If this node already has children, we need to do a fullNodePaint. // This happens for the <use> element when the <use> references // an element which appeared before in the document. if (node.hasDescendants()) { fullNodePaint(node); } else if (node.hasNodeRendering() && (node.canRenderState == 0)) { synchronized (lock) { if (!canvasConsumed) { try { lock.wait(); } catch (InterruptedException ie) { } } node.paint(rg); canvasConsumed = false; canvasUpdateListener.updateComplete(this); } } } /** * Utility method invoked when a node and its children need * to be painted. This is used, for example, when a node * which requires full loading before rendering is finally * fully loaded. * * @param node the node to paint fully, i.e, including its * children. */ protected void fullNodePaint(final ModelNode node) { if (node.canRenderState == 0) { synchronized (lock) { if (!canvasConsumed) { try { lock.wait(); } catch (InterruptedException ie) { } } node.paint(rg); canvasConsumed = false; canvasUpdateListener.updateComplete(this); } } } /** * Utility method to paint the input tree up to, but not * including the needLoadNode. This is a recursive method * which should be called with the root of the tree to * be painted. * * @param node the node to paint next. */ protected void partialPaint(final ModelNode node) { if (node == needLoadNode || (node.canRenderState != 0)) { return; } if (node.hasNodeRendering()) { synchronized (lock) { node.paint(rg); } } else { ModelNode child = node.getFirstExpandedChild(); while (child != null) { partialPaint(child); child = child.nextSibling; } child = node.getFirstChildNode(); while (child != null) { partialPaint(child); child = child.nextSibling; } } } }