FileDocCategorySizeDatePackage
SVGCanvas.javaAPI DocphoneME MR2 API (J2ME)27489Wed May 02 18:00:36 BST 2007com.sun.perseus.midp

SVGCanvas

public class SVGCanvas extends javax.microedition.lcdui.Canvas implements com.sun.perseus.model.CanvasUpdateListener
This class provides support for an LCDUI Canvas extension which can display an SVG Document.
version
$Id: SVGCanvas.java,v 1.16 2006/04/21 06:40:56 st125089 Exp $

Fields Summary
public static final int
CLEAR_COLOR
Color used to clear the canvas' background.
public static final int
STATE_STOPPED
Initial state.
public static final int
STATE_PLAYING
Playing state, i.e., playing animations and repainting buffer.
public static final int
STATE_PAUSED
Paused state, i.e., repainting buffer but no longer advancing the time.
public static final int
SMIL_ANIMATION_FRAME_LENGTH
SMIL Animation's frame length, in milliseconds
protected int
lastX
Last x position on a pointer pressed event.
protected int
lastY
Last y position on a pointer pressed event.
protected boolean
lastWasPressed
True if the last pointer event was a pointer pressed event.
protected int
state
The current player state.
protected com.sun.perseus.model.SimpleCanvasManager
canvasManager
The SimpleCanvasManager manages the area where the SVG content is rendered.
protected com.sun.perseus.model.DocumentNode
documentNode
This component displays a DocumentNode object, which is built from the URI
protected com.sun.pisces.NativeSurface
offscreen
Offscreen image
protected com.sun.pisces.GraphicsSurfaceDestination
gsd
Used to blit the offscreen onto the graphics destination.
protected int
offscreenWidth
Offscreen width
protected int
offscreenHeight
Offscreen height
protected com.sun.pisces.PiscesRenderer
pr
The PiscesRenderer associated with the offscreen.
protected com.sun.perseus.j2d.RenderGraphics
rg
RenderGraphics used to draw into the current offscreen
protected javax.microedition.m2g.SVGEventListener
svgEventListener
The associated SVGEventListener.
protected com.sun.perseus.util.RunnableQueue
updateQueue
The RunnableQueue is the _only_ valid way to access the model tree. No access to the model should be done other than from the RunnableQueue thread.
protected com.sun.perseus.model.SMILSample
smilSample
The animation sampler, which runs animations in the update thread.
protected SMILSample.DocumentWallClock
clock
The animation clock.
protected float
timeIncrement
The time increment for the animation.
protected com.sun.perseus.model.ModelNode
lastMouseTarget
The last mouse event target.
private boolean
ignoreCanvasUpdate
Boolean flag used to control when the SVGCanvas ignores a canvas manager update because it asked for a a full paint in response to a prior repaint. This avoid queuing an extra initial repaint() when building a new offscreen buffer.
Constructors Summary
public SVGCanvas(com.sun.perseus.model.DocumentNode documentNode)

