javauis/m2g_qt/javasrc/com/nokia/microedition/m2g/M2GSVGAnimator.java
author hgs
Fri, 29 Oct 2010 11:49:32 +0300
changeset 87 1627c337e51e
parent 80 d6dafc5d983f
permissions -rw-r--r--
v2.2.21_1

/*
* Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

package com.nokia.microedition.m2g;

import javax.microedition.m2g.*;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.GameCanvas;
import java.lang.ref.WeakReference;
import com.nokia.mj.impl.rt.support.Finalizer;
import com.nokia.mj.impl.utils.Logger;


public class M2GSVGAnimator extends SVGAnimator
{
    //--------------------------------------------------
    // STATIC CONSTANTS
    //--------------------------------------------------
    private static final String ANIMATOR_CANVAS_BASE_CLASS =
        "javax.microedition.lcdui.Canvas";

    //--------------------------------------------------
    // VARIABLES
    //--------------------------------------------------
    private M2GSVGCanvas    iSVGCanvas      = null;
    private Finalizer mFinalizer;

    //--------------------------------------------------
    // METHODS
    //--------------------------------------------------
    /**
     * Constructor
     * @param aImage
     */
    protected M2GSVGAnimator(SVGImage aImage)
    {

        iSVGCanvas = new M2GSVGCanvas(false, aImage);
        mFinalizer = new Finalizer()
        {
            public void finalizeImpl()
            {

                doFinalize();
            }
        };

    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#getTargetComponent()
     */
    public Object getTargetComponent()
    {
        return iSVGCanvas;
    }

    /**
    * @see javax.microedition.m2g.SVGAnimator#getTimeIncrement()
    */
    public float getTimeIncrement()
    {
        return iSVGCanvas.getTimeIncrement();
    }

    /**
    * @see javax.microedition.m2g.SVGAnimator#invokeAndWait()
    */
    public void invokeAndWait(java.lang.Runnable runnable)
    {
        if (runnable == null)
        {
            throw new NullPointerException(
                /*SF*/"The runnable is null."/*SF*/);
        }
        if (iSVGCanvas.isStopped())
        {
            throw new IllegalStateException(
                /*SF*/"The animator is in the stopped state."/*SF*/);
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "invokeAndWait()");
        runnable.run();
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#invokeLater()
     */
    public void invokeLater(java.lang.Runnable runnable)
    {
        if (runnable == null)
        {
            throw new NullPointerException(
                /*SF*/"The runnable is null."/*SF*/);
        }
        if (iSVGCanvas.isStopped())
        {
            throw new IllegalStateException(
                /*SF*/"The animator is in the stopped state."/*SF*/);
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "invokeLater()");
        Thread thread = new Thread(runnable);
        thread.start();
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#pause()
     */
    public void pause()
    {
        if (!iSVGCanvas.isPlaying())
        {
            throw new IllegalStateException(
                /*SF*/"The animator is not in the playing or paused state."/*SF*/);
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "pause()");
        iSVGCanvas.pause();
    }

    /**
    * @see javax.microedition.m2g.SVGAnimator#play()
    */
    public void play()
    {

        if (iSVGCanvas.isPlaying())
        {
            throw new IllegalStateException(
                /*SF*/"The animator is not currently in the stopped or paused state."/*SF*/);
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "play()");

        iSVGCanvas.play();
    }

    private void doFinalize()
    {
        if (mFinalizer != null)
        {

            registeredFinalize();
            mFinalizer = null;
        }
    }

    /**
     * Finalize
     */
    synchronized void registeredFinalize()
    {
        iSVGCanvas.cancel();
        iSVGCanvas = null;
    }


    /**
     * @see javax.microedition.m2g.SVGAnimator#setSVGEventListener()
     */
    public void setSVGEventListener(SVGEventListener svgEventListener)
    {
        iSVGCanvas.setEventListener(svgEventListener);
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#setTimeIncrement()
     */
    public void setTimeIncrement(float timeIncrement)
    {

        if (timeIncrement <= 0)
        {
            throw new IllegalArgumentException(
                /*SF*/"The time increment is less than or equal to zero."/*SF*/);
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "setTimeIncrement() - "
                   + timeIncrement);
        iSVGCanvas.setTimeIncrement(timeIncrement);

    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#stop()
     */
    public void stop()
    {

        if (iSVGCanvas.isStopped())
        {
            throw new IllegalStateException(
                /*SF*/"The animator is not in the playing or paused state."/*SF*/);
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "stop()");

        iSVGCanvas.stop();

    }

    //--------------------------------------------------
    // STATIC METHODS
    //--------------------------------------------------
    /**
     * Builds animator
     * @param svgImage -
     * @return SVGAnimator
     * @see javax.microedition.m2g.SVGAnimator#createAnimator()
     */
    public static SVGAnimator buildAnimator(SVGImage svgImage)
    {

        if (svgImage == null)
        {

            throw new NullPointerException();
        }

        return new M2GSVGAnimator(svgImage);
    }

    /**
     * Builds animator
     * @param svgImage -
     * @param componentBaseClass -
     * @return SVGAnimator
     * @see javax.microedition.m2g.SVGAnimator#createAnimator()
     */
    public static SVGAnimator buildAnimator(
        SVGImage svgImage, String componentBaseClass)
    {
        if (svgImage == null)
        {
            throw new NullPointerException();
        }
        if ((componentBaseClass != null) &&
                (!componentBaseClass.equals(ANIMATOR_CANVAS_BASE_CLASS)))
        {
            throw new IllegalArgumentException(
                /*SF*/"The requested componentBaseClass is not supported by the implementation."/*SF*/);
        }
        return buildAnimator(svgImage);
    }
}

//--------------------------------------------------
// OTHER CLASSES
//--------------------------------------------------

/**
 * Canvas
 */
class M2GSVGCanvas extends GameCanvas implements M2GDOMChangeObserver
{
    //--------------------------------------------------
    // STATIC CONSTANTS
    //--------------------------------------------------
    public static final int STATE_STOPPED = 1;
    public static final int STATE_PLAYING = 2;
    public static final int STATE_PAUSED  = 3;
    public static final float DEFAULT_DELTA_TIME = 0.1f;  // (10fps) - defined by specs

    //--------------------------------------------------
    // VARIABLES
    //--------------------------------------------------
    private int                 iState;
    private SVGImage            iSVGImage;
    private ScalableGraphics    iSg;
    private Graphics            iOffscreen      = null;
    private M2GSVGSVGElement    iRootElement    = null;

    private float               iDeltaTime;
    private Timer               iTimer          = null;
    private SVGCanvasTask       iTask           = null;

    private SVGEventListener    iEventListener  = null;

    /**
     * True if the GameCanvas is in background or false otherwise
     */
    private boolean iWasPlaying                 = false;

    //--------------------------------------------------
    // METHODS
    //--------------------------------------------------
    /**
     * @see javax.microedition.lcdui.game.GameCanvas#GameCanvas()
     */
    public M2GSVGCanvas(boolean aSuppressKeyEvents, SVGImage aSVGImage)
    {

        super(aSuppressKeyEvents);

        // get the instance to the Graphics of the offscreen buffer
        iOffscreen = getGraphics();

        iSVGImage     = aSVGImage;

        // down-casting to M2GDocument/M2GSVGSVGElement to have access to internal methods
        M2GDocument doc = (M2GDocument)iSVGImage.getDocument();
        iRootElement  = (M2GSVGSVGElement)iSVGImage.getDocument().getDocumentElement();


        iState = STATE_STOPPED;
        // Create render context
        iSg = ScalableGraphics.createInstance();
        iSg.setRenderingQuality(ScalableGraphics.RENDERING_QUALITY_HIGH);
        iDeltaTime = DEFAULT_DELTA_TIME;

        doc.registerDOMChangeObserver(this);



        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "Ctor - delta time:"
                   + iDeltaTime + ", state:" + iState);
    }

    /**
     * Handles any change in DOM.
     * <br>
     * While in PAUSE state, SVGAnimator must repaint any changes in SVGImage
     * done via the API (e.g. setTrait(), insertBefore())
     * <br>
     * @see M2GDOMChangeObserver.notifyDOMChange()
     * @since S60 3.2
     */
    public void notifyDOMChange()
    {
        if (isPaused())
        {
            repaint();
        }
    }

    /**
     * Cancel a timed task
     */
    public void cancel()
    {
        if (iTimer != null)
        {
            iTimer.cancel();
        }
        if (iTask != null)
        {
            iTask.cancel();
        }
        iTask = null;
        iTimer = null;
    }

    /**
     * Returns event listener
     * @return event listener
     */
    public synchronized SVGEventListener getEventListener()
    {
        return iEventListener;
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#getTimeIncrement()
     */
    public synchronized float getTimeIncrement()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "getTimeIncrement() - "
                   + iDeltaTime);
        return iDeltaTime;
    }

    /**
     * Increases the increment time of the SVGImage.
     * Increment is done only if the playing state is active.
     * @see javax.microedition.m2g.SVGImage#incrementTime()
     */
    public synchronized void increaseCurrentTime(float time)
    {
        if (iState == STATE_PLAYING)
        {
            // update the time only in java side
            // the time in engine side is updated during rendering
            iRootElement.incrementTimeNoUpdate(time);
        }
    }

    /**
    * Checks if playing
    * @return true if playing
    */
    public synchronized boolean isPlaying()
    {
        return iState == STATE_PLAYING;
    }

    /**
     * Checks if paused
     * @return true if paused
     */
    public synchronized boolean isPaused()
    {
        return iState == STATE_PAUSED;
    }

    /**
     * Checks if stopped
     * @return true if stopped
     */
    public synchronized boolean isStopped()
    {
        return iState == STATE_STOPPED;
    }

    /**
     * @see javax.microedition.lcdui.Canvas#keyPressed()
     */
    protected synchronized void keyPressed(int keyCode)
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "keyPressed() - " + keyCode);
        if (iEventListener != null)
        {
            iEventListener.keyPressed(keyCode);
        }
    }

    /**
     * @see javax.microedition.lcdui.Canvas#keyReleased()
     */
    protected synchronized void keyReleased(int keyCode)
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "keyReleased() - " + keyCode);
        if (iEventListener != null)
        {
            iEventListener.keyReleased(keyCode);
        }
    }

    /**
     * @see javax.microedition.lcdui.Canvas#pointerPressed()
     * @see javax.microedition.m2g.SVGEventListener#pointerPressed()
     */
    protected synchronized void pointerPressed(int x, int y)
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo,
                   "pointerPressed() - x:" + x + ", y:" + y);
        if (iEventListener != null)
        {
            iEventListener.pointerPressed(x, y);
        }
    }

    /**
     * @see javax.microedition.lcdui.Canvas#pointerReleased()
     * @see javax.microedition.m2g.SVGEventListener#pointerReleased()
     */
    protected synchronized void pointerReleased(int x, int y)
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo,
                   "pointerReleased() - x:" + x + ", y:" + y);
        if (iEventListener != null)
        {
            iEventListener.pointerReleased(x, y);
        }
    }

    /**
     * @see javax.microedition.lcdui.game.GameCanvas#paint()
     */
    public void paint(Graphics g)
    {
        // Clears bitmap

        g.setColor(255, 255, 255);
        g.fillRect(0, 0, getWidth(), getHeight());

        try
        {

            iSg.bindTarget(g);


            // NOTE: Source is defaultly fully opaque
            iSg.render(0, 0, iSVGImage);

        }
        finally
        {

            iSg.releaseTarget();

        }
    }

    /**
     * Paints a frame to the offscreen of this GameCanvas
     * @note GameCanvas.getGraphics() is not used since it always creates a new instance of Graphics
     */
    public void paintToOffscreen()
    {
        paint(iOffscreen);
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#setSVGEventListener()
     */
    public synchronized void setEventListener(SVGEventListener eventListener)
    {
        iEventListener = eventListener;
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#setTimeIncrement()
     */
    public synchronized void setTimeIncrement(float aDeltaTime)
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "setTimeIncrement() - "
                   + aDeltaTime);

        iDeltaTime = aDeltaTime;
    }


    /**
     * @see javax.microedition.lcdui.Canvas#showNotify()
     * @see javax.microedition.m2g.SVGEventListener#showNotify()
     */
    protected synchronized void showNotify()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "showNotify()");

        if (iEventListener != null)
        {
            iEventListener.showNotify();
        }

        // A common use-case could be that the developer plays the animation by themselves
        // in SVGEventListener.showNotify().
        // Therefore we play the animation only if the developer didn't resume it already.
        if (iWasPlaying)
        {
            iWasPlaying = false;
            play();
        }
    }

    /**
     * @see javax.microedition.lcdui.Canvas#hideNotify()
     * @see javax.microedition.m2g.SVGEventListener#hideNotify()
     */
    protected synchronized void hideNotify()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "hideNotify()");

        if (iEventListener != null)
        {
            iEventListener.hideNotify();
        }

        // A common use-case could be that developer pause the animation by themselves
        // in SVGEventListener.hideNotify().
        // Therefore we pause the animation only if the developer didn't pause it already.
        if (isPlaying())
        {
            pause();
            iWasPlaying = true;
        }
    }


    /**
     * @see javax.microedition.lcdui.Canvas#sizeChanged()
     * @see javax.microedition.m2g.SVGEventListener#sizeChanged()
     */
    protected synchronized void sizeChanged(int w, int h)
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "sizeChanged() - w:"
                   + w + ", h:" + h);

        // get a new instance of offscreen Graphics since the Graphics object
        // does not update its size or clipping area.
        // If not updating this, the GameCanvas is clipped when it is switched
        // to full-screen
        if (iOffscreen != null)
        {
            // sizeChanged() is called twice when the Canvas is set as Displayable.
            // If we try to get the Graphics object (first time), the Graphics object is not
            // fully initialized, so GameCanvas.getGraphics() will throw a NullPointerException
            iOffscreen = getGraphics();
        }

        if (iSVGImage != null)
        {
            iSVGImage.setViewportWidth(w);
            iSVGImage.setViewportHeight(h);
        }

        if (iEventListener != null)
        {
            iEventListener.sizeChanged(w, h);
        }
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#play()
     */
    public synchronized void play()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "play()");

        if (iState == STATE_PLAYING)
        {
            // don't do anything if animation is already playing
            return;
        }

        if ((iState == STATE_PAUSED)||(iState == STATE_STOPPED))
        {
            iTask = new SVGCanvasTask(this);
            iTimer = new Timer();
        }
        iState = STATE_PLAYING;
        if (iTimer != null && iTask != null)
        {

            iTimer.schedule(iTask,0,(long)(iDeltaTime*1000.0f));

        }
    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#pause()
     */
    public synchronized void pause()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "pause()");

        if (iState == STATE_PLAYING)
        {
            iTask.cancel();
            iTask = null;
            iTimer.cancel();
            iTimer = null;
        }
        iState = STATE_PAUSED;

    }

    /**
     * @see javax.microedition.m2g.SVGAnimator#stop()
     */
    public synchronized void stop()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "stop()");
        if (iState == STATE_PLAYING)
        {
            iTask.cancel();
            iTask = null;
            iTimer.cancel();
            iTimer = null;
        }
        iState = STATE_STOPPED;
    }
}

