1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 /*!
41     \class QParallelAnimationGroup
42     \inmodule QtCore
43     \brief The QParallelAnimationGroup class provides a parallel group of animations.
44     \since 4.6
45     \ingroup animation
46 
47     QParallelAnimationGroup--a \l{QAnimationGroup}{container for
48     animations}--starts all its animations when it is
49     \l{QAbstractAnimation::start()}{started} itself, i.e., runs all
50     animations in parallel. The animation group finishes when the
51     longest lasting animation has finished.
52 
53     You can treat QParallelAnimationGroup as any other QAbstractAnimation,
54     e.g., pause, resume, or add it to other animation groups.
55 
56     \snippet code/src_corelib_animation_qparallelanimationgroup.cpp 0
57 
58     In this example, \c anim1 and \c anim2 are two
59     \l{QPropertyAnimation}s that have already been set up.
60 
61     \sa QAnimationGroup, QPropertyAnimation, {The Animation Framework}
62 */
63 
64 
65 #include "qparallelanimationgroup.h"
66 #include "qparallelanimationgroup_p.h"
67 //#define QANIMATION_DEBUG
68 
69 QT_BEGIN_NAMESPACE
70 
71 typedef QList<QAbstractAnimation *>::ConstIterator AnimationListConstIt;
72 typedef QHash<QAbstractAnimation*, int>::Iterator AnimationTimeHashIt;
73 typedef QHash<QAbstractAnimation*, int>::ConstIterator AnimationTimeHashConstIt;
74 
75 /*!
76     Constructs a QParallelAnimationGroup.
77     \a parent is passed to QObject's constructor.
78 */
QParallelAnimationGroup(QObject * parent)79 QParallelAnimationGroup::QParallelAnimationGroup(QObject *parent)
80     : QAnimationGroup(*new QParallelAnimationGroupPrivate, parent)
81 {
82 }
83 
84 /*!
85     \internal
86 */
QParallelAnimationGroup(QParallelAnimationGroupPrivate & dd,QObject * parent)87 QParallelAnimationGroup::QParallelAnimationGroup(QParallelAnimationGroupPrivate &dd,
88                                                  QObject *parent)
89     : QAnimationGroup(dd, parent)
90 {
91 }
92 
93 /*!
94     Destroys the animation group. It will also destroy all its animations.
95 */
~QParallelAnimationGroup()96 QParallelAnimationGroup::~QParallelAnimationGroup()
97 {
98 }
99 
100 /*!
101     \reimp
102 */
duration() const103 int QParallelAnimationGroup::duration() const
104 {
105     Q_D(const QParallelAnimationGroup);
106     int ret = 0;
107 
108     for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
109         const int currentDuration = (*it)->totalDuration();
110         if (currentDuration == -1)
111             return -1; // Undetermined length
112 
113         ret = qMax(ret, currentDuration);
114     }
115 
116     return ret;
117 }
118 
119 /*!
120     \reimp
121 */
updateCurrentTime(int currentTime)122 void QParallelAnimationGroup::updateCurrentTime(int currentTime)
123 {
124     Q_D(QParallelAnimationGroup);
125     if (d->animations.isEmpty())
126         return;
127 
128     if (d->currentLoop > d->lastLoop) {
129         // simulate completion of the loop
130         int dura = duration();
131         if (dura > 0) {
132             for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
133                 QAbstractAnimation *animation = (*it);
134                 if (animation->state() != QAbstractAnimation::Stopped)
135                     animation->setCurrentTime(dura);   // will stop
136             }
137         }
138     } else if (d->currentLoop < d->lastLoop) {
139         // simulate completion of the loop seeking backwards
140         for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
141             QAbstractAnimation *animation = *it;
142             //we need to make sure the animation is in the right state
143             //and then rewind it
144             d->applyGroupState(animation);
145             animation->setCurrentTime(0);
146             animation->stop();
147         }
148     }
149 
150 #ifdef QANIMATION_DEBUG
151     qDebug("QParallellAnimationGroup %5d: setCurrentTime(%d), loop:%d, last:%d, timeFwd:%d, lastcurrent:%d, %d",
152         __LINE__, d->currentTime, d->currentLoop, d->lastLoop, timeFwd, d->lastCurrentTime, state());
153 #endif
154     // finally move into the actual time of the current loop
155     for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
156         QAbstractAnimation *animation = *it;
157         const int dura = animation->totalDuration();
158         //if the loopcount is bigger we should always start all animations
159         if (d->currentLoop > d->lastLoop
160             //if we're at the end of the animation, we need to start it if it wasn't already started in this loop
161             //this happens in Backward direction where not all animations are started at the same time
162             || d->shouldAnimationStart(animation, d->lastCurrentTime > dura /*startIfAtEnd*/)) {
163             d->applyGroupState(animation);
164         }
165 
166         if (animation->state() == state()) {
167             animation->setCurrentTime(currentTime);
168             if (dura > 0 && currentTime > dura)
169                 animation->stop();
170         }
171     }
172     d->lastLoop = d->currentLoop;
173     d->lastCurrentTime = currentTime;
174 }
175 
176 /*!
177     \reimp
178 */
updateState(QAbstractAnimation::State newState,QAbstractAnimation::State oldState)179 void QParallelAnimationGroup::updateState(QAbstractAnimation::State newState,
180                                           QAbstractAnimation::State oldState)
181 {
182     Q_D(QParallelAnimationGroup);
183     QAnimationGroup::updateState(newState, oldState);
184 
185     switch (newState) {
186     case Stopped:
187         for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it)
188             (*it)->stop();
189         d->disconnectUncontrolledAnimations();
190         break;
191     case Paused:
192         for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
193             if ((*it)->state() == Running)
194                 (*it)->pause();
195         }
196         break;
197     case Running:
198         d->connectUncontrolledAnimations();
199         for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it) {
200             QAbstractAnimation *animation = *it;
201             if (oldState == Stopped)
202                 animation->stop();
203             animation->setDirection(d->direction);
204             if (d->shouldAnimationStart(animation, oldState == Stopped))
205                 animation->start();
206         }
207         break;
208     }
209 }
210 
_q_uncontrolledAnimationFinished()211 void QParallelAnimationGroupPrivate::_q_uncontrolledAnimationFinished()
212 {
213     Q_Q(QParallelAnimationGroup);
214 
215     QAbstractAnimation *animation = qobject_cast<QAbstractAnimation *>(q->sender());
216     Q_ASSERT(animation);
217 
218     int uncontrolledRunningCount = 0;
219     if (animation->duration() == -1 || animation->loopCount() < 0) {
220         for (AnimationTimeHashIt it = uncontrolledFinishTime.begin(), cend = uncontrolledFinishTime.end(); it != cend; ++it) {
221             if (it.key() == animation) {
222                 *it = animation->currentTime();
223             }
224             if (it.value() == -1)
225                 ++uncontrolledRunningCount;
226         }
227     }
228 
229     if (uncontrolledRunningCount > 0)
230         return;
231 
232     int maxDuration = 0;
233     for (AnimationListConstIt it = animations.constBegin(), cend = animations.constEnd(); it != cend; ++it)
234         maxDuration = qMax(maxDuration, (*it)->totalDuration());
235 
236     if (currentTime >= maxDuration)
237         q->stop();
238 }
239 
disconnectUncontrolledAnimations()240 void QParallelAnimationGroupPrivate::disconnectUncontrolledAnimations()
241 {
242     for (AnimationTimeHashConstIt it = uncontrolledFinishTime.constBegin(), cend = uncontrolledFinishTime.constEnd(); it != cend; ++it)
243         disconnectUncontrolledAnimation(it.key());
244 
245     uncontrolledFinishTime.clear();
246 }
247 
connectUncontrolledAnimations()248 void QParallelAnimationGroupPrivate::connectUncontrolledAnimations()
249 {
250     for (AnimationListConstIt it = animations.constBegin(), cend = animations.constEnd(); it != cend; ++it) {
251         QAbstractAnimation *animation = *it;
252         if (animation->duration() == -1 || animation->loopCount() < 0) {
253             uncontrolledFinishTime[animation] = -1;
254             connectUncontrolledAnimation(animation);
255         }
256     }
257 }
258 
shouldAnimationStart(QAbstractAnimation * animation,bool startIfAtEnd) const259 bool QParallelAnimationGroupPrivate::shouldAnimationStart(QAbstractAnimation *animation, bool startIfAtEnd) const
260 {
261     const int dura = animation->totalDuration();
262     if (dura == -1)
263         return !isUncontrolledAnimationFinished(animation);
264     if (startIfAtEnd)
265         return currentTime <= dura;
266     if (direction == QAbstractAnimation::Forward)
267         return currentTime < dura;
268     else //direction == QAbstractAnimation::Backward
269         return currentTime && currentTime <= dura;
270 }
271 
applyGroupState(QAbstractAnimation * animation)272 void QParallelAnimationGroupPrivate::applyGroupState(QAbstractAnimation *animation)
273 {
274     switch (state)
275     {
276     case QAbstractAnimation::Running:
277         animation->start();
278         break;
279     case QAbstractAnimation::Paused:
280         animation->pause();
281         break;
282     case QAbstractAnimation::Stopped:
283     default:
284         break;
285     }
286 }
287 
288 
isUncontrolledAnimationFinished(QAbstractAnimation * anim) const289 bool QParallelAnimationGroupPrivate::isUncontrolledAnimationFinished(QAbstractAnimation *anim) const
290 {
291     return uncontrolledFinishTime.value(anim, -1) >= 0;
292 }
293 
animationRemoved(int index,QAbstractAnimation * anim)294 void QParallelAnimationGroupPrivate::animationRemoved(int index, QAbstractAnimation *anim)
295 {
296     QAnimationGroupPrivate::animationRemoved(index, anim);
297     disconnectUncontrolledAnimation(anim);
298     uncontrolledFinishTime.remove(anim);
299 }
300 
301 /*!
302     \reimp
303 */
updateDirection(QAbstractAnimation::Direction direction)304 void QParallelAnimationGroup::updateDirection(QAbstractAnimation::Direction direction)
305 {
306     Q_D(QParallelAnimationGroup);
307     //we need to update the direction of the current animation
308     if (state() != Stopped) {
309         for (AnimationListConstIt it = d->animations.constBegin(), cend = d->animations.constEnd(); it != cend; ++it)
310             (*it)->setDirection(direction);
311     } else {
312         if (direction == Forward) {
313             d->lastLoop = 0;
314             d->lastCurrentTime = 0;
315         } else {
316             // Looping backwards with loopCount == -1 does not really work well...
317             d->lastLoop = (d->loopCount == -1 ? 0 : d->loopCount - 1);
318             d->lastCurrentTime = duration();
319         }
320     }
321 }
322 
323 /*!
324     \reimp
325 */
event(QEvent * event)326 bool QParallelAnimationGroup::event(QEvent *event)
327 {
328     return QAnimationGroup::event(event);
329 }
330 
331 QT_END_NAMESPACE
332 
333 #include "moc_qparallelanimationgroup.cpp"
334