param
documentNode the documentNode this component will render. The input DocumentNode must be fully loaded before this method is called. Note: if the DocumentNode already has an associated RunnableQueue, it is simply replaced. It is the responsibility of the caller to stop that RunnableQueue if need be.
throws
IllegalArgumentException see {@link #setURI setURI}.


                                                                                           
        
        if (documentNode == null) {
            throw new NullPointerException();
        }

        if (!documentNode.isLoaded()) {
            throw new IllegalStateException();
        }

        this.documentNode = documentNode;

        // Set-up RunnableQueue
        updateQueue = RunnableQueue.getDefault();
        documentNode.setUpdateQueue(updateQueue);

        // Hook in the SimpleCanvasManager after creating the offscreen buffer.
        buildOffscreen(1, 1);
        canvasManager = new SimpleCanvasManager(rg,
                                                documentNode, 
                                                this);
        canvasManager.turnOff(); // disabled until we call play or pause.
        documentNode.setRunnableHandler(canvasManager);

        // Create a SMILSample instance that will be scheduled with the 
        // RunnableQueue whenever the component plays.
        clock = new SMILSample.DocumentWallClock(documentNode);
        smilSample = new SMILSample(documentNode, clock);

        // Initialize the timing engine.
        documentNode.initializeTimingEngine();

        // Apply animations at time 0
        documentNode.sample(new Time(0));
        documentNode.applyAnimations();
    
Methods Summary
protected voidbuildOffscreen(int width, int height)
The offscreen buffer has the size of the component. This method is called in the MIDP painting thread.

param
width the requested minimum buffer width
param
height the requested minimum buffer height

        if (width > 0 && height > 0) {
            // We build an offscreen of the requested size.
            offscreen = new NativeSurface(width, height);
            offscreenWidth = width;
            offscreenHeight = height;
        } else {
            // This is a degenerate case, just build with 1x1 offscreen
            if (offscreenWidth == 1 && offscreenHeight == 1) {
                return;
            }

            offscreen = new NativeSurface(1, 1);
            offscreenWidth = 1;
            offscreenHeight = 1;
        }

        // Build a new PiscesRenderer for the new rendering surface.
        pr = new PiscesRenderer(offscreen,
                                offscreenWidth, offscreenHeight, 0, offscreenWidth, 1,
                                RendererBase.TYPE_INT_ARGB);
        
        // Build a corresponding RenderGraphics
        rg = new RenderGraphics(pr, offscreenWidth, offscreenHeight);
        
        if (canvasManager != null) {
            // We need to force painting the offscreen buffer.
            // Offscreen buffer rendering happens in the update 
            // thread.
            try {
                updateQueue.invokeAndWait(new Runnable() {
                        public void run() {
                            synchronized (canvasManager.lock) {
                                // Automatically adjust the SVG image's viewport size.
                                documentNode.setSize(width, height);
                                
                                // Switch the SimpleCanvasManager to the new RenderGraphics
                                canvasManager.setRenderGraphics(rg);
                                
                                // Set the consumed flag to true to force painting 
                                // immediately.
                                canvasManager.consume();
                            }

                            // Now, update the new canvas.
                            // We set the ignoreCanvasUpdate flag to true so that the 
                            // canvas update does not trigger a repaint() request.
                            ignoreCanvasUpdate = true;
                            canvasManager.updateCanvas();
                            ignoreCanvasUpdate = false;
                        }
                    }, null);
            } catch (InterruptedException ie) {
                // This is a serious error, because it means the 
                // default Runnable Queue thread has been 
                // interrupted.                    
                ie.printStackTrace();
            }
        } else {
            pr.setColor(255, 255, 255);
            pr.clearRect(0, 0, offscreenWidth, offscreenHeight);
        }
    
protected voidcheckOffscreen()
Checks if the offscreen buffer needs to be built or rebuilt.

        if (offscreen == null) {
            // This is the very first time we build an offscreen.
            buildOffscreen(getWidth(), getHeight());
        } else {            
            // Check that the offscreen is large enough for the current size.
            int width = getWidth();
            int height = getHeight();

            // We use an offscreen size with is the smallest of the viewport
            // size and the canvas size. 
            if (width > documentNode.getWidth()) {
                width = documentNode.getWidth();
            }

            if (height > documentNode.getHeight()) {
                height = documentNode.getHeight();
            }

            if (width != offscreenWidth
                ||
                height != offscreenHeight) {
                buildOffscreen(width, height);
            }
        }
    
protected voiddispatchKeyEvent(java.lang.String eventType, int keyCode)
Dispatches a key event to the DOM tree.

param
eventType the DOM event type.
param
keyCode the key code.

        Runnable r = new Runnable() {
                public void run() {
                    documentNode.dispatchEvent
                        (new ModelEvent(eventType,
                                        documentNode, (char) keyCode));
                }
            };

        if (state != STATE_STOPPED) {
            invokeLater(r);
        }
    
protected voiddispatchPointerEvent(java.lang.String eventType, float[] pt)
Dispatches a mouse event to the DOM tree.

param
eventType the DOM event type.
param
pt the mouse event coordinates.

        if (state == STATE_STOPPED) {
            return;
        }

        invokeLater(new Runnable() {
                public void run() {
                    ModelNode target = documentNode.nodeHitAt(pt);
                    if (target == null) {
                        target = documentNode;
                    }
                    
                    // If the target is different from the lastMouseTarget
                    // dispatch a 'mouseout' event to the lastMouseTarget
                    // and dispatch a 'mouseover' to the new target
                    if (lastMouseTarget != target) {
                        if (lastMouseTarget != null && lastMouseTarget != documentNode) {
                            ModelEvent e = 
                                new ModelEvent(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, 
                                               lastMouseTarget);
                            documentNode.dispatchEvent(e);
                        }
                        ModelEvent e = 
                            new ModelEvent(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, 
                                           target);
                        documentNode.dispatchEvent(e);
                        lastMouseTarget = target;
                    }
                    
                    // Map the event type
                    // Build the DOM Event
                    ModelEvent evt = new ModelEvent(eventType, target);
                    
                    // Dispatch to the target tree
                    documentNode.dispatchEvent(evt);
                }
            });
    
public floatgetTimeIncrement()
Get the current time increment for animation rendering. The SVGAnimator increments the SVG document's current time by this amount upon each rendering. The default value is 0.1 (100 milliseconds).

return
the current time increment, in seconds, used for animation rendering.
see
#setTimeIncrement

        return timeIncrement;
    
protected voidhideNotify()
Invoked when the component is hidden.

        if (svgEventListener != null) {
            svgEventListener.hideNotify();
        }
    
public voidinitialLoadComplete(java.lang.Exception e)
Called by the SimpleCanvasManager when the initial load is complete. This method is called in the RunnableQueue thread.

param
e if not null, it means that the initial load failed due to this exception.

        if (e != null) {
            e.printStackTrace();
        }
    
voidinvokeAndWait(java.lang.Runnable runnable)
Invoke the Runnable in the Document update thread and return only after this Runnable has finished.

param
runnable the new Runnable to invoke.
throws
InterruptedException if the current thread is waiting, sleeping, or otherwise paused for a long time and another thread interrupts it.
throws
NullPointerException if runnable is null.
throws
IllegalStateException if the animator is in the stopped state.

        if (runnable == null) {
            throw new NullPointerException();
        }
        
        if (state == STATE_STOPPED) {
            throw new IllegalStateException
                (Messages.formatMessage(Messages.ERROR_INVALID_STATE,
                                        new Object[] {getClass().getName(),
                                                      stateToString(),
                                                      "invokeAndWait()",
                                                      "paused, playing"}));
        }

        updateQueue.invokeAndWait(runnable, canvasManager);
    
voidinvokeLater(java.lang.Runnable runnable)
Schedule execution of the input Runnable in the update thread at a later time.

param
runnable the new Runnable to execute in the Document's update thread when time permits.
throws
NullPointerException if runnable is null.
throws
IllegalStateException if the animator is in the stopped state.

        if (runnable == null) {
            throw new NullPointerException();
        }
        
        if (state == STATE_STOPPED) {
            throw new IllegalStateException
                (Messages.formatMessage(Messages.ERROR_INVALID_STATE,
                                        new Object[] {getClass().getName(),
                                                      stateToString(),
                                                      "invokeLater()",
                                                      "paused, playing"}));
        }

        updateQueue.invokeLater(runnable, canvasManager);
    
protected voidkeyPressed(int keyCode)
Invoked when a key has been pressed.

param
keyCode the code of the event key

        if (svgEventListener != null) {
            svgEventListener.keyPressed(keyCode);
        }
        dispatchKeyEvent(SVGConstants.SVG_KEYDOWN_EVENT_TYPE,
                         keyCode);
    
protected voidkeyReleased(int keyCode)
Invoked when a key has been released.

param
keyCode the code of the event key

        if (svgEventListener != null) {
            svgEventListener.keyReleased(keyCode);
        }
        dispatchKeyEvent(SVGConstants.SVG_KEYUP_EVENT_TYPE,
                         keyCode);
    
protected voidpaint(javax.microedition.lcdui.Graphics g)

see
javax.microedition.lcdui.Canvas#paint

    
        checkOffscreen();
        int x = g.getClipX();
        int y = g.getClipY();
        int w = g.getClipWidth();
        int h = g.getClipHeight();

        synchronized (canvasManager.lock) {
            if (x!= 0 
                || 
                y != 0 
                || 
                w != documentNode.getWidth()
                ||
                h != documentNode.getHeight()) {
                // The repaint area is not exactly the same as the viewport area
                // so we need to clear the background first.
                g.setColor(CLEAR_COLOR);
                g.fillRect(x, y, w, h);
            }

            if (gsd == null) {
                gsd = new GraphicsSurfaceDestination(g);
            }
            gsd.drawSurface(offscreen, 0, 0, 0, 0, offscreenWidth, offscreenHeight, 1);
            canvasManager.consume();
        }
    
public voidpause()
Transition this SVGAnimator to the paused state. The SVGAnimator stops advancing the document's current time automatically (see the SVGDocument's setCurrentTime method). In consequence, animation playback will be paused until another call to the play method is made, at which points animations will resume from the document's current time. SVGImage updates (through API calls) cause a rendering update while the SVGAnimator is in the paused state.

