src/hbcore/image/hbiconanimation.cpp
changeset 0 16d8024aca5e
child 5 627c4a0fd0e7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hbcore/image/hbiconanimation.cpp	Mon Apr 19 14:02:13 2010 +0300
@@ -0,0 +1,687 @@
+/****************************************************************************
+**
+** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (developer.feedback@nokia.com)
+**
+** This file is part of the HbCore module of the UI Extensions for Mobile.
+**
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at developer.feedback@nokia.com.
+**
+****************************************************************************/
+
+#include "hbiconanimation_p.h"
+#include "hbimagetraces_p.h"
+#include "hbiconloader_p.h"
+#include "hbiconanimationmanager_p.h"
+#include "hbiconanimator.h"
+#include "hbiconanimator_p.h"
+#include "hbtimer_p.h"
+
+
+#include <QSvgRenderer>
+#include <QImageReader>
+#include <QPainter>
+#include <QPixmap>
+#include <QDebug>
+#include <QStyleOption>
+#include <QApplication>
+#include <QPalette>
+
+HbIconAnimation::HbIconAnimation(HbIconAnimator *animator, const QString &iconName) :
+    mIconName(iconName),
+    mMirrored(false),
+    mResolutionCorrected(false),
+    mAspectRatioMode(Qt::KeepAspectRatio),
+    mMode(QIcon::Normal),
+    mPlayMode(HbIconAnimationDefinition::PlayOnce),
+    mStartSignalTimer(0),
+    mFresh(true),
+    mAnimMgrD(HbIconAnimationManagerPrivate::d_ptr(HbIconAnimationManager::global())),
+    mColor(QColor()),
+    mAnimator(animator),
+    mView(0),
+    mPaused(false),
+    mPausedDueToBackground(false)
+    {
+    Q_ASSERT(!(animator->d->animation));
+    // Set the animation in the animator, it takes ownership of this object.
+    animator->d->animation = this;
+
+#ifdef HB_ICON_TRACES
+    qDebug() << "HbIconAnimation created: " << mIconName;
+#endif
+    }
+
+HbIconAnimation::~HbIconAnimation()
+{
+#ifdef HB_ICON_TRACES
+    qDebug() << "HbIconAnimation destroyed: " << mIconName;
+#endif
+    mAnimMgrD->animNotPlaying(this);
+}
+
+QString HbIconAnimation::iconName() const
+{
+    return mIconName;
+}
+
+QSizeF HbIconAnimation::size() const
+{
+    return mSize;
+}
+
+void HbIconAnimation::setSize(const QSizeF &size)
+{
+    // If size changed, invalidate calculated render size
+    if (size != mSize) {
+        mRenderSize = QSizeF();
+    }
+
+    mSize = size;
+}
+
+QColor HbIconAnimation::color() const
+{
+    return mColor;
+}
+	
+void HbIconAnimation::setColor(const QColor &color)
+{
+    mColor = color;
+}
+
+Qt::AspectRatioMode HbIconAnimation::aspectRatioMode() const
+{
+    return mAspectRatioMode;
+}
+
+void HbIconAnimation::setAspectRatioMode(Qt::AspectRatioMode mode)
+{
+    // If aspect ratio mode changed, invalidate calculated render size
+    if (mode != mAspectRatioMode) {
+        mRenderSize = QSizeF();
+    }
+
+    mAspectRatioMode = mode;
+}
+
+QIcon::Mode HbIconAnimation::mode() const
+{
+    return mMode;
+}
+
+void HbIconAnimation::setMode(QIcon::Mode mode)
+{
+    mMode = mode;
+}
+
+QSizeF HbIconAnimation::defaultSize() const
+{
+    return mDefaultSize;
+}
+
+void HbIconAnimation::setDefaultSize(const QSizeF &size)
+{
+    mDefaultSize = size;
+}
+
+QSizeF HbIconAnimation::renderSize() const
+{
+    if (mRenderSize.isEmpty()) {
+        const_cast<QSizeF &>(mRenderSize) = mDefaultSize;
+        if (!mSize.isEmpty()) {
+            const_cast<QSizeF &>(mRenderSize).scale(mSize, mAspectRatioMode);
+        }
+        // Apply resolution correction
+        if (mResolutionCorrected) {
+            HbIconLoader *loader = HbIconLoader::global();
+            loader->applyResolutionCorrection(const_cast<QSizeF &>(mRenderSize));
+        }
+    }
+
+    return mRenderSize;
+}
+
+void HbIconAnimation::setRenderSize(const QSizeF &size)
+{
+    mRenderSize = size;
+}
+
+bool HbIconAnimation::mirrored() const
+{
+    return mMirrored;
+}
+
+void HbIconAnimation::setMirrored(bool mirrored)
+{
+    mMirrored = mirrored;
+}
+
+bool HbIconAnimation::resolutionCorrected() const
+{
+    return mResolutionCorrected;
+}
+
+void HbIconAnimation::setResolutionCorrected(bool corrected)
+{
+    mResolutionCorrected = corrected;
+}
+
+HbIconAnimationDefinition::PlayMode HbIconAnimation::playMode() const
+{
+    return mPlayMode;
+}
+
+void HbIconAnimation::setPlayMode(HbIconAnimationDefinition::PlayMode playMode)
+{
+    this->mPlayMode = playMode;
+}
+
+void HbIconAnimation::delayedEmitStarted()
+{
+    if (!mStartSignalTimer) {
+        mStartSignalTimer = new QTimer(this);
+        connect(mStartSignalTimer, SIGNAL(timeout()), SLOT(notifyAnimationStarted()));
+    }
+    mStartSignalTimer->setSingleShot(true);
+    mStartSignalTimer->start(0);
+}
+
+void HbIconAnimation::notifyAnimationStarted()
+{
+    mAnimMgrD->animPlaying(this);
+    emit animationStarted();
+}
+
+void HbIconAnimation::notifyAnimationStopped()
+{
+    mAnimMgrD->animNotPlaying(this);
+    emit animationStopped();
+}
+
+void HbIconAnimation::notifyAnimationFinished()
+{
+    mAnimMgrD->animNotPlaying(this);
+    emit animationFinished();
+}
+
+// -----------------------------------------------------------------------------
+// Class HbIconAnimationSvg
+// -----------------------------------------------------------------------------
+HbIconAnimationSvg::HbIconAnimationSvg(
+    HbIconAnimator *animator,
+    const QString &iconName,
+    QSvgRenderer *renderer,
+    const QString &iconPath) :
+
+    HbIconAnimation(animator, iconName),
+    mSvgRenderer(renderer),
+    mIconPath(iconPath)
+{
+    connect(mSvgRenderer, SIGNAL(repaintNeeded()), this, SLOT(handleSvgAnimationUpdated()));
+}
+
+HbIconAnimationSvg::~HbIconAnimationSvg()
+{
+    delete mSvgRenderer;
+}
+
+int HbIconAnimationSvg::type() const
+{
+    return SVG;
+}
+
+void HbIconAnimationSvg::start()
+{
+    // TODO
+}
+
+void HbIconAnimationSvg::stop()
+{
+    // TODO
+}
+
+void HbIconAnimationSvg::pause()
+{
+    // TODO
+}
+
+void HbIconAnimationSvg::resume()
+{
+    // TODO
+}
+
+QPixmap HbIconAnimationSvg::currentFrame() const
+{
+    QSize pixelSize = renderSize().toSize();
+    if (!pixelSize.isEmpty()) {
+        QPixmap canvasPixmap(pixelSize);
+        canvasPixmap.fill(Qt::transparent);
+        QPainter painter(&canvasPixmap);
+        mSvgRenderer->render(&painter, QRectF(QPointF(), pixelSize));
+        painter.end();
+
+        // Apply mirroring if required
+        if (mMirrored) {
+            QTransform t;
+            t.scale(-1,1);
+            canvasPixmap = canvasPixmap.transformed(t);
+        }
+
+        // Apply mode
+        if (mMode != QIcon::Normal) {
+            QStyleOption opt(0);
+            opt.palette = QApplication::palette();
+            canvasPixmap = QApplication::style()->generatedIconPixmap(mMode, canvasPixmap, &opt);
+        }
+
+        return canvasPixmap;
+    } else {
+#ifdef HB_ICON_TRACES
+        qDebug() << "Warning: HbIconAnimationSvg::currentFrame() - SVG default size is empty.";
+#endif
+        return QPixmap();
+    }
+}
+
+void HbIconAnimationSvg::handleSvgAnimationUpdated()
+{
+#ifdef HB_ICON_TRACES
+    qDebug() << "HbIconAnimation updated: " << iconName();
+#endif
+
+    emit animationUpdated();
+}
+
+// -----------------------------------------------------------------------------
+// Class HbIconAnimationImage
+// -----------------------------------------------------------------------------
+HbIconAnimationImage::HbIconAnimationImage(
+    HbIconAnimator *animator,
+    const QString &iconName,
+    const QString &iconFileName,
+    QImageReader *renderer,
+    int type) :
+
+    HbIconAnimation(animator, iconName),
+    mImageRenderer(renderer),
+    mIconFileName(iconFileName),
+    mType(type),
+    mTimerEntry(0)
+{
+    // This class supports these types
+    Q_ASSERT(mType == MNG || mType == GIF);
+
+    // Read the first frame. QImageReader::read() must be called before
+    // QImageReader::nextImageDelay()
+    QImage img = mImageRenderer->read();
+
+    // Store the first frame in the current frame pixmap
+    mCurrentFrame = QPixmap::fromImage(img);
+
+    // Set default size based on the first frame
+    setDefaultSize(mCurrentFrame.size());
+
+    // Do not start the timer or initiate any signal emission here.
+    // Do it in start() instead, since mFresh is true by default.
+}
+
+HbIconAnimationImage::~HbIconAnimationImage()
+{
+    delete mImageRenderer;
+}
+
+int HbIconAnimationImage::type() const
+{
+    return mType;
+}
+
+inline void stopTimer(HbTimerSignalEntry *&entry)
+{
+    // entry may be non-null and may already be deleted, however this is not a
+    // problem because we avoid double deletion by checking the return value of
+    // unregisterEntry().
+    if (entry && HbTimer::instance()->unregisterEntry(entry)) {
+        delete entry;
+        entry = 0;
+    }
+}
+
+void HbIconAnimationImage::start()
+{
+    if (mStartSignalTimer && mStartSignalTimer->isActive()) {
+        return;
+    }
+    if (mFresh) {
+        mFresh = false;
+        mTimerInterval = mImageRenderer->nextImageDelay();
+        mTimerEntry = HbTimer::instance()->addTimeout(mTimerInterval, this, SLOT(handleAnimationUpdated()));
+        // Emit the signal later as nobody is connected at this point (if the
+        // animation is auto-started, because the start() comes straight after
+        // construction in that case).
+        delayedEmitStarted();
+    } else {
+        stopTimer(mTimerEntry);
+
+        // Recreate the image reader. It's slow but QImageReader::jumpToImage does not work.
+        delete mImageRenderer;
+        mImageRenderer = 0;
+        mImageRenderer = new QImageReader(mIconFileName, mType == MNG ? "MNG" : "GIF");
+    
+        // New image reader starts from the first frame. Handle animation update.
+        notifyAnimationStarted();
+        handleAnimationUpdated();
+    }
+}
+
+void HbIconAnimationImage::stop()
+{
+    // If there is still a pending 'started' signal then drop it.
+    if (mStartSignalTimer && mStartSignalTimer->isActive()) {
+        mStartSignalTimer->stop();
+    }
+
+    // Stop the animation and go to the last frame.
+    stopTimer(mTimerEntry);
+
+    // Has the last frame been read already?
+    if (!mLastFrame.isNull()) {
+        mCurrentFrame = mLastFrame;
+    } else {
+        // Read all frames until the last one.
+        // QImageReader::jumpToImage does not work so cannot jump to the last frame if it has not been read yet.
+        while (true) {
+            QImage img = mImageRenderer->read();
+            // Reached last frame?
+            if (!mImageRenderer->canRead()) {
+                mCurrentFrame = QPixmap::fromImage(img);
+                mLastFrame = mCurrentFrame;
+                break;
+            }
+        }
+    }
+
+    // Inform client to update display
+    emit animationUpdated();
+    
+    notifyAnimationStopped();
+}
+
+void HbIconAnimationImage::pause()
+{
+    stopTimer(mTimerEntry);
+}
+
+void HbIconAnimationImage::resume()
+{
+    if (!mTimerEntry) {
+        mTimerEntry = HbTimer::instance()->addTimeout(mTimerInterval, this, SLOT(handleAnimationUpdated()));
+    }
+}
+
+QPixmap HbIconAnimationImage::currentFrame() const
+{
+    QSize pixelSize = renderSize().toSize();
+    QPixmap canvasPixmap;
+
+    if (!pixelSize.isEmpty()) {
+        if (pixelSize != mCurrentFrame.size()) {
+            canvasPixmap = mCurrentFrame.scaled(pixelSize, mAspectRatioMode, Qt::SmoothTransformation);
+        } else {
+            canvasPixmap = mCurrentFrame;
+        }
+
+        // Apply mirroring if required
+        if (mMirrored) {
+            QTransform t;
+            t.scale(-1,1);
+            canvasPixmap = canvasPixmap.transformed(t);
+        }
+
+        // Apply mode
+        if (mMode != QIcon::Normal) {
+            QStyleOption opt(0);
+            opt.palette = QApplication::palette();
+            canvasPixmap = QApplication::style()->generatedIconPixmap(mMode, canvasPixmap, &opt);
+        }
+
+        return canvasPixmap;
+    } else {
+#ifdef HB_ICON_TRACES
+        qDebug() << "Warning: HbIconAnimationImage::currentFrame() - default size is empty.";
+#endif
+        return QPixmap();
+    }
+}
+
+void HbIconAnimationImage::handleAnimationUpdated()
+{
+    bool finished = false;
+
+    // Read the new frame. QImageReader::read() must be called before
+    // QImageReader::nextImageDelay()
+    QImage img = mImageRenderer->read();
+
+    int delay = mImageRenderer->nextImageDelay();
+    // If there is next image, restart the timer
+    if (!img.isNull() && delay > 0) {
+        // Restart timer
+        mTimerInterval = delay;
+        mTimerEntry = HbTimer::instance()->addTimeout(mTimerInterval, this, SLOT(handleAnimationUpdated()));
+    }
+
+    // Store the new frame in the current frame pixmap
+    if (!img.isNull()) {
+        mCurrentFrame = QPixmap::fromImage(img);
+    }
+    // Reached the last frame. Store it so it can be used by stop().
+    else {
+        mLastFrame = mCurrentFrame;
+        finished = true;
+    }
+
+#ifdef HB_ICON_TRACES
+    qDebug() << "HbIconAnimation updated: " << iconName();
+#endif
+
+    // Inform client
+    emit animationUpdated();
+
+    if (finished) {
+        notifyAnimationFinished();
+    }
+}
+
+HbIconAnimationFrameSet::HbIconAnimationFrameSet(
+    HbIconAnimator *animator, const QString &iconName,const QList<FrameData> &frames) :
+        HbIconAnimation(animator, iconName),
+        mFrames(frames),
+        mCurrentFrameIndex(0),
+        mTimerEntry(0)
+{
+    // Do not start the timer or initiate any signal emission here.
+    // Do it in start() instead, since mFresh is true by default.
+}
+
+HbIconAnimationFrameSet::~HbIconAnimationFrameSet()
+{
+    mFrames.clear(); // This destroys the pixmaps
+}
+
+int HbIconAnimationFrameSet::type() const
+{
+    return FrameSet;
+}
+
+void HbIconAnimationFrameSet::resetJumpCount(FrameData &frame)
+{
+    for (int i = 0, ie = frame.jumps.count(); i != ie; ++i) {
+        // Note that the default value of execCount is 0 because frames are
+        // shown once anyway so if there is a loop element with count="1"
+        // then the frames need to be shown twice (because it "loops once").
+        frame.jumps[i].execCount = 0;
+    }
+}
+
+void HbIconAnimationFrameSet::resetJumpCounts()
+{
+    for (int i = 0, ie = mFrames.count(); i != ie; ++i) {
+        resetJumpCount(mFrames[i]);
+    }
+}
+
+void HbIconAnimationFrameSet::start()
+{
+    if (mStartSignalTimer && mStartSignalTimer->isActive()) {
+        return;
+    }
+#ifdef HB_ICON_TRACES
+    qDebug("HbIconAnimationFrameSet::start() mFresh=%d", mFresh);
+#endif
+    if (mFresh) {
+        mFresh = false;
+        mTimerInterval = mFrames.at(0).duration;
+        mTimerEntry = HbTimer::instance()->addTimeout(mTimerInterval, this, SLOT(animationTimeout()));
+        // Emit the signal later as nobody is connected at this point (if the
+        // animation is auto-started, because the start() comes straight after
+        // construction in that case).
+        delayedEmitStarted();
+    } else {
+        stopTimer(mTimerEntry);
+        resetJumpCounts();
+        // Go to first frame and handle animation update
+        mCurrentFrameIndex = -1;
+        notifyAnimationStarted();
+        animationTimeout();
+    }
+}
+
+void HbIconAnimationFrameSet::stop()
+{
+    stopTimer(mTimerEntry);
+    // Go to last frame and handle animation update
+    mCurrentFrameIndex = mFrames.count() - 1;
+    emit animationUpdated();
+    notifyAnimationStopped();
+}
+
+void HbIconAnimationFrameSet::pause()
+{
+    stopTimer(mTimerEntry);
+    mPaused = true;
+    mPausedDueToBackground = false;
+}
+
+void HbIconAnimationFrameSet::resume()
+{
+    if (!mTimerEntry) {
+        mTimerEntry = HbTimer::instance()->addTimeout(mTimerInterval, this, SLOT(animationTimeout()));
+    }
+    mPaused = false;
+    mPausedDueToBackground = false;
+}
+
+QPixmap HbIconAnimationFrameSet::currentFrame() const
+{
+    if (mFrames.count()) {
+        QPixmap pm = mFrames.at(mCurrentFrameIndex).pixmap;
+
+        // Mirroring is not needed here. Frames are mirrored if needed when they are loaded.
+
+        // Apply mode
+        if (mMode != QIcon::Normal) {
+            QStyleOption opt(0);
+            opt.palette = QApplication::palette();
+            pm = QApplication::style()->generatedIconPixmap(mMode, pm, &opt);
+        }
+
+        return pm;
+    } else {
+        return QPixmap();
+    }
+}
+
+void HbIconAnimationFrameSet::moveToNextFrame()
+{
+    if (mCurrentFrameIndex >= 0 && mCurrentFrameIndex < mFrames.count()) {
+        for (int i = 0, ie = mFrames[mCurrentFrameIndex].jumps.count(); i != ie; ++i) {
+            JumpData &jumpData(mFrames[mCurrentFrameIndex].jumps[i]);
+            if (jumpData.execCount < jumpData.repeatCount) {
+                ++jumpData.execCount;
+                // Before returning, the exec counts of all previous jumps in
+                // this frame must be resetted, because they were caused by
+                // <loop> elements that were embedded into the <loop> that
+                // generated this jump.
+                for (int j = 0; j < i; ++j) {
+                    mFrames[mCurrentFrameIndex].jumps[j].execCount = 0;
+                }
+                // And similarly, all jumps in frames that fall between the
+                // target (incl.) and the current frame (excl.) must be
+                // resetted. Note that jumping forward is not supported and such
+                // jumps are never generated by the animation xml parser.
+                for (int j = jumpData.targetFrameIndex; j < mCurrentFrameIndex; ++j) {
+                    resetJumpCount(mFrames[j]);
+                }
+                mCurrentFrameIndex = jumpData.targetFrameIndex;
+                return;
+            }
+        }
+    }
+
+    ++mCurrentFrameIndex;
+}
+
+void HbIconAnimationFrameSet::animationTimeout()
+{
+    // Frame delay has passed, pick the next frame index.
+    moveToNextFrame();
+
+    bool finished = false;
+
+    // Go back to the beginning if indexing goes beyond frame count
+    // and looping is enabled. Change to the last valid frame and
+    // finish if looping is disabled.
+    if (mCurrentFrameIndex >= mFrames.count()) {
+        if (playMode() == HbIconAnimationDefinition::Loop) {
+            mCurrentFrameIndex = 0;
+        } else {
+            finished = true;
+            mCurrentFrameIndex = mFrames.count() - 1;
+        }
+        resetJumpCounts();
+    }
+
+    // If there are more frames to come or the animation is looping,
+    // restart the timer.
+    if (!finished) {
+        mTimerInterval = mFrames.at(mCurrentFrameIndex).duration;
+        mTimerEntry = HbTimer::instance()->addTimeout(mTimerInterval, this, SLOT(animationTimeout()));
+    }
+
+#ifdef HB_ICON_TRACES
+    //qDebug() << "HbIconAnimation updated: " << iconName();
+#endif
+
+    // Inform client.
+    if (finished) {
+        notifyAnimationFinished();
+    } else {
+        emit animationUpdated();
+    }
+}
+
+// End of File