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
69QT_BEGIN_NAMESPACE
70
71typedef QList<QAbstractAnimation *>::ConstIterator AnimationListConstIt;
72typedef QHash<QAbstractAnimation*, int>::Iterator AnimationTimeHashIt;
73typedef QHash<QAbstractAnimation*, int>::ConstIterator AnimationTimeHashConstIt;
74
75/*!
76 Constructs a QParallelAnimationGroup.
77 \a parent is passed to QObject's constructor.
78*/
79QParallelAnimationGroup::QParallelAnimationGroup(QObject *parent)
80 : QAnimationGroup(*new QParallelAnimationGroupPrivate, parent)
81{
82}
83
84/*!
85 \internal
86*/
87QParallelAnimationGroup::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*/
96QParallelAnimationGroup::~QParallelAnimationGroup()
97{
98}
99
100/*!
101 \reimp
102*/
103int 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*/
122void 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*/
179void 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
211void 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
240void 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
248void 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
259bool 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
272void 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
289bool QParallelAnimationGroupPrivate::isUncontrolledAnimationFinished(QAbstractAnimation *anim) const
290{
291 return uncontrolledFinishTime.value(anim, -1) >= 0;
292}
293
294void 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*/
304void 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*/
326bool QParallelAnimationGroup::event(QEvent *event)
327{
328 return QAnimationGroup::event(event);
329}
330
331QT_END_NAMESPACE
332
333#include "moc_qparallelanimationgroup.cpp"
334