throws
IllegalStateException if the animator is not in the playing state.

        if (state != STATE_PLAYING) {
            throw new IllegalStateException
                (Messages.formatMessage(Messages.ERROR_INVALID_STATE,
                                        new Object[] {getClass().getName(),
                                                      stateToString(),
                                                      "pause()",
                                                      "playing"}));
        }

        state = STATE_PAUSED;
        
        // Mark the document as _not_ playing.
        updateQueue.preemptLater(new Runnable() {
                public void run() {
                    documentNode.setPlaying(false);
                }
            }, canvasManager);

        // Remove the SMILSampler
        updateQueue.unschedule(smilSample);

        // Turn on any updates to the offscreen canvas.
        canvasManager.turnOn();

    
public voidplay()
Transition this SVGAnimator to the playing state. In the playing state, both Animation and SVGImage updates cause rendering updates. Note that in the playing state, when the document's current time changes, the animator will seek to the new time, and continue to play animations from this place.

throws
IllegalStateException if the animator is not currently in the stopped or paused state.

        if (state == STATE_PLAYING) {
            throw new IllegalStateException
                (Messages.formatMessage(Messages.ERROR_INVALID_STATE,
                                        new Object[] {getClass().getName(),
                                                      stateToString(),
                                                      "play()",
                                                      "stopped, paused"}));
        }

        // Mark the document as playing.
        updateQueue.preemptLater(new Runnable() {
                public void run() {
                    documentNode.setPlaying(true);
                }
            }, canvasManager);

        // Now, schedule the SMILSampler
        clock.start();
        updateQueue.scheduleAtFixedRate(smilSample, canvasManager, (long) (1000 * timeIncrement));

        state = STATE_PLAYING;

        // Turn on any updates to the offscreen canvas.
        canvasManager.turnOn();
    
