Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ /*!
++ \class QSequentialAnimationGroup
++ \inmodule QtCore
++ \brief The QSequentialAnimationGroup class provides a sequential group of animations.
++ \since 4.6
++ \ingroup animation
++
++ QSequentialAnimationGroup is a QAnimationGroup that runs its
++ animations in sequence, i.e., it starts one animation after
++ another has finished playing. The animations are played in the
++ order they are added to the group (using
++ \l{QAnimationGroup::}{addAnimation()} or
++ \l{QAnimationGroup::}{insertAnimation()}). The animation group
++ finishes when its last animation has finished.
++
++ At each moment there is at most one animation that is active in
++ the group; it is returned by currentAnimation(). An empty group
++ has no current animation.
++
++ A sequential animation group can be treated as any other
++ animation, i.e., it can be started, stopped, and added to other
++ groups. You can also call addPause() or insertPause() to add a
++ pause to a sequential animation group.
++
++ \snippet code/src_corelib_animation_qsequentialanimationgroup.cpp 0
++
++ In this example, \c anim1 and \c anim2 are two already set up
++ \l{QPropertyAnimation}s.
++
++ \sa QAnimationGroup, QAbstractAnimation, {The Animation Framework}
++ */
++
++ #include "qsequentialanimationgroup.h"
++ #include "qsequentialanimationgroup_p.h"
++
++ #include "qpauseanimation.h"
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qnumeric.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ /*!
++ \internal
++ Returns INT_MAX in case of overflow
++ */
++ int addSaturating(int a, int b)
++ {
++ int r;
++ if (qAddOverflow(a, b, &r))
++ return std::numeric_limits<int>::max();
++ return r;
++ }
++
++ } // namespace
++
++ typedef QList<QAbstractAnimation *>::ConstIterator AnimationListConstIt;
++
++ bool QSequentialAnimationGroupPrivate::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 = QAbstractAnimationPrivate::get(currentAnimation)->totalCurrentTime;
++ return (currentLoop == loopCount - 1
++ && direction == QAbstractAnimation::Forward
++ && currentAnimation == animations.last()
++ && animTotalCurrentTime == animationActualTotalDuration(currentAnimationIndex));
++ }
++
++ int QSequentialAnimationGroupPrivate::animationActualTotalDuration(int index) const
++ {
++ QAbstractAnimation *anim = animations.at(index);
++ int ret = anim->totalDuration();
++ if (ret == -1 && actualDuration.size() > index)
++ ret = actualDuration.at(index); //we can try the actual duration there
++ return ret;
++ }
++
++ QSequentialAnimationGroupPrivate::AnimationIndex QSequentialAnimationGroupPrivate::indexForCurrentTime() const
++ {
++ Q_ASSERT(!animations.isEmpty());
++
++ AnimationIndex ret;
++ int duration = 0;
++
++ for (int i = 0; i < animations.size(); ++i) {
++ duration = animationActualTotalDuration(i);
++
++ // '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
++ const int offsetPlusDuration = addSaturating(ret.timeOffset, duration);
++ if (duration == -1 || currentTime < offsetPlusDuration
++ || (currentTime == offsetPlusDuration && direction == QAbstractAnimation::Backward)) {
++ ret.index = i;
++ return ret;
++ }
++
++ // 'animation' has a non-null defined duration and is not the one at time 'msecs'.
++ ret.timeOffset = offsetPlusDuration;
++ }
++
++ // 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.index = animations.size() - 1;
++ return ret;
++ }
++
++ void QSequentialAnimationGroupPrivate::restart()
++ {
++ // restarting the group by making the first/last animation the current one
++ if (direction == QAbstractAnimation::Forward) {
++ lastLoop = 0;
++ if (currentAnimationIndex == 0)
++ activateCurrentAnimation();
++ else
++ setCurrentAnimation(0);
++ } else { // direction == QAbstractAnimation::Backward
++ lastLoop = loopCount - 1;
++ int index = animations.size() - 1;
++ if (currentAnimationIndex == index)
++ activateCurrentAnimation();
++ else
++ setCurrentAnimation(index);
++ }
++ }
++
++ /*!
++ \internal
++ This manages advancing the execution of a group running forwards (time has gone forward),
++ which is the same behaviour for rewinding the execution of a group running backwards
++ (time has gone backward).
++ */
++ void QSequentialAnimationGroupPrivate::advanceForwards(const AnimationIndex &newAnimationIndex)
++ {
++ if (lastLoop < currentLoop) {
++ // we need to fast forward to the end
++ for (int i = currentAnimationIndex; i < animations.size(); ++i) {
++ QAbstractAnimation *anim = animations.at(i);
++ setCurrentAnimation(i, true);
++ anim->setCurrentTime(animationActualTotalDuration(i));
++ }
++ // this will make sure the current animation is reset to the beginning
++ if (animations.size() == 1)
++ // we need to force activation because setCurrentAnimation will have no effect
++ activateCurrentAnimation();
++ else
++ setCurrentAnimation(0, true);
++ }
++
++ // and now we need to fast forward from the current position to
++ for (int i = currentAnimationIndex; i < newAnimationIndex.index; ++i) { //### WRONG,
++ QAbstractAnimation *anim = animations.at(i);
++ setCurrentAnimation(i, true);
++ anim->setCurrentTime(animationActualTotalDuration(i));
++ }
++ // setting the new current animation will happen later
++ }
++
++ /*!
++ \internal
++ This manages rewinding the execution of a group running forwards (time has gone forward),
++ which is the same behaviour for advancing the execution of a group running backwards
++ (time has gone backward).
++ */
++ void QSequentialAnimationGroupPrivate::rewindForwards(const AnimationIndex &newAnimationIndex)
++ {
++ if (lastLoop > currentLoop) {
++ // we need to fast rewind to the beginning
++ for (int i = currentAnimationIndex; i >= 0 ; --i) {
++ QAbstractAnimation *anim = animations.at(i);
++ setCurrentAnimation(i, true);
++ anim->setCurrentTime(0);
++ }
++ // this will make sure the current animation is reset to the end
++ if (animations.size() == 1)
++ // we need to force activation because setCurrentAnimation will have no effect
++ activateCurrentAnimation();
++ else
++ setCurrentAnimation(animations.size() - 1, true);
++ }
++
++ // and now we need to fast rewind from the current position to
++ for (int i = currentAnimationIndex; i > newAnimationIndex.index; --i) {
++ QAbstractAnimation *anim = animations.at(i);
++ setCurrentAnimation(i, true);
++ anim->setCurrentTime(0);
++ }
++ // setting the new current animation will happen later
++ }
++
++ /*!
++ \fn QSequentialAnimationGroup::currentAnimationChanged(QAbstractAnimation *current)
++
++ QSequentialAnimationGroup emits this signal when currentAnimation
++ has been changed. \a current is the current animation.
++
++ \sa currentAnimation()
++ */
++
++
++ /*!
++ Constructs a QSequentialAnimationGroup.
++ \a parent is passed to QObject's constructor.
++ */
++ QSequentialAnimationGroup::QSequentialAnimationGroup(QObject *parent)
++ : QAnimationGroup(*new QSequentialAnimationGroupPrivate, parent)
++ {
++ }
++
++ /*!
++ \internal
++ */
++ QSequentialAnimationGroup::QSequentialAnimationGroup(QSequentialAnimationGroupPrivate &dd,
++ QObject *parent)
++ : QAnimationGroup(dd, parent)
++ {
++ }
++
++ /*!
++ Destroys the animation group. It will also destroy all its animations.
++ */
++ QSequentialAnimationGroup::~QSequentialAnimationGroup()
++ {
++ }
++
++ /*!
++ Adds a pause of \a msecs to this animation group.
++ The pause is considered as a special type of animation, thus
++ \l{QAnimationGroup::animationCount()}{animationCount} will be
++ increased by one.
++
++ \sa insertPause(), QAnimationGroup::addAnimation()
++ */
++ QPauseAnimation *QSequentialAnimationGroup::addPause(int msecs)
++ {
++ QPauseAnimation *pause = new QPauseAnimation(msecs);
++ addAnimation(pause);
++ return pause;
++ }
++
++ /*!
++ Inserts a pause of \a msecs milliseconds at \a index in this animation
++ group.
++
++ \sa addPause(), QAnimationGroup::insertAnimation()
++ */
++ QPauseAnimation *QSequentialAnimationGroup::insertPause(int index, int msecs)
++ {
++ Q_D(const QSequentialAnimationGroup);
++
++ if (index < 0 || index > d->animations.size()) {
++ qWarning("QSequentialAnimationGroup::insertPause: index is out of bounds");
++ return nullptr;
++ }
++
++ QPauseAnimation *pause = new QPauseAnimation(msecs);
++ insertAnimation(index, pause);
++ return pause;
++ }
++
++
++ /*!
++ \property QSequentialAnimationGroup::currentAnimation
++ \brief the animation in the current time.
++ */
++ QAbstractAnimation *QSequentialAnimationGroup::currentAnimation() const
++ {
++ Q_D(const QSequentialAnimationGroup);
++ return d->currentAnimation;
++ }
++
++ QBindable<QAbstractAnimation *> QSequentialAnimationGroup::bindableCurrentAnimation() const
++ {
++ return &d_func()->currentAnimation;
++ }
++
++ /*!
++ \reimp
++ */
++ int QSequentialAnimationGroup::duration() const
++ {
++ Q_D(const QSequentialAnimationGroup);
++ int ret = 0;
++
++ for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
++ const int currentDuration = (*it)->totalDuration();
++ if (currentDuration == -1)
++ return -1; // Undetermined length
++
++ if (qAddOverflow(ret, currentDuration, &ret))
++ return std::numeric_limits<int>::max();
++ }
++
++ return ret;
++ }
++
++ /*!
++ \reimp
++ */
++ void QSequentialAnimationGroup::updateCurrentTime(int currentTime)
++ {
++ Q_D(QSequentialAnimationGroup);
++ if (!d->currentAnimation)
++ return;
++
++ const QSequentialAnimationGroupPrivate::AnimationIndex newAnimationIndex = d->indexForCurrentTime();
++
++ // remove unneeded animations from actualDuration list
++ while (newAnimationIndex.index < d->actualDuration.size())
++ d->actualDuration.removeLast();
++
++ // newAnimationIndex.index is the new current animation
++ if (d->lastLoop < d->currentLoop
++ || (d->lastLoop == d->currentLoop && d->currentAnimationIndex < newAnimationIndex.index)) {
++ // advancing with forward direction is the same as rewinding with backwards direction
++ d->advanceForwards(newAnimationIndex);
++ } else if (d->lastLoop > d->currentLoop
++ || (d->lastLoop == d->currentLoop && d->currentAnimationIndex > newAnimationIndex.index)) {
++ // rewinding with forward direction is the same as advancing with backwards direction
++ d->rewindForwards(newAnimationIndex);
++ }
++
++ d->setCurrentAnimation(newAnimationIndex.index);
++
++ const int newCurrentTime = currentTime - newAnimationIndex.timeOffset;
++
++ if (d->currentAnimation) {
++ d->currentAnimation->setCurrentTime(newCurrentTime);
++ if (d->atEnd()) {
++ //we make sure that we don't exceed the duration here
++ const int diff =
++ QAbstractAnimationPrivate::get(d->currentAnimation)->totalCurrentTime - newCurrentTime;
++ d->currentTime = addSaturating(d->currentTime, diff);
++ stop();
++ }
++ } else {
++ //the only case where currentAnimation could be null
++ //is when all animations have been removed
++ Q_ASSERT(d->animations.isEmpty());
++ d->currentTime = 0;
++ stop();
++ }
++
++ d->lastLoop = d->currentLoop;
++ }
++
++ /*!
++ \reimp
++ */
++ void QSequentialAnimationGroup::updateState(QAbstractAnimation::State newState,
++ QAbstractAnimation::State oldState)
++ {
++ Q_D(QSequentialAnimationGroup);
++ QAnimationGroup::updateState(newState, oldState);
++
++ if (!d->currentAnimation)
++ return;
++
++ switch (newState) {
++ case Stopped:
++ d->currentAnimation->stop();
++ break;
++ case Paused:
++ if (oldState == d->currentAnimation->state()
++ && oldState == QSequentialAnimationGroup::Running) {
++ d->currentAnimation->pause();
++ }
++ else
++ d->restart();
++ break;
++ case Running:
++ if (oldState == d->currentAnimation->state()
++ && oldState == QSequentialAnimationGroup::Paused)
++ d->currentAnimation->start();
++ else
++ d->restart();
++ break;
++ }
++ }
++
++ /*!
++ \reimp
++ */
++ void QSequentialAnimationGroup::updateDirection(QAbstractAnimation::Direction direction)
++ {
++ Q_D(QSequentialAnimationGroup);
++ // we need to update the direction of the current animation
++ if (state() != Stopped && d->currentAnimation)
++ d->currentAnimation->setDirection(direction);
++ }
++
++ /*!
++ \reimp
++ */
++ bool QSequentialAnimationGroup::event(QEvent *event)
++ {
++ return QAnimationGroup::event(event);
++ }
++
++ void QSequentialAnimationGroupPrivate::setCurrentAnimation(int index, bool intermediate)
++ {
++ Q_Q(QSequentialAnimationGroup);
++ // currentAnimation.removeBindingUnlessInWrapper()
++ // is not necessary here, since it is read only
++
++ index = qMin(index, animations.size() - 1);
++
++ if (index == -1) {
++ Q_ASSERT(animations.isEmpty());
++ currentAnimationIndex = -1;
++ currentAnimation = nullptr;
++ return;
++ }
++
++ // need these two checks below because this func can be called after the current animation
++ // has been removed
++ if (index == currentAnimationIndex && animations.at(index) == currentAnimation)
++ return;
++
++ // stop the old current animation
++ if (currentAnimation)
++ currentAnimation->stop();
++
++ currentAnimationIndex = index;
++ currentAnimation = animations.at(index);
++
++ emit q->currentAnimationChanged(currentAnimation);
++
++ activateCurrentAnimation(intermediate);
++ }
++
++ void QSequentialAnimationGroupPrivate::activateCurrentAnimation(bool intermediate)
++ {
++ if (!currentAnimation || state == QSequentialAnimationGroup::Stopped)
++ return;
++
++ currentAnimation->stop();
++
++ // we ensure the direction is consistent with the group's direction
++ currentAnimation->setDirection(direction);
++
++ // connects to the finish signal of uncontrolled animations
++ if (currentAnimation->totalDuration() == -1)
++ connectUncontrolledAnimation(currentAnimation);
++
++ currentAnimation->start();
++ if (!intermediate && state == QSequentialAnimationGroup::Paused)
++ currentAnimation->pause();
++ }
++
++ void QSequentialAnimationGroupPrivate::_q_uncontrolledAnimationFinished()
++ {
++ Q_Q(QSequentialAnimationGroup);
++ Q_ASSERT(qobject_cast<QAbstractAnimation *>(q->sender()) == currentAnimation);
++
++ // we trust the duration returned by the animation
++ while (actualDuration.size() < (currentAnimationIndex + 1))
++ actualDuration.append(-1);
++ actualDuration[currentAnimationIndex] = currentAnimation->currentTime();
++
++ disconnectUncontrolledAnimation(currentAnimation);
++
++ if ((direction == QAbstractAnimation::Forward && currentAnimation == animations.last())
++ || (direction == QAbstractAnimation::Backward && currentAnimationIndex == 0)) {
++ // we don't handle looping of a group with undefined duration
++ q->stop();
++ } else if (direction == QAbstractAnimation::Forward) {
++ // set the current animation to be the next one
++ setCurrentAnimation(currentAnimationIndex + 1);
++ } else {
++ // set the current animation to be the previous one
++ setCurrentAnimation(currentAnimationIndex - 1);
++ }
++ }
++
++ /*!
++ \internal
++ This method is called whenever an animation is added to
++ the group at index \a index.
++ Note: We only support insertion after the current animation
++ */
++ void QSequentialAnimationGroupPrivate::animationInsertedAt(qsizetype index)
++ {
++ if (currentAnimation == nullptr) {
++ setCurrentAnimation(0); // initialize the current animation
++ Q_ASSERT(currentAnimation);
++ }
++
++ if (currentAnimationIndex == index
++ && currentAnimation->currentTime() == 0 && currentAnimation->currentLoop() == 0) {
++ //in this case we simply insert an animation before the current one has actually started
++ setCurrentAnimation(index);
++ }
++
++ //we update currentAnimationIndex in case it has changed (the animation pointer is still valid)
++ currentAnimationIndex = animations.indexOf(currentAnimation);
++
++ if (index < currentAnimationIndex || 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
++ }
++ }
++
++ /*!
++ \internal
++ This method is called whenever an animation is removed from
++ the group at index \a index. The animation is no more listed when this
++ method is called.
++ */
++ void QSequentialAnimationGroupPrivate::animationRemoved(qsizetype index, QAbstractAnimation *anim)
++ {
++ Q_Q(QSequentialAnimationGroup);
++ QAnimationGroupPrivate::animationRemoved(index, anim);
++
++ if (!currentAnimation)
++ return;
++
++ if (actualDuration.size() > index)
++ actualDuration.removeAt(index);
++
++ const qsizetype currentIndex = animations.indexOf(currentAnimation);
++ if (currentIndex == -1) {
++ //we're removing the current animation
++
++ disconnectUncontrolledAnimation(currentAnimation);
++
++ if (index < animations.size())
++ setCurrentAnimation(index); //let's try to take the next one
++ else if (index > 0)
++ setCurrentAnimation(index - 1);
++ else// case all animations were removed
++ setCurrentAnimation(-1);
++ } else if (currentAnimationIndex > index) {
++ currentAnimationIndex--;
++ }
++
++ // duration of the previous animations up to the current animation
++ currentTime = 0;
++ for (qsizetype i = 0; i < currentAnimationIndex; ++i) {
++ const int current = animationActualTotalDuration(i);
++ currentTime = addSaturating(currentTime, current);
++ }
++
++ if (currentIndex != -1) {
++ //the current animation is not the one being removed
++ //so we add its current time to the current time of this group
++ const int currAnimTotalTime =
++ QAbstractAnimationPrivate::get(currentAnimation)->totalCurrentTime;
++ currentTime = addSaturating(currentTime, currAnimTotalTime);
++ }
++
++ //let's also update the total current time
++ totalCurrentTime = addSaturating(currentTime, q->totalDuration());
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qsequentialanimationgroup.cpp"