/**
 * Timer task for rendering frames
 */
class SVGCanvasTask extends TimerTask
{
    //--------------------------------------------------
    // VARIABLES
    //--------------------------------------------------
    // private WeakReference   iWeakCanvas;

    private M2GSVGCanvas    iCanvas                 = null;
    private long           iPrevTime        = 0;

    //--------------------------------------------------
    // METHODS
    //--------------------------------------------------
    /**
     * Constructor
     * @param aCanvas SVG canvas
     */
    public SVGCanvasTask(M2GSVGCanvas aCanvas)
    {
        // iWeakCanvas = new WeakReference(aCanvas);
        // iCanvas = (M2GSVGCanvas)iWeakCanvas.get();

        iCanvas = aCanvas;
        iPrevTime = System.currentTimeMillis();
    }
    /**
        * Updates the animation time and generates frames which get flushed to the screen
        * <br>
        * * @note The timing for the next frame is decided upon the max(iDeltaTime*1000, elapsedTime)
          * iDeltaTime:  FrameTime set by client.
          * elapsedTime: Actual Time taken for rendering
              the  max(iDeltaTime*1000, elapsedTime) will be incremented on the SVGElement
        * @note rendering will be done only in PLAY state.
        * @note While in PAUSE state, SVGAnimator will repaint any changes done
        * to SVGImage via <code>M2GDOMChangeObserver.notifyDOMChange()</code>
        */
    public void run()
    {
        Logger.LOG(Logger.EJavaUI, Logger.EInfo,
                   "SVGCanvasTask: run() - begin");

        if (iCanvas == null)
        {
            return;
        }

        try
        {
            synchronized (iCanvas)
            {
                long elapsedTime = System.currentTimeMillis()- iPrevTime;
                if (elapsedTime > 0)
                    iCanvas.increaseCurrentTime((float)(elapsedTime/ 1000.));
                iPrevTime = System.currentTimeMillis();
                iCanvas.paintToOffscreen();
                iCanvas.flushGraphics();
            }
        }
        catch (Exception e)
        {
            Logger.ELOG(Logger.EJavaUI,
                        "SVGCanvasTask: run() - exception: " + e.toString());
        }
        Logger.LOG(Logger.EJavaUI, Logger.EInfo, "SVGCanvasTask: run() - end");

    }
}