protected voidpointerPressed(int x, int y)
Invoked when a mouse button has been pressed on a component.

param
x the x-axis coordinate of the pointer event
param
y the y-axis coordinate of the pointer event

        if (svgEventListener != null) {
            svgEventListener.pointerPressed(x, y);
        }

        lastX = x;
        lastY = y;
        lastWasPressed = true;

        float[] pt = {x, y};
        dispatchPointerEvent(SVGConstants.SVG_MOUSEDOWN_EVENT_TYPE, pt);
    
protected voidpointerReleased(int x, int y)
Invoked when a mouse button has been released on a component.

param
x the x-axis coordinate of the pointer event
param
y the y-axis coordinate of the pointer event

        if (svgEventListener != null) {
            svgEventListener.pointerReleased(x, y);
        }

        float[] pt = {x, y};
        dispatchPointerEvent(SVGConstants.SVG_MOUSEUP_EVENT_TYPE, pt);

        if (lastWasPressed && lastX == x && lastY == y) {
            dispatchPointerEvent(SVGConstants.SVG_CLICK_EVENT_TYPE, pt);
        }

        lastWasPressed = false;
    
public voidsetSVGEventListener(javax.microedition.m2g.SVGEventListener svgEventListener)
Associate the specified SVGEventListener with this SVGAnimator.

param
svgEventListener the SVGEventListener that will receive events forwarded by this SVGAnimator. If null, events will not be forwarded by the SVGAnimator.

        this.svgEventListener = svgEventListener;
    
public voidsetTimeIncrement(float timeIncrement)
Set the time increment to be used for animation rendering.

param
timeIncrement the minimal period of time, in seconds, that should elapse between frame. Must be greater than zero.
throws
IllegalArgumentException if timeIncrement is less than or equal to zero.
see
#getTimeIncrement

        if (timeIncrement <= 0) {
            throw new IllegalArgumentException();
        }

        this.timeIncrement = timeIncrement;

        if (state == STATE_PLAYING) {
            updateQueue.unschedule(smilSample);            
            updateQueue.scheduleAtFixedRate(smilSample, canvasManager, (long) (1000 * timeIncrement));
        }
    
protected voidshowNotify()
Invoked when the component is shown.

        if (svgEventListener != null) {
            svgEventListener.showNotify();
        }
    
protected voidsizeChanged(int w, int h)
Invoked when the component's size changes.

param
w the new width
param
h the new height

        if (svgEventListener != null) {
            svgEventListener.sizeChanged(w, h);
        }
    
java.lang.StringstateToString()
Helper method. Converts the current state to a String.

        switch (state) {
        case STATE_PLAYING:
            return "playing";
        case STATE_PAUSED:
            return "paused";
        case STATE_STOPPED:
        default:
            return "stopped";
        }
    
public voidstop()
Transition this SVGAnimator to the stopped state. In this state, no rendering updates are performed.

throws
IllegalStateException if the animator is not in the playing or paused state.

        if (state == STATE_STOPPED) {
            throw new IllegalStateException
                (Messages.formatMessage(Messages.ERROR_INVALID_STATE,
                                        new Object[] {getClass().getName(),
                                                      stateToString(),
                                                      "stop()",
                                                      "paused, playing"}));
        }
        
        state = STATE_STOPPED;
        
        // Remove the SMILSampler
        updateQueue.unschedule(smilSample);

        // Mark the document as _not_ playing.
        documentNode.setPlaying(false);

        // To unlock the canvasManager if it is waiting on the 
        // consumed flag.
        canvasManager.consume();

        // Turn off any updates to the offscreen canvas.
        canvasManager.turnOff();
    
public voidupdateComplete(java.lang.Object canvasManager)
Invoked by the SimpleCanvasManager when it is done updating the canvas. This is used during the progressive rendering loading phase and when a Runnable has been invoked on the RunnableQueue associated with the SVG image. This method is called in the RunnableQueue thread.

param
canvasManager the SimpleCanvasManager which is reporting the update.

        if (!ignoreCanvasUpdate) {
            repaint(0, 0, documentNode.getWidth(), documentNode.getHeight());
        }