--- /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