Add and use new animation backend.
authorMichael Brasser <michael.brasser@nokia.com>
Fri, 3 Feb 2012 02:26:37 +0000 (12:26 +1000)
committerQt by Nokia <qt-info@nokia.com>
Tue, 7 Feb 2012 04:32:47 +0000 (05:32 +0100)
The new backend improves performance, and allows us to create
multiple running animation jobs from a single Transition. It is
based off of the existing Qt animation framework.

Change-Id: Id1d0162f6e5c65bf31267f3f9f2042c354375d57
Reviewed-by: Yunqiao Yin <charles.yin@nokia.com>
65 files changed:
src/declarative/animations/animations.pri [new file with mode: 0644]
src/declarative/animations/qabstractanimationjob.cpp [new file with mode: 0644]
src/declarative/animations/qabstractanimationjob_p.h [new file with mode: 0644]
src/declarative/animations/qanimationgroupjob.cpp [new file with mode: 0644]
src/declarative/animations/qanimationgroupjob_p.h [new file with mode: 0644]
src/declarative/animations/qparallelanimationgroupjob.cpp [new file with mode: 0644]
src/declarative/animations/qparallelanimationgroupjob_p.h [new file with mode: 0644]
src/declarative/animations/qpauseanimationjob.cpp [new file with mode: 0644]
src/declarative/animations/qpauseanimationjob_p.h [new file with mode: 0644]
src/declarative/animations/qsequentialanimationgroupjob.cpp [new file with mode: 0644]
src/declarative/animations/qsequentialanimationgroupjob_p.h [new file with mode: 0644]
src/declarative/declarative.pro
src/quick/items/qquickanimation.cpp
src/quick/items/qquickanimation_p.h
src/quick/items/qquickanimation_p_p.h
src/quick/items/qquickgridview.cpp
src/quick/items/qquicklistview.cpp
src/quick/particles/qquickitemparticle.cpp
src/quick/particles/qquickitemparticle_p.h
src/quick/util/qdeclarativeanimation.cpp
src/quick/util/qdeclarativeanimation_p.h
src/quick/util/qdeclarativeanimation_p_p.h
src/quick/util/qdeclarativeanimationcontroller.cpp [new file with mode: 0644]
src/quick/util/qdeclarativeanimationcontroller_p.h [new file with mode: 0644]
src/quick/util/qdeclarativebehavior.cpp
src/quick/util/qdeclarativebehavior_p.h
src/quick/util/qdeclarativesmoothedanimation.cpp
src/quick/util/qdeclarativesmoothedanimation_p.h
src/quick/util/qdeclarativesmoothedanimation_p_p.h
src/quick/util/qdeclarativespringanimation.cpp
src/quick/util/qdeclarativespringanimation_p.h
src/quick/util/qdeclarativetimeline.cpp
src/quick/util/qdeclarativetimeline_p_p.h
src/quick/util/qdeclarativetimer.cpp
src/quick/util/qdeclarativetimer_p.h
src/quick/util/qdeclarativetransition.cpp
src/quick/util/qdeclarativetransition_p.h
src/quick/util/qdeclarativetransitionmanager.cpp
src/quick/util/qdeclarativetransitionmanager_p_p.h
src/quick/util/qdeclarativeutilmodule.cpp
src/quick/util/util.pri
tests/auto/declarative/animation/animation.pro [new file with mode: 0644]
tests/auto/declarative/animation/qabstractanimationjob/qabstractanimationjob.pro [new file with mode: 0644]
tests/auto/declarative/animation/qabstractanimationjob/tst_qabstractanimationjob.cpp [new file with mode: 0644]
tests/auto/declarative/animation/qanimationgroupjob/qanimationgroupjob.pro [new file with mode: 0644]
tests/auto/declarative/animation/qanimationgroupjob/tst_qanimationgroupjob.cpp [new file with mode: 0644]
tests/auto/declarative/animation/qparallelanimationgroupjob/qparallelanimationgroupjob.pro [new file with mode: 0644]
tests/auto/declarative/animation/qparallelanimationgroupjob/tst_qparallelanimationgroupjob.cpp [new file with mode: 0644]
tests/auto/declarative/animation/qpauseanimationjob/qpauseanimationjob.pro [new file with mode: 0644]
tests/auto/declarative/animation/qpauseanimationjob/tst_qpauseanimationjob.cpp [new file with mode: 0644]
tests/auto/declarative/animation/qsequentialanimationgroupjob/qsequentialanimationgroupjob.pro [new file with mode: 0644]
tests/auto/declarative/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp [new file with mode: 0644]
tests/auto/declarative/declarative.pro
tests/auto/qtquick2/qdeclarativeanimationcontroller/data/tst_numberanimation.qml [new file with mode: 0644]
tests/auto/qtquick2/qdeclarativeanimationcontroller/qdeclarativeanimationcontroller.pro [new file with mode: 0644]
tests/auto/qtquick2/qdeclarativeanimationcontroller/tst_qdeclarativeanimationcontroller.cpp [new file with mode: 0644]
tests/auto/qtquick2/qdeclarativeanimations/tst_qdeclarativeanimations.cpp
tests/auto/qtquick2/qdeclarativebehaviors/tst_qdeclarativebehaviors.cpp
tests/auto/qtquick2/qdeclarativesmoothedanimation/data/simpleanimation.qml [new file with mode: 0644]
tests/auto/qtquick2/qdeclarativesmoothedanimation/tst_qdeclarativesmoothedanimation.cpp
tests/auto/qtquick2/qdeclarativespringanimation/data/springanimation2.qml
tests/auto/qtquick2/qdeclarativespringanimation/tst_qdeclarativespringanimation.cpp
tests/benchmarks/declarative/animation/animation.pro [new file with mode: 0644]
tests/benchmarks/declarative/animation/data/animation.qml [new file with mode: 0644]
tests/benchmarks/declarative/animation/tst_animation.cpp [new file with mode: 0644]

diff --git a/src/declarative/animations/animations.pri b/src/declarative/animations/animations.pri
new file mode 100644 (file)
index 0000000..240ee96
--- /dev/null
@@ -0,0 +1,15 @@
+INCLUDEPATH += $$PWD
+
+HEADERS += \
+        $$PWD/qabstractanimationjob_p.h \
+        $$PWD/qanimationgroupjob_p.h \
+        $$PWD/qsequentialanimationgroupjob_p.h \
+        $$PWD/qparallelanimationgroupjob_p.h \
+        $$PWD/qpauseanimationjob_p.h
+
+SOURCES += \
+        $$PWD/qabstractanimationjob.cpp \
+        $$PWD/qanimationgroupjob.cpp \
+        $$PWD/qsequentialanimationgroupjob.cpp \
+        $$PWD/qparallelanimationgroupjob.cpp \
+        $$PWD/qpauseanimationjob.cpp
diff --git a/src/declarative/animations/qabstractanimationjob.cpp b/src/declarative/animations/qabstractanimationjob.cpp
new file mode 100644 (file)
index 0000000..a796016
--- /dev/null
@@ -0,0 +1,548 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qthreadstorage.h>
+
+#include "private/qabstractanimationjob_p.h"
+#include "private/qanimationgroupjob_p.h"
+
+#define DEFAULT_TIMER_INTERVAL 16
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_THREAD
+Q_GLOBAL_STATIC(QThreadStorage<QDeclarativeAnimationTimer *>, animationTimer)
+#endif
+
+QDeclarativeAnimationTimer::QDeclarativeAnimationTimer() :
+    QAbstractAnimationTimer(), lastTick(0), lastDelta(0),
+    currentAnimationIdx(0), insideTick(false),
+    startAnimationPending(false), stopTimerPending(false),
+    runningLeafAnimations(0)
+{
+}
+
+QDeclarativeAnimationTimer *QDeclarativeAnimationTimer::instance(bool create)
+{
+    QDeclarativeAnimationTimer *inst;
+#ifndef QT_NO_THREAD
+    if (create && !animationTimer()->hasLocalData()) {
+        inst = new QDeclarativeAnimationTimer;
+        animationTimer()->setLocalData(inst);
+    } else {
+        inst = animationTimer() ? animationTimer()->localData() : 0;
+    }
+#else
+    static QAnimationTimer unifiedTimer;
+    inst = &unifiedTimer;
+#endif
+    return inst;
+}
+
+QDeclarativeAnimationTimer *QDeclarativeAnimationTimer::instance()
+{
+    return instance(true);
+}
+
+void QDeclarativeAnimationTimer::ensureTimerUpdate()
+{
+    QDeclarativeAnimationTimer *inst = QDeclarativeAnimationTimer::instance(false);
+    QUnifiedTimer *instU = QUnifiedTimer::instance(false);
+    if (instU && inst && inst->isPaused)
+        instU->updateAnimationTimers(-1);
+}
+
+void QDeclarativeAnimationTimer::updateAnimationsTime(qint64 delta)
+{
+    //setCurrentTime can get this called again while we're the for loop. At least with pauseAnimations
+    if (insideTick)
+        return;
+
+    lastTick += delta;
+    lastDelta = delta;
+
+    //we make sure we only call update time if the time has actually changed
+    //it might happen in some cases that the time doesn't change because events are delayed
+    //when the CPU load is high
+    if (delta) {
+        insideTick = true;
+        for (currentAnimationIdx = 0; currentAnimationIdx < animations.count(); ++currentAnimationIdx) {
+            QAbstractAnimationJob *animation = animations.at(currentAnimationIdx);
+            int elapsed = animation->m_totalCurrentTime
+                          + (animation->direction() == QAbstractAnimationJob::Forward ? delta : -delta);
+            animation->setCurrentTime(elapsed);
+        }
+        insideTick = false;
+        currentAnimationIdx = 0;
+    }
+}
+
+void QDeclarativeAnimationTimer::updateAnimationTimer()
+{
+    QDeclarativeAnimationTimer *inst = QDeclarativeAnimationTimer::instance(false);
+    if (inst)
+        inst->restartAnimationTimer();
+}
+
+void QDeclarativeAnimationTimer::restartAnimationTimer()
+{
+    if (runningLeafAnimations == 0 && !runningPauseAnimations.isEmpty())
+        QUnifiedTimer::pauseAnimationTimer(this, closestPauseAnimationTimeToFinish());
+    else if (isPaused)
+        QUnifiedTimer::resumeAnimationTimer(this);
+    else if (!isRegistered)
+        QUnifiedTimer::startAnimationTimer(this);
+}
+
+void QDeclarativeAnimationTimer::startAnimations()
+{
+    startAnimationPending = false;
+    //force timer to update, which prevents large deltas for our newly added animations
+    if (!animations.isEmpty())
+        QUnifiedTimer::instance()->updateAnimationTimers(-1);
+
+    //we transfer the waiting animations into the "really running" state
+    animations += animationsToStart;
+    animationsToStart.clear();
+    if (!animations.isEmpty())
+        restartAnimationTimer();
+}
+
+void QDeclarativeAnimationTimer::stopTimer()
+{
+    stopTimerPending = false;
+    if (animations.isEmpty()) {
+        QUnifiedTimer::resumeAnimationTimer(this);
+        QUnifiedTimer::stopAnimationTimer(this);
+        // invalidate the start reference time
+        lastTick = 0;
+        lastDelta = 0;
+    }
+}
+
+void QDeclarativeAnimationTimer::registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel)
+{
+    QDeclarativeAnimationTimer *inst = instance(true); //we create the instance if needed
+    inst->registerRunningAnimation(animation);
+    if (isTopLevel) {
+        Q_ASSERT(!animation->m_hasRegisteredTimer);
+        animation->m_hasRegisteredTimer = true;
+        inst->animationsToStart << animation;
+        if (!inst->startAnimationPending) {
+            inst->startAnimationPending = true;
+            QMetaObject::invokeMethod(inst, "startAnimations", Qt::QueuedConnection);
+        }
+    }
+}
+
+void QDeclarativeAnimationTimer::unregisterAnimation(QAbstractAnimationJob *animation)
+{
+    QDeclarativeAnimationTimer *inst = QDeclarativeAnimationTimer::instance(false);
+    if (inst) {
+        //at this point the unified timer should have been created
+        //but it might also have been already destroyed in case the application is shutting down
+
+        inst->unregisterRunningAnimation(animation);
+
+        if (!animation->m_hasRegisteredTimer)
+            return;
+
+        int idx = inst->animations.indexOf(animation);
+        if (idx != -1) {
+            inst->animations.removeAt(idx);
+            // this is needed if we unregister an animation while its running
+            if (idx <= inst->currentAnimationIdx)
+                --inst->currentAnimationIdx;
+
+            if (inst->animations.isEmpty() && !inst->stopTimerPending) {
+                inst->stopTimerPending = true;
+                QMetaObject::invokeMethod(inst, "stopTimer", Qt::QueuedConnection);
+            }
+        } else {
+            inst->animationsToStart.removeOne(animation);
+        }
+    }
+    animation->m_hasRegisteredTimer = false;
+}
+
+void QDeclarativeAnimationTimer::registerRunningAnimation(QAbstractAnimationJob *animation)
+{
+    if (animation->m_isGroup)
+        return;
+
+    if (animation->m_isPause) {
+        runningPauseAnimations << animation;
+    } else
+        runningLeafAnimations++;
+}
+
+void QDeclarativeAnimationTimer::unregisterRunningAnimation(QAbstractAnimationJob *animation)
+{
+    if (animation->m_isGroup)
+        return;
+
+    if (animation->m_isPause)
+        runningPauseAnimations.removeOne(animation);
+    else
+        runningLeafAnimations--;
+    Q_ASSERT(runningLeafAnimations >= 0);
+}
+
+int QDeclarativeAnimationTimer::closestPauseAnimationTimeToFinish()
+{
+    int closestTimeToFinish = INT_MAX;
+    for (int i = 0; i < runningPauseAnimations.size(); ++i) {
+        QAbstractAnimationJob *animation = runningPauseAnimations.at(i);
+        int timeToFinish;
+
+        if (animation->direction() == QAbstractAnimationJob::Forward)
+            timeToFinish = animation->duration() - animation->currentLoopTime();
+        else
+            timeToFinish = animation->currentLoopTime();
+
+        if (timeToFinish < closestTimeToFinish)
+            closestTimeToFinish = timeToFinish;
+    }
+    return closestTimeToFinish;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+QAbstractAnimationJob::QAbstractAnimationJob()
+    : m_isPause(false)
+    , m_isGroup(false)
+    , m_loopCount(1)
+    , m_group(0)
+    , m_direction(QAbstractAnimationJob::Forward)
+    , m_state(QAbstractAnimationJob::Stopped)
+    , m_totalCurrentTime(0)
+    , m_currentTime(0)
+    , m_currentLoop(0)
+    , m_hasRegisteredTimer(false)
+    , m_uncontrolledFinishTime(-1)
+    , m_wasDeleted(0)
+    , m_nextSibling(0)
+    , m_previousSibling(0)
+{
+}
+
+QAbstractAnimationJob::~QAbstractAnimationJob()
+{
+    if (m_wasDeleted)
+        *m_wasDeleted = true;
+
+    //we can't call stop here. Otherwise we get pure virtual calls
+    if (m_state != Stopped) {
+        State oldState = m_state;
+        m_state = Stopped;
+        stateChanged(oldState, m_state);
+        if (oldState == Running)
+            QDeclarativeAnimationTimer::unregisterAnimation(this);
+    }
+
+    if (m_group)
+        m_group->removeAnimation(this);
+}
+
+void QAbstractAnimationJob::setState(QAbstractAnimationJob::State newState)
+{
+    if (m_state == newState)
+        return;
+
+    if (m_loopCount == 0)
+        return;
+
+    State oldState = m_state;
+    int oldCurrentTime = m_currentTime;
+    int oldCurrentLoop = m_currentLoop;
+    Direction oldDirection = m_direction;
+
+    // check if we should Rewind
+    if ((newState == Paused || newState == Running) && oldState == Stopped) {
+        //here we reset the time if needed
+        //we don't call setCurrentTime because this might change the way the animation
+        //behaves: changing the state or changing the current value
+        m_totalCurrentTime = m_currentTime = (m_direction == Forward) ?
+            0 : (m_loopCount == -1 ? duration() : totalDuration());
+    }
+
+    m_state = newState;
+    //(un)registration of the animation must always happen before calls to
+    //virtual function (updateState) to ensure a correct state of the timer
+    bool isTopLevel = !m_group || m_group->isStopped();
+    if (oldState == Running) {
+        if (newState == Paused && m_hasRegisteredTimer)
+            QDeclarativeAnimationTimer::ensureTimerUpdate();
+        //the animation, is not running any more
+        QDeclarativeAnimationTimer::unregisterAnimation(this);
+    } else if (newState == Running) {
+        QDeclarativeAnimationTimer::registerAnimation(this, isTopLevel);
+    }
+
+    //starting an animation qualifies as a top level loop change
+    if (newState == Running && oldState == Stopped && !m_group)
+        topLevelAnimationLoopChanged();
+
+    bool wasDeleted = false;
+    m_wasDeleted = &wasDeleted;
+    updateState(newState, oldState);
+    if (wasDeleted)
+        return;
+    m_wasDeleted = 0;
+
+    if (newState != m_state) //this is to be safe if updateState changes the state
+        return;
+
+    // Notify state change
+    stateChanged(newState, oldState);
+    if (newState != m_state) //this is to be safe if updateState changes the state
+        return;
+
+    switch (m_state) {
+    case Paused:
+        break;
+    case Running:
+        {
+            // this ensures that the value is updated now that the animation is running
+            if (oldState == Stopped) {
+                if (isTopLevel) {
+                    // currentTime needs to be updated if pauseTimer is active
+                    QDeclarativeAnimationTimer::ensureTimerUpdate();
+                    setCurrentTime(m_totalCurrentTime);
+                }
+            }
+        }
+        break;
+    case Stopped:
+        // Leave running state.
+        int dura = duration();
+
+        if (dura == -1 || m_loopCount < 0
+            || (oldDirection == Forward && (oldCurrentTime * (oldCurrentLoop + 1)) == (dura * m_loopCount))
+            || (oldDirection == Backward && oldCurrentTime == 0)) {
+               finished();
+        }
+        break;
+    }
+}
+
+void QAbstractAnimationJob::setDirection(Direction direction)
+{
+    if (m_direction == direction)
+        return;
+
+    if (m_state == Stopped) {
+        if (m_direction == Backward) {
+            m_currentTime = duration();
+            m_currentLoop = m_loopCount - 1;
+        } else {
+            m_currentTime = 0;
+            m_currentLoop = 0;
+        }
+    }
+
+    // the commands order below is important: first we need to setCurrentTime with the old direction,
+    // then update the direction on this and all children and finally restart the pauseTimer if needed
+    if (m_hasRegisteredTimer)
+        QDeclarativeAnimationTimer::ensureTimerUpdate();
+
+    m_direction = direction;
+    updateDirection(direction);
+
+    if (m_hasRegisteredTimer)
+        // needed to update the timer interval in case of a pause animation
+        QDeclarativeAnimationTimer::updateAnimationTimer();
+}
+
+void QAbstractAnimationJob::setLoopCount(int loopCount)
+{
+    m_loopCount = loopCount;
+}
+
+int QAbstractAnimationJob::totalDuration() const
+{
+    int dura = duration();
+    if (dura <= 0)
+        return dura;
+    int loopcount = loopCount();
+    if (loopcount < 0)
+        return -1;
+    return dura * loopcount;
+}
+
+void QAbstractAnimationJob::setCurrentTime(int msecs)
+{
+    msecs = qMax(msecs, 0);
+    // Calculate new time and loop.
+    int dura = duration();
+    int totalDura = dura <= 0 ? dura : ((m_loopCount < 0) ? -1 : dura * m_loopCount);
+    if (totalDura != -1)
+        msecs = qMin(totalDura, msecs);
+    m_totalCurrentTime = msecs;
+
+    // Update new values.
+    int oldLoop = m_currentLoop;
+    m_currentLoop = ((dura <= 0) ? 0 : (msecs / dura));
+    if (m_currentLoop == m_loopCount) {
+        //we're at the end
+        m_currentTime = qMax(0, dura);
+        m_currentLoop = qMax(0, m_loopCount - 1);
+    } else {
+        if (m_direction == Forward) {
+            m_currentTime = (dura <= 0) ? msecs : (msecs % dura);
+        } else {
+            m_currentTime = (dura <= 0) ? msecs : ((msecs - 1) % dura) + 1;
+            if (m_currentTime == dura)
+                --m_currentLoop;
+        }
+    }
+
+    if (m_currentLoop != oldLoop && !m_group)   //### verify Running as well?
+        topLevelAnimationLoopChanged();
+
+    updateCurrentTime(m_currentTime);
+
+    if (m_currentLoop != oldLoop)
+        currentLoopChanged(m_currentLoop);
+
+    // All animations are responsible for stopping the animation when their
+    // own end state is reached; in this case the animation is time driven,
+    // and has reached the end.
+    if ((m_direction == Forward && m_totalCurrentTime == totalDura)
+        || (m_direction == Backward && m_totalCurrentTime == 0)) {
+        stop();
+    }
+}
+
+void QAbstractAnimationJob::start()
+{
+    if (m_state == Running)
+        return;
+    setState(Running);
+}
+
+void QAbstractAnimationJob::stop()
+{
+    if (m_state == Stopped)
+        return;
+    setState(Stopped);
+}
+
+void QAbstractAnimationJob::pause()
+{
+    if (m_state == Stopped) {
+        qWarning("QAbstractAnimationJob::pause: Cannot pause a stopped animation");
+        return;
+    }
+
+    setState(Paused);
+}
+
+void QAbstractAnimationJob::resume()
+{
+    if (m_state != Paused) {
+        qWarning("QAbstractAnimationJob::resume: "
+                 "Cannot resume an animation that is not paused");
+        return;
+    }
+    setState(Running);
+}
+
+void QAbstractAnimationJob::updateState(QAbstractAnimationJob::State newState,
+                                     QAbstractAnimationJob::State oldState)
+{
+    Q_UNUSED(oldState);
+    Q_UNUSED(newState);
+}
+
+void QAbstractAnimationJob::updateDirection(QAbstractAnimationJob::Direction direction)
+{
+    Q_UNUSED(direction);
+}
+
+void QAbstractAnimationJob::finished()
+{
+    //TODO: update this code so it is valid to delete the animation in animationFinished
+    for (int i = 0; i < changeListeners.count(); ++i) {
+        const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
+        if (change.types & QAbstractAnimationJob::Completion)
+            change.listener->animationFinished(this);
+    }
+
+    if (m_group && (duration() == -1 || loopCount() < 0)) {
+        //this is an uncontrolled animation, need to notify the group animation we are finished
+        m_group->uncontrolledAnimationFinished(this);
+    }
+}
+
+void QAbstractAnimationJob::stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
+{
+    for (int i = 0; i < changeListeners.count(); ++i) {
+        const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
+        if (change.types & QAbstractAnimationJob::StateChange)
+            change.listener->animationStateChanged(this, newState, oldState);
+    }
+}
+
+void QAbstractAnimationJob::currentLoopChanged(int currentLoop)
+{
+    Q_UNUSED(currentLoop);
+    for (int i = 0; i < changeListeners.count(); ++i) {
+        const QAbstractAnimationJob::ChangeListener &change = changeListeners.at(i);
+        if (change.types & QAbstractAnimationJob::CurrentLoop)
+            change.listener->animationCurrentLoopChanged(this);
+    }
+}
+
+void QAbstractAnimationJob::addAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
+{
+    changeListeners.append(ChangeListener(listener, changes));
+}
+
+void QAbstractAnimationJob::removeAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes changes)
+{
+    changeListeners.removeOne(ChangeListener(listener, changes));
+}
+
+
+QT_END_NAMESPACE
+
+//#include "moc_qabstractanimation2_p.cpp"
diff --git a/src/declarative/animations/qabstractanimationjob_p.h b/src/declarative/animations/qabstractanimationjob_p.h
new file mode 100644 (file)
index 0000000..dcbc749
--- /dev/null
@@ -0,0 +1,232 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QABSTRACTANIMATIONJOB_P_H
+#define QABSTRACTANIMATIONJOB_P_H
+
+#include <QtCore/QObject>
+#include <QtCore/private/qabstractanimation_p.h>
+#include "private/qpodvector_p.h"
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class QAnimationGroupJob;
+class QAnimation2ChangeListener;
+class Q_DECLARATIVE_EXPORT QAbstractAnimationJob
+{
+    Q_DISABLE_COPY(QAbstractAnimationJob)
+public:
+    enum Direction {
+        Forward,
+        Backward
+    };
+
+    enum State {
+        Stopped,
+        Paused,
+        Running
+    };
+
+    QAbstractAnimationJob();
+    virtual ~QAbstractAnimationJob();
+
+    //definition
+    inline QAnimationGroupJob *group() const {return m_group;}
+
+    inline int loopCount() const {return m_loopCount;}
+    void setLoopCount(int loopCount);
+
+    int totalDuration() const;
+    virtual int duration() const {return 0;}
+
+    inline QAbstractAnimationJob::Direction direction() const {return m_direction;}
+    void setDirection(QAbstractAnimationJob::Direction direction);
+
+    //state
+    inline int currentTime() const {return m_totalCurrentTime;}
+    inline int currentLoopTime() const {return m_currentTime;}
+    inline int currentLoop() const {return m_currentLoop;}
+    inline QAbstractAnimationJob::State state() const {return m_state;}
+    inline bool isRunning() { return m_state == Running; }
+    inline bool isStopped() { return m_state == Stopped; }
+    inline bool isPaused() { return m_state == Paused; }
+
+    void setCurrentTime(int msecs);
+
+    void start();
+    void pause();
+    void resume();
+    void stop();
+
+    enum ChangeType {
+        Completion = 0x01,
+        StateChange = 0x02,
+        CurrentLoop = 0x04
+    };
+    Q_DECLARE_FLAGS(ChangeTypes, ChangeType)
+
+    void addAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes);
+    void removeAnimationChangeListener(QAnimation2ChangeListener *listener, QAbstractAnimationJob::ChangeTypes);
+
+    QAbstractAnimationJob *nextSibling() const { return m_nextSibling; }
+    QAbstractAnimationJob *previousSibling() const { return m_previousSibling; }
+
+protected:
+    virtual void updateCurrentTime(int) {}
+    virtual void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState);
+    virtual void updateDirection(QAbstractAnimationJob::Direction direction);
+    virtual void topLevelAnimationLoopChanged() {}
+
+    void setState(QAbstractAnimationJob::State state);
+
+    void finished();
+    void stateChanged(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState);
+    void currentLoopChanged(int currentLoop);
+    void directionChanged(QAbstractAnimationJob::Direction);
+
+    //definition
+    bool m_isPause;
+    bool m_isGroup;
+    int m_loopCount;
+    QAnimationGroupJob *m_group;
+    QAbstractAnimationJob::Direction m_direction;
+
+    //state
+    QAbstractAnimationJob::State m_state;
+    int m_totalCurrentTime;
+    int m_currentTime;
+    int m_currentLoop;
+    bool m_hasRegisteredTimer;
+    //records the finish time for an uncontrolled animation (used by animation groups)
+    int m_uncontrolledFinishTime;
+    bool *m_wasDeleted;
+
+    struct ChangeListener {
+        ChangeListener(QAnimation2ChangeListener *l, QAbstractAnimationJob::ChangeTypes t) : listener(l), types(t) {}
+        QAnimation2ChangeListener *listener;
+        QAbstractAnimationJob::ChangeTypes types;
+        bool operator==(const ChangeListener &other) const { return listener == other.listener && types == other.types; }
+    };
+    QPODVector<ChangeListener,4> changeListeners;
+
+    QAbstractAnimationJob *m_nextSibling;
+    QAbstractAnimationJob *m_previousSibling;
+
+    friend class QDeclarativeAnimationTimer;
+    friend class QAnimationGroupJob;
+};
+
+class Q_AUTOTEST_EXPORT QAnimation2ChangeListener
+{
+public:
+    virtual void animationFinished(QAbstractAnimationJob *) {}
+    virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State, QAbstractAnimationJob::State) {}
+    virtual void animationCurrentLoopChanged(QAbstractAnimationJob *) {}
+};
+
+class Q_DECLARATIVE_EXPORT QDeclarativeAnimationTimer : public QAbstractAnimationTimer
+{
+    Q_OBJECT
+private:
+    QDeclarativeAnimationTimer();
+
+public:
+    static QDeclarativeAnimationTimer *instance();
+    static QDeclarativeAnimationTimer *instance(bool create);
+
+    static void registerAnimation(QAbstractAnimationJob *animation, bool isTopLevel);
+    static void unregisterAnimation(QAbstractAnimationJob *animation);
+
+    /*
+        this is used for updating the currentTime of all animations in case the pause
+        timer is active or, otherwise, only of the animation passed as parameter.
+    */
+    static void ensureTimerUpdate();
+
+    /*
+        this will evaluate the need of restarting the pause timer in case there is still
+        some pause animations running.
+    */
+    static void updateAnimationTimer();
+
+    void restartAnimationTimer();
+    void updateAnimationsTime(qint64 timeStep);
+
+    int currentDelta() { return lastDelta; }
+
+    //useful for profiling/debugging
+    int runningAnimationCount() { return animations.count(); }
+
+private Q_SLOTS:
+    void startAnimations();
+    void stopTimer();
+
+private:
+    qint64 lastTick;
+    int lastDelta;
+    int currentAnimationIdx;
+    bool insideTick;
+    bool startAnimationPending;
+    bool stopTimerPending;
+
+    QList<QAbstractAnimationJob*> animations, animationsToStart;
+
+    // this is the count of running animations that are not a group neither a pause animation
+    int runningLeafAnimations;
+    QList<QAbstractAnimationJob*> runningPauseAnimations;
+
+    void registerRunningAnimation(QAbstractAnimationJob *animation);
+    void unregisterRunningAnimation(QAbstractAnimationJob *animation);
+
+    int closestPauseAnimationTimeToFinish();
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractAnimationJob::ChangeTypes)
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QABSTRACTANIMATIONJOB_P_H
diff --git a/src/declarative/animations/qanimationgroupjob.cpp b/src/declarative/animations/qanimationgroupjob.cpp
new file mode 100644 (file)
index 0000000..7e26f97
--- /dev/null
@@ -0,0 +1,164 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qanimationgroupjob_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAnimationGroupJob::QAnimationGroupJob()
+    : QAbstractAnimationJob(), m_firstChild(0), m_lastChild(0)
+{
+    m_isGroup = true;
+}
+
+QAnimationGroupJob::~QAnimationGroupJob()
+{
+    while (firstChild() != 0)
+        delete firstChild();
+}
+
+void QAnimationGroupJob::topLevelAnimationLoopChanged()
+{
+    for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling())
+        animation->topLevelAnimationLoopChanged();
+}
+
+void QAnimationGroupJob::appendAnimation(QAbstractAnimationJob *animation)
+{
+    if (QAnimationGroupJob *oldGroup = animation->m_group)
+        oldGroup->removeAnimation(animation);
+
+    Q_ASSERT(!animation->previousSibling() && !animation->nextSibling());
+
+    if (m_lastChild)
+        m_lastChild->m_nextSibling = animation;
+    else
+        m_firstChild = animation;
+    animation->m_previousSibling = m_lastChild;
+    m_lastChild = animation;
+
+    animation->m_group = this;
+    animationInserted(animation);
+}
+
+void QAnimationGroupJob::prependAnimation(QAbstractAnimationJob *animation)
+{
+    if (QAnimationGroupJob *oldGroup = animation->m_group)
+        oldGroup->removeAnimation(animation);
+
+    Q_ASSERT(!previousSibling() && !nextSibling());
+
+    if (m_firstChild)
+        m_firstChild->m_previousSibling = animation;
+    else
+        m_lastChild = animation;
+    animation->m_nextSibling = m_firstChild;
+    m_firstChild = animation;
+
+    animation->m_group = this;
+    animationInserted(animation);
+}
+
+void QAnimationGroupJob::removeAnimation(QAbstractAnimationJob *animation)
+{
+    Q_ASSERT(animation);
+    Q_ASSERT(animation->m_group == this);
+    QAbstractAnimationJob *prev = animation->previousSibling();
+    QAbstractAnimationJob *next = animation->nextSibling();
+
+    if (prev)
+        prev->m_nextSibling = next;
+    else
+        m_firstChild = next;
+
+    if (next)
+        next->m_previousSibling = prev;
+    else
+        m_lastChild = prev;
+
+    animation->m_previousSibling = 0;
+    animation->m_nextSibling = 0;
+
+    animation->m_group = 0;
+    animationRemoved(animation, prev, next);
+}
+
+void QAnimationGroupJob::clear()
+{
+    //### should this remove and delete, or just remove?
+    while (firstChild() != 0)
+        delete firstChild(); //removeAnimation(firstChild());
+}
+
+void QAnimationGroupJob::resetUncontrolledAnimationsFinishTime()
+{
+    for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+        if (animation->duration() == -1 || animation->loopCount() < 0) {
+            resetUncontrolledAnimationFinishTime(animation);
+        }
+    }
+}
+
+void QAnimationGroupJob::resetUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim)
+{
+    setUncontrolledAnimationFinishTime(anim, -1);
+}
+
+void QAnimationGroupJob::setUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim, int time)
+{
+    anim->m_uncontrolledFinishTime = time;
+}
+
+void QAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
+{
+    Q_UNUSED(animation);
+}
+
+void QAnimationGroupJob::animationRemoved(QAbstractAnimationJob* anim, QAbstractAnimationJob* , QAbstractAnimationJob* )
+{
+    resetUncontrolledAnimationFinishTime(anim);
+    if (!firstChild()) {
+        m_currentTime = 0;
+        stop();
+    }
+}
+
+QT_END_NAMESPACE
diff --git a/src/declarative/animations/qanimationgroupjob_p.h b/src/declarative/animations/qanimationgroupjob_p.h
new file mode 100644 (file)
index 0000000..d1917a5
--- /dev/null
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QANIMATIONGROUPJOB_P_H
+#define QANIMATIONGROUPJOB_P_H
+
+#include "private/qabstractanimationjob_p.h"
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class Q_DECLARATIVE_EXPORT QAnimationGroupJob : public QAbstractAnimationJob
+{
+    Q_DISABLE_COPY(QAnimationGroupJob)
+public:
+    QAnimationGroupJob();
+    ~QAnimationGroupJob();
+
+    void appendAnimation(QAbstractAnimationJob *animation);
+    void prependAnimation(QAbstractAnimationJob *animation);
+    void removeAnimation(QAbstractAnimationJob *animation);
+
+    QAbstractAnimationJob *firstChild() const { return m_firstChild; }
+    QAbstractAnimationJob *lastChild() const { return m_lastChild; }
+
+    void clear();
+
+    //called by QAbstractAnimationJob
+    virtual void uncontrolledAnimationFinished(QAbstractAnimationJob *animation);
+protected:
+    void topLevelAnimationLoopChanged();
+
+    virtual void animationInserted(QAbstractAnimationJob*) { }
+    virtual void animationRemoved(QAbstractAnimationJob*, QAbstractAnimationJob*, QAbstractAnimationJob*);
+
+    //TODO: confirm location of these (should any be moved into QAbstractAnimationJob?)
+    void resetUncontrolledAnimationsFinishTime();
+    void resetUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim);
+    int uncontrolledAnimationFinishTime(QAbstractAnimationJob *anim) const { return anim->m_uncontrolledFinishTime; }
+    void setUncontrolledAnimationFinishTime(QAbstractAnimationJob *anim, int time);
+
+private:
+    //definition
+    QAbstractAnimationJob *m_firstChild;
+    QAbstractAnimationJob *m_lastChild;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif //QANIMATIONGROUPJOB_P_H
diff --git a/src/declarative/animations/qparallelanimationgroupjob.cpp b/src/declarative/animations/qparallelanimationgroupjob.cpp
new file mode 100644 (file)
index 0000000..ff38be3
--- /dev/null
@@ -0,0 +1,226 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qparallelanimationgroupjob_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QParallelAnimationGroupJob::QParallelAnimationGroupJob()
+    : QAnimationGroupJob()
+    , m_previousLoop(0)
+    , m_previousCurrentTime(0)
+{
+}
+
+QParallelAnimationGroupJob::~QParallelAnimationGroupJob()
+{
+}
+
+int QParallelAnimationGroupJob::duration() const
+{
+    int ret = 0;
+
+    for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+        int currentDuration = animation->totalDuration();
+        //this takes care of the case where a parallel animation group has controlled and uncontrolled
+        //animations, and the uncontrolled stop before the controlled
+        if (currentDuration == -1)
+            currentDuration = uncontrolledAnimationFinishTime(animation);
+        if (currentDuration == -1)
+            return -1; // Undetermined length
+
+        ret = qMax(ret, currentDuration);
+    }
+
+    return ret;
+}
+
+void QParallelAnimationGroupJob::updateCurrentTime(int /*currentTime*/)
+{
+    if (!firstChild())
+        return;
+
+    if (m_currentLoop > m_previousLoop) {
+        // simulate completion of the loop
+        int dura = duration();
+        if (dura > 0) {
+            for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+                if (!animation->isStopped())
+                    animation->setCurrentTime(dura);   // will stop
+            }
+        }
+    } else if (m_currentLoop < m_previousLoop) {
+        // simulate completion of the loop seeking backwards
+        for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+            //we need to make sure the animation is in the right state
+            //and then rewind it
+            applyGroupState(animation);
+            animation->setCurrentTime(0);
+            animation->stop();
+        }
+    }
+
+    // finally move into the actual time of the current loop
+    for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+        const int dura = animation->totalDuration();
+        //if the loopcount is bigger we should always start all animations
+        if (m_currentLoop > m_previousLoop
+            //if we're at the end of the animation, we need to start it if it wasn't already started in this loop
+            //this happens in Backward direction where not all animations are started at the same time
+            || shouldAnimationStart(animation, m_previousCurrentTime > dura /*startIfAtEnd*/)) {
+            applyGroupState(animation);
+        }
+
+        if (animation->state() == state()) {
+            animation->setCurrentTime(m_currentTime);
+            if (dura > 0 && m_currentTime > dura)
+                animation->stop();
+        }
+    }
+    m_previousLoop = m_currentLoop;
+    m_previousCurrentTime = m_currentTime;
+}
+
+void QParallelAnimationGroupJob::updateState(QAbstractAnimationJob::State newState,
+                                          QAbstractAnimationJob::State oldState)
+{
+    QAnimationGroupJob::updateState(newState, oldState);
+
+    switch (newState) {
+    case Stopped:
+        for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling())
+            animation->stop();
+        break;
+    case Paused:
+        for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling())
+            if (animation->isRunning())
+                animation->pause();
+        break;
+    case Running:
+        for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+            if (oldState == Stopped)
+                animation->stop();
+            resetUncontrolledAnimationFinishTime(animation);
+            animation->setDirection(m_direction);
+            if (shouldAnimationStart(animation, oldState == Stopped))
+                animation->start();
+        }
+        break;
+    }
+}
+
+bool QParallelAnimationGroupJob::shouldAnimationStart(QAbstractAnimationJob *animation, bool startIfAtEnd) const
+{
+    const int dura = animation->totalDuration();
+
+    if (dura == -1)
+        return uncontrolledAnimationFinishTime(animation) == -1;
+
+    if (startIfAtEnd)
+        return m_currentTime <= dura;
+    if (m_direction == Forward)
+        return m_currentTime < dura;
+    else //direction == Backward
+        return m_currentTime && m_currentTime <= dura;
+}
+
+void QParallelAnimationGroupJob::applyGroupState(QAbstractAnimationJob *animation)
+{
+    switch (m_state)
+    {
+    case Running:
+        animation->start();
+        break;
+    case Paused:
+        animation->pause();
+        break;
+    case Stopped:
+    default:
+        break;
+    }
+}
+
+void QParallelAnimationGroupJob::updateDirection(QAbstractAnimationJob::Direction direction)
+{
+    //we need to update the direction of the current animation
+    if (!isStopped()) {
+        for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling()) {
+            animation->setDirection(direction);
+        }
+    } else {
+        if (direction == Forward) {
+            m_previousLoop = 0;
+            m_previousCurrentTime = 0;
+        } else {
+            // Looping backwards with loopCount == -1 does not really work well...
+            m_previousLoop = (m_loopCount == -1 ? 0 : m_loopCount - 1);
+            m_previousCurrentTime = duration();
+        }
+    }
+}
+
+void QParallelAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
+{
+    Q_ASSERT(animation && animation->duration() == -1 || animation->loopCount() < 0);
+    int uncontrolledRunningCount = 0;
+
+    for (QAbstractAnimationJob *child = firstChild(); child; child = child->nextSibling()) {
+        if (child == animation) {
+            setUncontrolledAnimationFinishTime(animation, animation->currentTime());
+        } else if (child->duration() == -1 || child->loopCount() < 0) {
+            if (uncontrolledAnimationFinishTime(child) == -1)
+                ++uncontrolledRunningCount;
+        }
+    }
+
+    if (uncontrolledRunningCount > 0)
+        return;
+
+    int maxDuration = 0;
+    for (QAbstractAnimationJob *animation = firstChild(); animation; animation = animation->nextSibling())
+        maxDuration = qMax(maxDuration, animation->totalDuration());
+
+    if (m_currentTime >= maxDuration)
+        stop();
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/declarative/animations/qparallelanimationgroupjob_p.h b/src/declarative/animations/qparallelanimationgroupjob_p.h
new file mode 100644 (file)
index 0000000..42a96b5
--- /dev/null
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QPARALLELANIMATIONGROUPJOB_P_H
+#define QPARALLELANIMATIONGROUPJOB_P_H
+
+#include "private/qanimationgroupjob_p.h"
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class Q_DECLARATIVE_EXPORT QParallelAnimationGroupJob : public QAnimationGroupJob
+{
+    Q_DISABLE_COPY(QParallelAnimationGroupJob)
+public:
+    QParallelAnimationGroupJob();
+    ~QParallelAnimationGroupJob();
+
+    int duration() const;
+
+protected:
+    void updateCurrentTime(int currentTime);
+    void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState);
+    void updateDirection(QAbstractAnimationJob::Direction direction);
+    void uncontrolledAnimationFinished(QAbstractAnimationJob *animation);
+
+private:
+    bool shouldAnimationStart(QAbstractAnimationJob *animation, bool startIfAtEnd) const;
+    void applyGroupState(QAbstractAnimationJob *animation);
+
+    //state
+    int m_previousLoop;
+    int m_previousCurrentTime;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QPARALLELANIMATIONGROUPJOB_P_H
diff --git a/src/declarative/animations/qpauseanimationjob.cpp b/src/declarative/animations/qpauseanimationjob.cpp
new file mode 100644 (file)
index 0000000..c362f5a
--- /dev/null
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qpauseanimationjob_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QPauseAnimationJob::QPauseAnimationJob(int duration)
+    : QAbstractAnimationJob()
+    , m_duration(duration)
+{
+    m_isPause = true;
+}
+
+QPauseAnimationJob::~QPauseAnimationJob()
+{
+}
+
+int QPauseAnimationJob::duration() const
+{
+    return m_duration;
+}
+
+void QPauseAnimationJob::setDuration(int msecs)
+{
+    m_duration = msecs;
+}
+
+void QPauseAnimationJob::updateCurrentTime(int)
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/declarative/animations/qpauseanimationjob_p.h b/src/declarative/animations/qpauseanimationjob_p.h
new file mode 100644 (file)
index 0000000..d4af832
--- /dev/null
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QPAUSEANIMATIONJOB_P_H
+#define QPAUSEANIMATIONJOB_P_H
+
+#include <private/qanimationgroupjob_p.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class Q_DECLARATIVE_EXPORT QPauseAnimationJob : public QAbstractAnimationJob
+{
+    Q_DISABLE_COPY(QPauseAnimationJob)
+public:
+    explicit QPauseAnimationJob(int duration = 250);
+    ~QPauseAnimationJob();
+
+    int duration() const;
+    void setDuration(int msecs);
+
+protected:
+    void updateCurrentTime(int);
+
+private:
+    //definition
+    int m_duration;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QPAUSEANIMATIONJOB_P_H
diff --git a/src/declarative/animations/qsequentialanimationgroupjob.cpp b/src/declarative/animations/qsequentialanimationgroupjob.cpp
new file mode 100644 (file)
index 0000000..f186f39
--- /dev/null
@@ -0,0 +1,386 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qsequentialanimationgroupjob_p.h"
+#include "private/qpauseanimationjob_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QSequentialAnimationGroupJob::QSequentialAnimationGroupJob()
+    : QAnimationGroupJob()
+    , m_currentAnimation(0)
+    , m_previousLoop(0)
+{
+}
+
+QSequentialAnimationGroupJob::~QSequentialAnimationGroupJob()
+{
+}
+
+bool QSequentialAnimationGroupJob::atEnd() const
+{
+    // we try to detect if we're at the end of the group
+    //this is true if the following conditions are true:
+    // 1. we're in the last loop
+    // 2. the direction is forward
+    // 3. the current animation is the last one
+    // 4. the current animation has reached its end
+    const int animTotalCurrentTime = m_currentAnimation->currentTime();
+    return (m_currentLoop == m_loopCount - 1
+        && m_direction == Forward
+        && !m_currentAnimation->nextSibling()
+        && animTotalCurrentTime == animationActualTotalDuration(m_currentAnimation));
+}
+
+int QSequentialAnimationGroupJob::animationActualTotalDuration(QAbstractAnimationJob *anim) const
+{
+    int ret = anim->totalDuration();
+    if (ret == -1)
+        ret = uncontrolledAnimationFinishTime(anim); //we can try the actual duration there
+    return ret;
+}
+
+QSequentialAnimationGroupJob::AnimationIndex QSequentialAnimationGroupJob::indexForCurrentTime() const
+{
+    Q_ASSERT(firstChild());
+
+    AnimationIndex ret;
+    QAbstractAnimationJob *anim = 0;
+    int duration = 0;
+
+    for (anim = firstChild(); anim; anim = anim->nextSibling()) {
+        duration = animationActualTotalDuration(anim);
+
+        // 'animation' is the current animation if one of these reasons is true:
+        // 1. it's duration is undefined
+        // 2. it ends after msecs
+        // 3. it is the last animation (this can happen in case there is at least 1 uncontrolled animation)
+        // 4. it ends exactly in msecs and the direction is backwards
+        if (duration == -1 || m_currentTime < (ret.timeOffset + duration)
+            || (m_currentTime == (ret.timeOffset + duration) && m_direction == QAbstractAnimationJob::Backward)) {
+            ret.animation = anim;
+            return ret;
+        }
+
+        if (anim == m_currentAnimation)
+            ret.afterCurrent = true;
+
+        // 'animation' has a non-null defined duration and is not the one at time 'msecs'.
+        ret.timeOffset += duration;
+    }
+
+    // this can only happen when one of those conditions is true:
+    // 1. the duration of the group is undefined and we passed its actual duration
+    // 2. there are only 0-duration animations in the group
+    ret.timeOffset -= duration;
+    ret.animation = lastChild();
+    return ret;
+}
+
+void QSequentialAnimationGroupJob::restart()
+{
+    // restarting the group by making the first/last animation the current one
+    if (m_direction == Forward) {
+        m_previousLoop = 0;
+        if (m_currentAnimation == firstChild())
+            activateCurrentAnimation();
+        else
+            setCurrentAnimation(firstChild());
+    }
+    else { // direction == Backward
+        m_previousLoop = m_loopCount - 1;
+        if (m_currentAnimation == lastChild())
+            activateCurrentAnimation();
+        else
+            setCurrentAnimation(lastChild());
+    }
+}
+
+void QSequentialAnimationGroupJob::advanceForwards(const AnimationIndex &newAnimationIndex)
+{
+    if (m_previousLoop < m_currentLoop) {
+        // we need to fast forward to the end
+        for (QAbstractAnimationJob *anim = m_currentAnimation; anim; anim = anim->nextSibling()) {
+            setCurrentAnimation(anim, true);
+            anim->setCurrentTime(animationActualTotalDuration(anim));
+        }
+        // this will make sure the current animation is reset to the beginning
+        if (firstChild() && !firstChild()->nextSibling())   //count == 1
+            // we need to force activation because setCurrentAnimation will have no effect
+            activateCurrentAnimation();
+        else
+            setCurrentAnimation(firstChild(), true);
+    }
+
+    // and now we need to fast forward from the current position to
+    for (QAbstractAnimationJob *anim = m_currentAnimation; anim && anim != newAnimationIndex.animation; anim = anim->nextSibling()) {     //### WRONG,
+        setCurrentAnimation(anim, true);
+        anim->setCurrentTime(animationActualTotalDuration(anim));
+    }
+    // setting the new current animation will happen later
+}
+
+void QSequentialAnimationGroupJob::rewindForwards(const AnimationIndex &newAnimationIndex)
+{
+    if (m_previousLoop > m_currentLoop) {
+        // we need to fast rewind to the beginning
+        for (QAbstractAnimationJob *anim = m_currentAnimation; anim; anim = anim->previousSibling()) {
+            setCurrentAnimation(anim, true);
+            anim->setCurrentTime(0);
+        }
+        // this will make sure the current animation is reset to the end
+        if (lastChild() && !lastChild()->previousSibling())   //count == 1
+            // we need to force activation because setCurrentAnimation will have no effect
+            activateCurrentAnimation();
+        else {
+            setCurrentAnimation(lastChild(), true);
+        }
+    }
+
+    // and now we need to fast rewind from the current position to
+    for (QAbstractAnimationJob *anim = m_currentAnimation; anim && anim != newAnimationIndex.animation; anim = anim->previousSibling()) {
+        setCurrentAnimation(anim, true);
+        anim->setCurrentTime(0);
+    }
+    // setting the new current animation will happen later
+}
+
+int QSequentialAnimationGroupJob::duration() const
+{
+    int ret = 0;
+
+    for (QAbstractAnimationJob *anim = firstChild(); anim; anim = anim->nextSibling()) {
+        const int currentDuration = anim->totalDuration();
+        if (currentDuration == -1)
+            return -1; // Undetermined length
+
+        ret += currentDuration;
+    }
+
+    return ret;
+}
+
+void QSequentialAnimationGroupJob::updateCurrentTime(int currentTime)
+{
+    if (!m_currentAnimation)
+        return;
+
+    const QSequentialAnimationGroupJob::AnimationIndex newAnimationIndex = indexForCurrentTime();
+
+    // newAnimationIndex.index is the new current animation
+    if (m_previousLoop < m_currentLoop
+        || (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && newAnimationIndex.afterCurrent)) {
+            // advancing with forward direction is the same as rewinding with backwards direction
+            advanceForwards(newAnimationIndex);
+    } else if (m_previousLoop > m_currentLoop
+        || (m_previousLoop == m_currentLoop && m_currentAnimation != newAnimationIndex.animation && !newAnimationIndex.afterCurrent)) {
+            // rewinding with forward direction is the same as advancing with backwards direction
+            rewindForwards(newAnimationIndex);
+    }
+
+    setCurrentAnimation(newAnimationIndex.animation);
+
+    const int newCurrentTime = currentTime - newAnimationIndex.timeOffset;
+
+    if (m_currentAnimation) {
+        m_currentAnimation->setCurrentTime(newCurrentTime);
+        if (atEnd()) {
+            //we make sure that we don't exceed the duration here
+            m_currentTime += m_currentAnimation->currentTime() - newCurrentTime;
+            stop();
+        }
+    } else {
+        //the only case where currentAnimation could be null
+        //is when all animations have been removed
+        Q_ASSERT(!firstChild());
+        m_currentTime = 0;
+        stop();
+    }
+
+    m_previousLoop = m_currentLoop;
+}
+
+void QSequentialAnimationGroupJob::updateState(QAbstractAnimationJob::State newState,
+                                            QAbstractAnimationJob::State oldState)
+{
+    QAnimationGroupJob::updateState(newState, oldState);
+
+    if (!m_currentAnimation)
+        return;
+
+    switch (newState) {
+    case Stopped:
+        m_currentAnimation->stop();
+        break;
+    case Paused:
+        if (oldState == m_currentAnimation->state() && oldState == Running)
+            m_currentAnimation->pause();
+        else
+            restart();
+        break;
+    case Running:
+        if (oldState == m_currentAnimation->state() && oldState == Paused)
+            m_currentAnimation->start();
+        else
+            restart();
+        break;
+    }
+}
+
+void QSequentialAnimationGroupJob::updateDirection(QAbstractAnimationJob::Direction direction)
+{
+    // we need to update the direction of the current animation
+    if (!isStopped() && m_currentAnimation)
+        m_currentAnimation->setDirection(direction);
+}
+
+void QSequentialAnimationGroupJob::setCurrentAnimation(QAbstractAnimationJob *anim, bool intermediate)
+{
+    if (!anim) {
+        Q_ASSERT(!firstChild());
+        m_currentAnimation = 0;
+        return;
+    }
+
+    if (anim == m_currentAnimation)
+        return;
+
+    // stop the old current animation
+    if (m_currentAnimation)
+        m_currentAnimation->stop();
+
+    m_currentAnimation = anim;
+
+    activateCurrentAnimation(intermediate);
+}
+
+void QSequentialAnimationGroupJob::activateCurrentAnimation(bool intermediate)
+{
+    if (!m_currentAnimation || isStopped())
+        return;
+
+    m_currentAnimation->stop();
+
+    // we ensure the direction is consistent with the group's direction
+    m_currentAnimation->setDirection(m_direction);
+
+    // reset the finish time of the animation if it is uncontrolled
+    if (m_currentAnimation->totalDuration() == -1)
+        resetUncontrolledAnimationFinishTime(m_currentAnimation);
+
+    m_currentAnimation->start();
+    if (!intermediate && isPaused())
+        m_currentAnimation->pause();
+}
+
+void QSequentialAnimationGroupJob::uncontrolledAnimationFinished(QAbstractAnimationJob *animation)
+{
+    Q_ASSERT(animation == m_currentAnimation);
+
+    setUncontrolledAnimationFinishTime(m_currentAnimation, m_currentAnimation->currentTime());
+
+    if ((m_direction == Forward && m_currentAnimation == lastChild())
+        || (m_direction == Backward && m_currentAnimation == firstChild())) {
+        // we don't handle looping of a group with undefined duration
+        stop();
+    } else if (m_direction == Forward) {
+        // set the current animation to be the next one
+        setCurrentAnimation(m_currentAnimation->nextSibling());
+    } else {
+        // set the current animation to be the previous one
+        setCurrentAnimation(m_currentAnimation->previousSibling());
+    }
+}
+
+void QSequentialAnimationGroupJob::animationInserted(QAbstractAnimationJob *anim)
+{
+    if (m_currentAnimation == 0)
+        setCurrentAnimation(firstChild()); // initialize the current animation
+
+    if (m_currentAnimation == anim->nextSibling()
+        && m_currentAnimation->currentTime() == 0 && m_currentAnimation->currentLoop() == 0) {
+            //in this case we simply insert the animation before the current one has actually started
+            setCurrentAnimation(anim);
+    }
+
+//    TODO
+//    if (index < m_currentAnimationIndex || m_currentLoop != 0) {
+//        qWarning("QSequentialGroup::insertAnimation only supports to add animations after the current one.");
+//        return; //we're not affected because it is added after the current one
+//    }
+}
+
+void QSequentialAnimationGroupJob::animationRemoved(QAbstractAnimationJob *anim, QAbstractAnimationJob *prev, QAbstractAnimationJob *next)
+{
+    QAnimationGroupJob::animationRemoved(anim, prev, next);
+
+    Q_ASSERT(m_currentAnimation); // currentAnimation should always be set
+
+    bool removingCurrent = anim == m_currentAnimation;
+    if (removingCurrent) {
+        if (next)
+            setCurrentAnimation(next); //let's try to take the next one
+        else if (prev)
+            setCurrentAnimation(prev);
+        else// case all animations were removed
+            setCurrentAnimation(0);
+    }
+
+    // duration of the previous animations up to the current animation
+    m_currentTime = 0;
+    for (QAbstractAnimationJob *anim = firstChild(); anim; anim = anim->nextSibling()) {
+        if (anim == m_currentAnimation)
+            break;
+        m_currentTime += animationActualTotalDuration(anim);
+
+    }
+
+    if (!removingCurrent) {
+        //the current animation is not the one being removed
+        //so we add its current time to the current time of this group
+        m_currentTime += m_currentAnimation->currentTime();
+    }
+
+    //let's also update the total current time
+    m_totalCurrentTime = m_currentTime + m_loopCount * duration();
+}
+
+QT_END_NAMESPACE
diff --git a/src/declarative/animations/qsequentialanimationgroupjob_p.h b/src/declarative/animations/qsequentialanimationgroupjob_p.h
new file mode 100644 (file)
index 0000000..4c1fb2d
--- /dev/null
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSEQUENTIALANIMATIONGROUPJOB_P_H
+#define QSEQUENTIALANIMATIONGROUPJOB_P_H
+
+#include <private/qanimationgroupjob_p.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class QPauseAnimationJob;
+class Q_DECLARATIVE_EXPORT QSequentialAnimationGroupJob : public QAnimationGroupJob
+{
+    Q_DISABLE_COPY(QSequentialAnimationGroupJob)
+public:
+    QSequentialAnimationGroupJob();
+    ~QSequentialAnimationGroupJob();
+
+    int duration() const;
+
+    QAbstractAnimationJob *currentAnimation() const { return m_currentAnimation; }
+
+protected:
+    void updateCurrentTime(int);
+    void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState);
+    void updateDirection(QAbstractAnimationJob::Direction direction);
+    void uncontrolledAnimationFinished(QAbstractAnimationJob *animation);
+
+private:
+    struct AnimationIndex
+    {
+        AnimationIndex() : afterCurrent(false), timeOffset(0), animation(0) {}
+        // AnimationIndex points to the animation at timeOffset, skipping 0 duration animations.
+        // Note that the index semantic is slightly different depending on the direction.
+        bool afterCurrent;  //whether animation is before or after m_currentAnimation   //TODO: make enum Before/After/Same
+        int timeOffset; // time offset when the animation at index starts.
+        QAbstractAnimationJob *animation; //points to the animation at timeOffset
+    };
+
+    int animationActualTotalDuration(QAbstractAnimationJob *anim) const;
+    AnimationIndex indexForCurrentTime() const;
+
+    void setCurrentAnimation(QAbstractAnimationJob *anim, bool intermediate = false);
+    void activateCurrentAnimation(bool intermediate = false);
+
+    void animationInserted(QAbstractAnimationJob *anim);
+    void animationRemoved(QAbstractAnimationJob *anim,QAbstractAnimationJob*,QAbstractAnimationJob*);
+
+    bool atEnd() const;
+
+    void restart();
+
+    // handle time changes
+    void rewindForwards(const AnimationIndex &newAnimationIndex);
+    void advanceForwards(const AnimationIndex &newAnimationIndex);
+
+    //state
+    QAbstractAnimationJob *m_currentAnimation;
+    int m_previousLoop;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif //QSEQUENTIALANIMATIONGROUPJOB_P_H
index ca81a4a..6c544bf 100644 (file)
@@ -31,3 +31,4 @@ HEADERS += qtdeclarativeversion.h
 include(util/util.pri)
 include(qml/qml.pri)
 include(debugger/debugger.pri)
+include(animations/animations.pri)
\ No newline at end of file
index 3c9e93d..baf1c8d 100644 (file)
@@ -48,8 +48,8 @@
 
 #include <QtDeclarative/qdeclarativeinfo.h>
 #include <QtCore/qmath.h>
-#include <QtCore/qsequentialanimationgroup.h>
-#include <QtCore/qparallelanimationgroup.h>
+#include "private/qsequentialanimationgroupjob_p.h"
+#include "private/qparallelanimationgroupjob_p.h"
 #include <QtGui/qtransform.h>
 
 QT_BEGIN_NAMESPACE
@@ -95,21 +95,6 @@ QT_BEGIN_NAMESPACE
 QQuickParentAnimation::QQuickParentAnimation(QObject *parent)
     : QDeclarativeAnimationGroup(*(new QQuickParentAnimationPrivate), parent)
 {
-    Q_D(QQuickParentAnimation);
-    d->topLevelGroup = new QSequentialAnimationGroup;
-    QDeclarative_setParent_noEvent(d->topLevelGroup, this);
-
-    d->startAction = new QActionAnimation;
-    QDeclarative_setParent_noEvent(d->startAction, d->topLevelGroup);
-    d->topLevelGroup->addAnimation(d->startAction);
-
-    d->ag = new QParallelAnimationGroup;
-    QDeclarative_setParent_noEvent(d->ag, d->topLevelGroup);
-    d->topLevelGroup->addAnimation(d->ag);
-
-    d->endAction = new QActionAnimation;
-    QDeclarative_setParent_noEvent(d->endAction, d->topLevelGroup);
-    d->topLevelGroup->addAnimation(d->endAction);
 }
 
 QQuickParentAnimation::~QQuickParentAnimation()
@@ -219,7 +204,7 @@ QPointF QQuickParentAnimationPrivate::computeTransformOrigin(QQuickItem::Transfo
     }
 }
 
-void QQuickParentAnimation::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QQuickParentAnimation::transition(QDeclarativeStateActions &actions,
                         QDeclarativeProperties &modified,
                         TransitionDirection direction)
 {
@@ -385,33 +370,46 @@ void QQuickParentAnimation::transition(QDeclarativeStateActions &actions,
         }
     }
 
+    QSequentialAnimationGroupJob *topLevelGroup = new QSequentialAnimationGroupJob;
+    QActionAnimation *viaAction = d->via ? new QActionAnimation : 0;
+    QActionAnimation *targetAction = new QActionAnimation;
+    //we'll assume the common case by far is to have children, and always create ag
+    QParallelAnimationGroupJob *ag = new QParallelAnimationGroupJob;
+
     if (data->actions.count()) {
-        if (direction == QDeclarativeAbstractAnimation::Forward) {
-            d->startAction->setAnimAction(d->via ? viaData : data, QActionAnimation::DeleteWhenStopped);
-            d->endAction->setAnimAction(d->via ? data : 0, QActionAnimation::DeleteWhenStopped);
+        if (d->via)
+            viaAction->setAnimAction(viaData);
+        targetAction->setAnimAction(data);
+
+        //take care of any child animations
+        bool valid = d->defaultProperty.isValid();
+        QAbstractAnimationJob* anim;
+        for (int ii = 0; ii < d->animations.count(); ++ii) {
+            if (valid)
+                d->animations.at(ii)->setDefaultTarget(d->defaultProperty);
+            anim = d->animations.at(ii)->transition(actions, modified, direction);
+            ag->appendAnimation(anim);
+        }
+
+        //TODO: simplify/clarify logic
+        bool forwards = direction == QDeclarativeAbstractAnimation::Forward;
+        if (forwards) {
+            topLevelGroup->appendAnimation(d->via ? viaAction : targetAction);
+            topLevelGroup->appendAnimation(ag);
+            if (d->via)
+                topLevelGroup->appendAnimation(targetAction);
         } else {
-            d->endAction->setAnimAction(d->via ? viaData : data, QActionAnimation::DeleteWhenStopped);
-            d->startAction->setAnimAction(d->via ? data : 0, QActionAnimation::DeleteWhenStopped);
+            if (d->via)
+                topLevelGroup->appendAnimation(targetAction);
+            topLevelGroup->appendAnimation(ag);
+            topLevelGroup->appendAnimation(d->via ? viaAction : targetAction);
         }
     } else {
         delete data;
         delete viaData;
     }
 
-    //take care of any child animations
-    bool valid = d->defaultProperty.isValid();
-    for (int ii = 0; ii < d->animations.count(); ++ii) {
-        if (valid)
-            d->animations.at(ii)->setDefaultTarget(d->defaultProperty);
-        d->animations.at(ii)->transition(actions, modified, direction);
-    }
-
-}
-
-QAbstractAnimation *QQuickParentAnimation::qtAnimation()
-{
-    Q_D(QQuickParentAnimation);
-    return d->topLevelGroup;
+    return initInstance(topLevelGroup);
 }
 
 /*!
@@ -442,21 +440,12 @@ QAbstractAnimation *QQuickParentAnimation::qtAnimation()
 QQuickAnchorAnimation::QQuickAnchorAnimation(QObject *parent)
 : QDeclarativeAbstractAnimation(*(new QQuickAnchorAnimationPrivate), parent)
 {
-    Q_D(QQuickAnchorAnimation);
-    d->va = new QDeclarativeBulkValueAnimator;
-    QDeclarative_setParent_noEvent(d->va, this);
 }
 
 QQuickAnchorAnimation::~QQuickAnchorAnimation()
 {
 }
 
-QAbstractAnimation *QQuickAnchorAnimation::qtAnimation()
-{
-    Q_D(QQuickAnchorAnimation);
-    return d->va;
-}
-
 /*!
     \qmlproperty list<Item> QtQuick2::AnchorAnimation::targets
     The items to reanchor.
@@ -479,7 +468,7 @@ QDeclarativeListProperty<QQuickItem> QQuickAnchorAnimation::targets()
 int QQuickAnchorAnimation::duration() const
 {
     Q_D(const QQuickAnchorAnimation);
-    return d->va->duration();
+    return d->duration;
 }
 
 void QQuickAnchorAnimation::setDuration(int duration)
@@ -490,9 +479,9 @@ void QQuickAnchorAnimation::setDuration(int duration)
     }
 
     Q_D(QQuickAnchorAnimation);
-    if (d->va->duration() == duration)
+    if (d->duration == duration)
         return;
-    d->va->setDuration(duration);
+    d->duration = duration;
     emit durationChanged(duration);
 }
 
@@ -517,20 +506,20 @@ void QQuickAnchorAnimation::setDuration(int duration)
 QEasingCurve QQuickAnchorAnimation::easing() const
 {
     Q_D(const QQuickAnchorAnimation);
-    return d->va->easingCurve();
+    return d->easing;
 }
 
 void QQuickAnchorAnimation::setEasing(const QEasingCurve &e)
 {
     Q_D(QQuickAnchorAnimation);
-    if (d->va->easingCurve() == e)
+    if (d->easing == e)
         return;
 
-    d->va->setEasingCurve(e);
+    d->easing = e;
     emit easingChanged(e);
 }
 
-void QQuickAnchorAnimation::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QQuickAnchorAnimation::transition(QDeclarativeStateActions &actions,
                         QDeclarativeProperties &modified,
                         TransitionDirection direction)
 {
@@ -539,7 +528,6 @@ void QQuickAnchorAnimation::transition(QDeclarativeStateActions &actions,
     QDeclarativeAnimationPropertyUpdater *data = new QDeclarativeAnimationPropertyUpdater;
     data->interpolatorType = QMetaType::QReal;
     data->interpolator = d->interpolator;
-
     data->reverse = direction == Backward ? true : false;
     data->fromSourced = false;
     data->fromDefined = false;
@@ -552,17 +540,15 @@ void QQuickAnchorAnimation::transition(QDeclarativeStateActions &actions,
         }
     }
 
+    QDeclarativeBulkValueAnimator *animator = new QDeclarativeBulkValueAnimator;
     if (data->actions.count()) {
-        if (!d->rangeIsSet) {
-            d->va->setStartValue(qreal(0));
-            d->va->setEndValue(qreal(1));
-            d->rangeIsSet = true;
-        }
-        d->va->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped);
-        d->va->setFromSourcedValue(&data->fromSourced);
+        animator->setAnimValue(data);
+        animator->setFromSourcedValue(&data->fromSourced);
     } else {
         delete data;
     }
+
+    return initInstance(animator);
 }
 
 /*!
@@ -593,13 +579,15 @@ void QQuickAnchorAnimation::transition(QDeclarativeStateActions &actions,
 QQuickPathAnimation::QQuickPathAnimation(QObject *parent)
 : QDeclarativeAbstractAnimation(*(new QQuickPathAnimationPrivate), parent)
 {
-    Q_D(QQuickPathAnimation);
-    d->pa = new QDeclarativeBulkValueAnimator;
-    QDeclarative_setParent_noEvent(d->pa, this);
 }
 
 QQuickPathAnimation::~QQuickPathAnimation()
 {
+    Q_D(QQuickPathAnimation);
+    QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator it;
+    for (it = d->activeAnimations.begin(); it != d->activeAnimations.end(); ++it) {
+        it.value()->clearTemplate();
+    }
 }
 
 /*!
@@ -611,7 +599,7 @@ QQuickPathAnimation::~QQuickPathAnimation()
 int QQuickPathAnimation::duration() const
 {
     Q_D(const QQuickPathAnimation);
-    return d->pa->duration();
+    return d->duration;
 }
 
 void QQuickPathAnimation::setDuration(int duration)
@@ -622,9 +610,9 @@ void QQuickPathAnimation::setDuration(int duration)
     }
 
     Q_D(QQuickPathAnimation);
-    if (d->pa->duration() == duration)
+    if (d->duration == duration)
         return;
-    d->pa->setDuration(duration);
+    d->duration = duration;
     emit durationChanged(duration);
 }
 
@@ -645,16 +633,16 @@ void QQuickPathAnimation::setDuration(int duration)
 QEasingCurve QQuickPathAnimation::easing() const
 {
     Q_D(const QQuickPathAnimation);
-    return d->pa->easingCurve();
+    return d->easingCurve;
 }
 
 void QQuickPathAnimation::setEasing(const QEasingCurve &e)
 {
     Q_D(QQuickPathAnimation);
-    if (d->pa->easingCurve() == e)
+    if (d->easingCurve == e)
         return;
 
-    d->pa->setEasingCurve(e);
+    d->easingCurve = e;
     emit easingChanged(e);
 }
 
@@ -833,24 +821,37 @@ void QQuickPathAnimation::setEndRotation(qreal rotation)
     emit endRotationChanged(d->endRotation);
 }
 
-
-QAbstractAnimation *QQuickPathAnimation::qtAnimation()
-{
-    Q_D(QQuickPathAnimation);
-    return d->pa;
-}
-
-void QQuickPathAnimation::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QQuickPathAnimation::transition(QDeclarativeStateActions &actions,
                                            QDeclarativeProperties &modified,
                                            TransitionDirection direction)
 {
     Q_D(QQuickPathAnimation);
-    QQuickPathAnimationUpdater *data = new QQuickPathAnimationUpdater;
+
+    QQuickPathAnimationUpdater prevData;
+    bool havePrevData = false;
+    if (d->activeAnimations.contains(d->target)) {
+        havePrevData = true;
+        prevData = *d->activeAnimations[d->target]->pathUpdater();
+    }
+
+    QList<QQuickItem*> keys = d->activeAnimations.keys();
+    foreach (QQuickItem *item, keys) {
+        QQuickPathAnimationAnimator *anim = d->activeAnimations.value(item);
+        if (anim->state() == QAbstractAnimationJob::Stopped) {
+            anim->clearTemplate();
+            d->activeAnimations.remove(item);
+        }
+    }
+
+    QQuickPathAnimationUpdater *data = new QQuickPathAnimationUpdater();
+    QQuickPathAnimationAnimator *pa = new QQuickPathAnimationAnimator(d);
+
+    d->activeAnimations[d->target] = pa;
 
     data->orientation = d->orientation;
     data->anchorPoint = d->anchorPoint;
-    data->entryInterval = duration() ? qreal(d->entryDuration) / duration() : qreal(0);
-    data->exitInterval = duration() ? qreal(d->exitDuration) / duration() : qreal(0);
+    data->entryInterval = d->duration ? qreal(d->entryDuration) / d->duration : qreal(0);
+    data->exitInterval = d->duration ? qreal(d->exitDuration) / d->duration : qreal(0);
     data->endRotation = d->endRotation;
     data->reverse = direction == Backward ? true : false;
     data->fromSourced = false;
@@ -879,46 +880,41 @@ void QQuickPathAnimation::transition(QDeclarativeStateActions &actions,
         data->target = d->target;
         data->path = d->path;
         data->path->invalidateSequentialHistory();
-        if (!d->rangeIsSet) {
-            d->pa->setStartValue(qreal(0));
-            d->pa->setEndValue(qreal(1));
-            d->rangeIsSet = true;
-        }
-        /*
-            NOTE: The following block relies on the fact that the previous value hasn't
-            yet been deleted, and has the same target, etc, which may be a bit fragile.
-         */
-        if (d->pa->getAnimValue()) {
-            QQuickPathAnimationUpdater *prevData = static_cast<QQuickPathAnimationUpdater*>(d->pa->getAnimValue());
 
+        if (havePrevData) {
             // get the original start angle that was used (so we can exactly reverse).
-            data->startRotation = prevData->startRotation;
+            data->startRotation = prevData.startRotation;
 
             // treat interruptions specially, otherwise we end up with strange paths
-            if ((data->reverse || prevData->reverse) && prevData->currentV > 0 && prevData->currentV < 1) {
-                if (!data->fromDefined && !data->toDefined && !prevData->painterPath.isEmpty()) {
-                    QPointF pathPos = QDeclarativePath::sequentialPointAt(prevData->painterPath, prevData->pathLength, prevData->attributePoints, prevData->prevBez, prevData->currentV);
-                    if (!prevData->anchorPoint.isNull())
-                        pathPos -= prevData->anchorPoint;
+            if ((data->reverse || prevData.reverse) && prevData.currentV > 0 && prevData.currentV < 1) {
+                if (!data->fromDefined && !data->toDefined && !prevData.painterPath.isEmpty()) {
+                    QPointF pathPos = QDeclarativePath::sequentialPointAt(prevData.painterPath, prevData.pathLength, prevData.attributePoints, prevData.prevBez, prevData.currentV);
+                    if (!prevData.anchorPoint.isNull())
+                        pathPos -= prevData.anchorPoint;
                     if (pathPos == data->target->pos()) {   //only treat as interruption if we interrupted ourself
-                        data->painterPath = prevData->painterPath;
+                        data->painterPath = prevData.painterPath;
                         data->toDefined = data->fromDefined = data->fromSourced = true;
                         data->prevBez.isValid = false;
-                        data->interruptStart = prevData->currentV;
-                        data->startRotation = prevData->startRotation;
-                        data->pathLength = prevData->pathLength;
-                        data->attributePoints = prevData->attributePoints;
+                        data->interruptStart = prevData.currentV;
+                        data->startRotation = prevData.startRotation;
+                        data->pathLength = prevData.pathLength;
+                        data->attributePoints = prevData.attributePoints;
                     }
                 }
             }
         }
-        d->pa->setFromSourcedValue(&data->fromSourced);
-        d->pa->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped);
+        pa->setFromSourcedValue(&data->fromSourced);
+        pa->setAnimValue(data);
     } else {
-        d->pa->setFromSourcedValue(0);
-        d->pa->setAnimValue(0, QAbstractAnimation::DeleteWhenStopped);
+        pa->setFromSourcedValue(0);
+        pa->setAnimValue(0);
+        delete pa;
         delete data;
     }
+
+    pa->setDuration(d->duration);
+    pa->setEasingCurve(d->easingCurve);
+    return initInstance(pa);
 }
 
 void QQuickPathAnimationUpdater::setValue(qreal v)
@@ -955,9 +951,7 @@ void QQuickPathAnimationUpdater::setValue(qreal v)
         }
     }
 
-    //### could cache properties rather than reconstructing each time
-    QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, QStringLiteral("x")), currentPos.x(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
-    QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, QStringLiteral("y")), currentPos.y(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
+    target->setPos(currentPos);
 
     //adjust angle according to orientation
     if (!fixed) {
@@ -1009,7 +1003,7 @@ void QQuickPathAnimationUpdater::setValue(qreal v)
             else if (v > exitStart)
                 angle = endRotation * (v - exitStart) / exitInterval + angle * (exitInterval - (v - exitStart)) / exitInterval;
         }
-        QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, QStringLiteral("rotation")), angle, QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
+        target->setRotation(angle);
     }
 
     /*
@@ -1024,4 +1018,19 @@ void QQuickPathAnimationUpdater::setValue(qreal v)
     }
 }
 
+QQuickPathAnimationAnimator::QQuickPathAnimationAnimator(QQuickPathAnimationPrivate *priv)
+    : animationTemplate(priv)
+{
+}
+
+QQuickPathAnimationAnimator::~QQuickPathAnimationAnimator()
+{
+    if (animationTemplate && pathUpdater()) {
+        QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator it =
+                animationTemplate->activeAnimations.find(pathUpdater()->target);
+        if (it != animationTemplate->activeAnimations.end() && it.value() == this)
+            animationTemplate->activeAnimations.erase(it);
+    }
+}
+
 QT_END_NAMESPACE
index e16c6fb..924fe5d 100644 (file)
 
 #include <QtQuick/private/qdeclarativeanimation_p.h>
 
-#include <QtCore/qabstractanimation.h>
-
 QT_BEGIN_HEADER
 
 QT_BEGIN_NAMESPACE
 
 class QQuickParentAnimationPrivate;
-class QQuickParentAnimation : public QDeclarativeAnimationGroup
+class Q_QUICK_PRIVATE_EXPORT QQuickParentAnimation : public QDeclarativeAnimationGroup
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QQuickParentAnimation)
@@ -82,14 +80,13 @@ Q_SIGNALS:
     void viaChanged();
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
 };
 
 class QQuickAnchorAnimationPrivate;
-class QQuickAnchorAnimation : public QDeclarativeAbstractAnimation
+class Q_QUICK_PRIVATE_EXPORT QQuickAnchorAnimation : public QDeclarativeAbstractAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QQuickAnchorAnimation)
@@ -114,16 +111,15 @@ Q_SIGNALS:
     void easingChanged(const QEasingCurve&);
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
 };
 
 class QQuickItem;
 class QDeclarativePath;
 class QQuickPathAnimationPrivate;
-class Q_AUTOTEST_EXPORT QQuickPathAnimation : public QDeclarativeAbstractAnimation
+class Q_QUICK_PRIVATE_EXPORT QQuickPathAnimation : public QDeclarativeAbstractAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QQuickPathAnimation)
@@ -179,11 +175,9 @@ public:
     void setEndRotation(qreal);
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
-
 Q_SIGNALS:
     void durationChanged(int);
     void easingChanged(const QEasingCurve &);
index 576abba..2e6157c 100644 (file)
@@ -65,18 +65,13 @@ class QQuickParentAnimationPrivate : public QDeclarativeAnimationGroupPrivate
 {
     Q_DECLARE_PUBLIC(QQuickParentAnimation)
 public:
-    QQuickParentAnimationPrivate()
-    : QDeclarativeAnimationGroupPrivate(), target(0), newParent(0),
-       via(0), topLevelGroup(0), startAction(0), endAction(0) {}
+ QQuickParentAnimationPrivate()
+    : QDeclarativeAnimationGroupPrivate(), target(0), newParent(0), via(0) {}
 
     QQuickItem *target;
     QQuickItem *newParent;
     QQuickItem *via;
 
-    QSequentialAnimationGroup *topLevelGroup;
-    QActionAnimation *startAction;
-    QActionAnimation *endAction;
-
     QPointF computeTransformOrigin(QQuickItem::TransformOrigin origin, qreal width, qreal height) const;
 };
 
@@ -84,12 +79,11 @@ class QQuickAnchorAnimationPrivate : public QDeclarativeAbstractAnimationPrivate
 {
     Q_DECLARE_PUBLIC(QQuickAnchorAnimation)
 public:
-    QQuickAnchorAnimationPrivate() : rangeIsSet(false), va(0),
-        interpolator(QVariantAnimationPrivate::getInterpolator(QMetaType::QReal)) {}
+    QQuickAnchorAnimationPrivate() : interpolator(QVariantAnimationPrivate::getInterpolator(QMetaType::QReal)), duration(250) {}
 
-    bool rangeIsSet;
-    QDeclarativeBulkValueAnimator *va;
     QVariantAnimation::Interpolator interpolator;
+    int duration;
+    QEasingCurve easing;
     QList<QQuickItem*> targets;
 };
 
@@ -102,7 +96,7 @@ public:
         entryInterval(0), exitInterval(0) {}
     ~QQuickPathAnimationUpdater() {}
 
-        void setValue(qreal v);
+    void setValue(qreal v);
 
     QDeclarativePath *path;
 
@@ -129,22 +123,37 @@ public:
     QDeclarativeNullableValue<qreal> startRotation;
 };
 
+class QQuickPathAnimationPrivate;
+class QQuickPathAnimationAnimator : public QDeclarativeBulkValueAnimator
+{
+public:
+    QQuickPathAnimationAnimator(QQuickPathAnimationPrivate * = 0);
+    ~QQuickPathAnimationAnimator();
+
+    void clearTemplate() { animationTemplate = 0; }
+
+    QQuickPathAnimationUpdater *pathUpdater() { return static_cast<QQuickPathAnimationUpdater*>(getAnimValue()); }
+private:
+    QQuickPathAnimationPrivate *animationTemplate;
+};
+
 class QQuickPathAnimationPrivate : public QDeclarativeAbstractAnimationPrivate
 {
     Q_DECLARE_PUBLIC(QQuickPathAnimation)
 public:
     QQuickPathAnimationPrivate() : path(0), target(0),
-        rangeIsSet(false), orientation(QQuickPathAnimation::Fixed), entryDuration(0), exitDuration(0), pa(0) {}
+        orientation(QQuickPathAnimation::Fixed), entryDuration(0), exitDuration(0), duration(250) {}
 
     QDeclarativePath *path;
     QQuickItem *target;
-    bool rangeIsSet;
     QQuickPathAnimation::Orientation orientation;
     QPointF anchorPoint;
     qreal entryDuration;
     qreal exitDuration;
     QDeclarativeNullableValue<qreal> endRotation;
-    QDeclarativeBulkValueAnimator *pa;
+    int duration;
+    QEasingCurve easingCurve;
+    QHash<QQuickItem*, QQuickPathAnimationAnimator* > activeAnimations;
 };
 
 
index 80ab2ac..522c09a 100644 (file)
@@ -217,6 +217,11 @@ public:
         , snapMode(QQuickGridView::NoSnap)
         , highlightXAnimator(0), highlightYAnimator(0)
     {}
+    ~QQuickGridViewPrivate()
+    {
+        delete highlightXAnimator;
+        delete highlightYAnimator;
+    }
 };
 
 Qt::Orientation QQuickGridViewPrivate::layoutOrientation() const
@@ -639,10 +644,10 @@ void QQuickGridViewPrivate::createHighlight()
             FxGridItemSG *newHighlight = new FxGridItemSG(item, q, true);
             if (autoHighlight)
                 resetHighlightPosition();
-            highlightXAnimator = new QSmoothedAnimation(q);
+            highlightXAnimator = new QSmoothedAnimation;
             highlightXAnimator->target = QDeclarativeProperty(item, QLatin1String("x"));
             highlightXAnimator->userDuration = highlightMoveDuration;
-            highlightYAnimator = new QSmoothedAnimation(q);
+            highlightYAnimator = new QSmoothedAnimation;
             highlightYAnimator->target = QDeclarativeProperty(item, QLatin1String("y"));
             highlightYAnimator->userDuration = highlightMoveDuration;
 
index 129db0c..424edc5 100644 (file)
@@ -168,6 +168,10 @@ public:
         , sectionCriteria(0), currentSectionItem(0), nextSectionItem(0)
         , overshootDist(0.0), correctFlick(false), inFlickCorrection(false)
     {}
+    ~QQuickListViewPrivate() {
+        delete highlightPosAnimator;
+        delete highlightSizeAnimator;
+    }
 
     friend class QQuickViewSection;
 };
@@ -787,13 +791,13 @@ void QQuickListViewPrivate::createHighlight()
                 newHighlight->setPosition(static_cast<FxListItemSG*>(currentItem)->itemPosition());
             }
             const QLatin1String posProp(orient == QQuickListView::Vertical ? "y" : "x");
-            highlightPosAnimator = new QSmoothedAnimation(q);
+            highlightPosAnimator = new QSmoothedAnimation;
             highlightPosAnimator->target = QDeclarativeProperty(item, posProp);
             highlightPosAnimator->velocity = highlightMoveSpeed;
             highlightPosAnimator->userDuration = highlightMoveDuration;
 
             const QLatin1String sizeProp(orient == QQuickListView::Vertical ? "height" : "width");
-            highlightSizeAnimator = new QSmoothedAnimation(q);
+            highlightSizeAnimator = new QSmoothedAnimation;
             highlightSizeAnimator->velocity = highlightResizeSpeed;
             highlightSizeAnimator->userDuration = highlightResizeDuration;
             highlightSizeAnimator->target = QDeclarativeProperty(item, sizeProp);
index 9c0e462..db9e3d1 100644 (file)
@@ -103,10 +103,14 @@ QQuickItemParticle::QQuickItemParticle(QQuickItem *parent) :
     QQuickParticlePainter(parent), m_fade(true), m_delegate(0)
 {
     setFlag(QQuickItem::ItemHasContents);
-    clock = new Clock(this, this);
+    clock = new Clock(this);
     clock->start();
 }
 
+QQuickItemParticle::~QQuickItemParticle()
+{
+    delete clock;
+}
 
 void QQuickItemParticle::freeze(QQuickItem* item)
 {
index 8bc01b6..df104a5 100644 (file)
@@ -59,6 +59,7 @@ class QQuickItemParticle : public QQuickParticlePainter
     Q_PROPERTY(QDeclarativeComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
 public:
     explicit QQuickItemParticle(QQuickItem *parent = 0);
+    ~QQuickItemParticle();
 
     bool fade() const { return m_fade; }
 
index 2e09fc8..9f6421b 100644 (file)
 #include <qvariant.h>
 #include <qcolor.h>
 #include <qfile.h>
-#include <QParallelAnimationGroup>
-#include <QSequentialAnimationGroup>
+#include "private/qparallelanimationgroupjob_p.h"
+#include "private/qsequentialanimationgroupjob_p.h"
 #include <QtCore/qset.h>
 #include <QtCore/qrect.h>
 #include <QtCore/qpoint.h>
 #include <QtCore/qsize.h>
 #include <QtCore/qmath.h>
 
-#include <private/qvariantanimation_p.h>
-
 QT_BEGIN_NAMESPACE
 
 /*!
@@ -90,6 +88,8 @@ QDeclarativeAbstractAnimation::QDeclarativeAbstractAnimation(QObject *parent)
 
 QDeclarativeAbstractAnimation::~QDeclarativeAbstractAnimation()
 {
+    Q_D(QDeclarativeAbstractAnimation);
+    delete d->animationInstance;
 }
 
 QDeclarativeAbstractAnimation::QDeclarativeAbstractAnimation(QDeclarativeAbstractAnimationPrivate &dd, QObject *parent)
@@ -97,6 +97,12 @@ QDeclarativeAbstractAnimation::QDeclarativeAbstractAnimation(QDeclarativeAbstrac
 {
 }
 
+QAbstractAnimationJob* QDeclarativeAbstractAnimation::qtAnimation()
+{
+    Q_D(QDeclarativeAbstractAnimation);
+    return d->animationInstance;
+}
+
 /*!
     \qmlproperty bool QtQuick2::Animation::running
     This property holds whether the animation is currently running.
@@ -155,10 +161,16 @@ void QDeclarativeAbstractAnimationPrivate::commence()
 
     QDeclarativeStateActions actions;
     QDeclarativeProperties properties;
-    q->transition(actions, properties, QDeclarativeAbstractAnimation::Forward);
 
-    q->qtAnimation()->start();
-    if (q->qtAnimation()->state() == QAbstractAnimation::Stopped) {
+    QAbstractAnimationJob *oldInstance = animationInstance;
+    animationInstance = q->transition(actions, properties, QDeclarativeAbstractAnimation::Forward);
+    if (oldInstance != animationInstance) {
+        animationInstance->addAnimationChangeListener(this, QAbstractAnimationJob::Completion);
+        if (oldInstance)
+            delete oldInstance;
+    }
+    animationInstance->start();
+    if (animationInstance->isStopped()) {
         running = false;
         emit q->completed();
     }
@@ -187,7 +199,10 @@ void QDeclarativeAbstractAnimation::setRunning(bool r)
         else if (!d->registered) {
             d->registered = true;
             QDeclarativeEnginePrivate *engPriv = QDeclarativeEnginePrivate::get(qmlEngine(this));
-            engPriv->registerFinalizeCallback(this, this->metaObject()->indexOfSlot("componentFinalized()"));
+            static int finalizedIdx = -1;
+            if (finalizedIdx < 0)
+                finalizedIdx = metaObject()->indexOfSlot("componentFinalized()");
+            engPriv->registerFinalizeCallback(this, finalizedIdx);
         }
         return;
     }
@@ -204,29 +219,26 @@ void QDeclarativeAbstractAnimation::setRunning(bool r)
     if (d->running) {
         bool supressStart = false;
         if (d->alwaysRunToEnd && d->loopCount != 1
-            && qtAnimation()->state() == QAbstractAnimation::Running) {
+            && d->animationInstance && d->animationInstance->isRunning()) {
             //we've restarted before the final loop finished; restore proper loop count
             if (d->loopCount == -1)
-                qtAnimation()->setLoopCount(d->loopCount);
+                d->animationInstance->setLoopCount(d->loopCount);
             else
-                qtAnimation()->setLoopCount(qtAnimation()->currentLoop() + d->loopCount);
+                d->animationInstance->setLoopCount(d->animationInstance->currentLoop() + d->loopCount);
             supressStart = true;    //we want the animation to continue, rather than restart
         }
-
-        if (!d->connectedTimeLine) {
-            FAST_CONNECT(qtAnimation(), SIGNAL(finished()), this, SLOT(timelineComplete()))
-            d->connectedTimeLine = true;
-        }
         if (!supressStart)
             d->commence();
         emit started();
     } else {
-        if (d->alwaysRunToEnd) {
-            if (d->loopCount != 1)
-                qtAnimation()->setLoopCount(qtAnimation()->currentLoop()+1);    //finish the current loop
-        } else
-            qtAnimation()->stop();
-
+        if (d->animationInstance) {
+            if (d->alwaysRunToEnd) {
+                if (d->loopCount != 1)
+                    d->animationInstance->setLoopCount(d->animationInstance->currentLoop()+1);    //finish the current loop
+            } else {
+                d->animationInstance->stop();
+            }
+        }
         emit completed();
     }
 
@@ -264,13 +276,13 @@ void QDeclarativeAbstractAnimation::setPaused(bool p)
 
     d->paused = p;
 
-    if (!d->componentComplete)
+    if (!d->componentComplete || !d->animationInstance)
         return;
 
     if (d->paused)
-        qtAnimation()->pause();
+        d->animationInstance->pause();
     else
-        qtAnimation()->resume();
+        d->animationInstance->resume();
 
     emit pausedChanged(d->paused);
 }
@@ -371,19 +383,27 @@ void QDeclarativeAbstractAnimation::setLoops(int loops)
         return;
 
     d->loopCount = loops;
-    qtAnimation()->setLoopCount(loops);
     emit loopCountChanged(loops);
 }
 
+int QDeclarativeAbstractAnimation::duration() const
+{
+    Q_D(const QDeclarativeAbstractAnimation);
+    return d->animationInstance ? d->animationInstance->duration() : 0;
+}
 
 int QDeclarativeAbstractAnimation::currentTime()
 {
-    return qtAnimation()->currentLoopTime();
+    Q_D(QDeclarativeAbstractAnimation);
+    return d->animationInstance ? d->animationInstance->currentLoopTime() : 0;
 }
 
 void QDeclarativeAbstractAnimation::setCurrentTime(int time)
 {
-    qtAnimation()->setCurrentTime(time);
+    Q_D(QDeclarativeAbstractAnimation);
+    if (d->animationInstance)
+        d->animationInstance->setCurrentTime(time);
+    //TODO save value for start?
 }
 
 QDeclarativeAnimationGroup *QDeclarativeAbstractAnimation::group() const
@@ -503,8 +523,9 @@ void QDeclarativeAbstractAnimation::restart()
 */
 void QDeclarativeAbstractAnimation::complete()
 {
-    if (isRunning()) {
-         qtAnimation()->setCurrentTime(qtAnimation()->duration());
+    Q_D(QDeclarativeAbstractAnimation);
+    if (isRunning() && d->animationInstance) {
+         d->animationInstance->setCurrentTime(d->animationInstance->duration());
     }
 }
 
@@ -539,22 +560,43 @@ void QDeclarativeAbstractAnimation::setDisableUserControl()
     d->disableUserControl = true;
 }
 
-void QDeclarativeAbstractAnimation::transition(QDeclarativeStateActions &actions,
+void QDeclarativeAbstractAnimation::setEnableUserControl()
+{
+    Q_D(QDeclarativeAbstractAnimation);
+    d->disableUserControl = false;
+
+}
+
+bool QDeclarativeAbstractAnimation::userControlDisabled() const
+{
+    Q_D(const QDeclarativeAbstractAnimation);
+    return d->disableUserControl;
+}
+
+QAbstractAnimationJob* QDeclarativeAbstractAnimation::initInstance(QAbstractAnimationJob *animation)
+{
+    Q_D(QDeclarativeAbstractAnimation);
+    animation->setLoopCount(d->loopCount);
+    return animation;
+}
+
+QAbstractAnimationJob* QDeclarativeAbstractAnimation::transition(QDeclarativeStateActions &actions,
                                       QDeclarativeProperties &modified,
                                       TransitionDirection direction)
 {
     Q_UNUSED(actions);
     Q_UNUSED(modified);
     Q_UNUSED(direction);
+    return 0;
 }
 
-void QDeclarativeAbstractAnimation::timelineComplete()
+void QDeclarativeAbstractAnimationPrivate::animationFinished(QAbstractAnimationJob*)
 {
-    Q_D(QDeclarativeAbstractAnimation);
-    setRunning(false);
-    if (d->alwaysRunToEnd && d->loopCount != 1) {
+    Q_Q(QDeclarativeAbstractAnimation);
+    q->setRunning(false);
+    if (alwaysRunToEnd && loopCount != 1) {
         //restore the proper loopCount for the next run
-        qtAnimation()->setLoopCount(d->loopCount);
+        animationInstance->setLoopCount(loopCount);
     }
 }
 
@@ -582,21 +624,12 @@ void QDeclarativeAbstractAnimation::timelineComplete()
 QDeclarativePauseAnimation::QDeclarativePauseAnimation(QObject *parent)
 : QDeclarativeAbstractAnimation(*(new QDeclarativePauseAnimationPrivate), parent)
 {
-    Q_D(QDeclarativePauseAnimation);
-    d->init();
 }
 
 QDeclarativePauseAnimation::~QDeclarativePauseAnimation()
 {
 }
 
-void QDeclarativePauseAnimationPrivate::init()
-{
-    Q_Q(QDeclarativePauseAnimation);
-    pa = new QPauseAnimation;
-    QDeclarative_setParent_noEvent(pa, q);
-}
-
 /*!
     \qmlproperty int QtQuick2::PauseAnimation::duration
     This property holds the duration of the pause in milliseconds
@@ -606,7 +639,7 @@ void QDeclarativePauseAnimationPrivate::init()
 int QDeclarativePauseAnimation::duration() const
 {
     Q_D(const QDeclarativePauseAnimation);
-    return d->pa->duration();
+    return d->duration;
 }
 
 void QDeclarativePauseAnimation::setDuration(int duration)
@@ -617,16 +650,22 @@ void QDeclarativePauseAnimation::setDuration(int duration)
     }
 
     Q_D(QDeclarativePauseAnimation);
-    if (d->pa->duration() == duration)
+    if (d->duration == duration)
         return;
-    d->pa->setDuration(duration);
+    d->duration = duration;
     emit durationChanged(duration);
 }
 
-QAbstractAnimation *QDeclarativePauseAnimation::qtAnimation()
+QAbstractAnimationJob* QDeclarativePauseAnimation::transition(QDeclarativeStateActions &actions,
+                                    QDeclarativeProperties &modified,
+                                    TransitionDirection direction)
 {
     Q_D(QDeclarativePauseAnimation);
-    return d->pa;
+    Q_UNUSED(actions);
+    Q_UNUSED(modified);
+    Q_UNUSED(direction);
+
+    return initInstance(new QPauseAnimationJob(d->duration));
 }
 
 /*!
@@ -663,8 +702,8 @@ QDeclarativeColorAnimation::QDeclarativeColorAnimation(QObject *parent)
 {
     Q_D(QDeclarativePropertyAnimation);
     d->interpolatorType = QMetaType::QColor;
-    d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType);
     d->defaultToInterpolatorType = true;
+    d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType);
 }
 
 QDeclarativeColorAnimation::~QDeclarativeColorAnimation()
@@ -731,7 +770,47 @@ void QDeclarativeColorAnimation::setTo(const QColor &t)
     QDeclarativePropertyAnimation::setTo(t);
 }
 
+QActionAnimation::QActionAnimation()
+    : QAbstractAnimationJob(), animAction(0)
+{
+}
+
+QActionAnimation::QActionAnimation(QAbstractAnimationAction *action)
+    : QAbstractAnimationJob(), animAction(action)
+{
+}
+
+QActionAnimation::~QActionAnimation()
+{
+    delete animAction;
+}
+
+int QActionAnimation::duration() const
+{
+    return 0;
+}
+
+void QActionAnimation::setAnimAction(QAbstractAnimationAction *action)
+{
+    if (isRunning())
+        stop();
+    animAction = action;
+}
+
+void QActionAnimation::updateCurrentTime(int)
+{
+}
 
+void QActionAnimation::updateState(State newState, State oldState)
+{
+    Q_UNUSED(oldState);
+
+    if (newState == Running) {
+        if (animAction) {
+            animAction->doAction();
+        }
+    }
+}
 
 /*!
     \qmlclass ScriptAction QDeclarativeScriptAction
@@ -764,20 +843,14 @@ void QDeclarativeColorAnimation::setTo(const QColor &t)
 QDeclarativeScriptAction::QDeclarativeScriptAction(QObject *parent)
     :QDeclarativeAbstractAnimation(*(new QDeclarativeScriptActionPrivate), parent)
 {
-    Q_D(QDeclarativeScriptAction);
-    d->init();
 }
 
 QDeclarativeScriptAction::~QDeclarativeScriptAction()
 {
 }
 
-void QDeclarativeScriptActionPrivate::init()
-{
-    Q_Q(QDeclarativeScriptAction);
-    rsa = new QActionAnimation(&proxy);
-    QDeclarative_setParent_noEvent(rsa, q);
-}
+QDeclarativeScriptActionPrivate::QDeclarativeScriptActionPrivate()
+    : QDeclarativeAbstractAnimationPrivate(), hasRunScriptScript(false), reversing(false){}
 
 /*!
     \qmlproperty script QtQuick2::ScriptAction::script
@@ -817,6 +890,11 @@ void QDeclarativeScriptAction::setStateChangeScriptName(const QString &name)
     d->name = name;
 }
 
+QAbstractAnimationAction* QDeclarativeScriptActionPrivate::createAction()
+{
+    return new Proxy(this);
+}
+
 void QDeclarativeScriptActionPrivate::execute()
 {
     Q_Q(QDeclarativeScriptAction);
@@ -833,7 +911,7 @@ void QDeclarativeScriptActionPrivate::execute()
     }
 }
 
-void QDeclarativeScriptAction::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QDeclarativeScriptAction::transition(QDeclarativeStateActions &actions,
                                     QDeclarativeProperties &modified,
                                     TransitionDirection direction)
 {
@@ -853,16 +931,9 @@ void QDeclarativeScriptAction::transition(QDeclarativeStateActions &actions,
             break;  //only match one (names should be unique)
         }
     }
+    return initInstance(new QActionAnimation(d->createAction()));
 }
 
-QAbstractAnimation *QDeclarativeScriptAction::qtAnimation()
-{
-    Q_D(QDeclarativeScriptAction);
-    return d->rsa;
-}
-
-
-
 /*!
     \qmlclass PropertyAction QDeclarativePropertyAction
     \inqmlmodule QtQuick 2
@@ -906,21 +977,12 @@ QAbstractAnimation *QDeclarativeScriptAction::qtAnimation()
 QDeclarativePropertyAction::QDeclarativePropertyAction(QObject *parent)
 : QDeclarativeAbstractAnimation(*(new QDeclarativePropertyActionPrivate), parent)
 {
-    Q_D(QDeclarativePropertyAction);
-    d->init();
 }
 
 QDeclarativePropertyAction::~QDeclarativePropertyAction()
 {
 }
 
-void QDeclarativePropertyActionPrivate::init()
-{
-    Q_Q(QDeclarativePropertyAction);
-    spa = new QActionAnimation;
-    QDeclarative_setParent_noEvent(spa, q);
-}
-
 QObject *QDeclarativePropertyAction::target() const
 {
     Q_D(const QDeclarativePropertyAction);
@@ -1023,13 +1085,7 @@ void QDeclarativePropertyAction::setValue(const QVariant &v)
     }
 }
 
-QAbstractAnimation *QDeclarativePropertyAction::qtAnimation()
-{
-    Q_D(QDeclarativePropertyAction);
-    return d->spa;
-}
-
-void QDeclarativePropertyAction::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QDeclarativePropertyAction::transition(QDeclarativeStateActions &actions,
                                       QDeclarativeProperties &modified,
                                       TransitionDirection direction)
 {
@@ -1117,11 +1173,13 @@ void QDeclarativePropertyAction::transition(QDeclarativeStateActions &actions,
         }
     }
 
+    QActionAnimation *action = new QActionAnimation;
     if (data->actions.count()) {
-        d->spa->setAnimAction(data, QAbstractAnimation::DeleteWhenStopped);
+        action->setAnimAction(data);
     } else {
         delete data;
     }
+    return initInstance(action);
 }
 
 /*!
@@ -1258,8 +1316,8 @@ QDeclarativeVector3dAnimation::QDeclarativeVector3dAnimation(QObject *parent)
 {
     Q_D(QDeclarativePropertyAnimation);
     d->interpolatorType = QMetaType::QVector3D;
-    d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType);
     d->defaultToInterpolatorType = true;
+    d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType);
 }
 
 QDeclarativeVector3dAnimation::~QDeclarativeVector3dAnimation()
@@ -1503,7 +1561,6 @@ void QDeclarativeRotationAnimation::setDirection(QDeclarativeRotationAnimation::
         d->interpolator = QVariantAnimationPrivate::getInterpolator(d->interpolatorType);
         break;
     }
-
     emit directionChanged();
 }
 
@@ -1524,9 +1581,6 @@ void QDeclarativeAnimationGroupPrivate::append_animation(QDeclarativeListPropert
     QDeclarativeAnimationGroup *q = qobject_cast<QDeclarativeAnimationGroup *>(list->object);
     if (q) {
         a->setGroup(q);
-        // This is an optimization for the parenting that already occurs via addAnimation
-        QDeclarative_setParent_noEvent(a->qtAnimation(), q->d_func()->ag);
-        q->d_func()->ag->addAnimation(a->qtAnimation());
     }
 }
 
@@ -1536,8 +1590,6 @@ void QDeclarativeAnimationGroupPrivate::clear_animation(QDeclarativeListProperty
     if (q) {
         while (q->d_func()->animations.count()) {
             QDeclarativeAbstractAnimation *firstAnim = q->d_func()->animations.at(0);
-            QDeclarative_setParent_noEvent(firstAnim->qtAnimation(), 0);
-            q->d_func()->ag->removeAnimation(firstAnim->qtAnimation());
             firstAnim->setGroup(0);
         }
     }
@@ -1592,27 +1644,20 @@ QDeclarativeListProperty<QDeclarativeAbstractAnimation> QDeclarativeAnimationGro
 QDeclarativeSequentialAnimation::QDeclarativeSequentialAnimation(QObject *parent) :
     QDeclarativeAnimationGroup(parent)
 {
-    Q_D(QDeclarativeAnimationGroup);
-    d->ag = new QSequentialAnimationGroup;
-    QDeclarative_setParent_noEvent(d->ag, this);
 }
 
 QDeclarativeSequentialAnimation::~QDeclarativeSequentialAnimation()
 {
 }
 
-QAbstractAnimation *QDeclarativeSequentialAnimation::qtAnimation()
-{
-    Q_D(QDeclarativeAnimationGroup);
-    return d->ag;
-}
-
-void QDeclarativeSequentialAnimation::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QDeclarativeSequentialAnimation::transition(QDeclarativeStateActions &actions,
                                     QDeclarativeProperties &modified,
                                     TransitionDirection direction)
 {
     Q_D(QDeclarativeAnimationGroup);
 
+    QSequentialAnimationGroupJob *ag = new QSequentialAnimationGroupJob;
+
     int inc = 1;
     int from = 0;
     if (direction == Backward) {
@@ -1621,11 +1666,15 @@ void QDeclarativeSequentialAnimation::transition(QDeclarativeStateActions &actio
     }
 
     bool valid = d->defaultProperty.isValid();
+    QAbstractAnimationJob* anim;
     for (int ii = from; ii < d->animations.count() && ii >= 0; ii += inc) {
         if (valid)
             d->animations.at(ii)->setDefaultTarget(d->defaultProperty);
-        d->animations.at(ii)->transition(actions, modified, direction);
+        anim = d->animations.at(ii)->transition(actions, modified, direction);
+        inc == -1 ? ag->prependAnimation(anim) : ag->appendAnimation(anim);
     }
+
+    return initInstance(ag);
 }
 
 
@@ -1661,36 +1710,30 @@ void QDeclarativeSequentialAnimation::transition(QDeclarativeStateActions &actio
 QDeclarativeParallelAnimation::QDeclarativeParallelAnimation(QObject *parent) :
     QDeclarativeAnimationGroup(parent)
 {
-    Q_D(QDeclarativeAnimationGroup);
-    d->ag = new QParallelAnimationGroup;
-    QDeclarative_setParent_noEvent(d->ag, this);
 }
 
 QDeclarativeParallelAnimation::~QDeclarativeParallelAnimation()
 {
 }
 
-QAbstractAnimation *QDeclarativeParallelAnimation::qtAnimation()
-{
-    Q_D(QDeclarativeAnimationGroup);
-    return d->ag;
-}
-
-void QDeclarativeParallelAnimation::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QDeclarativeParallelAnimation::transition(QDeclarativeStateActions &actions,
                                       QDeclarativeProperties &modified,
                                       TransitionDirection direction)
 {
     Q_D(QDeclarativeAnimationGroup);
+    QParallelAnimationGroupJob *ag = new QParallelAnimationGroupJob;
+
     bool valid = d->defaultProperty.isValid();
+    QAbstractAnimationJob* anim;
     for (int ii = 0; ii < d->animations.count(); ++ii) {
         if (valid)
             d->animations.at(ii)->setDefaultTarget(d->defaultProperty);
-        d->animations.at(ii)->transition(actions, modified, direction);
+        anim = d->animations.at(ii)->transition(actions, modified, direction);
+        ag->appendAnimation(anim);
     }
+    return initInstance(ag);
 }
 
-
-
 //convert a variant from string type to another animatable type
 void QDeclarativePropertyAnimationPrivate::convertVariant(QVariant &variant, int type)
 {
@@ -1744,6 +1787,41 @@ void QDeclarativePropertyAnimationPrivate::convertVariant(QVariant &variant, int
     }
 }
 
+QDeclarativeBulkValueAnimator::QDeclarativeBulkValueAnimator()
+    : QAbstractAnimationJob(), animValue(0), fromSourced(0), m_duration(250)
+{
+}
+
+QDeclarativeBulkValueAnimator::~QDeclarativeBulkValueAnimator()
+{
+    delete animValue;
+}
+
+void QDeclarativeBulkValueAnimator::setAnimValue(QDeclarativeBulkValueUpdater *value)
+{
+    if (isRunning())
+        stop();
+    animValue = value;
+}
+
+void QDeclarativeBulkValueAnimator::updateCurrentTime(int currentTime)
+{
+    if (isStopped())
+        return;
+
+    const qreal progress = easing.valueForProgress(((m_duration == 0) ? qreal(1) : qreal(currentTime) / qreal(m_duration)));
+
+    if (animValue)
+        animValue->setValue(progress);
+}
+
+void QDeclarativeBulkValueAnimator::topLevelAnimationLoopChanged()
+{
+    //check for new from every top-level loop (when the top level animation is started and all subsequent loops)
+    if (fromSourced)
+        *fromSourced = false;
+}
+
 /*!
     \qmlclass PropertyAnimation QDeclarativePropertyAnimation
     \inqmlmodule QtQuick 2
@@ -1809,28 +1887,17 @@ void QDeclarativePropertyAnimationPrivate::convertVariant(QVariant &variant, int
 QDeclarativePropertyAnimation::QDeclarativePropertyAnimation(QObject *parent)
 : QDeclarativeAbstractAnimation(*(new QDeclarativePropertyAnimationPrivate), parent)
 {
-    Q_D(QDeclarativePropertyAnimation);
-    d->init();
 }
 
 QDeclarativePropertyAnimation::QDeclarativePropertyAnimation(QDeclarativePropertyAnimationPrivate &dd, QObject *parent)
 : QDeclarativeAbstractAnimation(dd, parent)
 {
-    Q_D(QDeclarativePropertyAnimation);
-    d->init();
 }
 
 QDeclarativePropertyAnimation::~QDeclarativePropertyAnimation()
 {
 }
 
-void QDeclarativePropertyAnimationPrivate::init()
-{
-    Q_Q(QDeclarativePropertyAnimation);
-    va = new QDeclarativeBulkValueAnimator;
-    QDeclarative_setParent_noEvent(va, q);
-}
-
 /*!
     \qmlproperty int QtQuick2::PropertyAnimation::duration
     This property holds the duration of the animation, in milliseconds.
@@ -1840,7 +1907,7 @@ void QDeclarativePropertyAnimationPrivate::init()
 int QDeclarativePropertyAnimation::duration() const
 {
     Q_D(const QDeclarativePropertyAnimation);
-    return d->va->duration();
+    return d->duration;
 }
 
 void QDeclarativePropertyAnimation::setDuration(int duration)
@@ -1851,9 +1918,9 @@ void QDeclarativePropertyAnimation::setDuration(int duration)
     }
 
     Q_D(QDeclarativePropertyAnimation);
-    if (d->va->duration() == duration)
+    if (d->duration == duration)
         return;
-    d->va->setDuration(duration);
+    d->duration = duration;
     emit durationChanged(duration);
 }
 
@@ -2122,16 +2189,16 @@ void QDeclarativePropertyAnimation::setTo(const QVariant &t)
 QEasingCurve QDeclarativePropertyAnimation::easing() const
 {
     Q_D(const QDeclarativePropertyAnimation);
-    return d->va->easingCurve();
+    return d->easing;
 }
 
 void QDeclarativePropertyAnimation::setEasing(const QEasingCurve &e)
 {
     Q_D(QDeclarativePropertyAnimation);
-    if (d->va->easingCurve() == e)
+    if (d->easing == e)
         return;
 
-    d->va->setEasingCurve(e);
+    d->easing = e;
     emit easingChanged(e);
 }
 
@@ -2288,28 +2355,23 @@ QDeclarativeListProperty<QObject> QDeclarativePropertyAnimation::exclude()
     return QDeclarativeListProperty<QObject>(this, d->exclude);
 }
 
-QAbstractAnimation *QDeclarativePropertyAnimation::qtAnimation()
-{
-    Q_D(QDeclarativePropertyAnimation);
-    return d->va;
-}
-
 void QDeclarativeAnimationPropertyUpdater::setValue(qreal v)
 {
     bool deleted = false;
     wasDeleted = &deleted;
-    if (reverse)    //QVariantAnimation sends us 1->0 when reversed, but we are expecting 0->1
+    if (reverse)
         v = 1 - v;
     for (int ii = 0; ii < actions.count(); ++ii) {
         QDeclarativeAction &action = actions[ii];
 
-        if (v == 1.)
+        if (v == 1.) {
             QDeclarativePropertyPrivate::write(action.property, action.toValue, QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
-        else {
+        else {
             if (!fromSourced && !fromDefined) {
                 action.fromValue = action.property.read();
-                if (interpolatorType)
+                if (interpolatorType) {
                     QDeclarativePropertyAnimationPrivate::convertVariant(action.fromValue, interpolatorType);
+                }
             }
             if (!interpolatorType) {
                 int propType = action.property.propertyType();
@@ -2328,11 +2390,11 @@ void QDeclarativeAnimationPropertyUpdater::setValue(qreal v)
     fromSourced = true;
 }
 
-void QDeclarativePropertyAnimation::transition(QDeclarativeStateActions &actions,
-                                     QDeclarativeProperties &modified,
-                                     TransitionDirection direction)
+QDeclarativeStateActions QDeclarativePropertyAnimation::createTransitionActions(QDeclarativeStateActions &actions,
+                                                                                QDeclarativeProperties &modified)
 {
     Q_D(QDeclarativePropertyAnimation);
+    QDeclarativeStateActions newActions;
 
     QStringList props = d->properties.isEmpty() ? QStringList() : d->properties.split(QLatin1Char(','));
     for (int ii = 0; ii < props.count(); ++ii)
@@ -2356,13 +2418,6 @@ void QDeclarativePropertyAnimation::transition(QDeclarativeStateActions &actions
         props << d->defaultProperties.split(QLatin1Char(','));
     }
 
-    QDeclarativeAnimationPropertyUpdater *data = new QDeclarativeAnimationPropertyUpdater;
-    data->interpolatorType = d->interpolatorType;
-    data->interpolator = d->interpolator;
-    data->reverse = direction == Backward ? true : false;
-    data->fromSourced = false;
-    data->fromDefined = d->fromIsDefined;
-
     bool hasExplicit = false;
     //an explicit animation has been specified
     if (d->toIsDefined) {
@@ -2377,7 +2432,7 @@ void QDeclarativePropertyAnimation::transition(QDeclarativeStateActions &actions
                     }
                     myAction.toValue = d->to;
                     d->convertVariant(myAction.toValue, d->interpolatorType ? d->interpolatorType : myAction.property.propertyType());
-                    data->actions << myAction;
+                    newActions << myAction;
                     hasExplicit = true;
                     for (int ii = 0; ii < actions.count(); ++ii) {
                         QDeclarativeAction &action = actions[ii];
@@ -2420,31 +2475,39 @@ void QDeclarativePropertyAnimation::transition(QDeclarativeStateActions &actions
 
             modified << action.property;
 
-            data->actions << myAction;
+            newActions << myAction;
             action.fromValue = myAction.toValue;
         }
     }
-
-    if (data->actions.count()) {
-        if (!d->rangeIsSet) {
-            d->va->setStartValue(qreal(0));
-            d->va->setEndValue(qreal(1));
-            d->rangeIsSet = true;
-        }
-        d->va->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped);
-        d->va->setFromSourcedValue(&data->fromSourced);
-        d->actions = &data->actions;
-    } else {
-        delete data;
-        d->va->setFromSourcedValue(0);  //clear previous data
-        d->va->setAnimValue(0, QAbstractAnimation::DeleteWhenStopped);  //clear previous data
-        d->actions = 0;
-    }
+    return newActions;
 }
 
+QAbstractAnimationJob* QDeclarativePropertyAnimation::transition(QDeclarativeStateActions &actions,
+                                                                     QDeclarativeProperties &modified,
+                                                                     TransitionDirection direction)
+{
+    Q_D(QDeclarativePropertyAnimation);
 
-QDeclarativeScriptActionPrivate::QDeclarativeScriptActionPrivate()
-    : QDeclarativeAbstractAnimationPrivate(), hasRunScriptScript(false), reversing(false), proxy(this), rsa(0) {}
+    QDeclarativeStateActions dataActions = createTransitionActions(actions, modified);
+
+    QDeclarativeBulkValueAnimator *animator = new QDeclarativeBulkValueAnimator;
+    animator->setDuration(d->duration);
+    animator->setEasingCurve(d->easing);
+
+    if (!dataActions.isEmpty()) {
+        QDeclarativeAnimationPropertyUpdater *data = new QDeclarativeAnimationPropertyUpdater;
+        data->interpolatorType = d->interpolatorType;
+        data->interpolator = d->interpolator;
+        data->reverse = direction == Backward ? true : false;
+        data->fromSourced = false;
+        data->fromDefined = d->fromIsDefined;
+        data->actions = dataActions;
+        animator->setAnimValue(data);
+        animator->setFromSourcedValue(&data->fromSourced);
+        d->actions = &data->actions; //remove this?
+    }
 
+    return initInstance(animator);
+}
 
 QT_END_NAMESPACE
index 7cf6bbe..5b69c5f 100644 (file)
@@ -51,7 +51,7 @@
 
 #include <QtCore/qvariant.h>
 #include <QtCore/qeasingcurve.h>
-#include <QtCore/QAbstractAnimation>
+#include "private/qabstractanimationjob_p.h"
 #include <QtGui/qcolor.h>
 
 QT_BEGIN_HEADER
@@ -89,6 +89,7 @@ public:
 
     int loops() const;
     void setLoops(int);
+    int duration() const;
 
     int currentTime();
     void setCurrentTime(int);
@@ -98,7 +99,8 @@ public:
 
     void setDefaultTarget(const QDeclarativeProperty &);
     void setDisableUserControl();
-
+    void setEnableUserControl();
+    bool userControlDisabled() const;
     void classBegin();
     void componentComplete();
 
@@ -120,27 +122,26 @@ public Q_SLOTS:
 
 protected:
     QDeclarativeAbstractAnimation(QDeclarativeAbstractAnimationPrivate &dd, QObject *parent);
+    QAbstractAnimationJob* initInstance(QAbstractAnimationJob *animation);
 
 public:
     enum TransitionDirection { Forward, Backward };
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation() = 0;
+    QAbstractAnimationJob* qtAnimation();
 
 private Q_SLOTS:
-    void timelineComplete();
     void componentFinalized();
 private:
     virtual void setTarget(const QDeclarativeProperty &);
     void notifyRunningChanged(bool running);
     friend class QDeclarativeBehavior;
-
-
+    friend class QDeclarativeBehaviorPrivate;
 };
 
 class QDeclarativePauseAnimationPrivate;
-class Q_AUTOTEST_EXPORT QDeclarativePauseAnimation : public QDeclarativeAbstractAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativePauseAnimation : public QDeclarativeAbstractAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativePauseAnimation)
@@ -158,7 +159,9 @@ Q_SIGNALS:
     void durationChanged(int);
 
 protected:
-    virtual QAbstractAnimation *qtAnimation();
+    QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
+                                          QDeclarativeProperties &modified,
+                                          TransitionDirection direction);
 };
 
 class QDeclarativeScriptActionPrivate;
@@ -181,14 +184,13 @@ public:
     void setStateChangeScriptName(const QString &);
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
 };
 
 class QDeclarativePropertyActionPrivate;
-class QDeclarativePropertyAction : public QDeclarativeAbstractAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativePropertyAction : public QDeclarativeAbstractAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativePropertyAction)
@@ -226,14 +228,13 @@ Q_SIGNALS:
     void propertyChanged();
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
 };
 
 class QDeclarativePropertyAnimationPrivate;
-class Q_AUTOTEST_EXPORT QDeclarativePropertyAnimation : public QDeclarativeAbstractAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativePropertyAnimation : public QDeclarativeAbstractAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativePropertyAnimation)
@@ -277,12 +278,13 @@ public:
     QDeclarativeListProperty<QObject> exclude();
 
 protected:
+    QDeclarativeStateActions createTransitionActions(QDeclarativeStateActions &actions,
+                                                     QDeclarativeProperties &modified);
+
     QDeclarativePropertyAnimation(QDeclarativePropertyAnimationPrivate &dd, QObject *parent);
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
-
 Q_SIGNALS:
     void durationChanged(int);
     void fromChanged(QVariant);
@@ -293,7 +295,7 @@ Q_SIGNALS:
     void propertyChanged();
 };
 
-class Q_AUTOTEST_EXPORT QDeclarativeColorAnimation : public QDeclarativePropertyAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeColorAnimation : public QDeclarativePropertyAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativePropertyAnimation)
@@ -311,7 +313,7 @@ public:
     void setTo(const QColor &);
 };
 
-class Q_AUTOTEST_EXPORT QDeclarativeNumberAnimation : public QDeclarativePropertyAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeNumberAnimation : public QDeclarativePropertyAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativePropertyAnimation)
@@ -336,7 +338,7 @@ private:
     void init();
 };
 
-class Q_AUTOTEST_EXPORT QDeclarativeVector3dAnimation : public QDeclarativePropertyAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeVector3dAnimation : public QDeclarativePropertyAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativePropertyAnimation)
@@ -356,7 +358,7 @@ public:
 };
 
 class QDeclarativeRotationAnimationPrivate;
-class Q_AUTOTEST_EXPORT QDeclarativeRotationAnimation : public QDeclarativePropertyAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeRotationAnimation : public QDeclarativePropertyAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativeRotationAnimation)
@@ -385,7 +387,7 @@ Q_SIGNALS:
 };
 
 class QDeclarativeAnimationGroupPrivate;
-class Q_AUTOTEST_EXPORT QDeclarativeAnimationGroup : public QDeclarativeAbstractAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeAnimationGroup : public QDeclarativeAbstractAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativeAnimationGroup)
@@ -414,13 +416,12 @@ public:
     virtual ~QDeclarativeSequentialAnimation();
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
 };
 
-class QDeclarativeParallelAnimation : public QDeclarativeAnimationGroup
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeParallelAnimation : public QDeclarativeAnimationGroup
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativeAnimationGroup)
@@ -430,10 +431,9 @@ public:
     virtual ~QDeclarativeParallelAnimation();
 
 protected:
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    virtual QAbstractAnimation *qtAnimation();
 };
 
 
index 53408e1..5bcd704 100644 (file)
@@ -39,8 +39,8 @@
 **
 ****************************************************************************/
 
-#ifndef QDECLARATIVEANIMATION_P_H
-#define QDECLARATIVEANIMATION_P_H
+#ifndef QDECLARATIVEANIMATION2_P_H
+#define QDECLARATIVEANIMATION2_P_H
 
 //
 //  W A R N I N G
 #include <qdeclarative.h>
 #include <qdeclarativecontext.h>
 
-#include <QtCore/QPauseAnimation>
-#include <QtCore/QVariantAnimation>
-#include <QtCore/QAnimationGroup>
+#include <private/qvariantanimation_p.h>
+#include "private/qpauseanimationjob_p.h"
 #include <QDebug>
 
 #include <private/qobject_p.h>
-#include <private/qvariantanimation_p.h>
+#include "private/qanimationgroupjob_p.h"
+#include <QDebug>
+
+#include <private/qobject_p.h>
+
 
 QT_BEGIN_NAMESPACE
 
@@ -85,51 +88,32 @@ template<class T, void (T::*method)()>
 class QAnimationActionProxy : public QAbstractAnimationAction
 {
 public:
-    QAnimationActionProxy(T *p) : m_p(p) {}
-    virtual void doAction() { (m_p->*method)(); }
+    QAnimationActionProxy(T *instance) : m_instance(instance) {}
+    virtual void doAction() { (m_instance->*method)(); }
 
 private:
-    T *m_p;
+    T *m_instance;
 };
 
 //performs an action of type QAbstractAnimationAction
-class Q_AUTOTEST_EXPORT QActionAnimation : public QAbstractAnimation
+class Q_AUTOTEST_EXPORT QActionAnimation : public QAbstractAnimationJob
 {
-    Q_OBJECT
+    Q_DISABLE_COPY(QActionAnimation)
 public:
-    QActionAnimation(QObject *parent = 0) : QAbstractAnimation(parent), animAction(0), policy(KeepWhenStopped) {}
-    QActionAnimation(QAbstractAnimationAction *action, QObject *parent = 0)
-        : QAbstractAnimation(parent), animAction(action), policy(KeepWhenStopped) {}
-    ~QActionAnimation() { if (policy == DeleteWhenStopped) { delete animAction; animAction = 0; } }
-    virtual int duration() const { return 0; }
-    void setAnimAction(QAbstractAnimationAction *action, DeletionPolicy p)
-    {
-        if (state() == Running)
-            stop();
-        if (policy == DeleteWhenStopped)
-            delete animAction;
-        animAction = action;
-        policy = p;
-    }
+    QActionAnimation();
+
+    QActionAnimation(QAbstractAnimationAction *action);
+    ~QActionAnimation();
+
+    virtual int duration() const;
+    void setAnimAction(QAbstractAnimationAction *action);
+
 protected:
-    virtual void updateCurrentTime(int) {}
-
-    virtual void updateState(State newState, State /*oldState*/)
-    {
-        if (newState == Running) {
-            if (animAction) {
-                animAction->doAction();
-                if (state() == Stopped && policy == DeleteWhenStopped) {
-                    delete animAction;
-                    animAction = 0;
-                }
-            }
-        }
-    }
+    virtual void updateCurrentTime(int);
+    virtual void updateState(State newState, State oldState);
 
 private:
     QAbstractAnimationAction *animAction;
-    DeletionPolicy policy;
 };
 
 class QDeclarativeBulkValueUpdater
@@ -140,83 +124,64 @@ public:
 };
 
 //animates QDeclarativeBulkValueUpdater (assumes start and end values will be reals or compatible)
-class Q_AUTOTEST_EXPORT QDeclarativeBulkValueAnimator : public QVariantAnimation
+class Q_AUTOTEST_EXPORT QDeclarativeBulkValueAnimator : public QAbstractAnimationJob
 {
-    Q_OBJECT
+    Q_DISABLE_COPY(QDeclarativeBulkValueAnimator)
 public:
-    QDeclarativeBulkValueAnimator(QObject *parent = 0) : QVariantAnimation(parent), animValue(0), fromSourced(0), policy(KeepWhenStopped) {}
-    ~QDeclarativeBulkValueAnimator() { if (policy == DeleteWhenStopped) { delete animValue; animValue = 0; } }
-    void setAnimValue(QDeclarativeBulkValueUpdater *value, DeletionPolicy p)
-    {
-        if (state() == Running)
-            stop();
-        if (policy == DeleteWhenStopped)
-            delete animValue;
-        animValue = value;
-        policy = p;
-    }
-    QDeclarativeBulkValueUpdater *getAnimValue() const
-    {
-        return animValue;
-    }
-    void setFromSourcedValue(bool *value)
-    {
-        fromSourced = value;
-    }
+    QDeclarativeBulkValueAnimator();
+    ~QDeclarativeBulkValueAnimator();
+
+    void setAnimValue(QDeclarativeBulkValueUpdater *value);
+    QDeclarativeBulkValueUpdater *getAnimValue() const { return animValue; }
+
+    void setFromSourcedValue(bool *value) { fromSourced = value; }
+
+    int duration() const { return m_duration; }
+    void setDuration(int msecs) { m_duration = msecs; }
+
+    QEasingCurve easingCurve() const { return easing; }
+    void setEasingCurve(const QEasingCurve &curve) { easing = curve; }
+
 protected:
-    virtual void updateCurrentValue(const QVariant &value)
-    {
-        if (state() == QAbstractAnimation::Stopped)
-            return;
-
-        if (animValue)
-            animValue->setValue(value.toReal());
-    }
-    virtual void updateState(State newState, State oldState)
-    {   
-        QVariantAnimation::updateState(newState, oldState);
-        if (newState == Running) {
-            //check for new from every loop
-            if (fromSourced)
-                *fromSourced = false;
-        }
-    }
+    void updateCurrentTime(int currentTime);
+    void topLevelAnimationLoopChanged();
 
 private:
     QDeclarativeBulkValueUpdater *animValue;
     bool *fromSourced;
-    DeletionPolicy policy;
+    int m_duration;
+    QEasingCurve easing;
 };
 
 //an animation that just gives a tick
 template<class T, void (T::*method)(int)>
-class QTickAnimationProxy : public QAbstractAnimation
+class QTickAnimationProxy : public QAbstractAnimationJob
 {
-    //Q_OBJECT //doesn't work with templating
+    Q_DISABLE_COPY(QTickAnimationProxy)
 public:
-    QTickAnimationProxy(T *p, QObject *parent = 0) : QAbstractAnimation(parent), m_p(p) {}
+    QTickAnimationProxy(T *instance) : QAbstractAnimationJob(), m_instance(instance) {}
     virtual int duration() const { return -1; }
 protected:
-    virtual void updateCurrentTime(int msec) { (m_p->*method)(msec); }
+    virtual void updateCurrentTime(int msec) { (m_instance->*method)(msec); }
 
 private:
-    T *m_p;
+    T *m_instance;
 };
 
-class QDeclarativeAbstractAnimationPrivate : public QObjectPrivate
+class QDeclarativeAbstractAnimationPrivate : public QObjectPrivate, public QAnimation2ChangeListener
 {
     Q_DECLARE_PUBLIC(QDeclarativeAbstractAnimation)
 public:
     QDeclarativeAbstractAnimationPrivate()
     : running(false), paused(false), alwaysRunToEnd(false),
-      connectedTimeLine(false), componentComplete(true),
+      /*connectedTimeLine(false), */componentComplete(true),
       avoidPropertyValueSourceStart(false), disableUserControl(false),
-      registered(false), loopCount(1), group(0) {}
+      registered(false), loopCount(1), group(0), animationInstance(0) {}
 
     bool running:1;
     bool paused:1;
     bool alwaysRunToEnd:1;
-    bool connectedTimeLine:1;
+    //bool connectedTimeLine:1;
     bool componentComplete:1;
     bool avoidPropertyValueSourceStart:1;
     bool disableUserControl:1;
@@ -225,10 +190,12 @@ public:
     int loopCount;
 
     void commence();
+    virtual void animationFinished(QAbstractAnimationJob *);
 
     QDeclarativeProperty defaultProperty;
 
     QDeclarativeAnimationGroup *group;
+    QAbstractAnimationJob* animationInstance;
 
     static QDeclarativeProperty createProperty(QObject *obj, const QString &str, QObject *infoObj);
 };
@@ -238,11 +205,9 @@ class QDeclarativePauseAnimationPrivate : public QDeclarativeAbstractAnimationPr
     Q_DECLARE_PUBLIC(QDeclarativePauseAnimation)
 public:
     QDeclarativePauseAnimationPrivate()
-    : QDeclarativeAbstractAnimationPrivate(), pa(0) {}
+        : QDeclarativeAbstractAnimationPrivate(), duration(250) {}
 
-    void init();
-
-    QPauseAnimation *pa;
+    int duration;
 };
 
 class QDeclarativeScriptActionPrivate : public QDeclarativeAbstractAnimationPrivate
@@ -251,8 +216,6 @@ class QDeclarativeScriptActionPrivate : public QDeclarativeAbstractAnimationPriv
 public:
     QDeclarativeScriptActionPrivate();
 
-    void init();
-
     QDeclarativeScriptString script;
     QString name;
     QDeclarativeScriptString runScriptScript;
@@ -260,10 +223,9 @@ public:
     bool reversing;
 
     void execute();
-
-    QAnimationActionProxy<QDeclarativeScriptActionPrivate,
-                  &QDeclarativeScriptActionPrivate::execute> proxy;
-    QActionAnimation *rsa;
+    QAbstractAnimationAction* createAction();
+    typedef QAnimationActionProxy<QDeclarativeScriptActionPrivate,
+                                 &QDeclarativeScriptActionPrivate::execute> Proxy;
 };
 
 class QDeclarativePropertyActionPrivate : public QDeclarativeAbstractAnimationPrivate
@@ -271,9 +233,7 @@ class QDeclarativePropertyActionPrivate : public QDeclarativeAbstractAnimationPr
     Q_DECLARE_PUBLIC(QDeclarativePropertyAction)
 public:
     QDeclarativePropertyActionPrivate()
-    : QDeclarativeAbstractAnimationPrivate(), target(0), spa(0) {}
-
-    void init();
+    : QDeclarativeAbstractAnimationPrivate(), target(0) {}
 
     QObject *target;
     QString propertyName;
@@ -282,8 +242,6 @@ public:
     QList<QObject *> exclude;
 
     QDeclarativeNullableValue<QVariant> value;
-
-    QActionAnimation *spa;
 };
 
 class QDeclarativeAnimationGroupPrivate : public QDeclarativeAbstractAnimationPrivate
@@ -291,12 +249,11 @@ class QDeclarativeAnimationGroupPrivate : public QDeclarativeAbstractAnimationPr
     Q_DECLARE_PUBLIC(QDeclarativeAnimationGroup)
 public:
     QDeclarativeAnimationGroupPrivate()
-    : QDeclarativeAbstractAnimationPrivate(), ag(0) {}
+    : QDeclarativeAbstractAnimationPrivate() {}
 
     static void append_animation(QDeclarativeListProperty<QDeclarativeAbstractAnimation> *list, QDeclarativeAbstractAnimation *role);
     static void clear_animation(QDeclarativeListProperty<QDeclarativeAbstractAnimation> *list);
     QList<QDeclarativeAbstractAnimation *> animations;
-    QAnimationGroup *ag;
 };
 
 class QDeclarativePropertyAnimationPrivate : public QDeclarativeAbstractAnimationPrivate
@@ -305,9 +262,7 @@ class QDeclarativePropertyAnimationPrivate : public QDeclarativeAbstractAnimatio
 public:
     QDeclarativePropertyAnimationPrivate()
     : QDeclarativeAbstractAnimationPrivate(), target(0), fromSourced(false), fromIsDefined(false), toIsDefined(false),
-      rangeIsSet(false), defaultToInterpolatorType(0), interpolatorType(0), interpolator(0), va(0), actions(0) {}
-
-    void init();
+      defaultToInterpolatorType(0), interpolatorType(0), interpolator(0), duration(250), actions(0) {}
 
     QVariant from;
     QVariant to;
@@ -322,12 +277,11 @@ public:
     bool fromSourced;
     bool fromIsDefined:1;
     bool toIsDefined:1;
-    bool rangeIsSet:1;
     bool defaultToInterpolatorType:1;
     int interpolatorType;
     QVariantAnimation::Interpolator interpolator;
-
-    QDeclarativeBulkValueAnimator *va;
+    int duration;
+    QEasingCurve easing;
 
     // for animations that don't use the QDeclarativeBulkValueAnimator
     QDeclarativeStateActions *actions;
@@ -348,19 +302,21 @@ public:
 class Q_AUTOTEST_EXPORT QDeclarativeAnimationPropertyUpdater : public QDeclarativeBulkValueUpdater
 {
 public:
+    QDeclarativeAnimationPropertyUpdater() : prevInterpolatorType(0), wasDeleted(0) {}
+    ~QDeclarativeAnimationPropertyUpdater() { if (wasDeleted) *wasDeleted = true; }
+
+    void setValue(qreal v);
+
     QDeclarativeStateActions actions;
     int interpolatorType;       //for Number/ColorAnimation
-    int prevInterpolatorType;   //for generic
     QVariantAnimation::Interpolator interpolator;
+    int prevInterpolatorType;   //for generic
     bool reverse;
     bool fromSourced;
     bool fromDefined;
     bool *wasDeleted;
-    QDeclarativeAnimationPropertyUpdater() : prevInterpolatorType(0), wasDeleted(0) {}
-    ~QDeclarativeAnimationPropertyUpdater() { if (wasDeleted) *wasDeleted = true; }
-    void setValue(qreal v);
 };
 
 QT_END_NAMESPACE
 
-#endif // QDECLARATIVEANIMATION_P_H
+#endif // QDECLARATIVEANIMATION2_P_H
diff --git a/src/quick/util/qdeclarativeanimationcontroller.cpp b/src/quick/util/qdeclarativeanimationcontroller.cpp
new file mode 100644 (file)
index 0000000..3901a65
--- /dev/null
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdeclarativeanimationcontroller_p.h"
+#include <QtDeclarative/qdeclarativeinfo.h>
+#include <private/qdeclarativeengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+
+class QDeclarativeAnimationControllerPrivate : public QObjectPrivate
+{
+    Q_DECLARE_PUBLIC(QDeclarativeAnimationController)
+public:
+    QDeclarativeAnimationControllerPrivate()
+        : progress(0.0), animation(0), animationInstance(0), finalized(false) {}
+
+    qreal progress;
+    QDeclarativeAbstractAnimation *animation;
+    QAbstractAnimationJob *animationInstance;
+    bool finalized:1;
+
+};
+
+/*!
+    \qmlclass AnimationController QDeclarativeAnimationController
+    \inqmlmodule QtQuick 2
+    \ingroup qml-animation-transition
+    \brief The AnimationController element allows you to control animations manually.
+
+    Normally animations are driven by an internal timer, but the AnimationController
+    allows the given \a animation to be driven by a \a progress value explicitly.
+*/
+
+
+QDeclarativeAnimationController::QDeclarativeAnimationController(QObject *parent)
+: QObject(*(new QDeclarativeAnimationControllerPrivate), parent)
+{
+}
+
+QDeclarativeAnimationController::~QDeclarativeAnimationController()
+{
+    Q_D(QDeclarativeAnimationController);
+    delete d->animationInstance;
+}
+
+/*!
+    \qmlproperty real QtQuick2::AnimationController::progress
+    This property holds the animation progress value.
+
+    The valid \c progress value is 0.0 to 1.0, setting values less than 0 will be converted to 0,
+    setting values great than 1 will be converted to 1.
+*/
+qreal QDeclarativeAnimationController::progress() const
+{
+    Q_D(const QDeclarativeAnimationController);
+    return d->progress;
+}
+
+void QDeclarativeAnimationController::setProgress(qreal progress)
+{
+    Q_D(QDeclarativeAnimationController);
+    progress = qBound(qreal(0), progress, qreal(1));
+
+    if (progress != d->progress) {
+        d->progress = progress;
+        updateProgress();
+        emit progressChanged();
+    }
+}
+
+/*!
+    \qmlproperty real QtQuick2::AnimationController::animation
+    \default
+
+    This property holds the animation to be controlled by the AnimationController.
+
+    Note:An animation controlled by AnimationController will always have its
+         \c running and \c paused properties set to true. It can not be manually
+         started or stopped (much like an animation in a Behavior can not be manually started or stopped).
+*/
+QDeclarativeAbstractAnimation *QDeclarativeAnimationController::animation() const
+{
+    Q_D(const QDeclarativeAnimationController);
+    return d->animation;
+}
+
+void QDeclarativeAnimationController::classBegin()
+{
+    QDeclarativeEnginePrivate *engPriv = QDeclarativeEnginePrivate::get(qmlEngine(this));
+    engPriv->registerFinalizeCallback(this, this->metaObject()->indexOfSlot("componentFinalized()"));
+}
+
+
+void QDeclarativeAnimationController::setAnimation(QDeclarativeAbstractAnimation *animation)
+{
+    Q_D(QDeclarativeAnimationController);
+
+    if (animation != d->animation) {
+        if (animation) {
+            if (animation->userControlDisabled()) {
+                qmlInfo(this) << "QDeclarativeAnimationController::setAnimation: the animation is controlled by others, can't be used in AnimationController.";
+                return;
+            }
+            animation->setDisableUserControl();
+        }
+
+        if (d->animation)
+            d->animation->setEnableUserControl();
+
+        d->animation = animation;
+        reload();
+        emit animationChanged();
+    }
+}
+
+/*!
+    \qmlmethod QtQuick2::AnimationController::reload()
+    \brief Reloads the animation properties.
+
+    If the animation properties changed, calling this method to reload the animation definations.
+*/
+void QDeclarativeAnimationController::reload()
+{
+    Q_D(QDeclarativeAnimationController);
+    if (!d->finalized)
+        return;
+
+    if (!d->animation) {
+        d->animationInstance = 0;
+    } else {
+        QDeclarativeStateActions actions;
+        QDeclarativeProperties properties;
+        QAbstractAnimationJob *oldInstance = d->animationInstance;
+        d->animationInstance = d->animation->transition(actions, properties, QDeclarativeAbstractAnimation::Forward);
+        if (oldInstance && oldInstance != d->animationInstance)
+            delete oldInstance;
+        d->animationInstance->setLoopCount(1);
+        d->animationInstance->start();
+        d->animationInstance->pause();
+        updateProgress();
+    }
+}
+
+void QDeclarativeAnimationController::updateProgress()
+{
+    Q_D(QDeclarativeAnimationController);
+    if (!d->animationInstance)
+        return;
+
+    d->animationInstance->start();
+    QDeclarativeAnimationTimer::unregisterAnimation(d->animationInstance);
+    d->animationInstance->setCurrentTime(d->progress * d->animationInstance->duration());
+}
+
+void QDeclarativeAnimationController::componentFinalized()
+{
+    Q_D(QDeclarativeAnimationController);
+    d->finalized = true;
+    reload();
+}
+
+
+QT_END_NAMESPACE
+
+
diff --git a/src/quick/util/qdeclarativeanimationcontroller_p.h b/src/quick/util/qdeclarativeanimationcontroller_p.h
new file mode 100644 (file)
index 0000000..d4000b5
--- /dev/null
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QDECLARATIVEANIMATIONCONTROLLER_H
+#define QDECLARATIVEANIMATIONCONTROLLER_H
+
+#include <qdeclarative.h>
+#include "qdeclarativeanimation_p.h"
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Declarative)
+
+class QDeclarativeAnimationControllerPrivate;
+class Q_AUTOTEST_EXPORT QDeclarativeAnimationController : public QObject, public QDeclarativeParserStatus
+{
+    Q_OBJECT
+    Q_DECLARE_PRIVATE(QDeclarativeAnimationController)
+    Q_CLASSINFO("DefaultProperty", "animation")
+
+    Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
+    Q_PROPERTY(QDeclarativeAbstractAnimation *animation READ animation WRITE setAnimation NOTIFY animationChanged)
+
+public:
+    QDeclarativeAnimationController(QObject *parent=0);
+    ~QDeclarativeAnimationController();
+
+    qreal progress() const;
+    void setProgress(qreal progress);
+
+    QDeclarativeAbstractAnimation *animation() const;
+    void setAnimation(QDeclarativeAbstractAnimation *animation);
+
+    void classBegin();
+    void componentComplete() {}
+Q_SIGNALS:
+    void progressChanged();
+    void animationChanged();
+public Q_SLOTS:
+    void reload();
+private Q_SLOTS:
+    void componentFinalized();
+    void updateProgress();
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QDeclarativeAnimationController)
+
+QT_END_HEADER
+
+#endif // QDECLARATIVEANIMATIONCONTROLLER_H
index daed2e8..726c39f 100644 (file)
 #include <private/qdeclarativeproperty_p.h>
 #include <private/qdeclarativeguard_p.h>
 #include <private/qdeclarativeengine_p.h>
+#include <private/qabstractanimationjob_p.h>
+#include <private/qdeclarativetransition_p.h>
 
 #include <private/qobject_p.h>
 
 QT_BEGIN_NAMESPACE
 
-class QDeclarativeBehaviorPrivate : public QObjectPrivate
+class QDeclarativeBehaviorPrivate : public QObjectPrivate, public QAnimation2ChangeListener
 {
     Q_DECLARE_PUBLIC(QDeclarativeBehavior)
 public:
-    QDeclarativeBehaviorPrivate() : animation(0), enabled(true), finalized(false)
+    QDeclarativeBehaviorPrivate() : animation(0), animationInstance(0), enabled(true), finalized(false)
       , blockRunningChanged(false) {}
 
+    virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState);
+
     QDeclarativeProperty property;
     QVariant targetValue;
     QDeclarativeGuard<QDeclarativeAbstractAnimation> animation;
+    QAbstractAnimationJob *animationInstance;
     bool enabled;
     bool finalized;
     bool blockRunningChanged;
@@ -102,6 +107,8 @@ QDeclarativeBehavior::QDeclarativeBehavior(QObject *parent)
 
 QDeclarativeBehavior::~QDeclarativeBehavior()
 {
+    Q_D(QDeclarativeBehavior);
+    delete d->animationInstance;
 }
 
 /*!
@@ -129,22 +136,16 @@ void QDeclarativeBehavior::setAnimation(QDeclarativeAbstractAnimation *animation
     if (d->animation) {
         d->animation->setDefaultTarget(d->property);
         d->animation->setDisableUserControl();
-        FAST_CONNECT(d->animation->qtAnimation(),
-                     SIGNAL(stateChanged(QAbstractAnimation::State,QAbstractAnimation::State)),
-                     this,
-                     SLOT(qtAnimationStateChanged(QAbstractAnimation::State,QAbstractAnimation::State)))
     }
 }
 
 
-void QDeclarativeBehavior::qtAnimationStateChanged(QAbstractAnimation::State newState,QAbstractAnimation::State)
+void QDeclarativeBehaviorPrivate::animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState,QAbstractAnimationJob::State)
 {
-    Q_D(QDeclarativeBehavior);
-    if (!d->blockRunningChanged)
-        d->animation->notifyRunningChanged(newState == QAbstractAnimation::Running);
+    if (!blockRunningChanged)
+        animation->notifyRunningChanged(newState == QAbstractAnimationJob::Running);
 }
 
-
 /*!
     \qmlproperty bool QtQuick2::Behavior::enabled
 
@@ -187,10 +188,10 @@ void QDeclarativeBehavior::write(const QVariant &value)
     const QVariant &currentValue = d->property.read();
     d->targetValue = value;
 
-    if (d->animation->qtAnimation()->duration() != -1
-            && d->animation->qtAnimation()->state() != QAbstractAnimation::Stopped) {
+    if (d->animationInstance && d->animationInstance->duration() != -1
+            && !d->animationInstance->isStopped()) {
         d->blockRunningChanged = true;
-        d->animation->qtAnimation()->stop();
+        d->animationInstance->stop();
     }
 
     QDeclarativeStateOperation::ActionList actions;
@@ -201,8 +202,14 @@ void QDeclarativeBehavior::write(const QVariant &value)
     actions << action;
 
     QList<QDeclarativeProperty> after;
-    d->animation->transition(actions, after, QDeclarativeAbstractAnimation::Forward);
-    d->animation->qtAnimation()->start();
+    QAbstractAnimationJob *prev = d->animationInstance;
+    d->animationInstance = d->animation->transition(actions, after, QDeclarativeAbstractAnimation::Forward);
+    if (d->animationInstance != prev) {
+        d->animationInstance->addAnimationChangeListener(d, QAbstractAnimationJob::StateChange);
+        if (prev)
+            delete prev;
+    }
+    d->animationInstance->start();
     d->blockRunningChanged = false;
     if (!after.contains(d->property))
         QDeclarativePropertyPrivate::write(d->property, value, QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding);
@@ -216,7 +223,10 @@ void QDeclarativeBehavior::setTarget(const QDeclarativeProperty &property)
         d->animation->setDefaultTarget(property);
 
     QDeclarativeEnginePrivate *engPriv = QDeclarativeEnginePrivate::get(qmlEngine(this));
-    engPriv->registerFinalizeCallback(this, this->metaObject()->indexOfSlot("componentFinalized()"));
+    static int finalizedIdx = -1;
+    if (finalizedIdx < 0)
+        finalizedIdx = metaObject()->indexOfSlot("componentFinalized()");
+    engPriv->registerFinalizeCallback(this, finalizedIdx);
 }
 
 void QDeclarativeBehavior::componentFinalized()
index 5b26aa9..c95304d 100644 (file)
@@ -46,7 +46,6 @@
 
 #include <private/qdeclarativepropertyvalueinterceptor_p.h>
 #include <qdeclarative.h>
-#include <QtCore/QAbstractAnimation>
 
 QT_BEGIN_HEADER
 
@@ -83,7 +82,6 @@ Q_SIGNALS:
 
 private Q_SLOTS:
     void componentFinalized();
-    void qtAnimationStateChanged(QAbstractAnimation::State,QAbstractAnimation::State);
 };
 
 QT_END_NAMESPACE
index 52e870a..9b61561 100644 (file)
 
 QT_BEGIN_NAMESPACE
 
-QSmoothedAnimation::QSmoothedAnimation(QObject *parent)
-    : QAbstractAnimation(parent), to(0), velocity(200), userDuration(-1), maximumEasingTime(-1),
+
+QSmoothedAnimationTimer::QSmoothedAnimationTimer(QSmoothedAnimation *animation, QObject *parent)
+    : QTimer(parent)
+    , m_animation(animation)
+{
+    connect(this, SIGNAL(timeout()), this, SLOT(stopAnimation()));
+}
+
+QSmoothedAnimationTimer::~QSmoothedAnimationTimer()
+{
+}
+
+void QSmoothedAnimationTimer::stopAnimation()
+{
+    m_animation->stop();
+}
+
+QSmoothedAnimation::QSmoothedAnimation(QDeclarativeSmoothedAnimationPrivate *priv)
+    : QAbstractAnimationJob(), to(0), velocity(200), userDuration(-1), maximumEasingTime(-1),
       reversingMode(QDeclarativeSmoothedAnimation::Eased), initialVelocity(0),
-      trackVelocity(0), initialValue(0), invert(false), finalDuration(-1), lastTime(0)
+      trackVelocity(0), initialValue(0), invert(false), finalDuration(-1), lastTime(0),
+      useDelta(false), delayedStopTimer(new QSmoothedAnimationTimer(this)), animationTemplate(priv)
+{
+    delayedStopTimer->setInterval(DELAY_STOP_TIMER_INTERVAL);
+    delayedStopTimer->setSingleShot(true);
+}
+
+QSmoothedAnimation::~QSmoothedAnimation()
 {
+    delete delayedStopTimer;
+    if (animationTemplate) {
+        if (target.object()) {
+            QHash<QDeclarativeProperty, QSmoothedAnimation* >::iterator it =
+                    animationTemplate->activeAnimations.find(target);
+            if (it != animationTemplate->activeAnimations.end() && it.value() == this)
+                animationTemplate->activeAnimations.erase(it);
+        } else {
+            //target is no longer valid, need to search linearly
+            QHash<QDeclarativeProperty, QSmoothedAnimation* >::iterator it;
+            for (it = animationTemplate->activeAnimations.begin(); it != animationTemplate->activeAnimations.end(); ++it) {
+                if (it.value() == this) {
+                    animationTemplate->activeAnimations.erase(it);
+                    break;
+                }
+            }
+        }
+    }
 }
 
 void QSmoothedAnimation::restart()
 {
     initialVelocity = trackVelocity;
-    if (state() != QAbstractAnimation::Running)
-        start();
-    else
+    if (isRunning())
         init();
+    else
+        start();
 }
 
-void QSmoothedAnimation::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State /*oldState*/)
+void QSmoothedAnimation::prepareForRestart()
 {
-    if (newState == QAbstractAnimation::Running)
+    initialVelocity = trackVelocity;
+    if (isRunning()) {
+        //we are joining a new wrapper group while running, our times need to be restarted
+        useDelta = true;
         init();
+        lastTime = 0;
+    } else {
+        useDelta = false;
+        //we'll be started when the group starts, which will force an init()
+    }
 }
 
-void QSmoothedAnimation::timerEvent(QTimerEvent *event)
+void QSmoothedAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/)
 {
-    if (event->timerId() == delayedStopTimer.timerId()) {
-        delayedStopTimer.stop();
-        stop();
-    } else {
-        QAbstractAnimation::timerEvent(event);
-    }
+    if (newState == QAbstractAnimationJob::Running)
+        init();
 }
 
 void QSmoothedAnimation::delayedStop()
 {
-    if (!delayedStopTimer.isActive())
-        delayedStopTimer.start(DELAY_STOP_TIMER_INTERVAL, this);
+    if (!delayedStopTimer->isActive())
+        delayedStopTimer->start();
 }
 
 int QSmoothedAnimation::duration() const
@@ -196,7 +242,9 @@ qreal QSmoothedAnimation::easeFollow(qreal time_seconds)
 
 void QSmoothedAnimation::updateCurrentTime(int t)
 {
-    qreal time_seconds = qreal(t - lastTime) / 1000.;
+    qreal time_seconds = useDelta ? qreal(QDeclarativeAnimationTimer::instance()->currentDelta()) / 1000. : qreal(t - lastTime) / 1000.;
+    if (useDelta)
+        useDelta = false;
 
     qreal value = easeFollow(time_seconds);
     value *= (invert? -1.0: 1.0);
@@ -212,8 +260,8 @@ void QSmoothedAnimation::init()
         return;
     }
 
-    if (delayedStopTimer.isActive())
-        delayedStopTimer.stop();
+    if (delayedStopTimer->isActive())
+        delayedStopTimer->stop();
 
     initialValue = target.read().toReal();
     lastTime = this->currentTime();
@@ -312,14 +360,22 @@ QDeclarativeSmoothedAnimation::QDeclarativeSmoothedAnimation(QObject *parent)
 
 QDeclarativeSmoothedAnimation::~QDeclarativeSmoothedAnimation()
 {
+
 }
 
 QDeclarativeSmoothedAnimationPrivate::QDeclarativeSmoothedAnimationPrivate()
-    : wrapperGroup(new QParallelAnimationGroup), anim(new QSmoothedAnimation)
+    : anim(0)
 {
-    Q_Q(QDeclarativeSmoothedAnimation);
-    QDeclarative_setParent_noEvent(wrapperGroup, q);
-    QDeclarative_setParent_noEvent(anim, q);
+    anim = new QSmoothedAnimation;
+}
+
+QDeclarativeSmoothedAnimationPrivate::~QDeclarativeSmoothedAnimationPrivate()
+{
+    delete anim;
+    QHash<QDeclarativeProperty, QSmoothedAnimation* >::iterator it;
+    for (it = activeAnimations.begin(); it != activeAnimations.end(); ++it) {
+        it.value()->clearTemplate();
+    }
 }
 
 void QDeclarativeSmoothedAnimationPrivate::updateRunningAnimations()
@@ -333,59 +389,56 @@ void QDeclarativeSmoothedAnimationPrivate::updateRunningAnimations()
     }
 }
 
-QAbstractAnimation* QDeclarativeSmoothedAnimation::qtAnimation()
-{
-    Q_D(QDeclarativeSmoothedAnimation);
-    return d->wrapperGroup;
-}
-
-void QDeclarativeSmoothedAnimation::transition(QDeclarativeStateActions &actions,
+QAbstractAnimationJob* QDeclarativeSmoothedAnimation::transition(QDeclarativeStateActions &actions,
                                                QDeclarativeProperties &modified,
                                                TransitionDirection direction)
 {
+    Q_UNUSED(direction);
     Q_D(QDeclarativeSmoothedAnimation);
-    QDeclarativeNumberAnimation::transition(actions, modified, direction);
-
-    if (!d->actions)
-        return;
 
-    QSet<QAbstractAnimation*> anims;
-    for (int i = 0; i < d->actions->size(); i++) {
-        QSmoothedAnimation *ease;
-        bool needsRestart;
-        if (!d->activeAnimations.contains((*d->actions)[i].property)) {
-            ease = new QSmoothedAnimation();
-            d->wrapperGroup->addAnimation(ease);
-            d->activeAnimations.insert((*d->actions)[i].property, ease);
-            needsRestart = false;
-        } else {
-            ease = d->activeAnimations.value((*d->actions)[i].property);
-            needsRestart = true;
+    QDeclarativeStateActions dataActions = QDeclarativePropertyAnimation::createTransitionActions(actions, modified);
+
+    QParallelAnimationGroupJob *wrapperGroup = new QParallelAnimationGroupJob();
+
+    if (!dataActions.isEmpty()) {
+        QSet<QAbstractAnimationJob*> anims;
+        for (int i = 0; i < dataActions.size(); i++) {
+            QSmoothedAnimation *ease;
+            bool isActive;
+            if (!d->activeAnimations.contains(dataActions[i].property)) {
+                ease = new QSmoothedAnimation(d);
+                d->activeAnimations.insert(dataActions[i].property, ease);
+                ease->target = dataActions[i].property;
+                isActive = false;
+            } else {
+                ease = d->activeAnimations.value(dataActions[i].property);
+                isActive = true;
+            }
+            wrapperGroup->appendAnimation(initInstance(ease));
+
+            ease->to = dataActions[i].toValue.toReal();
+
+            // copying public members from main value holder animation
+            ease->maximumEasingTime = d->anim->maximumEasingTime;
+            ease->reversingMode = d->anim->reversingMode;
+            ease->velocity = d->anim->velocity;
+            ease->userDuration = d->anim->userDuration;
+
+            ease->initialVelocity = ease->trackVelocity;
+
+            if (isActive)
+                ease->prepareForRestart();
+            anims.insert(ease);
         }
-        ease->target = (*d->actions)[i].property;
-        ease->to = (*d->actions)[i].toValue.toReal();
-
-        // copying public members from main value holder animation
-        ease->maximumEasingTime = d->anim->maximumEasingTime;
-        ease->reversingMode = d->anim->reversingMode;
-        ease->velocity = d->anim->velocity;
-        ease->userDuration = d->anim->userDuration;
-
-        ease->initialVelocity = ease->trackVelocity;
-
-        if (needsRestart)
-            ease->init();
-        anims.insert(ease);
-    }
 
-    for (int i = d->wrapperGroup->animationCount() - 1; i >= 0 ; --i) {
-        if (!anims.contains(d->wrapperGroup->animationAt(i))) {
-            QSmoothedAnimation *ease = static_cast<QSmoothedAnimation*>(d->wrapperGroup->animationAt(i));
-            d->activeAnimations.remove(ease->target);
-            d->wrapperGroup->takeAnimation(i);
-            delete ease;
+        foreach (QSmoothedAnimation *ease, d->activeAnimations.values()){
+            if (!anims.contains(ease)) {
+                ease->clearTemplate();
+                d->activeAnimations.remove(ease->target);
+            }
         }
     }
+    return wrapperGroup;
 }
 
 /*!
index 02acf20..d065a20 100644 (file)
@@ -53,7 +53,7 @@ QT_BEGIN_NAMESPACE
 
 class QDeclarativeProperty;
 class QDeclarativeSmoothedAnimationPrivate;
-class Q_AUTOTEST_EXPORT QDeclarativeSmoothedAnimation : public QDeclarativeNumberAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeSmoothedAnimation : public QDeclarativeNumberAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativeSmoothedAnimation)
@@ -81,11 +81,9 @@ public:
     int maximumEasingTime() const;
     void setMaximumEasingTime(int);
 
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
-    QAbstractAnimation* qtAnimation();
-
 Q_SIGNALS:
     void velocityChanged();
     void reversingModeChanged();
index 07d8b2b..9afb650 100644 (file)
@@ -39,8 +39,8 @@
 **
 ****************************************************************************/
 
-#ifndef QDECLARATIVESMOOTHEDANIMATION_P_H
-#define QDECLARATIVESMOOTHEDANIMATION_P_H
+#ifndef QDECLARATIVESMOOTHEDANIMATION2_P_H
+#define QDECLARATIVESMOOTHEDANIMATION2_P_H
 
 //
 //  W A R N I N G
 
 #include "qdeclarativeanimation_p_p.h"
 
-#include <qparallelanimationgroup.h>
+#include "private/qparallelanimationgroupjob_p.h"
 
 #include <private/qobject_p.h>
 #include <QBasicTimer>
 
 QT_BEGIN_NAMESPACE
+class QSmoothedAnimation;
+class QSmoothedAnimationTimer : public QTimer
+{
+    Q_OBJECT
+public:
+    explicit QSmoothedAnimationTimer(QSmoothedAnimation *animation, QObject *parent = 0);
+    ~QSmoothedAnimationTimer();
+public Q_SLOTS:
+    void stopAnimation();
+private:
+    QSmoothedAnimation *m_animation;
+};
 
-class Q_AUTOTEST_EXPORT QSmoothedAnimation : public QAbstractAnimation
+class QDeclarativeSmoothedAnimationPrivate;
+class Q_AUTOTEST_EXPORT QSmoothedAnimation : public QAbstractAnimationJob
 {
+    Q_DISABLE_COPY(QSmoothedAnimation)
 public:
-    QSmoothedAnimation(QObject *parent=0);
+    QSmoothedAnimation(QDeclarativeSmoothedAnimationPrivate * = 0);
 
+    ~QSmoothedAnimation();
     qreal to;
     qreal velocity;
     int userDuration;
@@ -86,10 +101,12 @@ public:
     void restart();
     void init();
 
+    void prepareForRestart();
+    void clearTemplate() { animationTemplate = 0; }
+
 protected:
     virtual void updateCurrentTime(int);
-    virtual void updateState(QAbstractAnimation::State, QAbstractAnimation::State);
-    virtual void timerEvent(QTimerEvent *);
+    virtual void updateState(QAbstractAnimationJob::State, QAbstractAnimationJob::State);
 
 private:
     qreal easeFollow(qreal);
@@ -112,11 +129,12 @@ private:
     qreal s;  // Total s
 
     int lastTime;
+    bool useDelta;
 
     bool recalc();
     void delayedStop();
-
-    QBasicTimer delayedStopTimer;
+    QSmoothedAnimationTimer *delayedStopTimer;
+    QDeclarativeSmoothedAnimationPrivate *animationTemplate;
 };
 
 class QDeclarativeSmoothedAnimationPrivate : public QDeclarativePropertyAnimationPrivate
@@ -124,13 +142,13 @@ class QDeclarativeSmoothedAnimationPrivate : public QDeclarativePropertyAnimatio
     Q_DECLARE_PUBLIC(QDeclarativeSmoothedAnimation)
 public:
     QDeclarativeSmoothedAnimationPrivate();
+    ~QDeclarativeSmoothedAnimationPrivate();
     void updateRunningAnimations();
 
-    QParallelAnimationGroup *wrapperGroup;
     QSmoothedAnimation *anim;
     QHash<QDeclarativeProperty, QSmoothedAnimation*> activeAnimations;
 };
 
 QT_END_NAMESPACE
 
-#endif // QDECLARATIVESMOOTHEDANIMATION_P_H
+#endif // QDECLARATIVESMOOTHEDANIMATION2_P_H
index 27ef5bd..40408bb 100644 (file)
@@ -43,6 +43,7 @@
 
 #include "qdeclarativeanimation_p_p.h"
 #include <private/qdeclarativeproperty_p.h>
+#include "private/qparallelanimationgroupjob_p.h"
 
 #include <QtCore/qdebug.h>
 
 #include <limits.h>
 #include <math.h>
 
-QT_BEGIN_NAMESPACE
+#define DELAY_STOP_TIMER_INTERVAL 32
 
+QT_BEGIN_NAMESPACE
 
-class QDeclarativeSpringAnimationPrivate : public QDeclarativePropertyAnimationPrivate
+class QDeclarativeSpringAnimationPrivate;
+class Q_AUTOTEST_EXPORT QSpringAnimation : public QAbstractAnimationJob
 {
-    Q_DECLARE_PUBLIC(QDeclarativeSpringAnimation)
+    Q_DISABLE_COPY(QSpringAnimation)
 public:
-
-
-    struct SpringAnimation {
-        SpringAnimation()
-            : currentValue(0), to(0), velocity(0), start(0), duration(0) {}
-        qreal currentValue;
-        qreal to;
-        qreal velocity;
-        int start;
-        int duration;
+    QSpringAnimation(QDeclarativeSpringAnimationPrivate * = 0);
+
+    ~QSpringAnimation();
+    int duration() const;
+    void restart();
+    void init();
+
+    qreal currentValue;
+    qreal to;
+    qreal velocity;
+    int startTime;
+    int dura;
+    int lastTime;
+    int stopTime;
+    enum Mode {
+        Track,
+        Velocity,
+        Spring
     };
-    QHash<QDeclarativeProperty, SpringAnimation> activeAnimations;
+    Mode mode;
+    QDeclarativeProperty target;
 
-    qreal maxVelocity;
     qreal velocityms;
-    int lastTime;
+    qreal maxVelocity;
     qreal mass;
     qreal spring;
     qreal damping;
@@ -82,75 +93,161 @@ public:
 
     bool useMass : 1;
     bool haveModulus : 1;
+    bool useDelta : 1;
+    typedef QHash<QDeclarativeProperty, QSpringAnimation*> ActiveAnimationHash;
 
-    enum Mode {
-        Track,
-        Velocity,
-        Spring
-    };
-    Mode mode;
+    void clearTemplate() { animationTemplate = 0; }
 
+protected:
+    virtual void updateCurrentTime(int time);
+    virtual void updateState(QAbstractAnimationJob::State, QAbstractAnimationJob::State);
+
+private:
+    QDeclarativeSpringAnimationPrivate *animationTemplate;
+};
+
+class QDeclarativeSpringAnimationPrivate : public QDeclarativePropertyAnimationPrivate
+{
+    Q_DECLARE_PUBLIC(QDeclarativeSpringAnimation)
+public:
     QDeclarativeSpringAnimationPrivate()
-          : maxVelocity(0), velocityms(0), lastTime(0)
-          , mass(1.0), spring(0.), damping(0.), epsilon(0.01)
-          , modulus(0.0), useMass(false), haveModulus(false)
-          , mode(Track), clock(0)
-    { }
-
-    void tick(int time);
-    bool animate(const QDeclarativeProperty &property, SpringAnimation &animation, int elapsed);
+    : QDeclarativePropertyAnimationPrivate()
+    , velocityms(0)
+    , maxVelocity(0)
+    , mass(1.0)
+    , spring(0.)
+    , damping(0.)
+    , epsilon(0.01)
+    , modulus(0.0)
+    , useMass(false)
+    , haveModulus(false)
+    , mode(QSpringAnimation::Track)
+    { elapsed.start(); }
+
     void updateMode();
+    qreal velocityms;
+    qreal maxVelocity;
+    qreal mass;
+    qreal spring;
+    qreal damping;
+    qreal epsilon;
+    qreal modulus;
+
+    bool useMass : 1;
+    bool haveModulus : 1;
+    QSpringAnimation::Mode mode;
 
-    typedef QTickAnimationProxy<QDeclarativeSpringAnimationPrivate, &QDeclarativeSpringAnimationPrivate::tick> Clock;
-    Clock *clock;
+    QSpringAnimation::ActiveAnimationHash activeAnimations;
+    QElapsedTimer elapsed;
 };
 
-void QDeclarativeSpringAnimationPrivate::tick(int time)
+QSpringAnimation::QSpringAnimation(QDeclarativeSpringAnimationPrivate *priv)
+    : QAbstractAnimationJob()
+    , currentValue(0)
+    , to(0)
+    , velocity(0)
+    , startTime(0)
+    , dura(0)
+    , lastTime(0)
+    , stopTime(-1)
+    , mode(Track)
+    , velocityms(0)
+    , maxVelocity(0)
+    , mass(1.0)
+    , spring(0.)
+    , damping(0.)
+    , epsilon(0.01)
+    , modulus(0.0)
+    , useMass(false)
+    , haveModulus(false)
+    , useDelta(false)
+    , animationTemplate(priv)
+{
+}
+
+QSpringAnimation::~QSpringAnimation()
+{
+    if (animationTemplate) {
+        if (target.object()) {
+            QSpringAnimation::ActiveAnimationHash::iterator it =
+                    animationTemplate->activeAnimations.find(target);
+            if (it != animationTemplate->activeAnimations.end() && it.value() == this)
+                animationTemplate->activeAnimations.erase(it);
+        } else {
+            //target is no longer valid, need to search linearly
+            QSpringAnimation::ActiveAnimationHash::iterator it;
+            for (it = animationTemplate->activeAnimations.begin(); it != animationTemplate->activeAnimations.end(); ++it) {
+                if (it.value() == this) {
+                    animationTemplate->activeAnimations.erase(it);
+                    break;
+                }
+            }
+        }
+    }
+}
+
+int QSpringAnimation::duration() const
+{
+    return -1;
+}
+
+void QSpringAnimation::restart()
+{
+    if (isRunning() || (stopTime != -1 && (animationTemplate->elapsed.elapsed() - stopTime) < DELAY_STOP_TIMER_INTERVAL)) {
+        useDelta = true;
+        init();
+        lastTime = 0;
+    } else {
+        useDelta = false;
+        //init() will be triggered when group starts
+    }
+}
+
+void QSpringAnimation::init()
+{
+    lastTime = startTime = 0;
+    stopTime = -1;
+}
+
+void QSpringAnimation::updateCurrentTime(int time)
 {
     if (mode == Track) {
-        clock->stop();
+        stop();
         return;
     }
-    int elapsed = time - lastTime;
+
+    int elapsed = useDelta ? QDeclarativeAnimationTimer::instance()->currentDelta() : time - lastTime;
+    if (useDelta) {
+        startTime = time - elapsed;
+        useDelta = false;
+    }
+
     if (!elapsed)
         return;
 
+    int count = elapsed / 16;
+
     if (mode == Spring) {
         if (elapsed < 16) // capped at 62fps.
             return;
-        int count = elapsed / 16;
         lastTime = time - (elapsed - count * 16);
     } else {
         lastTime = time;
     }
 
-    QMutableHashIterator<QDeclarativeProperty, SpringAnimation> it(activeAnimations);
-    while (it.hasNext()) {
-        it.next();
-        if (animate(it.key(), it.value(), elapsed))
-            it.remove();
-    }
-
-    if (activeAnimations.isEmpty())
-        clock->stop();
-}
-
-bool QDeclarativeSpringAnimationPrivate::animate(const QDeclarativeProperty &property, SpringAnimation &animation, int elapsed)
-{
-    qreal srcVal = animation.to;
+    qreal srcVal = to;
 
-    bool stop = false;
+    bool stopped = false;
 
     if (haveModulus) {
-        animation.currentValue = fmod(animation.currentValue, modulus);
+        currentValue = fmod(currentValue, modulus);
         srcVal = fmod(srcVal, modulus);
     }
     if (mode == Spring) {
         // Real men solve the spring DEs using RK4.
         // We'll do something much simpler which gives a result that looks fine.
-        int count = elapsed / 16;
         for (int i = 0; i < count; ++i) {
-            qreal diff = srcVal - animation.currentValue;
+            qreal diff = srcVal - currentValue;
             if (haveModulus && qAbs(diff) > modulus / 2) {
                 if (diff < 0)
                     diff += modulus;
@@ -158,31 +255,31 @@ bool QDeclarativeSpringAnimationPrivate::animate(const QDeclarativeProperty &pro
                     diff -= modulus;
             }
             if (useMass)
-                animation.velocity = animation.velocity + (spring * diff - damping * animation.velocity) / mass;
+                velocity = velocity + (spring * diff - damping * velocity) / mass;
             else
-                animation.velocity = animation.velocity + spring * diff - damping * animation.velocity;
+                velocity = velocity + spring * diff - damping * velocity;
             if (maxVelocity > 0.) {
                 // limit velocity
-                if (animation.velocity > maxVelocity)
-                    animation.velocity = maxVelocity;
-                else if (animation.velocity < -maxVelocity)
-                    animation.velocity = -maxVelocity;
+                if (velocity > maxVelocity)
+                    velocity = maxVelocity;
+                else if (velocity < -maxVelocity)
+                    velocity = -maxVelocity;
             }
-            animation.currentValue += animation.velocity * 16.0 / 1000.0;
+            currentValue += velocity * 16.0 / 1000.0;
             if (haveModulus) {
-                animation.currentValue = fmod(animation.currentValue, modulus);
-                if (animation.currentValue < 0.0)
-                    animation.currentValue += modulus;
+                currentValue = fmod(currentValue, modulus);
+                if (currentValue < 0.0)
+                    currentValue += modulus;
             }
         }
-        if (qAbs(animation.velocity) < epsilon && qAbs(srcVal - animation.currentValue) < epsilon) {
-            animation.velocity = 0.0;
-            animation.currentValue = srcVal;
-            stop = true;
+        if (qAbs(velocity) < epsilon && qAbs(srcVal - currentValue) < epsilon) {
+            velocity = 0.0;
+            currentValue = srcVal;
+            stopped = true;
         }
     } else {
         qreal moveBy = elapsed * velocityms;
-        qreal diff = srcVal - animation.currentValue;
+        qreal diff = srcVal - currentValue;
         if (haveModulus && qAbs(diff) > modulus / 2) {
             if (diff < 0)
                 diff += modulus;
@@ -190,45 +287,54 @@ bool QDeclarativeSpringAnimationPrivate::animate(const QDeclarativeProperty &pro
                 diff -= modulus;
         }
         if (diff > 0) {
-            animation.currentValue += moveBy;
+            currentValue += moveBy;
             if (haveModulus)
-                animation.currentValue = fmod(animation.currentValue, modulus);
+                currentValue = fmod(currentValue, modulus);
         } else {
-            animation.currentValue -= moveBy;
-            if (haveModulus && animation.currentValue < 0.0)
-                animation.currentValue = fmod(animation.currentValue, modulus) + modulus;
+            currentValue -= moveBy;
+            if (haveModulus && currentValue < 0.0)
+                currentValue = fmod(currentValue, modulus) + modulus;
         }
-        if (lastTime - animation.start >= animation.duration) {
-            animation.currentValue = animation.to;
-            stop = true;
+        if (lastTime - startTime >= dura) {
+            currentValue = to;
+            stopped = true;
         }
     }
 
-    qreal old_to = animation.to;
+    qreal old_to = to;
 
-    QDeclarativePropertyPrivate::write(property, animation.currentValue,
+    QDeclarativePropertyPrivate::write(target, currentValue,
                                        QDeclarativePropertyPrivate::BypassInterceptor |
                                        QDeclarativePropertyPrivate::DontRemoveBinding);
 
-    return (stop && old_to == animation.to); // do not stop if we got restarted
+    if (stopped && old_to == to) { // do not stop if we got restarted
+        stopTime = animationTemplate->elapsed.elapsed();
+        stop();
+    }
+}
+
+void QSpringAnimation::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State /*oldState*/)
+{
+    if (newState == QAbstractAnimationJob::Running)
+        init();
 }
 
 void QDeclarativeSpringAnimationPrivate::updateMode()
 {
     if (spring == 0. && maxVelocity == 0.)
-        mode = Track;
+        mode = QSpringAnimation::Track;
     else if (spring > 0.)
-        mode = Spring;
+        mode = QSpringAnimation::Spring;
     else {
-        mode = Velocity;
-        QHash<QDeclarativeProperty, SpringAnimation>::iterator it;
+        mode = QSpringAnimation::Velocity;
+        QSpringAnimation::ActiveAnimationHash::iterator it;
         for (it = activeAnimations.begin(); it != activeAnimations.end(); ++it) {
-            SpringAnimation &animation = *it;
-            animation.start = lastTime;
-            qreal dist = qAbs(animation.currentValue - animation.to);
+            QSpringAnimation *animation = *it;
+            animation->startTime = animation->lastTime;
+            qreal dist = qAbs(animation->currentValue - animation->to);
             if (haveModulus && dist > modulus / 2)
                 dist = modulus - fmod(dist, modulus);
-            animation.duration = dist / velocityms;
+            animation->dura = dist / velocityms;
         }
     }
 }
@@ -264,12 +370,15 @@ void QDeclarativeSpringAnimationPrivate::updateMode()
 QDeclarativeSpringAnimation::QDeclarativeSpringAnimation(QObject *parent)
 : QDeclarativeNumberAnimation(*(new QDeclarativeSpringAnimationPrivate),parent)
 {
-    Q_D(QDeclarativeSpringAnimation);
-    d->clock = new QDeclarativeSpringAnimationPrivate::Clock(d, this);
 }
 
 QDeclarativeSpringAnimation::~QDeclarativeSpringAnimation()
 {
+    Q_D(QDeclarativeSpringAnimation);
+    QSpringAnimation::ActiveAnimationHash::iterator it;
+    for (it = d->activeAnimations.begin(); it != d->activeAnimations.end(); ++it) {
+        it.value()->clearTemplate();
+    }
 }
 
 /*!
@@ -415,48 +524,69 @@ void QDeclarativeSpringAnimation::setMass(qreal mass)
     }
 }
 
-void QDeclarativeSpringAnimation::transition(QDeclarativeStateActions &actions,
-                                             QDeclarativeProperties &modified,
-                                             TransitionDirection direction)
+QAbstractAnimationJob* QDeclarativeSpringAnimation::transition(QDeclarativeStateActions &actions,
+                                                                   QDeclarativeProperties &modified,
+                                                                   TransitionDirection direction)
 {
     Q_D(QDeclarativeSpringAnimation);
     Q_UNUSED(direction);
 
-    if (d->clock->state() != QAbstractAnimation::Running) {
-        d->lastTime = 0;
-    }
-
-    QDeclarativeNumberAnimation::transition(actions, modified, direction);
-
-    if (!d->actions)
-        return;
+    QParallelAnimationGroupJob *wrapperGroup = new QParallelAnimationGroupJob();
+
+    QDeclarativeStateActions dataActions = QDeclarativeNumberAnimation::createTransitionActions(actions, modified);
+    if (!dataActions.isEmpty()) {
+        QSet<QAbstractAnimationJob*> anims;
+        for (int i = 0; i < dataActions.size(); ++i) {
+            QSpringAnimation *animation;
+            bool needsRestart = false;
+            const QDeclarativeProperty &property = dataActions.at(i).property;
+            if (d->activeAnimations.contains(property)) {
+                animation = d->activeAnimations[property];
+                needsRestart = true;
+            } else {
+                animation = new QSpringAnimation(d);
+                d->activeAnimations.insert(property, animation);
+                animation->target = property;
+            }
+            wrapperGroup->appendAnimation(initInstance(animation));
+
+            animation->to = dataActions.at(i).toValue.toReal();
+            animation->startTime = 0;
+            animation->velocityms = d->velocityms;
+            animation->mass = d->mass;
+            animation->spring = d->spring;
+            animation->damping = d->damping;
+            animation->epsilon = d->epsilon;
+            animation->modulus = d->modulus;
+            animation->useMass = d->useMass;
+            animation->haveModulus = d->haveModulus;
+            animation->mode = d->mode;
+            animation->dura = -1;
+            animation->maxVelocity = d->maxVelocity;
 
-    if (!d->actions->isEmpty()) {
-        for (int i = 0; i < d->actions->size(); ++i) {
-            const QDeclarativeProperty &property = d->actions->at(i).property;
-            QDeclarativeSpringAnimationPrivate::SpringAnimation &animation
-                    = d->activeAnimations[property];
-            animation.to = d->actions->at(i).toValue.toReal();
-            animation.start = d->lastTime;
             if (d->fromIsDefined)
-                animation.currentValue = d->actions->at(i).fromValue.toReal();
+                animation->currentValue = dataActions.at(i).fromValue.toReal();
             else
-                animation.currentValue = property.read().toReal();
-            if (d->mode == QDeclarativeSpringAnimationPrivate::Velocity) {
-                qreal dist = qAbs(animation.currentValue - animation.to);
+                animation->currentValue = property.read().toReal();
+            if (animation->mode == QSpringAnimation::Velocity) {
+                qreal dist = qAbs(animation->currentValue - animation->to);
                 if (d->haveModulus && dist > d->modulus / 2)
                     dist = d->modulus - fmod(dist, d->modulus);
-                animation.duration = dist / d->velocityms;
+                animation->dura = dist / animation->velocityms;
+            }
+
+            if (needsRestart)
+                animation->restart();
+            anims.insert(animation);
+        }
+        foreach (QSpringAnimation *anim, d->activeAnimations.values()){
+            if (!anims.contains(anim)) {
+                anim->clearTemplate();
+                d->activeAnimations.remove(anim->target);
             }
         }
     }
-}
-
-
-QAbstractAnimation *QDeclarativeSpringAnimation::qtAnimation()
-{
-    Q_D(QDeclarativeSpringAnimation);
-    return d->clock;
+    return wrapperGroup;
 }
 
 QT_END_NAMESPACE
index 169cd89..21515d0 100644 (file)
@@ -52,7 +52,7 @@ QT_BEGIN_HEADER
 QT_BEGIN_NAMESPACE
 
 class QDeclarativeSpringAnimationPrivate;
-class Q_AUTOTEST_EXPORT QDeclarativeSpringAnimation : public QDeclarativeNumberAnimation
+class Q_QUICK_PRIVATE_EXPORT QDeclarativeSpringAnimation : public QDeclarativeNumberAnimation
 {
     Q_OBJECT
     Q_DECLARE_PRIVATE(QDeclarativeSpringAnimation)
@@ -87,13 +87,10 @@ public:
     qreal modulus() const;
     void setModulus(qreal modulus);
 
-    virtual void transition(QDeclarativeStateActions &actions,
+    virtual QAbstractAnimationJob* transition(QDeclarativeStateActions &actions,
                             QDeclarativeProperties &modified,
                             TransitionDirection direction);
 
-protected:
-    virtual QAbstractAnimation *qtAnimation();
-
 Q_SIGNALS:
     void modulusChanged();
     void massChanged();
index b38f361..a33fdd5 100644 (file)
@@ -311,7 +311,7 @@ qreal QDeclarativeTimeLinePrivate::value(const Op &op, int time, qreal base, boo
     Construct a new QDeclarativeTimeLine with the specified \a parent.
 */
 QDeclarativeTimeLine::QDeclarativeTimeLine(QObject *parent)
-: QAbstractAnimation(parent)
+    : QObject(parent)
 {
     d = new QDeclarativeTimeLinePrivate(this);
 }
index 2f82bd7..421e2e5 100644 (file)
@@ -54,7 +54,7 @@
 //
 
 #include <QtCore/QObject>
-#include <QtCore/QAbstractAnimation>
+#include "private/qabstractanimationjob_p.h"
 
 QT_BEGIN_NAMESPACE
 
@@ -63,7 +63,7 @@ class QDeclarativeTimeLineValue;
 class QDeclarativeTimeLineCallback;
 struct QDeclarativeTimeLinePrivate;
 class QDeclarativeTimeLineObject;
-class Q_AUTOTEST_EXPORT QDeclarativeTimeLine : public QAbstractAnimation
+class Q_AUTOTEST_EXPORT QDeclarativeTimeLine : public QObject, QAbstractAnimationJob
 {
 Q_OBJECT
 public:
index e4ec54f..f54e9b3 100644 (file)
@@ -42,7 +42,7 @@
 #include "qdeclarativetimer_p.h"
 
 #include <QtCore/qcoreapplication.h>
-#include <QtCore/qpauseanimation.h>
+#include "private/qpauseanimationjob_p.h"
 #include <qdebug.h>
 
 #include <private/qobject_p.h>
@@ -51,15 +51,19 @@ QT_BEGIN_NAMESPACE
 
 
 
-class QDeclarativeTimerPrivate : public QObjectPrivate
+class QDeclarativeTimerPrivate : public QObjectPrivate, public QAnimation2ChangeListener
 {
     Q_DECLARE_PUBLIC(QDeclarativeTimer)
 public:
     QDeclarativeTimerPrivate()
         : interval(1000), running(false), repeating(false), triggeredOnStart(false)
         , classBegun(false), componentComplete(false), firstTick(true) {}
+
+    virtual void animationFinished(QAbstractAnimationJob *);
+    virtual void animationCurrentLoopChanged(QAbstractAnimationJob *)  { Q_Q(QDeclarativeTimer); q->ticked(); }
+
     int interval;
-    QPauseAnimation pause;
+    QPauseAnimationJob pause;
     bool running : 1;
     bool repeating : 1;
     bool triggeredOnStart : 1;
@@ -111,8 +115,7 @@ QDeclarativeTimer::QDeclarativeTimer(QObject *parent)
     : QObject(*(new QDeclarativeTimerPrivate), parent)
 {
     Q_D(QDeclarativeTimer);
-    connect(&d->pause, SIGNAL(currentLoopChanged(int)), this, SLOT(ticked()));
-    connect(&d->pause, SIGNAL(finished()), this, SLOT(finished()));
+    d->pause.addAnimationChangeListener(d, QAbstractAnimationJob::Completion | QAbstractAnimationJob::CurrentLoop);
     d->pause.setLoopCount(1);
     d->pause.setDuration(d->interval);
 }
@@ -310,15 +313,15 @@ void QDeclarativeTimer::ticked()
     d->firstTick = false;
 }
 
-void QDeclarativeTimer::finished()
+void QDeclarativeTimerPrivate::animationFinished(QAbstractAnimationJob *)
 {
-    Q_D(QDeclarativeTimer);
-    if (d->repeating || !d->running)
+    Q_Q(QDeclarativeTimer);
+    if (repeating || !running)
         return;
-    d->running = false;
-    d->firstTick = false;
-    emit triggered();
-    emit runningChanged();
+    running = false;
+    firstTick = false;
+    emit q->triggered();
+    emit q->runningChanged();
 }
 
 QT_END_NAMESPACE
index 8cec031..e400b3d 100644 (file)
@@ -45,7 +45,6 @@
 #include <qdeclarative.h>
 
 #include <QtCore/qobject.h>
-#include <QtCore/qabstractanimation.h>
 
 #include <private/qtquickglobal_p.h>
 
@@ -101,7 +100,6 @@ private:
 
 private Q_SLOTS:
     void ticked();
-    void finished();
 };
 
 QT_END_NAMESPACE
index 12966b5..2a05dd2 100644 (file)
@@ -48,7 +48,7 @@
 #include "qdeclarativeanimation_p_p.h"
 #include "qdeclarativetransitionmanager_p_p.h"
 
-#include <QParallelAnimationGroup>
+#include "private/qparallelanimationgroupjob_p.h"
 
 QT_BEGIN_NAMESPACE
 
@@ -96,16 +96,44 @@ QT_BEGIN_NAMESPACE
     \sa {QML Animation and Transitions}, {declarative/animation/states}{states example}, {qmlstates}{States}, {QtDeclarative}
 */
 
+QDeclarativeTransitionInstance::QDeclarativeTransitionInstance()
+    : m_anim(0)
+{
+}
+
+QDeclarativeTransitionInstance::~QDeclarativeTransitionInstance()
+{
+    delete m_anim;
+}
+
+void QDeclarativeTransitionInstance::start()
+{
+    if (m_anim)
+        m_anim->start();
+}
+
+void QDeclarativeTransitionInstance::stop()
+{
+    if (m_anim)
+        m_anim->stop();
+}
+
+bool QDeclarativeTransitionInstance::isRunning() const
+{
+    return m_anim && m_anim->state() == QAbstractAnimationJob::Running;
+}
+
+
 //ParallelAnimationWrapper allows us to do a "callback" when the animation finishes, rather than connecting
 //and disconnecting signals and slots frequently
-class ParallelAnimationWrapper : public QParallelAnimationGroup
+class ParallelAnimationWrapper : public QParallelAnimationGroupJob
 {
-    Q_OBJECT
 public:
-    ParallelAnimationWrapper(QObject *parent = 0) : QParallelAnimationGroup(parent) {}
-    QDeclarativeTransitionPrivate *trans;
+    ParallelAnimationWrapper() : QParallelAnimationGroupJob() {}
+    QDeclarativeTransitionManager *manager;
+
 protected:
-    virtual void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState);
+    virtual void updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState);
 };
 
 class QDeclarativeTransitionPrivate : public QObjectPrivate
@@ -114,9 +142,8 @@ class QDeclarativeTransitionPrivate : public QObjectPrivate
 public:
     QDeclarativeTransitionPrivate()
     : fromState(QLatin1String("*")), toState(QLatin1String("*")),
-      reversed(false), reversible(false), enabled(true), manager(0)
+        reversed(false), reversible(false), enabled(true)
     {
-        group.trans = this;
     }
 
     QString fromState;
@@ -124,13 +151,7 @@ public:
     bool reversed;
     bool reversible;
     bool enabled;
-    ParallelAnimationWrapper group;
-    QDeclarativeTransitionManager *manager;
 
-    void complete()
-    {
-        manager->complete();
-    }
     static void append_animation(QDeclarativeListProperty<QDeclarativeAbstractAnimation> *list, QDeclarativeAbstractAnimation *a);
     static int animation_count(QDeclarativeListProperty<QDeclarativeAbstractAnimation> *list);
     static QDeclarativeAbstractAnimation* animation_at(QDeclarativeListProperty<QDeclarativeAbstractAnimation> *list, int pos);
@@ -142,7 +163,6 @@ void QDeclarativeTransitionPrivate::append_animation(QDeclarativeListProperty<QD
 {
     QDeclarativeTransition *q = static_cast<QDeclarativeTransition *>(list->object);
     q->d_func()->animations.append(a);
-    q->d_func()->group.addAnimation(a->qtAnimation());
     a->setDisableUserControl();
 }
 
@@ -163,24 +183,22 @@ void QDeclarativeTransitionPrivate::clear_animations(QDeclarativeListProperty<QD
     QDeclarativeTransition *q = static_cast<QDeclarativeTransition *>(list->object);
     while (q->d_func()->animations.count()) {
         QDeclarativeAbstractAnimation *firstAnim = q->d_func()->animations.at(0);
-        q->d_func()->group.removeAnimation(firstAnim->qtAnimation());
         q->d_func()->animations.removeAll(firstAnim);
     }
 }
 
-void ParallelAnimationWrapper::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
+void ParallelAnimationWrapper::updateState(QAbstractAnimationJob::State newState, QAbstractAnimationJob::State oldState)
 {
-    QParallelAnimationGroup::updateState(newState, oldState);
+    QParallelAnimationGroupJob::updateState(newState, oldState);
     if (newState == Stopped && (duration() == -1
-        || (direction() == QAbstractAnimation::Forward && currentLoopTime() == duration())
-        || (direction() == QAbstractAnimation::Backward && currentLoopTime() == 0)))
+        || (direction() == QAbstractAnimationJob::Forward && currentLoopTime() == duration())
+        || (direction() == QAbstractAnimationJob::Backward && currentLoopTime() == 0)))
     {
-        trans->complete();
+         manager->complete();
     }
 }
 
 
-
 QDeclarativeTransition::QDeclarativeTransition(QObject *parent)
     : QObject(*(new QDeclarativeTransitionPrivate), parent)
 {
@@ -188,12 +206,7 @@ QDeclarativeTransition::QDeclarativeTransition(QObject *parent)
 
 QDeclarativeTransition::~QDeclarativeTransition()
 {
-}
-
-void QDeclarativeTransition::stop()
-{
     Q_D(QDeclarativeTransition);
-    d->group.stop();
 }
 
 void QDeclarativeTransition::setReversed(bool r)
@@ -202,7 +215,7 @@ void QDeclarativeTransition::setReversed(bool r)
     d->reversed = r;
 }
 
-void QDeclarativeTransition::prepare(QDeclarativeStateOperation::ActionList &actions,
+QDeclarativeTransitionInstance *QDeclarativeTransition::prepare(QDeclarativeStateOperation::ActionList &actions,
                             QList<QDeclarativeProperty> &after,
                             QDeclarativeTransitionManager *manager)
 {
@@ -210,19 +223,26 @@ void QDeclarativeTransition::prepare(QDeclarativeStateOperation::ActionList &act
 
     qmlExecuteDeferred(this);
 
-    if (d->reversed) {
-        for (int ii = d->animations.count() - 1; ii >= 0; --ii) {
-            d->animations.at(ii)->transition(actions, after, QDeclarativeAbstractAnimation::Backward);
-        }
-    } else {
-        for (int ii = 0; ii < d->animations.count(); ++ii) {
-            d->animations.at(ii)->transition(actions, after, QDeclarativeAbstractAnimation::Forward);
-        }
+    ParallelAnimationWrapper *group = new ParallelAnimationWrapper();
+    group->manager = manager;
+
+    QDeclarativeAbstractAnimation::TransitionDirection direction = d->reversed ? QDeclarativeAbstractAnimation::Backward : QDeclarativeAbstractAnimation::Forward;
+    int start = d->reversed ? d->animations.count() - 1 : 0;
+    int end = d->reversed ? -1 : d->animations.count();
+
+    QAbstractAnimationJob *anim = 0;
+    for (int i = start; i != end;) {
+        anim = d->animations.at(i)->transition(actions, after, direction);
+        if (anim)
+            d->reversed ? group->prependAnimation(anim) : group->appendAnimation(anim);
+        d->reversed ? --i : ++i;
     }
 
-    d->manager = manager;
-    d->group.setDirection(d->reversed ? QAbstractAnimation::Backward : QAbstractAnimation::Forward);
-    d->group.start();
+    group->setDirection(d->reversed ? QAbstractAnimationJob::Backward : QAbstractAnimationJob::Forward);
+
+    QDeclarativeTransitionInstance *wrapper = new QDeclarativeTransitionInstance;
+    wrapper->m_anim = group;
+    return wrapper;
 }
 
 /*!
@@ -389,4 +409,4 @@ QDeclarativeListProperty<QDeclarativeAbstractAnimation> QDeclarativeTransition::
 
 QT_END_NAMESPACE
 
-#include <qdeclarativetransition.moc>
+//#include <qdeclarativetransition.moc>
index a4f6dc4..64fd45b 100644 (file)
@@ -43,7 +43,6 @@
 #define QDECLARATIVETRANSITION_H
 
 #include "qdeclarativestate_p.h"
-
 #include <qdeclarative.h>
 
 #include <QtCore/qobject.h>
@@ -55,6 +54,25 @@ QT_BEGIN_NAMESPACE
 class QDeclarativeAbstractAnimation;
 class QDeclarativeTransitionPrivate;
 class QDeclarativeTransitionManager;
+class QDeclarativeTransition;
+class QAbstractAnimationJob;
+
+class Q_QUICK_EXPORT QDeclarativeTransitionInstance
+{
+public:
+    QDeclarativeTransitionInstance();
+    ~QDeclarativeTransitionInstance();
+
+    void start();
+    void stop();
+
+    bool isRunning() const;
+
+private:
+    QAbstractAnimationJob *m_anim;
+    friend class QDeclarativeTransition;
+};
+
 class Q_QUICK_EXPORT QDeclarativeTransition : public QObject
 {
     Q_OBJECT
@@ -86,12 +104,11 @@ public:
 
     QDeclarativeListProperty<QDeclarativeAbstractAnimation> animations();
 
-    void prepare(QDeclarativeStateOperation::ActionList &actions,
+    QDeclarativeTransitionInstance *prepare(QDeclarativeStateOperation::ActionList &actions,
                  QList<QDeclarativeProperty> &after,
                  QDeclarativeTransitionManager *end);
 
     void setReversed(bool r);
-    void stop();
 
 Q_SIGNALS:
     void fromChanged();
index 5542548..a2de7db 100644 (file)
@@ -43,7 +43,6 @@
 
 #include "qdeclarativetransition_p.h"
 #include "qdeclarativestate_p_p.h"
-#include "qdeclarativestate_p.h"
 
 #include <private/qdeclarativebinding_p.h>
 #include <private/qdeclarativeglobal_p.h>
@@ -59,12 +58,12 @@ class QDeclarativeTransitionManagerPrivate
 {
 public:
     QDeclarativeTransitionManagerPrivate()
-        : state(0) {}
+        : state(0), transitionInstance(0) {}
 
     void applyBindings();
     typedef QList<QDeclarativeSimpleAction> SimpleActionList;
     QDeclarativeState *state;
-    QDeclarativeGuard<QDeclarativeTransition> transition;
+    QDeclarativeTransitionInstance *transitionInstance;
     QDeclarativeStateOperation::ActionList bindingsList;
     SimpleActionList completeList;
 };
@@ -81,9 +80,15 @@ void QDeclarativeTransitionManager::setState(QDeclarativeState *s)
 
 QDeclarativeTransitionManager::~QDeclarativeTransitionManager()
 {
+    delete d->transitionInstance;
     delete d; d = 0;
 }
 
+bool QDeclarativeTransitionManager::isRunning() const
+{
+    return d->transitionInstance && d->transitionInstance->isRunning();
+}
+
 void QDeclarativeTransitionManager::complete() 
 {
     d->applyBindings();
@@ -97,6 +102,8 @@ void QDeclarativeTransitionManager::complete()
 
     if (d->state) 
         static_cast<QDeclarativeStatePrivate*>(QObjectPrivate::get(d->state))->complete();
+
+    finished();
 }
 
 void QDeclarativeTransitionManagerPrivate::applyBindings()
@@ -116,6 +123,10 @@ void QDeclarativeTransitionManagerPrivate::applyBindings()
     bindingsList.clear();
 }
 
+void QDeclarativeTransitionManager::finished()
+{
+}
+
 void QDeclarativeTransitionManager::transition(const QList<QDeclarativeAction> &list,
                                       QDeclarativeTransition *transition)
 {
@@ -196,8 +207,11 @@ void QDeclarativeTransitionManager::transition(const QList<QDeclarativeAction> &
 
     if (transition) {
         QList<QDeclarativeProperty> touched;
-        d->transition = transition;
-        d->transition->prepare(applyList, touched, this);
+        QDeclarativeTransitionInstance *oldInstance = d->transitionInstance;
+        d->transitionInstance = transition->prepare(applyList, touched, this);
+        d->transitionInstance->start();
+        if (oldInstance && oldInstance != d->transitionInstance)
+            delete oldInstance;
 
         // Modify the action list to remove actions handled in the transition
         for (int ii = 0; ii < applyList.count(); ++ii) {
@@ -257,11 +271,8 @@ void QDeclarativeTransitionManager::transition(const QList<QDeclarativeAction> &
 
 void QDeclarativeTransitionManager::cancel()
 {
-    if (d->transition) {
-        // ### this could potentially trigger a complete in rare circumstances
-        d->transition->stop();
-        d->transition = 0;
-    }
+    if (d->transitionInstance && d->transitionInstance->isRunning())
+        d->transitionInstance->stop();
 
     for(int i = 0; i < d->bindingsList.count(); ++i) {
         QDeclarativeAction action = d->bindingsList[i];
index adbde00..4e5d1a9 100644 (file)
@@ -54,6 +54,7 @@
 //
 
 #include "qdeclarativestateoperations_p.h"
+#include "qdeclarativeanimation_p.h"
 
 QT_BEGIN_NAMESPACE
 
@@ -65,10 +66,15 @@ public:
     QDeclarativeTransitionManager();
     ~QDeclarativeTransitionManager();
 
+    bool isRunning() const;
+
     void transition(const QList<QDeclarativeAction> &, QDeclarativeTransition *transition);
 
     void cancel();
 
+protected:
+    virtual void finished();
+
 private:
     Q_DISABLE_COPY(QDeclarativeTransitionManager)
     QDeclarativeTransitionManagerPrivate *d;
@@ -77,7 +83,7 @@ private:
     void setState(QDeclarativeState *);
 
     friend class QDeclarativeState;
-    friend class QDeclarativeTransitionPrivate;
+    friend class ParallelAnimationWrapper;
 };
 
 QT_END_NAMESPACE
index 80f517c..1841a8c 100644 (file)
@@ -59,6 +59,7 @@
 #include "qdeclarativetransition_p.h"
 #include <qdeclarativeinfo.h>
 #include <private/qdeclarativetypenotavailable_p.h>
+#include <private/qdeclarativeanimationcontroller_p.h>
 #include <QtCore/qcoreapplication.h>
 #include <QtGui/QInputPanel>
 
@@ -83,6 +84,7 @@ void QDeclarativeUtilModule::defineModule()
     qmlRegisterType<QDeclarativeScriptAction>("QtQuick",2,0,"ScriptAction");
     qmlRegisterType<QDeclarativeSequentialAnimation>("QtQuick",2,0,"SequentialAnimation");
     qmlRegisterType<QDeclarativeSpringAnimation>("QtQuick",2,0,"SpringAnimation");
+    qmlRegisterType<QDeclarativeAnimationController>("QtQuick",2,0,"AnimationController");
     qmlRegisterType<QDeclarativeStateChangeScript>("QtQuick",2,0,"StateChangeScript");
     qmlRegisterType<QDeclarativeStateGroup>("QtQuick",2,0,"StateGroup");
     qmlRegisterType<QDeclarativeState>("QtQuick",2,0,"State");
index 9c8964b..d933459 100644 (file)
@@ -6,6 +6,7 @@ SOURCES += \
     $$PWD/qdeclarativesystempalette.cpp \
     $$PWD/qdeclarativespringanimation.cpp \
     $$PWD/qdeclarativesmoothedanimation.cpp \
+    $$PWD/qdeclarativeanimationcontroller.cpp \
     $$PWD/qdeclarativestate.cpp\
     $$PWD/qdeclarativetransitionmanager.cpp \
     $$PWD/qdeclarativestateoperations.cpp \
@@ -34,6 +35,7 @@ HEADERS += \
     $$PWD/qdeclarativeanimation_p_p.h \
     $$PWD/qdeclarativesystempalette_p.h \
     $$PWD/qdeclarativespringanimation_p.h \
+    $$PWD/qdeclarativeanimationcontroller_p.h \
     $$PWD/qdeclarativesmoothedanimation_p.h \
     $$PWD/qdeclarativesmoothedanimation_p_p.h \
     $$PWD/qdeclarativestate_p.h\
@@ -56,4 +58,4 @@ HEADERS += \
     $$PWD/qdeclarativechangeset_p.h \
     $$PWD/qdeclarativelistcompositor_p.h \
     $$PWD/qdeclarativepathinterpolator_p.h \
-    $$PWD/qdeclarativesvgparser_p.h
+    $$PWD/qdeclarativesvgparser_p.h
\ No newline at end of file
diff --git a/tests/auto/declarative/animation/animation.pro b/tests/auto/declarative/animation/animation.pro
new file mode 100644 (file)
index 0000000..a9c0cee
--- /dev/null
@@ -0,0 +1,7 @@
+TEMPLATE=subdirs
+SUBDIRS=\
+   qabstractanimationjob \
+   qanimationgroupjob \
+   qparallelanimationgroupjob \
+   qpauseanimationjob \
+   qsequentialanimationgroupjob
diff --git a/tests/auto/declarative/animation/qabstractanimationjob/qabstractanimationjob.pro b/tests/auto/declarative/animation/qabstractanimationjob/qabstractanimationjob.pro
new file mode 100644 (file)
index 0000000..db8649b
--- /dev/null
@@ -0,0 +1,5 @@
+CONFIG += testcase parallel_test
+macx:CONFIG -= app_bundle
+TARGET = tst_qabstractanimationjob
+QT = core-private declarative-private testlib
+SOURCES = tst_qabstractanimationjob.cpp
diff --git a/tests/auto/declarative/animation/qabstractanimationjob/tst_qabstractanimationjob.cpp b/tests/auto/declarative/animation/qabstractanimationjob/tst_qabstractanimationjob.cpp
new file mode 100644 (file)
index 0000000..772605d
--- /dev/null
@@ -0,0 +1,229 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtDeclarative/private/qabstractanimationjob_p.h>
+#include <QtDeclarative/private/qanimationgroupjob_p.h>
+#include <QtTest>
+
+class tst_QAbstractAnimationJob : public QObject
+{
+  Q_OBJECT
+private slots:
+    void construction();
+    void destruction();
+    void currentLoop();
+    void currentLoopTime();
+    void currentTime();
+    void direction();
+    void group();
+    void loopCount();
+    void state();
+    void totalDuration();
+    void avoidJumpAtStart();
+    void avoidJumpAtStartWithStop();
+    void avoidJumpAtStartWithRunning();
+};
+
+class TestableQAbstractAnimation : public QAbstractAnimationJob
+{
+public:
+    TestableQAbstractAnimation() : m_duration(10) {}
+    virtual ~TestableQAbstractAnimation() {};
+
+    int duration() const { return m_duration; }
+    virtual void updateCurrentTime(int) {}
+
+    void setDuration(int duration) { m_duration = duration; }
+private:
+    int m_duration;
+};
+
+class DummyQAnimationGroup : public QAnimationGroupJob
+{
+public:
+    int duration() const { return 10; }
+    virtual void updateCurrentTime(int) {}
+};
+
+void tst_QAbstractAnimationJob::construction()
+{
+    TestableQAbstractAnimation anim;
+}
+
+void tst_QAbstractAnimationJob::destruction()
+{
+    TestableQAbstractAnimation *anim = new TestableQAbstractAnimation;
+    delete anim;
+}
+
+void tst_QAbstractAnimationJob::currentLoop()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.currentLoop(), 0);
+}
+
+void tst_QAbstractAnimationJob::currentLoopTime()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.currentLoopTime(), 0);
+}
+
+void tst_QAbstractAnimationJob::currentTime()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.currentTime(), 0);
+    anim.setCurrentTime(10);
+    QCOMPARE(anim.currentTime(), 10);
+}
+
+void tst_QAbstractAnimationJob::direction()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.direction(), QAbstractAnimationJob::Forward);
+    anim.setDirection(QAbstractAnimationJob::Backward);
+    QCOMPARE(anim.direction(), QAbstractAnimationJob::Backward);
+    anim.setDirection(QAbstractAnimationJob::Forward);
+    QCOMPARE(anim.direction(), QAbstractAnimationJob::Forward);
+}
+
+void tst_QAbstractAnimationJob::group()
+{
+    TestableQAbstractAnimation *anim = new TestableQAbstractAnimation;
+    DummyQAnimationGroup group;
+    group.appendAnimation(anim);
+    QCOMPARE(anim->group(), &group);
+}
+
+void tst_QAbstractAnimationJob::loopCount()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.loopCount(), 1);
+    anim.setLoopCount(10);
+    QCOMPARE(anim.loopCount(), 10);
+}
+
+void tst_QAbstractAnimationJob::state()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.state(), QAbstractAnimationJob::Stopped);
+}
+
+void tst_QAbstractAnimationJob::totalDuration()
+{
+    TestableQAbstractAnimation anim;
+    QCOMPARE(anim.duration(), 10);
+    anim.setLoopCount(5);
+    QCOMPARE(anim.totalDuration(), 50);
+}
+
+void tst_QAbstractAnimationJob::avoidJumpAtStart()
+{
+    TestableQAbstractAnimation anim;
+    anim.setDuration(1000);
+
+    /*
+        the timer shouldn't actually start until we hit the event loop,
+        so the sleep should have no effect
+    */
+    anim.start();
+    QTest::qSleep(300);
+    QCoreApplication::processEvents();
+    QVERIFY(anim.currentTime() < 50);
+}
+
+void tst_QAbstractAnimationJob::avoidJumpAtStartWithStop()
+{
+    TestableQAbstractAnimation anim;
+    anim.setDuration(1000);
+
+    TestableQAbstractAnimation anim2;
+    anim2.setDuration(1000);
+
+    TestableQAbstractAnimation anim3;
+    anim3.setDuration(1000);
+
+    anim.start();
+    QTest::qWait(300);
+    anim.stop();
+
+    /*
+        same test as avoidJumpAtStart, but after there is a
+        running animation that is stopped
+    */
+    anim2.start();
+    QTest::qSleep(300);
+    anim3.start();
+    QCoreApplication::processEvents();
+    QVERIFY(anim2.currentTime() < 50);
+    QVERIFY(anim3.currentTime() < 50);
+}
+
+void tst_QAbstractAnimationJob::avoidJumpAtStartWithRunning()
+{
+    TestableQAbstractAnimation anim;
+    anim.setDuration(2000);
+
+    TestableQAbstractAnimation anim2;
+    anim2.setDuration(1000);
+
+    TestableQAbstractAnimation anim3;
+    anim3.setDuration(1000);
+
+    anim.start();
+    QTest::qWait(300);  //make sure timer has started
+
+    /*
+        same test as avoidJumpAtStart, but with an
+        existing running animation
+    */
+    anim2.start();
+    QTest::qSleep(300); //force large delta for next tick
+    anim3.start();
+    QCoreApplication::processEvents();
+    QVERIFY(anim2.currentTime() < 50);
+    QVERIFY(anim3.currentTime() < 50);
+}
+
+
+QTEST_MAIN(tst_QAbstractAnimationJob)
+
+#include "tst_qabstractanimationjob.moc"
diff --git a/tests/auto/declarative/animation/qanimationgroupjob/qanimationgroupjob.pro b/tests/auto/declarative/animation/qanimationgroupjob/qanimationgroupjob.pro
new file mode 100644 (file)
index 0000000..b9c9d29
--- /dev/null
@@ -0,0 +1,5 @@
+CONFIG += testcase parallel_test
+macx:CONFIG -= app_bundle
+TARGET = tst_qanimationgroupjob
+QT = core-private declarative-private testlib
+SOURCES = tst_qanimationgroupjob.cpp
diff --git a/tests/auto/declarative/animation/qanimationgroupjob/tst_qanimationgroupjob.cpp b/tests/auto/declarative/animation/qanimationgroupjob/tst_qanimationgroupjob.cpp
new file mode 100644 (file)
index 0000000..196f917
--- /dev/null
@@ -0,0 +1,310 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+
+#include <QtDeclarative/private/qanimationgroupjob_p.h>
+#include <QtDeclarative/private/qsequentialanimationgroupjob_p.h>
+#include <QtDeclarative/private/qparallelanimationgroupjob_p.h>
+
+Q_DECLARE_METATYPE(QAbstractAnimationJob::State)
+
+class tst_QAnimationGroupJob : public QObject
+{
+    Q_OBJECT
+public Q_SLOTS:
+    void initTestCase();
+
+private slots:
+    void construction();
+    void emptyGroup();
+    void setCurrentTime();
+    void addChildTwice();
+};
+
+void tst_QAnimationGroupJob::initTestCase()
+{
+    qRegisterMetaType<QAbstractAnimationJob::State>("QAbstractAnimationJob::State");
+}
+
+void tst_QAnimationGroupJob::construction()
+{
+    QSequentialAnimationGroupJob animationgroup;
+}
+
+class TestableGenericAnimation : public QAbstractAnimationJob
+{
+public:
+    TestableGenericAnimation(int duration = 250) : m_duration(duration) {}
+    int duration() const { return m_duration; }
+
+private:
+    int m_duration;
+};
+
+class UncontrolledAnimation : public QObject, public QAbstractAnimationJob
+{
+    Q_OBJECT
+public:
+    UncontrolledAnimation()
+        : id(0)
+    {
+    }
+
+    int duration() const { return -1; /* not time driven */ }
+
+protected:
+    void timerEvent(QTimerEvent *event)
+    {
+        if (event->timerId() == id)
+            stop();
+    }
+
+    void updateRunning(bool running)
+    {
+        if (running) {
+            id = startTimer(500);
+        } else {
+            killTimer(id);
+            id = 0;
+        }
+    }
+
+private:
+    int id;
+};
+
+class StateChangeListener: public QAnimation2ChangeListener
+{
+public:
+    virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
+    {
+        states << newState;
+    }
+
+    int count()
+    {
+        return states.count();
+    }
+
+    QList<QAbstractAnimationJob::State> states;
+};
+
+void tst_QAnimationGroupJob::emptyGroup()
+{
+    QSequentialAnimationGroupJob group;
+    StateChangeListener groupStateChangedSpy;
+    group.addAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    group.start();
+
+    QCOMPARE(groupStateChangedSpy.count(), 2);
+
+    QCOMPARE(groupStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(groupStateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+
+    QTest::ignoreMessage(QtWarningMsg, "QAbstractAnimationJob::pause: Cannot pause a stopped animation");
+    group.pause();
+
+    QCOMPARE(groupStateChangedSpy.count(), 2);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+
+    group.start();
+
+    QCOMPARE(groupStateChangedSpy.states.at(2),
+             QAnimationGroupJob::Running);
+    QCOMPARE(groupStateChangedSpy.states.at(3),
+             QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+
+    group.stop();
+
+    QCOMPARE(groupStateChangedSpy.count(), 4);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QAnimationGroupJob::setCurrentTime()
+{
+    // was originally sequence operating on same object/property
+    QSequentialAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    QAbstractAnimationJob *a1_s_o1 = new TestableGenericAnimation;
+    QAbstractAnimationJob *a2_s_o1 = new TestableGenericAnimation;
+    QAbstractAnimationJob *a3_s_o1 = new TestableGenericAnimation;
+    a2_s_o1->setLoopCount(3);
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a2_s_o1);
+    sequence->appendAnimation(a3_s_o1);
+
+    // was originally sequence operating on different object/properties
+    QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob();
+    QAbstractAnimationJob *a1_s_o2 = new TestableGenericAnimation;
+    QAbstractAnimationJob *a1_s_o3 = new TestableGenericAnimation;
+    sequence2->appendAnimation(a1_s_o2);
+    sequence2->appendAnimation(a1_s_o3);
+
+    // was originally parallel operating on different object/properties
+    QAnimationGroupJob *parallel = new QParallelAnimationGroupJob();
+    QAbstractAnimationJob *a1_p_o1 = new TestableGenericAnimation;
+    QAbstractAnimationJob *a1_p_o2 = new TestableGenericAnimation;
+    QAbstractAnimationJob *a1_p_o3 = new TestableGenericAnimation;
+    a1_p_o2->setLoopCount(3);
+    parallel->appendAnimation(a1_p_o1);
+    parallel->appendAnimation(a1_p_o2);
+    parallel->appendAnimation(a1_p_o3);
+
+    QAbstractAnimationJob *notTimeDriven = new UncontrolledAnimation;
+    QCOMPARE(notTimeDriven->totalDuration(), -1);
+
+    QAbstractAnimationJob *loopsForever = new TestableGenericAnimation;
+    loopsForever->setLoopCount(-1);
+    QCOMPARE(loopsForever->totalDuration(), -1);
+
+    QParallelAnimationGroupJob group;
+    group.appendAnimation(sequence);
+    group.appendAnimation(sequence2);
+    group.appendAnimation(parallel);
+    group.appendAnimation(notTimeDriven);
+    group.appendAnimation(loopsForever);
+
+    // Current time = 1
+    group.setCurrentTime(1);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(parallel->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_p_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_p_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_p_o3->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(sequence->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+    QCOMPARE(a1_p_o1->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o2->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o3->currentLoopTime(), 1);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 1);
+    QCOMPARE(loopsForever->currentLoopTime(), 1);
+
+    // Current time = 250
+    group.setCurrentTime(250);
+    QCOMPARE(group.currentLoopTime(), 250);
+    QCOMPARE(sequence->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+    QCOMPARE(a1_p_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_p_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_p_o2->currentLoop(), 1);
+    QCOMPARE(a1_p_o3->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 250);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+    QCOMPARE(loopsForever->currentLoop(), 1);
+    QCOMPARE(sequence->currentAnimation(), a2_s_o1);
+
+    // Current time = 251
+    group.setCurrentTime(251);
+    QCOMPARE(group.currentLoopTime(), 251);
+    QCOMPARE(sequence->currentLoopTime(), 251);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a2_s_o1->currentLoop(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence2->currentLoopTime(), 251);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_p_o2->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o2->currentLoop(), 1);
+    QCOMPARE(a1_p_o3->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 251);
+    QCOMPARE(loopsForever->currentLoopTime(), 1);
+    QCOMPARE(sequence->currentAnimation(), a2_s_o1);
+}
+
+void tst_QAnimationGroupJob::addChildTwice()
+{
+    QAbstractAnimationJob *subGroup;
+    QAbstractAnimationJob *subGroup2;
+    QAnimationGroupJob *parent = new QSequentialAnimationGroupJob();
+
+    subGroup = new QAbstractAnimationJob;
+    parent->appendAnimation(subGroup);
+    parent->appendAnimation(subGroup);
+    QVERIFY(parent->firstChild() && !parent->firstChild()->nextSibling());
+
+    parent->clear();
+
+    QVERIFY(!parent->firstChild());
+
+    // adding the same item twice to a group will remove the item from its current position
+    // and append it to the end
+    subGroup = new QAbstractAnimationJob;
+    parent->appendAnimation(subGroup);
+    subGroup2 = new QAbstractAnimationJob;
+    parent->appendAnimation(subGroup2);
+
+    QCOMPARE(parent->firstChild(), subGroup);
+    QCOMPARE(parent->lastChild(), subGroup2);
+
+    parent->appendAnimation(subGroup);
+
+    QCOMPARE(parent->firstChild(), subGroup2);
+    QCOMPARE(parent->lastChild(), subGroup);
+
+    delete parent;
+}
+
+QTEST_MAIN(tst_QAnimationGroupJob)
+#include "tst_qanimationgroupjob.moc"
diff --git a/tests/auto/declarative/animation/qparallelanimationgroupjob/qparallelanimationgroupjob.pro b/tests/auto/declarative/animation/qparallelanimationgroupjob/qparallelanimationgroupjob.pro
new file mode 100644 (file)
index 0000000..332fa45
--- /dev/null
@@ -0,0 +1,5 @@
+CONFIG += testcase
+macx:CONFIG -= app_bundle
+TARGET = tst_qparallelanimationgroupjob
+QT = core-private gui declarative-private testlib
+SOURCES = tst_qparallelanimationgroupjob.cpp
diff --git a/tests/auto/declarative/animation/qparallelanimationgroupjob/tst_qparallelanimationgroupjob.cpp b/tests/auto/declarative/animation/qparallelanimationgroupjob/tst_qparallelanimationgroupjob.cpp
new file mode 100644 (file)
index 0000000..52c0ff2
--- /dev/null
@@ -0,0 +1,931 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+
+#include <QtDeclarative/private/qparallelanimationgroupjob_p.h>
+
+Q_DECLARE_METATYPE(QAbstractAnimationJob::State)
+
+class tst_QParallelAnimationGroupJob : public QObject
+{
+    Q_OBJECT
+public Q_SLOTS:
+    void initTestCase();
+
+private slots:
+    void construction();
+    void setCurrentTime();
+    void stateChanged();
+    void clearGroup();
+    void propagateGroupUpdateToChildren();
+    void updateChildrenWithRunningGroup();
+    void deleteChildrenWithRunningGroup();
+    void startChildrenWithStoppedGroup();
+    void stopGroupWithRunningChild();
+    void startGroupWithRunningChild();
+    void zeroDurationAnimation();
+    void stopUncontrolledAnimations();
+    void loopCount_data();
+    void loopCount();
+    void addAndRemoveDuration();
+    void pauseResume();
+
+    void crashWhenRemovingUncontrolledAnimation();
+};
+
+void tst_QParallelAnimationGroupJob::initTestCase()
+{
+    qRegisterMetaType<QAbstractAnimationJob::State>("QAbstractAnimationJob::State");
+#if defined(Q_OS_MAC) || defined(Q_OS_WINCE)
+    // give the mac/wince app start event queue time to clear
+    QTest::qWait(1000);
+#endif
+}
+
+void tst_QParallelAnimationGroupJob::construction()
+{
+    QParallelAnimationGroupJob animationgroup;
+}
+
+class TestAnimation : public QAbstractAnimationJob
+{
+public:
+    TestAnimation(int duration = 250) : m_duration(duration) {}
+    int duration() const { return m_duration; }
+
+private:
+    int m_duration;
+};
+
+class UncontrolledAnimation : public QObject, public QAbstractAnimationJob
+{
+    Q_OBJECT
+public:
+    UncontrolledAnimation()
+        : id(0)
+    {
+    }
+
+    int duration() const { return -1; /* not time driven */ }
+
+protected:
+    void timerEvent(QTimerEvent *event)
+    {
+        if (event->timerId() == id)
+            stop();
+    }
+
+    void updateRunning(bool running)
+    {
+        if (running) {
+            id = startTimer(500);
+        } else {
+            killTimer(id);
+            id = 0;
+        }
+    }
+
+private:
+    int id;
+};
+
+class StateChangeListener: public QAnimation2ChangeListener
+{
+public:
+    virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
+    {
+        states << newState;
+    }
+
+    void clear() { states.clear(); }
+    int count() { return states.count(); }
+
+    QList<QAbstractAnimationJob::State> states;
+};
+
+class FinishedListener: public QAnimation2ChangeListener
+{
+public:
+    FinishedListener() : m_count(0) {}
+
+    virtual void animationFinished(QAbstractAnimationJob *) { ++m_count; }
+    void clear() { m_count = 0; }
+    int count() { return m_count; }
+
+private:
+    int m_count;
+};
+
+void tst_QParallelAnimationGroupJob::setCurrentTime()
+{
+    // originally was parallel operating on different object/properties
+    QAnimationGroupJob *parallel = new QParallelAnimationGroupJob();
+    TestAnimation *a1_p_o1 = new TestAnimation;
+    TestAnimation *a1_p_o2 = new TestAnimation;
+    TestAnimation *a1_p_o3 = new TestAnimation;
+    a1_p_o2->setLoopCount(3);
+    parallel->appendAnimation(a1_p_o1);
+    parallel->appendAnimation(a1_p_o2);
+    parallel->appendAnimation(a1_p_o3);
+
+    UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation;
+    QCOMPARE(notTimeDriven->totalDuration(), -1);
+
+    TestAnimation *loopsForever = new TestAnimation;
+    loopsForever->setLoopCount(-1);
+    QCOMPARE(loopsForever->totalDuration(), -1);
+
+    QParallelAnimationGroupJob group;
+    group.appendAnimation(parallel);
+    group.appendAnimation(notTimeDriven);
+    group.appendAnimation(loopsForever);
+
+    // Current time = 1
+    group.setCurrentTime(1);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(parallel->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_p_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_p_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_p_o3->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(a1_p_o1->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o2->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o3->currentLoopTime(), 1);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 1);
+    QCOMPARE(loopsForever->currentLoopTime(), 1);
+
+    // Current time = 250
+    group.setCurrentTime(250);
+    QCOMPARE(group.currentLoopTime(), 250);
+    QCOMPARE(a1_p_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_p_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_p_o2->currentLoop(), 1);
+    QCOMPARE(a1_p_o3->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 250);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+    QCOMPARE(loopsForever->currentLoop(), 1);
+
+    // Current time = 251
+    group.setCurrentTime(251);
+    QCOMPARE(group.currentLoopTime(), 251);
+    QCOMPARE(a1_p_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_p_o2->currentLoopTime(), 1);
+    QCOMPARE(a1_p_o2->currentLoop(), 1);
+    QCOMPARE(a1_p_o3->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 251);
+    QCOMPARE(loopsForever->currentLoopTime(), 1);
+}
+
+void tst_QParallelAnimationGroupJob::stateChanged()
+{
+    //this ensures that the correct animations are started when starting the group
+    TestAnimation *anim1 = new TestAnimation(1000);
+    TestAnimation *anim2 = new TestAnimation(2000);
+    TestAnimation *anim3 = new TestAnimation(3000);
+    TestAnimation *anim4 = new TestAnimation(3000);
+
+    QParallelAnimationGroupJob group;
+    group.appendAnimation(anim1);
+    group.appendAnimation(anim2);
+    group.appendAnimation(anim3);
+    group.appendAnimation(anim4);
+
+    StateChangeListener spy1;
+    anim1->addAnimationChangeListener(&spy1, QAbstractAnimationJob::StateChange);
+    StateChangeListener spy2;
+    anim2->addAnimationChangeListener(&spy2, QAbstractAnimationJob::StateChange);
+    StateChangeListener spy3;
+    anim3->addAnimationChangeListener(&spy3, QAbstractAnimationJob::StateChange);
+    StateChangeListener spy4;
+    anim4->addAnimationChangeListener(&spy4, QAbstractAnimationJob::StateChange);
+
+    //first; let's start forward
+    group.start();
+    //all the animations should be started
+    QCOMPARE(spy1.count(), 1);
+    QCOMPARE(spy1.states.last(), TestAnimation::Running);
+    QCOMPARE(spy2.count(), 1);
+    QCOMPARE(spy2.states.last(), TestAnimation::Running);
+    QCOMPARE(spy3.count(), 1);
+    QCOMPARE(spy3.states.last(), TestAnimation::Running);
+    QCOMPARE(spy4.count(), 1);
+    QCOMPARE(spy4.states.last(), TestAnimation::Running);
+
+    group.setCurrentTime(1500); //anim1 should be finished
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy1.count(), 2);
+    QCOMPARE(spy1.states.last(), TestAnimation::Stopped);
+    QCOMPARE(spy2.count(), 1); //no change
+    QCOMPARE(spy3.count(), 1); //no change
+    QCOMPARE(spy4.count(), 1); //no change
+
+    group.setCurrentTime(2500); //anim2 should be finished
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy1.count(), 2); //no change
+    QCOMPARE(spy2.count(), 2);
+    QCOMPARE(spy2.states.last(), TestAnimation::Stopped);
+    QCOMPARE(spy3.count(), 1); //no change
+    QCOMPARE(spy4.count(), 1); //no change
+
+    group.setCurrentTime(3500); //everything should be finished
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(spy1.count(), 2); //no change
+    QCOMPARE(spy2.count(), 2); //no change
+    QCOMPARE(spy3.count(), 2);
+    QCOMPARE(spy3.states.last(), TestAnimation::Stopped);
+    QCOMPARE(spy4.count(), 2);
+    QCOMPARE(spy4.states.last(), TestAnimation::Stopped);
+
+    //cleanup
+    spy1.clear();
+    spy2.clear();
+    spy3.clear();
+    spy4.clear();
+
+    //now let's try to reverse that
+    group.setDirection(QAbstractAnimationJob::Backward);
+    group.start();
+
+    //only anim3 and anim4 should be started
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy1.count(), 0);
+    QCOMPARE(spy2.count(), 0);
+    QCOMPARE(spy3.count(), 1);
+    QCOMPARE(spy3.states.last(), TestAnimation::Running);
+    QCOMPARE(spy4.count(), 1);
+    QCOMPARE(spy4.states.last(), TestAnimation::Running);
+
+    group.setCurrentTime(1500); //anim2 should be started
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy1.count(), 0); //no change
+    QCOMPARE(spy2.count(), 1);
+    QCOMPARE(spy2.states.last(), TestAnimation::Running);
+    QCOMPARE(spy3.count(), 1); //no change
+    QCOMPARE(spy4.count(), 1); //no change
+
+    group.setCurrentTime(500); //anim1 is finally also started
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy1.count(), 1);
+    QCOMPARE(spy1.states.last(), TestAnimation::Running);
+    QCOMPARE(spy2.count(), 1); //no change
+    QCOMPARE(spy3.count(), 1); //no change
+    QCOMPARE(spy4.count(), 1); //no change
+
+    group.setCurrentTime(0); //everything should be stopped
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(spy1.count(), 2);
+    QCOMPARE(spy1.states.last(), TestAnimation::Stopped);
+    QCOMPARE(spy2.count(), 2);
+    QCOMPARE(spy2.states.last(), TestAnimation::Stopped);
+    QCOMPARE(spy3.count(), 2);
+    QCOMPARE(spy3.states.last(), TestAnimation::Stopped);
+    QCOMPARE(spy4.count(), 2);
+    QCOMPARE(spy4.states.last(), TestAnimation::Stopped);
+}
+
+void tst_QParallelAnimationGroupJob::clearGroup()
+{
+    QParallelAnimationGroupJob group;
+    static const int animationCount = 10;
+
+    for (int i = 0; i < animationCount; ++i) {
+        group.appendAnimation(new QParallelAnimationGroupJob);
+    }
+
+    int count = 0;
+    for (QAbstractAnimationJob *anim = group.firstChild(); anim; anim = anim->nextSibling())
+        ++count;
+    QCOMPARE(count, animationCount);
+
+    group.clear();
+
+    QVERIFY(!group.firstChild() && !group.lastChild());
+    QCOMPARE(group.currentLoopTime(), 0);
+}
+
+void tst_QParallelAnimationGroupJob::propagateGroupUpdateToChildren()
+{
+    // this test verifies if group state changes are updating its children correctly
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(100);
+    TestAnimation anim2(200);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    group.start();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Running);
+
+    group.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QParallelAnimationGroupJob::updateChildrenWithRunningGroup()
+{
+    // assert that its possible to modify a child's state directly while their group is running
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim(200);
+
+    StateChangeListener groupStateChangedSpy;
+    group.addAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
+    StateChangeListener childStateChangedSpy;
+    anim.addAnimationChangeListener(&childStateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    QCOMPARE(groupStateChangedSpy.count(), 0);
+    QCOMPARE(childStateChangedSpy.count(), 0);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim);
+
+    group.start();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Running);
+
+    QCOMPARE(groupStateChangedSpy.count(), 1);
+    QCOMPARE(childStateChangedSpy.count(), 1);
+
+    QCOMPARE(groupStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(childStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+
+    // starting directly a running child will not have any effect
+    anim.start();
+
+    QCOMPARE(groupStateChangedSpy.count(), 1);
+    QCOMPARE(childStateChangedSpy.count(), 1);
+
+    anim.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Paused);
+
+    // in the animation stops directly, the group will still be running
+    anim.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
+
+    //cleanup
+    group.removeAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
+    anim.removeAnimationChangeListener(&childStateChangedSpy, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QParallelAnimationGroupJob::deleteChildrenWithRunningGroup()
+{
+    // test if children can be activated when their group is stopped
+    QParallelAnimationGroupJob group;
+
+    TestAnimation *anim1 = new TestAnimation(200);
+    group.appendAnimation(anim1);
+
+    QCOMPARE(group.duration(), anim1->duration());
+
+    group.start();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
+
+    QTest::qWait(80);
+    QVERIFY(group.currentLoopTime() > 0);
+
+    delete anim1;
+    QVERIFY(!group.firstChild());
+    QCOMPARE(group.duration(), 0);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(group.currentLoopTime(), 0); //that's the invariant
+}
+
+void tst_QParallelAnimationGroupJob::startChildrenWithStoppedGroup()
+{
+    // test if children can be activated when their group is stopped
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(200);
+    TestAnimation anim2(200);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    anim1.start();
+    anim2.start();
+    anim2.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+}
+
+void tst_QParallelAnimationGroupJob::stopGroupWithRunningChild()
+{
+    // children that started independently will not be affected by a group stop
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(200);
+    TestAnimation anim2(200);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    anim1.start();
+    anim2.start();
+    anim2.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+
+    anim1.stop();
+    anim2.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QParallelAnimationGroupJob::startGroupWithRunningChild()
+{
+    // as the group has precedence over its children, starting a group will restart all the children
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(200);
+    TestAnimation anim2(200);
+
+    StateChangeListener stateChangedSpy1;
+    anim1.addAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
+    StateChangeListener stateChangedSpy2;
+    anim2.addAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
+
+    QCOMPARE(stateChangedSpy1.count(), 0);
+    QCOMPARE(stateChangedSpy2.count(), 0);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    anim1.start();
+    anim2.start();
+    anim2.pause();
+
+    QCOMPARE(stateChangedSpy1.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(stateChangedSpy2.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(stateChangedSpy2.states.at(1), QAnimationGroupJob::Paused);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+
+    group.start();
+
+    QCOMPARE(stateChangedSpy1.count(), 3);
+    QCOMPARE(stateChangedSpy1.states.at(1), QAnimationGroupJob::Stopped);
+    QCOMPARE(stateChangedSpy1.states.at(2), QAnimationGroupJob::Running);
+
+    QCOMPARE(stateChangedSpy2.count(), 4);
+    QCOMPARE(stateChangedSpy2.states.at(2), QAnimationGroupJob::Stopped);
+    QCOMPARE(stateChangedSpy2.states.at(3), QAnimationGroupJob::Running);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Running);
+
+    //cleanup
+    anim1.removeAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
+    anim2.removeAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QParallelAnimationGroupJob::zeroDurationAnimation()
+{
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(0);
+    TestAnimation anim2(100);
+    TestAnimation anim3(10);
+
+    StateChangeListener stateChangedSpy1;
+    anim1.addAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
+    FinishedListener finishedSpy1;
+    anim1.addAnimationChangeListener(&finishedSpy1, QAbstractAnimationJob::Completion);
+
+    StateChangeListener stateChangedSpy2;
+    anim2.addAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
+    FinishedListener finishedSpy2;
+    anim2.addAnimationChangeListener(&finishedSpy2, QAbstractAnimationJob::Completion);
+
+    StateChangeListener stateChangedSpy3;
+    anim3.addAnimationChangeListener(&stateChangedSpy3, QAbstractAnimationJob::StateChange);
+    FinishedListener finishedSpy3;
+    anim3.addAnimationChangeListener(&finishedSpy3, QAbstractAnimationJob::Completion);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+    group.appendAnimation(&anim3);
+    QCOMPARE(stateChangedSpy1.count(), 0);
+    group.start();
+    QCOMPARE(stateChangedSpy1.count(), 2);
+    QCOMPARE(finishedSpy1.count(), 1);
+    QCOMPARE(stateChangedSpy1.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(stateChangedSpy1.states.at(1), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(stateChangedSpy2.count(), 1);
+    QCOMPARE(finishedSpy2.count(), 0);
+    QCOMPARE(stateChangedSpy1.states.at(0), QAnimationGroupJob::Running);
+
+    QCOMPARE(stateChangedSpy3.count(), 1);
+    QCOMPARE(finishedSpy3.count(), 0);
+    QCOMPARE(stateChangedSpy3.states.at(0), QAnimationGroupJob::Running);
+
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim3.state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+
+    group.stop();
+    group.setLoopCount(4);
+    stateChangedSpy1.clear();
+    stateChangedSpy2.clear();
+    stateChangedSpy3.clear();
+
+    group.start();
+    QCOMPARE(stateChangedSpy1.count(), 2);
+    QCOMPARE(stateChangedSpy2.count(), 1);
+    QCOMPARE(stateChangedSpy3.count(), 1);
+    group.setCurrentTime(50);
+    QCOMPARE(stateChangedSpy1.count(), 2);
+    QCOMPARE(stateChangedSpy2.count(), 1);
+    QCOMPARE(stateChangedSpy3.count(), 2);
+    group.setCurrentTime(150);
+    QCOMPARE(stateChangedSpy1.count(), 4);
+    QCOMPARE(stateChangedSpy2.count(), 3);
+    QCOMPARE(stateChangedSpy3.count(), 4);
+    group.setCurrentTime(50);
+    QCOMPARE(stateChangedSpy1.count(), 6);
+    QCOMPARE(stateChangedSpy2.count(), 5);
+    QCOMPARE(stateChangedSpy3.count(), 6);
+
+    //cleanup
+    anim1.removeAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
+    anim1.removeAnimationChangeListener(&finishedSpy1, QAbstractAnimationJob::Completion);
+    anim2.removeAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
+    anim2.removeAnimationChangeListener(&finishedSpy2, QAbstractAnimationJob::Completion);
+    anim3.removeAnimationChangeListener(&stateChangedSpy3, QAbstractAnimationJob::StateChange);
+    anim3.removeAnimationChangeListener(&finishedSpy3, QAbstractAnimationJob::Completion);
+}
+
+void tst_QParallelAnimationGroupJob::stopUncontrolledAnimations()
+{
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(0);
+
+    UncontrolledAnimation notTimeDriven;
+    QCOMPARE(notTimeDriven.totalDuration(), -1);
+
+    TestAnimation loopsForever(100);
+    loopsForever.setLoopCount(-1);
+
+    StateChangeListener stateChangedSpy;
+    anim1.addAnimationChangeListener(&stateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&notTimeDriven);
+    group.appendAnimation(&loopsForever);
+
+    group.start();
+
+    QCOMPARE(stateChangedSpy.count(), 2);
+    QCOMPARE(stateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(stateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
+    QCOMPARE(loopsForever.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+
+    notTimeDriven.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever.state(), QAnimationGroupJob::Running);
+
+    loopsForever.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever.state(), QAnimationGroupJob::Stopped);
+}
+
+struct AnimState {
+    AnimState(int time = -1) : time(time), state(-1) {}
+    AnimState(int time, int state) : time(time), state(state) {}
+    int time;
+    int state;
+};
+
+#define Running QAbstractAnimationJob::Running
+#define Stopped QAbstractAnimationJob::Stopped
+
+Q_DECLARE_METATYPE(AnimState)
+void tst_QParallelAnimationGroupJob::loopCount_data()
+{
+    QTest::addColumn<bool>("directionBackward");
+    QTest::addColumn<int>("setLoopCount");
+    QTest::addColumn<int>("initialGroupTime");
+    QTest::addColumn<int>("currentGroupTime");
+    QTest::addColumn<AnimState>("expected1");
+    QTest::addColumn<AnimState>("expected2");
+    QTest::addColumn<AnimState>("expected3");
+
+    //                                                                                  D U R A T I O N
+    //                                                              100                           60*2                           0
+    // direction = Forward
+    QTest::newRow("50")  << false << 3 << 0 <<  50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("100") << false << 3 << 0 << 100 << AnimState(100         ) << AnimState( 40, Running) << AnimState(  0, Stopped);
+    QTest::newRow("110") << false << 3 << 0 << 110 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("120") << false << 3 << 0 << 120 << AnimState(  0, Running) << AnimState(  0, Running) << AnimState(  0, Stopped);
+
+    QTest::newRow("170") << false << 3 << 0 << 170 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("220") << false << 3 << 0 << 220 << AnimState(100         ) << AnimState( 40, Running) << AnimState(  0, Stopped);
+    QTest::newRow("230") << false << 3 << 0 << 230 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("240") << false << 3 << 0 << 240 << AnimState(  0, Running) << AnimState(  0, Running) << AnimState(  0, Stopped);
+
+    QTest::newRow("290") << false << 3 << 0 << 290 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("340") << false << 3 << 0 << 340 << AnimState(100         ) << AnimState( 40, Running) << AnimState(  0, Stopped);
+    QTest::newRow("350") << false << 3 << 0 << 350 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("360") << false << 3 << 0 << 360 << AnimState(100, Stopped) << AnimState( 60         ) << AnimState(  0, Stopped);
+
+    QTest::newRow("410") << false << 3 << 0 << 410 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState(  0, Stopped);
+    QTest::newRow("460") << false << 3 << 0 << 460 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState(  0, Stopped);
+    QTest::newRow("470") << false << 3 << 0 << 470 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState(  0, Stopped);
+    QTest::newRow("480") << false << 3 << 0 << 480 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState(  0, Stopped);
+
+    // direction = Forward, rewind
+    QTest::newRow("120-110") << false << 3 << 120 << 110 << AnimState(   0, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("120-50")  << false << 3 << 120 <<  50 << AnimState(  50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("120-0")   << false << 3 << 120 <<  0  << AnimState(   0, Running) << AnimState(  0, Running) << AnimState(  0, Stopped);
+    QTest::newRow("300-110") << false << 3 << 300 << 110 << AnimState(   0, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("300-50")  << false << 3 << 300 <<  50 << AnimState(  50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("300-0")   << false << 3 << 300 <<  0  << AnimState(   0, Running) << AnimState(  0, Running) << AnimState(  0, Stopped);
+    QTest::newRow("115-105") << false << 3 << 115 << 105 << AnimState(  42, Stopped) << AnimState( 45, Running) << AnimState(  0, Stopped);
+
+    // direction = Backward
+    QTest::newRow("b120-120") << true << 3 << 120 << 120 << AnimState( 42, Stopped) << AnimState( 60, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b120-110") << true << 3 << 120 << 110 << AnimState( 42, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b120-100") << true << 3 << 120 << 100 << AnimState(100, Running) << AnimState( 40, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b120-50")  << true << 3 << 120 <<  50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b120-0")   << true << 3 << 120 <<   0 << AnimState(  0, Stopped) << AnimState(  0, Stopped) << AnimState(  0, Stopped);
+    QTest::newRow("b360-170") << true << 3 << 360 << 170 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b360-220") << true << 3 << 360 << 220 << AnimState(100, Running) << AnimState( 40, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b360-210") << true << 3 << 360 << 210 << AnimState( 90, Running) << AnimState( 30, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b360-120") << true << 3 << 360 << 120 << AnimState(  0, Stopped) << AnimState( 60, Running) << AnimState(  0, Stopped);
+
+    // rewind, direction = Backward
+    QTest::newRow("b50-110")  << true << 3 <<  50 << 110 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b50-120")  << true << 3 <<  50 << 120 << AnimState(100, Stopped) << AnimState( 60, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b50-140")  << true << 3 <<  50 << 140 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b50-240")  << true << 3 <<  50 << 240 << AnimState(100, Stopped) << AnimState( 60, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b50-260")  << true << 3 <<  50 << 260 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b50-350")  << true << 3 <<  50 << 350 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+
+    // infinite looping
+    QTest::newRow("inf1220")  << false << -1 <<  0 << 1220 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState(  0, Stopped);
+    QTest::newRow("inf1310")  << false << -1 <<  0 << 1310 << AnimState( 100, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+    // infinite looping, direction = Backward (will only loop once)
+    QTest::newRow("b.inf120-120") << true  << -1 << 120 << 120 << AnimState( 42, Stopped) << AnimState( 60, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b.inf120-20")  << true  << -1 << 120 <<  20 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState(  0, Stopped);
+    QTest::newRow("b.inf120-110") << true  << -1 << 120 << 110 << AnimState( 42, Stopped) << AnimState( 50, Running) << AnimState(  0, Stopped);
+
+
+}
+
+void tst_QParallelAnimationGroupJob::loopCount()
+{
+    QFETCH(bool, directionBackward);
+    QFETCH(int, setLoopCount);
+    QFETCH(int, initialGroupTime);
+    QFETCH(int, currentGroupTime);
+    QFETCH(AnimState, expected1);
+    QFETCH(AnimState, expected2);
+    QFETCH(AnimState, expected3);
+
+    QParallelAnimationGroupJob group;
+
+    TestAnimation anim1(100);
+    TestAnimation anim2(60);  //total 120
+    anim2.setLoopCount(2);
+    TestAnimation anim3(0);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+    group.appendAnimation(&anim3);
+
+    group.setLoopCount(setLoopCount);
+    if (initialGroupTime >= 0)
+        group.setCurrentTime(initialGroupTime);
+    if (directionBackward)
+        group.setDirection(QAbstractAnimationJob::Backward);
+
+    group.start();
+    if (initialGroupTime >= 0)
+        group.setCurrentTime(initialGroupTime);
+
+    anim1.setCurrentTime(42);   // 42 is "untouched"
+    anim2.setCurrentTime(42);
+
+    group.setCurrentTime(currentGroupTime);
+
+    QCOMPARE(anim1.currentLoopTime(), expected1.time);
+    QCOMPARE(anim2.currentLoopTime(), expected2.time);
+    QCOMPARE(anim3.currentLoopTime(), expected3.time);
+
+    if (expected1.state >=0)
+        QCOMPARE(int(anim1.state()), expected1.state);
+    if (expected2.state >=0)
+        QCOMPARE(int(anim2.state()), expected2.state);
+    if (expected3.state >=0)
+        QCOMPARE(int(anim3.state()), expected3.state);
+
+}
+
+void tst_QParallelAnimationGroupJob::addAndRemoveDuration()
+{
+    QParallelAnimationGroupJob group;
+    QCOMPARE(group.duration(), 0);
+    TestAnimation *test = new TestAnimation(250);      // 0, duration = 250;
+    group.appendAnimation(test);
+    QCOMPARE(test->group(), static_cast<QAnimationGroupJob*>(&group));
+    QCOMPARE(test->duration(), 250);
+    QCOMPARE(group.duration(), 250);
+
+    TestAnimation *test2 = new TestAnimation(750);     // 1
+    group.appendAnimation(test2);
+    QCOMPARE(test2->group(), static_cast<QAnimationGroupJob*>(&group));
+    QCOMPARE(group.duration(), 750);
+
+    TestAnimation *test3 = new TestAnimation(500);     // 2
+    group.appendAnimation(test3);
+    QCOMPARE(test3->group(), static_cast<QAnimationGroupJob*>(&group));
+    QCOMPARE(group.duration(), 750);
+
+    group.removeAnimation(test2);    // remove the one with duration = 750
+    delete test2;
+    QCOMPARE(group.duration(), 500);
+
+    group.removeAnimation(test3);    // remove the one with duration = 500
+    delete test3;
+    QCOMPARE(group.duration(), 250);
+
+    group.removeAnimation(test);    // remove the last one (with duration = 250)
+    QCOMPARE(test->group(), static_cast<QAnimationGroupJob*>(0));
+    QCOMPARE(group.duration(), 0);
+    delete test;
+}
+
+void tst_QParallelAnimationGroupJob::pauseResume()
+{
+    QParallelAnimationGroupJob group;
+    TestAnimation *anim = new TestAnimation(250);      // 0, duration = 250;
+    group.appendAnimation(anim);
+    StateChangeListener spy;
+    anim->addAnimationChangeListener(&spy, QAbstractAnimationJob::StateChange);
+    QCOMPARE(group.duration(), 250);
+    group.start();
+    QTest::qWait(100);
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim->state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy.count(), 1);
+    spy.clear();
+    const int currentTime = group.currentLoopTime();
+    QCOMPARE(anim->currentLoopTime(), currentTime);
+
+    group.pause();
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(group.currentLoopTime(), currentTime);
+    QCOMPARE(anim->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(anim->currentLoopTime(), currentTime);
+    QCOMPARE(spy.count(), 1);
+    spy.clear();
+
+    group.resume();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.currentLoopTime(), currentTime);
+    QCOMPARE(anim->state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim->currentLoopTime(), currentTime);
+    QCOMPARE(spy.count(), 1);
+
+    group.stop();
+    spy.clear();
+    group.appendAnimation(new TestAnimation(500));
+    group.start();
+    QCOMPARE(spy.count(), 1); //the animation should have been started
+    QCOMPARE(spy.states.at(0), TestAnimation::Running);
+    group.setCurrentTime(250); //end of first animation
+    QCOMPARE(spy.count(), 2); //the animation should have been stopped
+    QCOMPARE(spy.states.at(1), TestAnimation::Stopped);
+    group.pause();
+    QCOMPARE(spy.count(), 2); //this shouldn't have changed
+    group.resume();
+    QCOMPARE(spy.count(), 2); //this shouldn't have changed
+}
+
+// This is a regression test for QTBUG-8910, where a crash occurred when the
+// last animation was removed from a group.
+void tst_QParallelAnimationGroupJob::crashWhenRemovingUncontrolledAnimation()
+{
+    QParallelAnimationGroupJob group;
+    TestAnimation *anim = new TestAnimation;
+    anim->setLoopCount(-1);
+    TestAnimation *anim2 = new TestAnimation;
+    anim2->setLoopCount(-1);
+    group.appendAnimation(anim);
+    group.appendAnimation(anim2);
+    group.start();
+    delete anim;
+    // it would crash here because the internals of the group would still have a reference to anim
+    delete anim2;
+}
+
+
+QTEST_MAIN(tst_QParallelAnimationGroupJob)
+#include "tst_qparallelanimationgroupjob.moc"
diff --git a/tests/auto/declarative/animation/qpauseanimationjob/qpauseanimationjob.pro b/tests/auto/declarative/animation/qpauseanimationjob/qpauseanimationjob.pro
new file mode 100644 (file)
index 0000000..d2b48a6
--- /dev/null
@@ -0,0 +1,5 @@
+CONFIG += testcase
+macx:CONFIG -= app_bundle
+TARGET = tst_qpauseanimationjob
+QT = core-private gui-private declarative-private testlib
+SOURCES = tst_qpauseanimationjob.cpp
diff --git a/tests/auto/declarative/animation/qpauseanimationjob/tst_qpauseanimationjob.cpp b/tests/auto/declarative/animation/qpauseanimationjob/tst_qpauseanimationjob.cpp
new file mode 100644 (file)
index 0000000..97be325
--- /dev/null
@@ -0,0 +1,470 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+
+#include <QtDeclarative/private/qpauseanimationjob_p.h>
+#include <QtDeclarative/private/qsequentialanimationgroupjob_p.h>
+#include <QtDeclarative/private/qparallelanimationgroupjob_p.h>
+
+#ifdef Q_OS_WIN
+static const char winTimerError[] = "On windows, consistent timing is not working properly due to bad timer resolution";
+#endif
+
+class TestablePauseAnimation : public QPauseAnimationJob
+{
+public:
+    TestablePauseAnimation()
+        : m_updateCurrentTimeCount(0)
+    {
+    }
+
+    TestablePauseAnimation(int duration)
+        : QPauseAnimationJob(duration), m_updateCurrentTimeCount(0)
+    {
+    }
+
+    int m_updateCurrentTimeCount;
+protected:
+    void updateCurrentTime(int currentTime)
+    {
+        QPauseAnimationJob::updateCurrentTime(currentTime);
+        ++m_updateCurrentTimeCount;
+    }
+};
+
+class TestableGenericAnimation : public QAbstractAnimationJob
+{
+public:
+    TestableGenericAnimation(int duration = 250) : m_duration(duration) {}
+    int duration() const { return m_duration; }
+
+private:
+    int m_duration;
+};
+
+class EnableConsistentTiming
+{
+public:
+    EnableConsistentTiming()
+    {
+        QUnifiedTimer *timer = QUnifiedTimer::instance();
+        timer->setConsistentTiming(true);
+    }
+    ~EnableConsistentTiming()
+    {
+        QUnifiedTimer *timer = QUnifiedTimer::instance();
+        timer->setConsistentTiming(false);
+    }
+};
+
+class tst_QPauseAnimationJob : public QObject
+{
+  Q_OBJECT
+public Q_SLOTS:
+    void initTestCase();
+
+private slots:
+    void changeDirectionWhileRunning();
+    void noTimerUpdates_data();
+    void noTimerUpdates();
+    void multiplePauseAnimations();
+    void pauseAndPropertyAnimations();
+    void pauseResume();
+    void sequentialPauseGroup();
+    void sequentialGroupWithPause();
+    void multipleSequentialGroups();
+    void zeroDuration();
+};
+
+void tst_QPauseAnimationJob::initTestCase()
+{
+//    qRegisterMetaType<QAbstractAnimationJob::State>("QAbstractAnimationJob::State");
+}
+
+void tst_QPauseAnimationJob::changeDirectionWhileRunning()
+{
+    EnableConsistentTiming enabled;
+
+    TestablePauseAnimation animation;
+    animation.setDuration(400);
+    animation.start();
+    QTest::qWait(100);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Running);
+    animation.setDirection(QAbstractAnimationJob::Backward);
+    QTest::qWait(animation.totalDuration() + 50);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+}
+
+void tst_QPauseAnimationJob::noTimerUpdates_data()
+{
+    QTest::addColumn<int>("duration");
+    QTest::addColumn<int>("loopCount");
+
+    QTest::newRow("0") << 200 << 1;
+    QTest::newRow("1") << 160 << 1;
+    QTest::newRow("2") << 160 << 2;
+    QTest::newRow("3") << 200 << 3;
+}
+
+void tst_QPauseAnimationJob::noTimerUpdates()
+{
+    EnableConsistentTiming enabled;
+
+    QFETCH(int, duration);
+    QFETCH(int, loopCount);
+
+    TestablePauseAnimation animation;
+    animation.setDuration(duration);
+    animation.setLoopCount(loopCount);
+    animation.start();
+    QTest::qWait(animation.totalDuration() + 100);
+
+#ifdef Q_OS_WIN
+    if (animation.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+    const int expectedLoopCount = 1 + loopCount;
+
+#ifdef Q_OS_WIN
+    if (animation.m_updateCurrentTimeCount != expectedLoopCount)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QCOMPARE(animation.m_updateCurrentTimeCount, expectedLoopCount);
+}
+
+void tst_QPauseAnimationJob::multiplePauseAnimations()
+{
+    EnableConsistentTiming enabled;
+
+    TestablePauseAnimation animation;
+    animation.setDuration(200);
+
+    TestablePauseAnimation animation2;
+    animation2.setDuration(800);
+
+    animation.start();
+    animation2.start();
+    QTest::qWait(animation.totalDuration() + 100);
+
+#ifdef Q_OS_WIN
+    if (animation.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (animation2.state() != QAbstractAnimationJob::Running)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(animation2.state() == QAbstractAnimationJob::Running);
+
+#ifdef Q_OS_WIN
+    if (animation.m_updateCurrentTimeCount != 2)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QCOMPARE(animation.m_updateCurrentTimeCount, 2);
+
+#ifdef Q_OS_WIN
+    if (animation2.m_updateCurrentTimeCount != 2)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QCOMPARE(animation2.m_updateCurrentTimeCount, 2);
+
+    QTest::qWait(550);
+
+#ifdef Q_OS_WIN
+    if (animation2.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(animation2.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (animation2.m_updateCurrentTimeCount != 3)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QCOMPARE(animation2.m_updateCurrentTimeCount, 3);
+}
+
+void tst_QPauseAnimationJob::pauseAndPropertyAnimations()
+{
+    EnableConsistentTiming enabled;
+
+    TestablePauseAnimation pause;
+    pause.setDuration(200);
+
+    TestableGenericAnimation animation;
+
+    pause.start();
+
+    QTest::qWait(100);
+    animation.start();
+
+    QVERIFY(animation.state() == QAbstractAnimationJob::Running);
+    QVERIFY(pause.state() == QAbstractAnimationJob::Running);
+    QCOMPARE(pause.m_updateCurrentTimeCount, 2);
+
+    QTest::qWait(animation.totalDuration() + 100);
+
+#ifdef Q_OS_WIN
+    if (animation.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(pause.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(pause.m_updateCurrentTimeCount > 3);
+}
+
+void tst_QPauseAnimationJob::pauseResume()
+{
+    TestablePauseAnimation animation;
+    animation.setDuration(400);
+    animation.start();
+    QVERIFY(animation.state() == QAbstractAnimationJob::Running);
+    QTest::qWait(200);
+    animation.pause();
+    QVERIFY(animation.state() == QAbstractAnimationJob::Paused);
+    animation.start();
+    QTest::qWait(300);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (animation.m_updateCurrentTimeCount != 3)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QCOMPARE(animation.m_updateCurrentTimeCount, 3);
+}
+
+void tst_QPauseAnimationJob::sequentialPauseGroup()
+{
+    QSequentialAnimationGroupJob group;
+
+    TestablePauseAnimation animation1(200);
+    group.appendAnimation(&animation1);
+    TestablePauseAnimation animation2(200);
+    group.appendAnimation(&animation2);
+    TestablePauseAnimation animation3(200);
+    group.appendAnimation(&animation3);
+
+    group.start();
+    QCOMPARE(animation1.m_updateCurrentTimeCount, 1);
+    QCOMPARE(animation2.m_updateCurrentTimeCount, 0);
+    QCOMPARE(animation3.m_updateCurrentTimeCount, 0);
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation1.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation2.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(animation3.state() == QAbstractAnimationJob::Stopped);
+
+    group.setCurrentTime(250);
+    QCOMPARE(animation1.m_updateCurrentTimeCount, 2);
+    QCOMPARE(animation2.m_updateCurrentTimeCount, 1);
+    QCOMPARE(animation3.m_updateCurrentTimeCount, 0);
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation1.state() == QAbstractAnimationJob::Stopped);
+    QCOMPARE((QAbstractAnimationJob*)&animation2, group.currentAnimation());
+    QVERIFY(animation2.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation3.state() == QAbstractAnimationJob::Stopped);
+
+    group.setCurrentTime(500);
+    QCOMPARE(animation1.m_updateCurrentTimeCount, 2);
+    QCOMPARE(animation2.m_updateCurrentTimeCount, 2);
+    QCOMPARE(animation3.m_updateCurrentTimeCount, 1);
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation1.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(animation2.state() == QAbstractAnimationJob::Stopped);
+    QCOMPARE((QAbstractAnimationJob*)&animation3, group.currentAnimation());
+    QVERIFY(animation3.state() == QAbstractAnimationJob::Running);
+
+    group.setCurrentTime(750);
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(animation1.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(animation2.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(animation3.state() == QAbstractAnimationJob::Stopped);
+
+    QCOMPARE(animation1.m_updateCurrentTimeCount, 2);
+    QCOMPARE(animation2.m_updateCurrentTimeCount, 2);
+    QCOMPARE(animation3.m_updateCurrentTimeCount, 2);
+}
+
+void tst_QPauseAnimationJob::sequentialGroupWithPause()
+{
+    QSequentialAnimationGroupJob group;
+
+    TestableGenericAnimation animation;
+    group.appendAnimation(&animation);
+
+    TestablePauseAnimation pause;
+    pause.setDuration(250);
+    group.appendAnimation(&pause);
+
+    group.start();
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Running);
+    QVERIFY(pause.state() == QAbstractAnimationJob::Stopped);
+
+    group.setCurrentTime(300);
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Running);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+    QCOMPARE((QAbstractAnimationJob*)&pause, group.currentAnimation());
+    QVERIFY(pause.state() == QAbstractAnimationJob::Running);
+
+    group.setCurrentTime(600);
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+    QVERIFY(pause.state() == QAbstractAnimationJob::Stopped);
+
+    QCOMPARE(pause.m_updateCurrentTimeCount, 2);
+}
+
+void tst_QPauseAnimationJob::multipleSequentialGroups()
+{
+    EnableConsistentTiming enabled;
+
+    QParallelAnimationGroupJob group;
+    group.setLoopCount(2);
+
+    QSequentialAnimationGroupJob subgroup1;
+    group.appendAnimation(&subgroup1);
+
+    TestableGenericAnimation animation(300);
+    subgroup1.appendAnimation(&animation);
+
+    TestablePauseAnimation pause(200);
+    subgroup1.appendAnimation(&pause);
+
+    QSequentialAnimationGroupJob subgroup2;
+    group.appendAnimation(&subgroup2);
+
+    TestableGenericAnimation animation2(200);
+    subgroup2.appendAnimation(&animation2);
+
+    TestablePauseAnimation pause2(250);
+    subgroup2.appendAnimation(&pause2);
+
+    QSequentialAnimationGroupJob subgroup3;
+    group.appendAnimation(&subgroup3);
+
+    TestablePauseAnimation pause3(400);
+    subgroup3.appendAnimation(&pause3);
+
+    TestableGenericAnimation animation3(200);
+    subgroup3.appendAnimation(&animation3);
+
+    QSequentialAnimationGroupJob subgroup4;
+    group.appendAnimation(&subgroup4);
+
+    TestablePauseAnimation pause4(310);
+    subgroup4.appendAnimation(&pause4);
+
+    TestablePauseAnimation pause5(60);
+    subgroup4.appendAnimation(&pause5);
+
+    group.start();
+
+    QVERIFY(group.state() == QAbstractAnimationJob::Running);
+    QVERIFY(subgroup1.state() == QAbstractAnimationJob::Running);
+    QVERIFY(subgroup2.state() == QAbstractAnimationJob::Running);
+    QVERIFY(subgroup3.state() == QAbstractAnimationJob::Running);
+    QVERIFY(subgroup4.state() == QAbstractAnimationJob::Running);
+
+    // This is a pretty long animation so it tends to get rather out of sync
+    // when using the consistent timer, so run for an extra half second for good
+    // measure...
+    QTest::qWait(group.totalDuration() + 500);
+
+#ifdef Q_OS_WIN
+    if (group.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(group.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (subgroup1.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(subgroup1.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (subgroup2.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(subgroup2.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (subgroup3.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(subgroup3.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (subgroup4.state() != QAbstractAnimationJob::Stopped)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QVERIFY(subgroup4.state() == QAbstractAnimationJob::Stopped);
+
+#ifdef Q_OS_WIN
+    if (pause5.m_updateCurrentTimeCount != 4)
+        QEXPECT_FAIL("", winTimerError, Abort);
+#endif
+    QCOMPARE(pause5.m_updateCurrentTimeCount, 4);
+}
+
+void tst_QPauseAnimationJob::zeroDuration()
+{
+    TestablePauseAnimation animation;
+    animation.setDuration(0);
+    animation.start();
+    QTest::qWait(animation.totalDuration() + 100);
+    QVERIFY(animation.state() == QAbstractAnimationJob::Stopped);
+    QCOMPARE(animation.m_updateCurrentTimeCount, 1);
+}
+
+QTEST_MAIN(tst_QPauseAnimationJob)
+#include "tst_qpauseanimationjob.moc"
diff --git a/tests/auto/declarative/animation/qsequentialanimationgroupjob/qsequentialanimationgroupjob.pro b/tests/auto/declarative/animation/qsequentialanimationgroupjob/qsequentialanimationgroupjob.pro
new file mode 100644 (file)
index 0000000..914fc3c
--- /dev/null
@@ -0,0 +1,5 @@
+CONFIG += testcase parallel_test
+macx:CONFIG -= app_bundle
+TARGET = tst_qsequentialanimationgroupjob
+QT = core-private declarative-private testlib
+SOURCES = tst_qsequentialanimationgroupjob.cpp
diff --git a/tests/auto/declarative/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp b/tests/auto/declarative/animation/qsequentialanimationgroupjob/tst_qsequentialanimationgroupjob.cpp
new file mode 100644 (file)
index 0000000..895e4f8
--- /dev/null
@@ -0,0 +1,1617 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtDeclarative/private/qsequentialanimationgroupjob_p.h>
+#include <QtDeclarative/private/qparallelanimationgroupjob_p.h>
+#include <QtDeclarative/private/qpauseanimationjob_p.h>
+
+Q_DECLARE_METATYPE(QAbstractAnimationJob::State)
+Q_DECLARE_METATYPE(QAbstractAnimationJob*)
+
+class tst_QSequentialAnimationGroupJob : public QObject
+{
+    Q_OBJECT
+public Q_SLOTS:
+    void initTestCase();
+
+private slots:
+    void construction();
+    void setCurrentTime();
+    void setCurrentTimeWithUncontrolledAnimation();
+    void seekingForwards();
+    void seekingBackwards();
+    void pauseAndResume();
+    void restart();
+    void looping();
+    void startDelay();
+    void clearGroup();
+    void groupWithZeroDurationAnimations();
+    void propagateGroupUpdateToChildren();
+    void updateChildrenWithRunningGroup();
+    void deleteChildrenWithRunningGroup();
+    void startChildrenWithStoppedGroup();
+    void stopGroupWithRunningChild();
+    void startGroupWithRunningChild();
+    void zeroDurationAnimation();
+    void stopUncontrolledAnimations();
+    void finishWithUncontrolledAnimation();
+    void addRemoveAnimation();
+    void currentAnimation();
+    void currentAnimationWithZeroDuration();
+    void insertAnimation();
+    void clear();
+    void pauseResume();
+};
+
+void tst_QSequentialAnimationGroupJob::initTestCase()
+{
+    qRegisterMetaType<QAbstractAnimationJob::State>("QAbstractAnimationJob::State");
+    qRegisterMetaType<QAbstractAnimationJob*>("QAbstractAnimationJob*");
+}
+
+void tst_QSequentialAnimationGroupJob::construction()
+{
+    QSequentialAnimationGroupJob animationgroup;
+}
+
+class TestAnimation : public QAbstractAnimationJob
+{
+public:
+    TestAnimation(int duration = 250) : m_duration(duration) {}
+    int duration() const { return m_duration; }
+
+private:
+    int m_duration;
+};
+
+class TestValueAnimation : public TestAnimation
+{
+public:
+    TestValueAnimation(int duration = 250)
+        : TestAnimation(duration), start(0), end(0), value(0) {}
+
+    void updateCurrentTime(int msecs)
+    {
+        if (msecs >= duration())
+            value = end;
+        else
+            value = start + (end - start) * (qreal(msecs) / duration());
+    }
+
+    qreal start, end;
+    qreal value;
+};
+
+class UncontrolledAnimation : public QObject, public QAbstractAnimationJob
+{
+    Q_OBJECT
+public:
+    int duration() const { return -1; /* not time driven */ }
+
+protected:
+    void updateCurrentTime(int currentTime)
+    {
+        if (currentTime >= 250)
+            stop();
+    }
+};
+
+class StateChangeListener: public QAnimation2ChangeListener
+{
+public:
+    virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
+    {
+        states << newState;
+    }
+
+    void clear() { states.clear(); }
+    int count() const { return states.count(); }
+
+    QList<QAbstractAnimationJob::State> states;
+};
+
+class FinishedListener: public QAnimation2ChangeListener
+{
+public:
+    FinishedListener() : m_count(0) {}
+
+    virtual void animationFinished(QAbstractAnimationJob *) { ++m_count; }
+    void clear() { m_count = 0; }
+    int count() { return m_count; }
+
+private:
+    int m_count;
+};
+
+void tst_QSequentialAnimationGroupJob::setCurrentTime()
+{
+    // sequence operating on same object/property
+    QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    TestAnimation *a1_s_o1 = new TestAnimation;
+    TestAnimation *a2_s_o1 = new TestAnimation;
+    TestAnimation *a3_s_o1 = new TestAnimation;
+    a2_s_o1->setLoopCount(3);
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a2_s_o1);
+    sequence->appendAnimation(a3_s_o1);
+
+    // sequence operating on different object/properties
+    QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob();
+    TestAnimation *a1_s_o2 = new TestAnimation;
+    TestAnimation *a1_s_o3 = new TestAnimation;
+    sequence2->appendAnimation(a1_s_o2);
+    sequence2->appendAnimation(a1_s_o3);
+
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(sequence);
+    group.appendAnimation(sequence2);
+
+    // Current time = 1
+    group.setCurrentTime(1);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(sequence->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 250
+    group.setCurrentTime(250);
+    QCOMPARE(group.currentLoopTime(), 250);
+    QCOMPARE(sequence->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 251
+    group.setCurrentTime(251);
+    QCOMPARE(group.currentLoopTime(), 251);
+    QCOMPARE(sequence->currentLoopTime(), 251);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a2_s_o1->currentLoop(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 750
+    group.setCurrentTime(750);
+    QCOMPARE(group.currentLoopTime(), 750);
+    QCOMPARE(sequence->currentLoopTime(), 750);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 1000
+    group.setCurrentTime(1000);
+    QCOMPARE(group.currentLoopTime(), 1000);
+    QCOMPARE(sequence->currentLoopTime(), 1000);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 1010
+    group.setCurrentTime(1010);
+    QCOMPARE(group.currentLoopTime(), 1010);
+    QCOMPARE(sequence->currentLoopTime(), 1010);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 10);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 1250
+    group.setCurrentTime(1250);
+    QCOMPARE(group.currentLoopTime(), 1250);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 1500
+    group.setCurrentTime(1500);
+    QCOMPARE(group.currentLoopTime(), 1500);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 1750
+    group.setCurrentTime(1750);
+    QCOMPARE(group.currentLoopTime(), 1750);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 250);
+
+    // Current time = 2000
+    group.setCurrentTime(2000);
+    QCOMPARE(group.currentLoopTime(), 1750);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 250);
+}
+
+void tst_QSequentialAnimationGroupJob::setCurrentTimeWithUncontrolledAnimation()
+{
+    // sequence operating on different object/properties
+    QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    TestAnimation *a1_s_o1 = new TestAnimation;
+    TestAnimation *a1_s_o2 = new TestAnimation;
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a1_s_o2);
+
+    UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation;
+    QCOMPARE(notTimeDriven->totalDuration(), -1);
+
+    TestAnimation *loopsForever = new TestAnimation;
+    loopsForever->setLoopCount(-1);
+    QCOMPARE(loopsForever->totalDuration(), -1);
+
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(sequence);
+    group.appendAnimation(notTimeDriven);
+    group.appendAnimation(loopsForever);
+    group.start();
+    group.pause(); // this allows the group to listen for the finish signal of its children
+
+    // Current time = 1
+    group.setCurrentTime(1);
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(sequence->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 0);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+
+    // Current time = 250
+    group.setCurrentTime(250);
+    QCOMPARE(group.currentLoopTime(), 250);
+    QCOMPARE(sequence->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 0);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+
+    // Current time = 500
+    group.setCurrentTime(500);
+    QCOMPARE(group.currentLoopTime(), 500);
+    QCOMPARE(sequence->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 0);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob *>(notTimeDriven));
+
+    // Current time = 505
+    group.setCurrentTime(505);
+    QCOMPARE(group.currentLoopTime(), 505);
+    QCOMPARE(sequence->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 5);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob *>(notTimeDriven));
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
+
+    // Current time = 750 (end of notTimeDriven animation)
+    group.setCurrentTime(750);
+    QCOMPARE(group.currentLoopTime(), 750);
+    QCOMPARE(sequence->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 250);
+    QCOMPARE(loopsForever->currentLoopTime(), 0);
+    QCOMPARE(group.currentAnimation(), loopsForever);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever->state(), QAnimationGroupJob::Paused);
+
+    // Current time = 800 (as notTimeDriven was finished at 750, loopsforever should still run)
+    group.setCurrentTime(800);
+    QCOMPARE(group.currentLoopTime(), 800);
+    QCOMPARE(group.currentAnimation(), loopsForever);
+    QCOMPARE(sequence->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(notTimeDriven->currentLoopTime(), 250);
+    QCOMPARE(loopsForever->currentLoopTime(), 50);
+
+    loopsForever->stop(); // this should stop the group
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QSequentialAnimationGroupJob::seekingForwards()
+{
+
+    // sequence operating on same object/property
+    QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob;
+    TestAnimation *a1_s_o1 = new TestAnimation;
+    TestAnimation *a2_s_o1 = new TestAnimation;
+    TestAnimation *a3_s_o1 = new TestAnimation;
+    a2_s_o1->setLoopCount(3);
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a2_s_o1);
+    sequence->appendAnimation(a3_s_o1);
+
+    // sequence operating on different object/properties
+    QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob;
+    TestAnimation *a1_s_o2 = new TestAnimation;
+    TestAnimation *a1_s_o3 = new TestAnimation;
+    sequence2->appendAnimation(a1_s_o2);
+    sequence2->appendAnimation(a1_s_o3);
+
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(sequence);
+    group.appendAnimation(sequence2);
+
+    // Current time = 1
+    group.setCurrentTime(1);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(sequence->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // Current time = 1500
+    group.setCurrentTime(1500);
+    QCOMPARE(group.currentLoopTime(), 1500);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    // this will restart the group
+    group.start();
+    group.pause();
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
+
+    // Current time = 1750
+    group.setCurrentTime(1750);
+    QCOMPARE(group.currentLoopTime(), 1750);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 250);
+}
+
+void tst_QSequentialAnimationGroupJob::seekingBackwards()
+{
+    // sequence operating on same object/property
+    QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    TestAnimation *a1_s_o1 = new TestAnimation;
+    TestAnimation *a2_s_o1 = new TestAnimation;
+    TestAnimation *a3_s_o1 = new TestAnimation;
+    a2_s_o1->setLoopCount(3);
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a2_s_o1);
+    sequence->appendAnimation(a3_s_o1);
+
+    // sequence operating on different object/properties
+    QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob();
+    TestAnimation *a1_s_o2 = new TestAnimation;
+    TestAnimation *a1_s_o3 = new TestAnimation;
+    sequence2->appendAnimation(a1_s_o2);
+    sequence2->appendAnimation(a1_s_o3);
+
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(sequence);
+    group.appendAnimation(sequence2);
+
+    group.start();
+
+    // Current time = 1600
+    group.setCurrentTime(1600);
+    QCOMPARE(group.currentLoopTime(), 1600);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 350);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 100);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Running);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Running);
+
+    // Seeking backwards, current time = 1
+    group.setCurrentTime(1);
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(sequence->currentLoopTime(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 1);
+
+    QEXPECT_FAIL("", "rewinding in nested groups is considered as a restart from the children,"
+        "hence they don't reset from their current animation", Continue);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 0);
+    QEXPECT_FAIL("", "rewinding in nested groups is considered as a restart from the children,"
+        "hence they don't reset from their current animation", Continue);
+    QCOMPARE(a2_s_o1->currentLoop(), 0);
+    QEXPECT_FAIL("", "rewinding in nested groups is considered as a restart from the children,"
+        "hence they don't reset from their current animation", Continue);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 0);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 0);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Running);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Running);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
+
+    // Current time = 2000
+    group.setCurrentTime(2000);
+    QCOMPARE(group.currentLoopTime(), 1750);
+    QCOMPARE(sequence->currentLoopTime(), 1250);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 2);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+    QCOMPARE(sequence2->currentLoopTime(), 500);
+    QCOMPARE(a1_s_o2->currentLoopTime(), 250);
+    QCOMPARE(a1_s_o3->currentLoopTime(), 250);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
+}
+
+typedef QList<QAbstractAnimationJob::State> StateList;
+
+static bool compareStates(const StateChangeListener& spy, const StateList &expectedStates)
+{
+    bool equals = true;
+    for (int i = 0; i < qMax(expectedStates.count(), spy.count()); ++i) {
+        if (i >= spy.count() || i >= expectedStates.count()) {
+            equals = false;
+            break;
+        }
+        QAbstractAnimationJob::State st = expectedStates.at(i);
+        QAbstractAnimationJob::State actual = spy.states.at(i);
+        if (equals && actual != st) {
+            equals = false;
+            break;
+        }
+    }
+    if (!equals) {
+        const char *stateStrings[] = {"Stopped", "Paused", "Running"};
+        QString e,a;
+        for (int i = 0; i < qMax(expectedStates.count(), spy.count()); ++i) {
+            if (i < expectedStates.count()) {
+                int exp = int(expectedStates.at(i));
+                    if (!e.isEmpty())
+                        e += QLatin1String(", ");
+                e += QLatin1String(stateStrings[exp]);
+            }
+            if (i < spy.count()) {
+                QAbstractAnimationJob::State actual = spy.states.at(i);
+                if (!a.isEmpty())
+                    a += QLatin1String(", ");
+                if (int(actual) >= 0 && int(actual) <= 2) {
+                    a += QLatin1String(stateStrings[int(actual)]);
+                } else {
+                    a += QLatin1String("NaN");
+                }
+            }
+
+        }
+        qDebug("\n"
+               "expected (count == %d): %s\n"
+               "actual   (count == %d): %s\n", expectedStates.count(), qPrintable(e), spy.count(), qPrintable(a));
+    }
+    return equals;
+}
+
+void tst_QSequentialAnimationGroupJob::pauseAndResume()
+{
+    // sequence operating on same object/property
+    QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    TestAnimation *a1_s_o1 = new TestAnimation;
+    TestAnimation *a2_s_o1 = new TestAnimation;
+    TestAnimation *a3_s_o1 = new TestAnimation;
+    a2_s_o1->setLoopCount(2);
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a2_s_o1);
+    sequence->appendAnimation(a3_s_o1);
+    sequence->setLoopCount(2);
+
+    StateChangeListener a1StateChangedSpy;
+    a1_s_o1->addAnimationChangeListener(&a1StateChangedSpy, QAbstractAnimationJob::StateChange);
+    StateChangeListener seqStateChangedSpy;
+    sequence->addAnimationChangeListener(&seqStateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(sequence);
+
+    group.start();
+    group.pause();
+
+    // Current time = 1751
+    group.setCurrentTime(1751);
+    QCOMPARE(group.currentLoopTime(), 1751);
+    QCOMPARE(sequence->currentLoopTime(), 751);
+    QCOMPARE(sequence->currentLoop(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 1);
+    QCOMPARE(a3_s_o1->currentLoop(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 1);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Paused);
+
+    QCOMPARE(a1StateChangedSpy.count(), 5);     // Running,Paused,Stopped,Running,Stopped
+    QCOMPARE(seqStateChangedSpy.count(), 2);    // Running,Paused
+
+    QVERIFY(compareStates(a1StateChangedSpy, (StateList() << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Paused
+                                              << QAbstractAnimationJob::Stopped
+                                              << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Stopped)));
+
+    //### is this the same test as compareStates test above?
+    QCOMPARE(a1StateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(a1StateChangedSpy.states.at(1), QAnimationGroupJob::Paused);
+    QCOMPARE(a1StateChangedSpy.states.at(2), QAnimationGroupJob::Stopped);
+    QCOMPARE(a1StateChangedSpy.states.at(3), QAnimationGroupJob::Running);
+    QCOMPARE(a1StateChangedSpy.states.at(4), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(seqStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(seqStateChangedSpy.states.at(1), QAnimationGroupJob::Paused);
+
+    group.resume();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Running);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Running);
+
+    QVERIFY(group.currentLoopTime() >= 1751);
+    QVERIFY(sequence->currentLoopTime() >= 751);
+    QCOMPARE(sequence->currentLoop(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 1);
+    QCOMPARE(a3_s_o1->currentLoop(), 0);
+    QVERIFY(a3_s_o1->currentLoopTime() >= 1);
+
+    QCOMPARE(seqStateChangedSpy.count(), 3);    // Running,Paused,Running
+    QCOMPARE(seqStateChangedSpy.states.at(2), QAnimationGroupJob::Running);
+
+    group.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Paused);
+
+    QVERIFY(group.currentLoopTime() >= 1751);
+    QVERIFY(sequence->currentLoopTime() >= 751);
+    QCOMPARE(sequence->currentLoop(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 1);
+    QCOMPARE(a3_s_o1->currentLoop(), 0);
+    QVERIFY(a3_s_o1->currentLoopTime() >= 1);
+
+    QCOMPARE(seqStateChangedSpy.count(), 4);    // Running,Paused,Running,Paused
+    QCOMPARE(seqStateChangedSpy.states.at(3), QAnimationGroupJob::Paused);
+
+    group.stop();
+
+    QCOMPARE(seqStateChangedSpy.count(), 5);    // Running,Paused,Running,Paused,Stopped
+    QCOMPARE(seqStateChangedSpy.states.at(4), QAnimationGroupJob::Stopped);
+}
+
+void tst_QSequentialAnimationGroupJob::restart()
+{
+    // originally was sequence operating on same object/property
+    QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    //### no equivilant signal
+    //QSignalSpy seqCurrentAnimChangedSpy(sequence, SIGNAL(currentAnimationChanged(QAbstractAnimationJob*)));
+
+    StateChangeListener seqStateChangedSpy;
+    sequence->addAnimationChangeListener(&seqStateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    TestAnimation *anims[3];
+    StateChangeListener *animsStateChanged[3];
+
+    for (int i = 0; i < 3; i++) {
+        anims[i] = new TestAnimation(100);
+        animsStateChanged[i] = new StateChangeListener;
+        anims[i]->addAnimationChangeListener(animsStateChanged[i], QAbstractAnimationJob::StateChange);
+    }
+
+    anims[1]->setLoopCount(2);
+    sequence->appendAnimation(anims[0]);
+    sequence->appendAnimation(anims[1]);
+    sequence->appendAnimation(anims[2]);
+    sequence->setLoopCount(2);
+
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(sequence);
+
+    group.start();
+
+    QTest::qWait(500);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+
+    QTest::qWait(300);
+    QTRY_COMPARE(group.state(), QAnimationGroupJob::Stopped);
+
+    for (int i = 0; i < 3; i++) {
+        QCOMPARE(animsStateChanged[i]->count(), 4);
+        QCOMPARE(animsStateChanged[i]->states.at(0), QAnimationGroupJob::Running);
+        QCOMPARE(animsStateChanged[i]->states.at(1), QAnimationGroupJob::Stopped);
+        QCOMPARE(animsStateChanged[i]->states.at(2), QAnimationGroupJob::Running);
+        QCOMPARE(animsStateChanged[i]->states.at(3), QAnimationGroupJob::Stopped);
+    }
+
+    QCOMPARE(seqStateChangedSpy.count(), 2);
+    QCOMPARE(seqStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(seqStateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
+
+    //QCOMPARE(seqCurrentAnimChangedSpy.count(), 6);
+    //for(int i=0; i<seqCurrentAnimChangedSpy.count(); i++)
+    //        QCOMPARE(static_cast<QAbstractAnimationJob*>(anims[i%3]), qVariantValue<QAbstractAnimationJob*>(seqCurrentAnimChangedSpy.at(i).at(0)));
+
+    group.start();
+
+    QCOMPARE(animsStateChanged[0]->count(), 5);
+    QCOMPARE(animsStateChanged[1]->count(), 4);
+    QCOMPARE(animsStateChanged[2]->count(), 4);
+    QCOMPARE(seqStateChangedSpy.count(), 3);
+}
+
+void tst_QSequentialAnimationGroupJob::looping()
+{
+    // originally was sequence operating on same object/property
+    QSequentialAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
+    QAbstractAnimationJob *a1_s_o1 = new TestAnimation;
+    QAbstractAnimationJob *a2_s_o1 = new TestAnimation;
+    QAbstractAnimationJob *a3_s_o1 = new TestAnimation;
+
+    StateChangeListener a1Spy;
+    a1_s_o1->addAnimationChangeListener(&a1Spy, QAbstractAnimationJob::StateChange);
+    StateChangeListener a2Spy;
+    a2_s_o1->addAnimationChangeListener(&a2Spy, QAbstractAnimationJob::StateChange);
+    StateChangeListener a3Spy;
+    a3_s_o1->addAnimationChangeListener(&a3Spy, QAbstractAnimationJob::StateChange);
+    StateChangeListener seqSpy;
+    sequence->addAnimationChangeListener(&seqSpy, QAbstractAnimationJob::StateChange);
+
+    a2_s_o1->setLoopCount(2);
+    sequence->appendAnimation(a1_s_o1);
+    sequence->appendAnimation(a2_s_o1);
+    sequence->appendAnimation(a3_s_o1);
+    sequence->setLoopCount(2);
+
+    QSequentialAnimationGroupJob group;
+    StateChangeListener groupSpy;
+    group.addAnimationChangeListener(&groupSpy, QAbstractAnimationJob::StateChange);
+
+    group.appendAnimation(sequence);
+    group.setLoopCount(2);
+
+    group.start();
+    group.pause();
+
+    // Current time = 1750
+    group.setCurrentTime(1750);
+    QCOMPARE(group.currentLoopTime(), 1750);
+    QCOMPARE(sequence->currentLoopTime(), 750);
+    QCOMPARE(sequence->currentLoop(), 1);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 1);
+    // this animation is at the beginning because it is the current one inside sequence
+    QCOMPARE(a3_s_o1->currentLoop(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 0);
+    QCOMPARE(sequence->currentAnimation(), a3_s_o1);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Paused);
+
+    QCOMPARE(a1Spy.count(), 5);     // Running,Paused,Stopped,Running,Stopped
+    QVERIFY(compareStates(a1Spy, (StateList() << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Paused
+                                              << QAbstractAnimationJob::Stopped
+                                              << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Stopped)));
+
+    QCOMPARE(a2Spy.count(), 4);     // Running,Stopped,Running,Stopped
+    QVERIFY(compareStates(a3Spy, (StateList() << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Stopped
+                                              << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Paused)));
+
+    QCOMPARE(seqSpy.count(), 2);    // Running,Paused
+    QCOMPARE(groupSpy.count(), 2);  // Running,Paused
+
+    // Looping, current time = duration + 1
+    group.setCurrentTime(group.duration() + 1);
+    QCOMPARE(group.currentLoopTime(), 1);
+    QCOMPARE(group.currentLoop(), 1);
+    QCOMPARE(sequence->currentLoopTime(), 1);
+    QCOMPARE(sequence->currentLoop(), 0);
+    QCOMPARE(a1_s_o1->currentLoopTime(), 1);
+    QCOMPARE(a2_s_o1->currentLoopTime(), 250);
+    QCOMPARE(a2_s_o1->currentLoop(), 1);
+    // this animation is at the end because it was run on the previous loop
+    QCOMPARE(a3_s_o1->currentLoop(), 0);
+    QCOMPARE(a3_s_o1->currentLoopTime(), 250);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(a1Spy.count(), 7); // Running,Paused,Stopped,Running,Stopped,Running,Stopped
+    QCOMPARE(a2Spy.count(), 4); // Running, Stopped, Running, Stopped
+    QVERIFY(compareStates(a3Spy, (StateList() << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Stopped
+                                              << QAbstractAnimationJob::Running
+                                              << QAbstractAnimationJob::Paused
+                                              << QAbstractAnimationJob::Stopped)));
+    QVERIFY(compareStates(seqSpy, (StateList() << QAbstractAnimationJob::Running
+                                               << QAbstractAnimationJob::Paused
+                                               << QAbstractAnimationJob::Stopped
+                                               << QAbstractAnimationJob::Running
+                                               << QAbstractAnimationJob::Paused)));
+    QCOMPARE(groupSpy.count(), 2);
+
+    //cleanup
+    a1_s_o1->removeAnimationChangeListener(&a1Spy, QAbstractAnimationJob::StateChange);
+    a2_s_o1->removeAnimationChangeListener(&a2Spy, QAbstractAnimationJob::StateChange);
+    a3_s_o1->removeAnimationChangeListener(&a3Spy, QAbstractAnimationJob::StateChange);
+    sequence->removeAnimationChangeListener(&seqSpy, QAbstractAnimationJob::StateChange);
+    group.removeAnimationChangeListener(&groupSpy, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QSequentialAnimationGroupJob::startDelay()
+{
+    QSequentialAnimationGroupJob group;
+    group.appendAnimation(new QPauseAnimationJob(250));
+    group.appendAnimation(new QPauseAnimationJob(125));
+    QCOMPARE(group.totalDuration(), 375);
+
+    group.start();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+
+    QTest::qWait(500);
+
+    QTRY_COMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QVERIFY(group.currentLoopTime() == 375);
+}
+
+void tst_QSequentialAnimationGroupJob::clearGroup()
+{
+    QSequentialAnimationGroupJob group;
+
+    static const int animationCount = 20;
+
+    for (int i = 0; i < animationCount/2; ++i) {
+        QSequentialAnimationGroupJob *subGroup = new QSequentialAnimationGroupJob;
+        group.appendAnimation(subGroup);
+        group.appendAnimation(new QPauseAnimationJob(100));
+        subGroup->appendAnimation(new QPauseAnimationJob(10));
+    }
+
+    int count = 0;
+    for (QAbstractAnimationJob *anim = group.firstChild(); anim; anim = anim->nextSibling())
+        ++count;
+    QCOMPARE(count, animationCount);
+
+    group.clear();
+
+    QVERIFY(!group.firstChild() && !group.lastChild());
+    QCOMPARE(group.currentLoopTime(), 0);
+}
+
+void tst_QSequentialAnimationGroupJob::groupWithZeroDurationAnimations()
+{
+    QSequentialAnimationGroupJob group;
+
+    TestValueAnimation *a1 = new TestValueAnimation(0);
+    a1->start = 42;
+    a1->end = 43;
+    group.appendAnimation(a1);
+
+    //this should just run fine and change nothing
+    group.setCurrentTime(0);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(a1));
+
+    TestValueAnimation *a2 = new TestValueAnimation(500);
+    a2->start = 13;
+    a2->end = 31;
+    group.appendAnimation(a2);
+
+    TestValueAnimation *a3 = new TestValueAnimation(0);
+    a3->start = 43;
+    a3->end = 44;
+    group.appendAnimation(a3);
+
+    TestValueAnimation *a4 = new TestValueAnimation(250);
+    a4->start = 13;
+    a4->end = 75;
+    group.appendAnimation(a4);
+
+    TestValueAnimation *a5 = new TestValueAnimation(0);
+    a5->start = 42;
+    a5->end = 12;
+    group.appendAnimation(a5);
+
+    QCOMPARE((int)a1->value, 43);   //### is this actually the behavior we want?
+    QCOMPARE((int)a2->value, 0);
+    QCOMPARE((int)a3->value, 0);
+    QCOMPARE((int)a4->value, 0);
+    QCOMPARE((int)a5->value, 0);
+
+    group.start();
+
+    QCOMPARE((int)a1->value, 43);   //### is this actually the behavior we want?
+    QCOMPARE((int)a2->value, 13);
+    QCOMPARE((int)a3->value, 0);
+    QCOMPARE((int)a4->value, 0);
+    QCOMPARE((int)a5->value, 0);
+
+    QTest::qWait(100);
+
+    QCOMPARE((int)a1->value, 43);
+    QVERIFY(a2->value > 13 && a2->value < 31);
+    QCOMPARE((int)a3->value, 0);
+    QCOMPARE((int)a4->value, 0);
+    QCOMPARE((int)a5->value, 0);
+
+    QTest::qWait(500);
+
+    QTRY_COMPARE((int)a3->value, 44);
+    QCOMPARE((int)a1->value, 43);
+    QCOMPARE((int)a2->value, 31);
+    //QCOMPARE((int)a4->value, 36);
+    QCOMPARE((int)a5->value, 0);
+    QCOMPARE(a1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a4->state(), QAnimationGroupJob::Running);
+    QCOMPARE(a5->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QTest::qWait(500);
+
+    QTRY_COMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE((int)a1->value, 43);
+    QCOMPARE((int)a2->value, 31);
+    QCOMPARE((int)a3->value, 44);
+    QCOMPARE((int)a4->value, 75);
+    QCOMPARE((int)a5->value, 12);
+    QCOMPARE(a1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a2->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a3->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a4->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(a5->state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QSequentialAnimationGroupJob::propagateGroupUpdateToChildren()
+{
+    // this test verifies if group state changes are updating its children correctly
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation anim1(100);
+    TestAnimation anim2(200);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    group.start();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QSequentialAnimationGroupJob::updateChildrenWithRunningGroup()
+{
+    // assert that its possible to modify a child's state directly while their group is running
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation anim(200);
+
+    StateChangeListener groupStateChangedSpy;
+    group.addAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
+    StateChangeListener childStateChangedSpy;
+    anim.addAnimationChangeListener(&childStateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    QCOMPARE(groupStateChangedSpy.count(), 0);
+    QCOMPARE(childStateChangedSpy.count(), 0);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim);
+
+    group.start();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Running);
+
+    QCOMPARE(groupStateChangedSpy.count(), 1);
+    QCOMPARE(childStateChangedSpy.count(), 1);
+
+    QCOMPARE(groupStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(childStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+
+    // starting directly a running child will not have any effect
+    anim.start();
+
+    QCOMPARE(groupStateChangedSpy.count(), 1);
+    QCOMPARE(childStateChangedSpy.count(), 1);
+
+    anim.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Paused);
+
+    // in the animation stops directly, the group will still be running
+    anim.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
+
+    //cleanup
+    group.removeAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
+    anim.removeAnimationChangeListener(&childStateChangedSpy, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QSequentialAnimationGroupJob::deleteChildrenWithRunningGroup()
+{
+    // test if children can be activated when their group is stopped
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation *anim1 = new TestAnimation(200);
+    group.appendAnimation(anim1);
+
+    QCOMPARE(group.duration(), anim1->duration());
+
+    group.start();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
+
+    QTest::qWait(100);
+    QTRY_VERIFY(group.currentLoopTime() > 0);
+
+    delete anim1;
+    QVERIFY(!group.firstChild());
+    QCOMPARE(group.duration(), 0);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(group.currentLoopTime(), 0); //that's the invariant
+}
+
+void tst_QSequentialAnimationGroupJob::startChildrenWithStoppedGroup()
+{
+    // test if children can be activated when their group is stopped
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation anim1(200);
+    TestAnimation anim2(200);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    anim1.start();
+    anim2.start();
+    anim2.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+}
+
+void tst_QSequentialAnimationGroupJob::stopGroupWithRunningChild()
+{
+    // children that started independently will not be affected by a group stop
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation anim1(200);
+    TestAnimation anim2(200);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(&anim1);
+    group.appendAnimation(&anim2);
+
+    anim1.start();
+    anim2.start();
+    anim2.pause();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
+
+    anim1.stop();
+    anim2.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QSequentialAnimationGroupJob::startGroupWithRunningChild()
+{
+    // as the group has precedence over its children, starting a group will restart all the children
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation *anim1 = new TestAnimation(200);
+    TestAnimation *anim2 = new TestAnimation(200);
+
+    StateChangeListener stateChangedSpy1;
+    anim1->addAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
+    StateChangeListener stateChangedSpy2;
+    anim2->addAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
+
+    QCOMPARE(stateChangedSpy1.count(), 0);
+    QCOMPARE(stateChangedSpy2.count(), 0);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Stopped);
+
+    group.appendAnimation(anim1);
+    group.appendAnimation(anim2);
+
+    anim1->start();
+    anim2->start();
+    anim2->pause();
+
+    QVERIFY(compareStates(stateChangedSpy1, (StateList() << QAbstractAnimationJob::Running)));
+
+    QVERIFY(compareStates(stateChangedSpy2, (StateList() << QAbstractAnimationJob::Running
+                                                         << QAbstractAnimationJob::Paused)));
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Paused);
+
+    group.start();
+
+    QVERIFY(compareStates(stateChangedSpy1, (StateList() << QAbstractAnimationJob::Running
+                                                         << QAbstractAnimationJob::Stopped
+                                                         << QAbstractAnimationJob::Running)));
+    QVERIFY(compareStates(stateChangedSpy2, (StateList() << QAbstractAnimationJob::Running
+                                                         << QAbstractAnimationJob::Paused)));
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Paused);
+
+    QTest::qWait(300);
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Running);
+
+    QCOMPARE(stateChangedSpy2.count(), 4);
+    QCOMPARE(stateChangedSpy2.states.at(2), QAnimationGroupJob::Stopped);
+    QCOMPARE(stateChangedSpy2.states.at(3), QAnimationGroupJob::Running);
+
+    group.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Stopped);
+
+    anim1->removeAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
+    anim2->removeAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QSequentialAnimationGroupJob::zeroDurationAnimation()
+{
+    QSequentialAnimationGroupJob group;
+
+    TestAnimation *anim1 = new TestAnimation(0);
+    TestAnimation *anim2 = new TestAnimation(100);
+    TestValueAnimation *anim3 = new TestValueAnimation(0);
+    anim3->end = 100;
+
+    StateChangeListener stateChangedSpy;
+    anim1->addAnimationChangeListener(&stateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    group.appendAnimation(anim1);
+    group.appendAnimation(anim2);
+    group.appendAnimation(anim3);
+    group.setLoopCount(2);
+    group.start();
+
+    QCOMPARE(stateChangedSpy.count(), 2);
+    QCOMPARE(stateChangedSpy.states.at(0), QAnimationGroupJob::Running);
+    QCOMPARE(stateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
+
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+
+    //now let's try to seek to the next loop
+    group.setCurrentTime(group.duration() + 1);
+    QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim2->state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim3->state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    //TODO: test that anim3 was run
+    QCOMPARE(anim3->value, qreal(100)); //anim3 should have been run
+
+    anim1->removeAnimationChangeListener(&stateChangedSpy, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QSequentialAnimationGroupJob::stopUncontrolledAnimations()
+{
+    QSequentialAnimationGroupJob group;
+
+    UncontrolledAnimation notTimeDriven;
+    QCOMPARE(notTimeDriven.totalDuration(), -1);
+
+    TestAnimation loopsForever(100);
+    loopsForever.setLoopCount(-1);
+
+    group.appendAnimation(&notTimeDriven);
+    group.appendAnimation(&loopsForever);
+
+    group.start();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
+    QCOMPARE(loopsForever.state(), QAnimationGroupJob::Stopped);
+
+    notTimeDriven.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever.state(), QAnimationGroupJob::Running);
+
+    loopsForever.stop();
+
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(loopsForever.state(), QAnimationGroupJob::Stopped);
+}
+
+void tst_QSequentialAnimationGroupJob::finishWithUncontrolledAnimation()
+{
+    //1st case:
+    //first we test a group with one uncontrolled animation
+    QSequentialAnimationGroupJob group;
+    UncontrolledAnimation notTimeDriven;
+    group.appendAnimation(&notTimeDriven);
+    FinishedListener spy;
+    group.addAnimationChangeListener(&spy, QAbstractAnimationJob::Completion);
+
+    group.start();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.currentLoopTime(), 0);
+    QCOMPARE(notTimeDriven.currentLoopTime(), 0);
+
+    QTest::qWait(300); //wait for the end of notTimeDriven
+    QTRY_COMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
+    const int actualDuration = notTimeDriven.currentLoopTime();
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(group.currentLoopTime(), actualDuration);
+    QCOMPARE(spy.count(), 1);
+
+    //2nd case:
+    // lets make sure the seeking will work again
+    spy.clear();
+    TestAnimation anim;
+    group.appendAnimation(&anim);
+    StateChangeListener animStateChangedSpy;
+    anim.addAnimationChangeListener(&animStateChangedSpy, QAbstractAnimationJob::StateChange);
+
+    group.setCurrentTime(300);
+    QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(notTimeDriven.currentLoopTime(), actualDuration);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
+
+    //3rd case:
+    //now let's add a perfectly defined animation at the end
+    QCOMPARE(animStateChangedSpy.count(), 0);
+    group.start();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.currentLoopTime(), 0);
+    QCOMPARE(notTimeDriven.currentLoopTime(), 0);
+
+    QCOMPARE(animStateChangedSpy.count(), 0);
+
+    QTest::qWait(300); //wait for the end of notTimeDriven
+    QTRY_COMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim.state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
+    QCOMPARE(animStateChangedSpy.count(), 1);
+    QTest::qWait(300); //wait for the end of anim
+
+    QTRY_COMPARE(anim.state(), QAnimationGroupJob::Stopped);
+    QCOMPARE(anim.currentLoopTime(), anim.duration());
+
+    //we should simply be at the end
+    QCOMPARE(spy.count(), 1);
+    QCOMPARE(animStateChangedSpy.count(), 2);
+    QCOMPARE(group.currentLoopTime(), notTimeDriven.currentLoopTime() + anim.currentLoopTime());
+
+    //cleanup
+    group.removeAnimationChangeListener(&spy, QAbstractAnimationJob::Completion);
+    anim.removeAnimationChangeListener(&animStateChangedSpy, QAbstractAnimationJob::StateChange);
+}
+
+void tst_QSequentialAnimationGroupJob::addRemoveAnimation()
+{
+    //this test is specific to the sequential animation group
+    QSequentialAnimationGroupJob group;
+
+    QCOMPARE(group.duration(), 0);
+    QCOMPARE(group.currentLoopTime(), 0);
+    QAbstractAnimationJob *anim1 = new TestAnimation;
+    group.appendAnimation(anim1);
+    QCOMPARE(group.duration(), 250);
+    QCOMPARE(group.currentLoopTime(), 0);
+    QCOMPARE(group.currentAnimation(), anim1);
+
+    //let's append an animation
+    QAbstractAnimationJob *anim2 = new TestAnimation;
+    group.appendAnimation(anim2);
+    QCOMPARE(group.duration(), 500);
+    QCOMPARE(group.currentLoopTime(), 0);
+    QCOMPARE(group.currentAnimation(), anim1);
+
+    //let's prepend an animation
+    QAbstractAnimationJob *anim0 = new TestAnimation;
+    group.prependAnimation(anim0);
+    QCOMPARE(group.duration(), 750);
+    QCOMPARE(group.currentLoopTime(), 0);
+    QCOMPARE(group.currentAnimation(), anim0); //anim0 has become the new currentAnimation
+
+    group.setCurrentTime(300); //anim0 | anim1 | anim2
+    QCOMPARE(group.currentLoopTime(), 300);
+    QCOMPARE(group.currentAnimation(), anim1);
+    QCOMPARE(anim1->currentLoopTime(), 50);
+
+    group.removeAnimation(anim0); //anim1 | anim2
+    QCOMPARE(group.currentLoopTime(), 50);
+    QCOMPARE(group.currentAnimation(), anim1);
+    QCOMPARE(anim1->currentLoopTime(), 50);
+
+    group.setCurrentTime(0);
+    group.prependAnimation(anim0); //anim0 | anim1 | anim2
+    group.setCurrentTime(300);
+    QCOMPARE(group.currentLoopTime(), 300);
+    QCOMPARE(group.currentAnimation(), anim1);
+    QCOMPARE(anim1->currentLoopTime(), 50);
+
+    group.removeAnimation(anim1); //anim0 | anim2
+    QCOMPARE(group.currentLoopTime(), 250);
+    QCOMPARE(group.currentAnimation(), anim2);
+    QCOMPARE(anim0->currentLoopTime(), 250);
+}
+
+void tst_QSequentialAnimationGroupJob::currentAnimation()
+{
+    QSequentialAnimationGroupJob group;
+    QVERIFY(group.currentAnimation() == 0);
+
+    TestAnimation anim(0);
+    group.appendAnimation(&anim);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
+}
+
+void tst_QSequentialAnimationGroupJob::currentAnimationWithZeroDuration()
+{
+    QSequentialAnimationGroupJob group;
+    QVERIFY(group.currentAnimation() == 0);
+
+    TestAnimation zero1(0);
+    TestAnimation zero2(0);
+
+    TestAnimation anim;
+
+    TestAnimation zero3(0);
+    TestAnimation zero4(0);
+
+    group.appendAnimation(&zero1);
+    group.appendAnimation(&zero2);
+    group.appendAnimation(&anim);
+    group.appendAnimation(&zero3);
+    group.appendAnimation(&zero4);
+
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&zero1));
+
+    group.setCurrentTime(0);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
+
+    group.setCurrentTime(group.duration());
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&zero4));
+
+    group.setDirection(QAbstractAnimationJob::Backward);
+
+    group.setCurrentTime(0);
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&zero1));
+
+    group.setCurrentTime(group.duration());
+    QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
+}
+
+void tst_QSequentialAnimationGroupJob::insertAnimation()
+{
+    QSequentialAnimationGroupJob group;
+    group.setLoopCount(2);
+    TestAnimation *anim = new TestAnimation;
+    group.appendAnimation(anim);
+    QCOMPARE(group.duration(), anim->duration());
+    group.setCurrentTime(300);
+    QCOMPARE(group.currentLoop(), 1);
+
+    //this will crash if the sequential group calls duration on the created animation
+    group.appendAnimation(new TestAnimation);
+}
+
+class ClearFinishedListener: public QAnimation2ChangeListener
+{
+public:
+    ClearFinishedListener(QSequentialAnimationGroupJob *g) : group(g) {}
+
+    virtual void animationFinished(QAbstractAnimationJob *)
+    {
+        group->clear();
+    }
+
+    QSequentialAnimationGroupJob *group;
+};
+
+class RefillFinishedListener: public QAnimation2ChangeListener
+{
+public:
+    RefillFinishedListener(QSequentialAnimationGroupJob *g) : group(g) {}
+
+    virtual void animationFinished(QAbstractAnimationJob *)
+    {
+        group->stop();
+        group->clear();
+        group->appendAnimation(new TestAnimation);
+        group->start();
+    }
+
+    QSequentialAnimationGroupJob *group;
+};
+
+void tst_QSequentialAnimationGroupJob::clear()
+{
+    QSKIP("deleting an animation when finished is not currently supported");
+    QSequentialAnimationGroupJob group;
+    TestAnimation *anim1 = new TestAnimation;
+    group.appendAnimation(anim1);
+    ClearFinishedListener clearListener(&group);
+    anim1->addAnimationChangeListener(&clearListener, QAbstractAnimationJob::Completion);
+
+    TestAnimation *anim2 = new TestAnimation;
+    group.appendAnimation(anim2);
+    QCOMPARE(group.firstChild(), anim1);
+    QCOMPARE(group.lastChild(), anim2);
+
+    group.start();
+    QTest::qWait(anim1->duration() + 100);
+    QTRY_VERIFY(!group.firstChild());
+    QCOMPARE(group.state(), QAbstractAnimationJob::Stopped);
+    QCOMPARE(group.currentLoopTime(), 0);
+
+    anim1 = new TestAnimation;
+    group.appendAnimation(anim1);
+    RefillFinishedListener refillListener(&group);
+    anim1->addAnimationChangeListener(&refillListener, QAbstractAnimationJob::Completion);
+    group.start();
+    QTest::qWait(anim1->duration() + 100);
+    QTRY_COMPARE(group.state(), QAbstractAnimationJob::Running);
+}
+
+void tst_QSequentialAnimationGroupJob::pauseResume()
+{
+    QParallelAnimationGroupJob group;
+    TestAnimation *anim = new TestAnimation;
+    group.appendAnimation(anim);
+    StateChangeListener spy;
+    anim->addAnimationChangeListener(&spy, QAbstractAnimationJob::StateChange);
+    QCOMPARE(group.duration(), 250);
+    group.start();
+    QTest::qWait(100);
+    QTRY_COMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim->state(), QAnimationGroupJob::Running);
+    QCOMPARE(spy.count(), 1);
+    spy.clear();
+    const int currentTime = group.currentLoopTime();
+    QCOMPARE(anim->currentLoopTime(), currentTime);
+
+    group.pause();
+    QCOMPARE(group.state(), QAnimationGroupJob::Paused);
+    QCOMPARE(group.currentLoopTime(), currentTime);
+    QCOMPARE(anim->state(), QAnimationGroupJob::Paused);
+    QCOMPARE(anim->currentLoopTime(), currentTime);
+    QCOMPARE(spy.count(), 1);
+    spy.clear();
+
+    group.resume();
+    QCOMPARE(group.state(), QAnimationGroupJob::Running);
+    QCOMPARE(group.currentLoopTime(), currentTime);
+    QCOMPARE(anim->state(), QAnimationGroupJob::Running);
+    QCOMPARE(anim->currentLoopTime(), currentTime);
+    QCOMPARE(spy.count(), 1);
+
+    anim->removeAnimationChangeListener(&spy, QAbstractAnimationJob::StateChange);
+}
+
+QTEST_MAIN(tst_QSequentialAnimationGroupJob)
+#include "tst_qsequentialanimationgroupjob.moc"
index 6780a87..7288da2 100644 (file)
@@ -27,6 +27,7 @@ PUBLICTESTS += \
     qmlplugindump
 
 PRIVATETESTS += \
+    animation \
     qdeclarativebinding \
     qdeclarativechangeset \
     qdeclarativeconnection \
diff --git a/tests/auto/qtquick2/qdeclarativeanimationcontroller/data/tst_numberanimation.qml b/tests/auto/qtquick2/qdeclarativeanimationcontroller/data/tst_numberanimation.qml
new file mode 100644 (file)
index 0000000..7c4496b
--- /dev/null
@@ -0,0 +1,38 @@
+import QtQuick 2.0
+import QtTest 1.0
+
+Rectangle {
+  id:container
+  width:50
+  height:50
+
+  Rectangle {id:rect; x:0; y:0; color:"red"; width:10; height:10}
+  AnimationController {
+     id:numberAnimationcontroller
+     progress:1
+     animation: NumberAnimation {target: rect; property: "x"; from:0; to:40; duration: 1000}
+  }
+
+  TestCase {
+    name:"AnimationController"
+    when:windowShown
+    function test_numberAnimation() {
+      numberAnimationcontroller.progress = 0;
+      compare(rect.x, 0);
+      numberAnimationcontroller.progress = 0.5;
+      compare(rect.x, 20);
+
+      // <=0 -> 0
+      numberAnimationcontroller.progress = -1;
+      compare(rect.x, 0);
+
+      //>=1 -> 1
+      numberAnimationcontroller.progress = 1.1;
+      compare(rect.x, 40);
+
+      //make sure the progress can be set backward
+      numberAnimationcontroller.progress = 0.5;
+      compare(rect.x, 20);
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/auto/qtquick2/qdeclarativeanimationcontroller/qdeclarativeanimationcontroller.pro b/tests/auto/qtquick2/qdeclarativeanimationcontroller/qdeclarativeanimationcontroller.pro
new file mode 100644 (file)
index 0000000..52cafc3
--- /dev/null
@@ -0,0 +1,10 @@
+QT += core-private gui-private declarative-private
+TEMPLATE=app
+TARGET=tst_qdeclarativeanimationcontroller
+
+CONFIG += warn_on qmltestcase
+SOURCES += tst_qdeclarativeanimationcontroller.cpp
+
+importFiles.files = data
+importFiles.path = .
+DEPLOYMENT += importFiles
diff --git a/tests/auto/qtquick2/qdeclarativeanimationcontroller/tst_qdeclarativeanimationcontroller.cpp b/tests/auto/qtquick2/qdeclarativeanimationcontroller/tst_qdeclarativeanimationcontroller.cpp
new file mode 100644 (file)
index 0000000..744f92b
--- /dev/null
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QtQuickTest/quicktest.h>
+QUICK_TEST_MAIN(qdeclarativeanimationcontroller)
index 1340b51..3151a99 100644 (file)
@@ -130,8 +130,8 @@ void tst_qdeclarativeanimations::simpleProperty()
 
     rect.setPos(QPointF(0,0));
     animation.start();
-    animation.pause();
     QVERIFY(animation.isRunning());
+    animation.pause();
     QVERIFY(animation.isPaused());
     animation.setCurrentTime(125);
     QVERIFY(animation.currentTime() == 125);
@@ -887,8 +887,8 @@ void tst_qdeclarativeanimations::propertyValueSourceDefaultStart()
         QVERIFY(rect);
 
         QDeclarativeAbstractAnimation *myAnim = rect->findChild<QDeclarativeAbstractAnimation*>("MyAnim");
-        QVERIFY(myAnim && myAnim->qtAnimation());
-        QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimation::Stopped);
+        QVERIFY(myAnim && !myAnim->qtAnimation());
+        //QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimationJob::Stopped);
     }
 }
 
@@ -906,8 +906,8 @@ void tst_qdeclarativeanimations::dontStart()
         QVERIFY(rect);
 
         QDeclarativeAbstractAnimation *myAnim = rect->findChild<QDeclarativeAbstractAnimation*>("MyAnim");
-        QVERIFY(myAnim && myAnim->qtAnimation());
-        QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimation::Stopped);
+        QVERIFY(myAnim && !myAnim->qtAnimation());
+        //QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimationJob::Stopped);
     }
 
     {
@@ -921,8 +921,8 @@ void tst_qdeclarativeanimations::dontStart()
         QVERIFY(rect);
 
         QDeclarativeAbstractAnimation *myAnim = rect->findChild<QDeclarativeAbstractAnimation*>("MyAnim");
-        QVERIFY(myAnim && myAnim->qtAnimation());
-        QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimation::Stopped);
+        QVERIFY(myAnim && !myAnim->qtAnimation());
+        //QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimationJob::Stopped);
     }
 }
 
@@ -1086,7 +1086,7 @@ void tst_qdeclarativeanimations::doubleRegistrationBug()
 
     QDeclarativeAbstractAnimation *anim = rect->findChild<QDeclarativeAbstractAnimation*>("animation");
     QVERIFY(anim != 0);
-    QTRY_COMPARE(anim->qtAnimation()->state(), QAbstractAnimation::Stopped);
+    QTRY_COMPARE(anim->qtAnimation()->state(), QAbstractAnimationJob::Stopped);
 }
 
 //QTBUG-16736
@@ -1110,7 +1110,7 @@ void tst_qdeclarativeanimations::alwaysRunToEndRestartBug()
     QVERIFY(rect.x() != qreal(200));
     QTest::qWait(800);
     QTIMED_COMPARE(rect.x(), qreal(200));
-    QCOMPARE(static_cast<QDeclarativeAbstractAnimation*>(&animation)->qtAnimation()->state(), QAbstractAnimation::Stopped);
+    QCOMPARE(static_cast<QDeclarativeAbstractAnimation*>(&animation)->qtAnimation()->state(), QAbstractAnimationJob::Stopped);
 }
 
 //QTBUG-20227
@@ -1134,7 +1134,7 @@ void tst_qdeclarativeanimations::pauseBindingBug()
     QQuickRectangle *rect = qobject_cast<QQuickRectangle*>(c.create());
     QVERIFY(rect != 0);
     QDeclarativeAbstractAnimation *anim = rect->findChild<QDeclarativeAbstractAnimation*>("animation");
-    QVERIFY(anim->qtAnimation()->state() == QAbstractAnimation::Paused);
+    QVERIFY(anim->qtAnimation()->state() == QAbstractAnimationJob::Paused);
 
     delete rect;
 }
@@ -1147,7 +1147,7 @@ void tst_qdeclarativeanimations::pauseBug()
     QDeclarativeComponent c(&engine, testFileUrl("pauseBug.qml"));
     QDeclarativeAbstractAnimation *anim = qobject_cast<QDeclarativeAbstractAnimation*>(c.create());
     QVERIFY(anim != 0);
-    QCOMPARE(anim->qtAnimation()->state(), QAbstractAnimation::Paused);
+    QCOMPARE(anim->qtAnimation()->state(), QAbstractAnimationJob::Paused);
     QCOMPARE(anim->isPaused(), true);
     QCOMPARE(anim->isRunning(), true);
 
index 085f400..c1e60f1 100644 (file)
@@ -335,8 +335,7 @@ void tst_qdeclarativebehaviors::dontStart()
     QVERIFY(rect);
 
     QDeclarativeAbstractAnimation *myAnim = rect->findChild<QDeclarativeAbstractAnimation*>("MyAnim");
-    QVERIFY(myAnim && myAnim->qtAnimation());
-    QVERIFY(myAnim->qtAnimation()->state() == QAbstractAnimation::Stopped);
+    QVERIFY(myAnim && !myAnim->qtAnimation());
 
     delete rect;
 }
diff --git a/tests/auto/qtquick2/qdeclarativesmoothedanimation/data/simpleanimation.qml b/tests/auto/qtquick2/qdeclarativesmoothedanimation/data/simpleanimation.qml
new file mode 100644 (file)
index 0000000..b2be63e
--- /dev/null
@@ -0,0 +1,12 @@
+import QtQuick 2.0
+
+Rectangle {
+    width: 300; height: 300;
+    Rectangle {
+        objectName: "rect"
+        color: "red"
+        width: 60; height: 60;
+        x: 100; y: 100;
+    }
+    SmoothedAnimation { objectName: "anim"}
+}
\ No newline at end of file
index 5ab3e96..f60955c 100644 (file)
@@ -41,7 +41,7 @@
 #include <qtest.h>
 #include <QtDeclarative/qdeclarativeengine.h>
 #include <QtDeclarative/qdeclarativecomponent.h>
-#include <private/qdeclarativesmoothedanimation_p.h>
+#include <QtQuick/private/qdeclarativesmoothedanimation_p.h>
 #include <QtQuick/private/qquickrectangle_p.h>
 #include <private/qdeclarativevaluetype_p.h>
 #include "../../shared/util.h"
@@ -120,28 +120,40 @@ void tst_qdeclarativesmoothedanimation::disabled()
 
 void tst_qdeclarativesmoothedanimation::simpleAnimation()
 {
-    QQuickRectangle rect;
-    QDeclarativeSmoothedAnimation animation;
-    animation.setTarget(&rect);
-    animation.setProperty("x");
-    animation.setTo(200);
-    animation.setDuration(250);
-    QVERIFY(animation.target() == &rect);
-    QVERIFY(animation.property() == "x");
-    QVERIFY(animation.to() == 200);
-    animation.start();
-    QVERIFY(animation.isRunning());
-    QTest::qWait(animation.duration());
-    QTRY_COMPARE(rect.x(), qreal(200));
-
-    rect.setX(0);
-    animation.start();
-    animation.pause();
-    QVERIFY(animation.isRunning());
-    QVERIFY(animation.isPaused());
-    animation.setCurrentTime(125);
-    QVERIFY(animation.currentTime() == 125);
-    QCOMPARE(rect.x(), qreal(100));
+    QDeclarativeEngine engine;
+    QDeclarativeComponent c(&engine, testFileUrl("simpleanimation.qml"));
+    QObject *obj = c.create();
+    QVERIFY(obj);
+
+    QQuickRectangle *rect = obj->findChild<QQuickRectangle*>("rect");
+    QVERIFY(rect);
+
+    QDeclarativeSmoothedAnimation *animation = obj->findChild<QDeclarativeSmoothedAnimation*>("anim");
+    QVERIFY(animation);
+
+    animation->setTarget(rect);
+    animation->setProperty("x");
+    animation->setTo(200);
+    animation->setDuration(250);
+    QVERIFY(animation->target() == rect);
+    QVERIFY(animation->property() == "x");
+    QVERIFY(animation->to() == 200);
+    animation->start();
+    QVERIFY(animation->isRunning());
+    QTest::qWait(animation->duration());
+    QTRY_COMPARE(rect->x(), qreal(200));
+    QTest::qWait(100);  //smoothed animation doesn't report stopped until delayed timer fires
+
+    QVERIFY(!animation->isRunning());
+    rect->setX(0);
+    animation->start();
+    QVERIFY(animation->isRunning());
+    animation->pause();
+    QVERIFY(animation->isRunning());
+    QVERIFY(animation->isPaused());
+    animation->setCurrentTime(125);
+    QVERIFY(animation->currentTime() == 125);
+    QCOMPARE(rect->x(), qreal(100));
 }
 
 void tst_qdeclarativesmoothedanimation::valueSource()
index 172cc57..9f72e51 100644 (file)
@@ -1,9 +1,16 @@
 import QtQuick 2.0
 
-SpringAnimation {
-    to: 1.44; velocity: 0.9
-    spring: 1.0; damping: 0.5
-    epsilon: 0.25; modulus: 360.0
-    mass: 2.0; 
-    running: true;
+Item {
+    Item {
+        id: item
+    }
+
+    SpringAnimation {
+        target: item; property: "x"
+        to: 1.44; velocity: 0.9
+        spring: 1.0; damping: 0.5
+        epsilon: 0.25; modulus: 360.0
+        mass: 2.0;
+        running: true;
+    }
 }
index 15a3263..64956d7 100644 (file)
@@ -88,7 +88,9 @@ void tst_qdeclarativespringanimation::values()
 {
     QDeclarativeEngine engine;
     QDeclarativeComponent c(&engine, testFileUrl("springanimation2.qml"));
-    QDeclarativeSpringAnimation *obj = qobject_cast<QDeclarativeSpringAnimation*>(c.create());
+    QObject *root = c.create();
+
+    QDeclarativeSpringAnimation *obj = root->findChild<QDeclarativeSpringAnimation*>();
 
     QVERIFY(obj != 0);
 
diff --git a/tests/benchmarks/declarative/animation/animation.pro b/tests/benchmarks/declarative/animation/animation.pro
new file mode 100644 (file)
index 0000000..fc72040
--- /dev/null
@@ -0,0 +1,10 @@
+TEMPLATE = app
+TARGET = tst_animation
+QT += declarative
+macx:CONFIG -= app_bundle
+
+SOURCES += tst_animation.cpp
+
+DEFINES += SRCDIR=\\\"$$PWD\\\"
+
+QT += testlib core-private gui-private declarative-private quick-private v8-private
diff --git a/tests/benchmarks/declarative/animation/data/animation.qml b/tests/benchmarks/declarative/animation/data/animation.qml
new file mode 100644 (file)
index 0000000..c48f5cc
--- /dev/null
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+ParallelAnimation {
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+    NumberAnimation {}
+}
diff --git a/tests/benchmarks/declarative/animation/tst_animation.cpp b/tests/benchmarks/declarative/animation/tst_animation.cpp
new file mode 100644 (file)
index 0000000..6273d97
--- /dev/null
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qtest.h>
+#include <QDeclarativeEngine>
+#include <QDeclarativeComponent>
+#include <private/qdeclarativemetatype_p.h>
+#include <private/qdeclarativeanimation_p_p.h>
+#include <QDeclarativeContext>
+
+class tst_animation : public QObject
+{
+    Q_OBJECT
+public:
+    tst_animation();
+
+private slots:
+    void abstractAnimation();
+    void bulkValueAnimator();
+    void propertyUpdater();
+
+    void animationtree_qml();
+
+    void animationelements_data();
+    void animationelements();
+
+    void numberAnimation();
+    void numberAnimationStarted();
+    void numberAnimationMultipleTargets();
+    void numberAnimationEmpty();
+
+private:
+    QDeclarativeEngine engine;
+};
+
+tst_animation::tst_animation()
+{
+}
+
+inline QUrl TEST_FILE(const QString &filename)
+{
+    return QUrl::fromLocalFile(QLatin1String(SRCDIR) + QLatin1String("/data/") + filename);
+}
+
+void tst_animation::abstractAnimation()
+{
+    QBENCHMARK {
+        QAbstractAnimationJob *animation = new QAbstractAnimationJob;
+        delete animation;
+    }
+}
+
+void tst_animation::bulkValueAnimator()
+{
+    QBENCHMARK {
+        QDeclarativeBulkValueAnimator *animator = new QDeclarativeBulkValueAnimator;
+        delete animator;
+    }
+}
+
+void tst_animation::propertyUpdater()
+{
+    QBENCHMARK {
+        QDeclarativeAnimationPropertyUpdater *updater = new QDeclarativeAnimationPropertyUpdater;
+        delete updater;
+    }
+}
+
+void tst_animation::animationtree_qml()
+{
+    QDeclarativeComponent component(&engine, TEST_FILE("animation.qml"));
+    QObject *obj = component.create();
+    delete obj;
+
+    QBENCHMARK {
+        QObject *obj = component.create();
+        delete obj;
+    }
+}
+
+void tst_animation::animationelements_data()
+{
+    QTest::addColumn<QString>("type");
+
+    QSet<QString> types = QDeclarativeMetaType::qmlTypeNames().toSet();
+    foreach (const QString &type, types) {
+        if (type.contains(QLatin1String("Animation")))
+            QTest::newRow(type.toLatin1()) << type;
+    }
+
+    QTest::newRow("QtQuick/Behavior") << "QtQuick/Behavior";
+    QTest::newRow("QtQuick/Transition") << "QtQuick/Transition";
+}
+
+void tst_animation::animationelements()
+{
+    QFETCH(QString, type);
+    QDeclarativeType *t = QDeclarativeMetaType::qmlType(type, 2, 0);
+    if (!t || !t->isCreatable())
+        QSKIP("Non-creatable type");
+
+    QBENCHMARK {
+        QObject *obj = t->create();
+        delete obj;
+    }
+}
+
+void tst_animation::numberAnimation()
+{
+    QDeclarativeComponent component(&engine);
+    component.setData("import QtQuick 2.0\nItem { Rectangle { id: rect; NumberAnimation { target: rect; property: \"x\"; to: 100; duration: 500; easing.type: Easing.InOutQuad } } }", QUrl());
+
+    QObject *obj = component.create();
+    delete obj;
+
+    QBENCHMARK {
+        QObject *obj = component.create();
+        delete obj;
+    }
+}
+
+void tst_animation::numberAnimationStarted()
+{
+    QDeclarativeComponent component(&engine);
+    component.setData("import QtQuick 2.0\nItem { Rectangle { id: rect; NumberAnimation { target: rect; property: \"x\"; to: 100; duration: 500; easing.type: Easing.InOutQuad; running: true; paused: true } } }", QUrl());
+
+    QObject *obj = component.create();
+    delete obj;
+
+    QBENCHMARK {
+        QObject *obj = component.create();
+        delete obj;
+    }
+}
+
+void tst_animation::numberAnimationMultipleTargets()
+{
+    QDeclarativeComponent component(&engine);
+    component.setData("import QtQuick 2.0\nItem { Rectangle { id: rect; NumberAnimation { target: rect; properties: \"x,y,z,width,height,implicitWidth,implicitHeight\"; to: 100; duration: 500; easing.type: Easing.InOutQuad; running: true; paused: true } } }", QUrl());
+
+    QObject *obj = component.create();
+    delete obj;
+
+    QBENCHMARK {
+        QObject *obj = component.create();
+        delete obj;
+    }
+}
+
+void tst_animation::numberAnimationEmpty()
+{
+    QDeclarativeComponent component(&engine);
+    component.setData("import QtQuick 2.0\nNumberAnimation { }", QUrl());
+
+    QObject *obj = component.create();
+    delete obj;
+
+    QBENCHMARK {
+        QObject *obj = component.create();
+        delete obj;
+    }
+}
+
+QTEST_MAIN(tst_animation)
+
+#include "tst_animation.moc"