source: trunk/src/corelib/animation/qsequentialanimationgroup.cpp@ 769

Last change on this file since 769 was 769, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.3 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 19.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtCore module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42/*!
43 \class QSequentialAnimationGroup
44 \brief The QSequentialAnimationGroup class provides a sequential group of animations.
45 \since 4.6
46 \ingroup animation
47
48 QSequentialAnimationGroup is a QAnimationGroup that runs its
49 animations in sequence, i.e., it starts one animation after
50 another has finished playing. The animations are played in the
51 order they are added to the group (using
52 \l{QAnimationGroup::}{addAnimation()} or
53 \l{QAnimationGroup::}{insertAnimation()}). The animation group
54 finishes when its last animation has finished.
55
56 At each moment there is at most one animation that is active in
57 the group; it is returned by currentAnimation(). An empty group
58 has no current animation.
59
60 A sequential animation group can be treated as any other
61 animation, i.e., it can be started, stopped, and added to other
62 groups. You can also call addPause() or insertPause() to add a
63 pause to a sequential animation group.
64
65 \code
66 QSequentialAnimationGroup *group = new QSequentialAnimationGroup;
67
68 group->addAnimation(anim1);
69 group->addAnimation(anim2);
70
71 group->start();
72 \endcode
73
74 In this example, \c anim1 and \c anim2 are two already set up
75 \l{QPropertyAnimation}s.
76
77 \sa QAnimationGroup, QAbstractAnimation, {The Animation Framework}
78*/
79
80#include "qsequentialanimationgroup.h"
81#include "qsequentialanimationgroup_p.h"
82
83#include "qpauseanimation.h"
84
85#include <QtCore/qdebug.h>
86
87#ifndef QT_NO_ANIMATION
88
89QT_BEGIN_NAMESPACE
90
91bool QSequentialAnimationGroupPrivate::atEnd() const
92{
93 // we try to detect if we're at the end of the group
94 //this is true if the following conditions are true:
95 // 1. we're in the last loop
96 // 2. the direction is forward
97 // 3. the current animation is the last one
98 // 4. the current animation has reached its end
99 const int animTotalCurrentTime = QAbstractAnimationPrivate::get(currentAnimation)->totalCurrentTime;
100 return (currentLoop == loopCount - 1
101 && direction == QAbstractAnimation::Forward
102 && currentAnimation == animations.last()
103 && animTotalCurrentTime == animationActualTotalDuration(currentAnimationIndex));
104}
105
106int QSequentialAnimationGroupPrivate::animationActualTotalDuration(int index) const
107{
108 QAbstractAnimation *anim = animations.at(index);
109 int ret = anim->totalDuration();
110 if (ret == -1 && actualDuration.size() > index)
111 ret = actualDuration.at(index); //we can try the actual duration there
112 return ret;
113}
114
115QSequentialAnimationGroupPrivate::AnimationIndex QSequentialAnimationGroupPrivate::indexForCurrentTime() const
116{
117 Q_ASSERT(!animations.isEmpty());
118
119 AnimationIndex ret;
120 int duration = 0;
121
122 for (int i = 0; i < animations.size(); ++i) {
123 duration = animationActualTotalDuration(i);
124
125 // 'animation' is the current animation if one of these reasons is true:
126 // 1. it's duration is undefined
127 // 2. it ends after msecs
128 // 3. it is the last animation (this can happen in case there is at least 1 uncontrolled animation)
129 // 4. it ends exactly in msecs and the direction is backwards
130 if (duration == -1 || currentTime < (ret.timeOffset + duration)
131 || (currentTime == (ret.timeOffset + duration) && direction == QAbstractAnimation::Backward)) {
132 ret.index = i;
133 return ret;
134 }
135
136 // 'animation' has a non-null defined duration and is not the one at time 'msecs'.
137 ret.timeOffset += duration;
138 }
139
140 // this can only happen when one of those conditions is true:
141 // 1. the duration of the group is undefined and we passed its actual duration
142 // 2. there are only 0-duration animations in the group
143 ret.timeOffset -= duration;
144 ret.index = animations.size() - 1;
145 return ret;
146}
147
148void QSequentialAnimationGroupPrivate::restart()
149{
150 // restarting the group by making the first/last animation the current one
151 if (direction == QAbstractAnimation::Forward) {
152 lastLoop = 0;
153 if (currentAnimationIndex == 0)
154 activateCurrentAnimation();
155 else
156 setCurrentAnimation(0);
157 } else { // direction == QAbstractAnimation::Backward
158 lastLoop = loopCount - 1;
159 int index = animations.size() - 1;
160 if (currentAnimationIndex == index)
161 activateCurrentAnimation();
162 else
163 setCurrentAnimation(index);
164 }
165}
166
167/*!
168 \internal
169 This manages advancing the execution of a group running forwards (time has gone forward),
170 which is the same behaviour for rewinding the execution of a group running backwards
171 (time has gone backward).
172*/
173void QSequentialAnimationGroupPrivate::advanceForwards(const AnimationIndex &newAnimationIndex)
174{
175 if (lastLoop < currentLoop) {
176 // we need to fast forward to the end
177 for (int i = currentAnimationIndex; i < animations.size(); ++i) {
178 QAbstractAnimation *anim = animations.at(i);
179 setCurrentAnimation(i, true);
180 anim->setCurrentTime(animationActualTotalDuration(i));
181 }
182 // this will make sure the current animation is reset to the beginning
183 if (animations.size() == 1)
184 // we need to force activation because setCurrentAnimation will have no effect
185 activateCurrentAnimation();
186 else
187 setCurrentAnimation(0, true);
188 }
189
190 // and now we need to fast forward from the current position to
191 for (int i = currentAnimationIndex; i < newAnimationIndex.index; ++i) { //### WRONG,
192 QAbstractAnimation *anim = animations.at(i);
193 setCurrentAnimation(i, true);
194 anim->setCurrentTime(animationActualTotalDuration(i));
195 }
196 // setting the new current animation will happen later
197}
198
199/*!
200 \internal
201 This manages rewinding the execution of a group running forwards (time has gone forward),
202 which is the same behaviour for advancing the execution of a group running backwards
203 (time has gone backward).
204*/
205void QSequentialAnimationGroupPrivate::rewindForwards(const AnimationIndex &newAnimationIndex)
206{
207 if (lastLoop > currentLoop) {
208 // we need to fast rewind to the beginning
209 for (int i = currentAnimationIndex; i >= 0 ; --i) {
210 QAbstractAnimation *anim = animations.at(i);
211 setCurrentAnimation(i, true);
212 anim->setCurrentTime(0);
213 }
214 // this will make sure the current animation is reset to the end
215 if (animations.size() == 1)
216 // we need to force activation because setCurrentAnimation will have no effect
217 activateCurrentAnimation();
218 else
219 setCurrentAnimation(animations.count() - 1, true);
220 }
221
222 // and now we need to fast rewind from the current position to
223 for (int i = currentAnimationIndex; i > newAnimationIndex.index; --i) {
224 QAbstractAnimation *anim = animations.at(i);
225 setCurrentAnimation(i, true);
226 anim->setCurrentTime(0);
227 }
228 // setting the new current animation will happen later
229}
230
231/*!
232 \fn QSequentialAnimationGroup::currentAnimationChanged(QAbstractAnimation *current)
233
234 QSequentialAnimationGroup emits this signal when currentAnimation
235 has been changed. \a current is the current animation.
236
237 \sa currentAnimation()
238*/
239
240
241/*!
242 Constructs a QSequentialAnimationGroup.
243 \a parent is passed to QObject's constructor.
244*/
245QSequentialAnimationGroup::QSequentialAnimationGroup(QObject *parent)
246 : QAnimationGroup(*new QSequentialAnimationGroupPrivate, parent)
247{
248}
249
250/*!
251 \internal
252*/
253QSequentialAnimationGroup::QSequentialAnimationGroup(QSequentialAnimationGroupPrivate &dd,
254 QObject *parent)
255 : QAnimationGroup(dd, parent)
256{
257}
258
259/*!
260 Destroys the animation group. It will also destroy all its animations.
261*/
262QSequentialAnimationGroup::~QSequentialAnimationGroup()
263{
264}
265
266/*!
267 Adds a pause of \a msecs to this animation group.
268 The pause is considered as a special type of animation, thus
269 \l{QAnimationGroup::animationCount()}{animationCount} will be
270 increased by one.
271
272 \sa insertPause(), QAnimationGroup::addAnimation()
273*/
274QPauseAnimation *QSequentialAnimationGroup::addPause(int msecs)
275{
276 QPauseAnimation *pause = new QPauseAnimation(msecs);
277 addAnimation(pause);
278 return pause;
279}
280
281/*!
282 Inserts a pause of \a msecs milliseconds at \a index in this animation
283 group.
284
285 \sa addPause(), QAnimationGroup::insertAnimation()
286*/
287QPauseAnimation *QSequentialAnimationGroup::insertPause(int index, int msecs)
288{
289 Q_D(const QSequentialAnimationGroup);
290
291 if (index < 0 || index > d->animations.size()) {
292 qWarning("QSequentialAnimationGroup::insertPause: index is out of bounds");
293 return 0;
294 }
295
296 QPauseAnimation *pause = new QPauseAnimation(msecs);
297 insertAnimation(index, pause);
298 return pause;
299}
300
301
302/*!
303 \property QSequentialAnimationGroup::currentAnimation
304 Returns the animation in the current time.
305
306 \sa currentAnimationChanged()
307*/
308QAbstractAnimation *QSequentialAnimationGroup::currentAnimation() const
309{
310 Q_D(const QSequentialAnimationGroup);
311 return d->currentAnimation;
312}
313
314/*!
315 \reimp
316*/
317int QSequentialAnimationGroup::duration() const
318{
319 Q_D(const QSequentialAnimationGroup);
320 int ret = 0;
321
322 for (int i = 0; i < d->animations.size(); ++i) {
323 QAbstractAnimation *animation = d->animations.at(i);
324 const int currentDuration = animation->totalDuration();
325 if (currentDuration == -1)
326 return -1; // Undetermined length
327
328 ret += currentDuration;
329 }
330
331 return ret;
332}
333
334/*!
335 \reimp
336*/
337void QSequentialAnimationGroup::updateCurrentTime(int currentTime)
338{
339 Q_D(QSequentialAnimationGroup);
340 if (!d->currentAnimation)
341 return;
342
343 const QSequentialAnimationGroupPrivate::AnimationIndex newAnimationIndex = d->indexForCurrentTime();
344
345 // remove unneeded animations from actualDuration list
346 while (newAnimationIndex.index < d->actualDuration.size())
347 d->actualDuration.removeLast();
348
349 // newAnimationIndex.index is the new current animation
350 if (d->lastLoop < d->currentLoop
351 || (d->lastLoop == d->currentLoop && d->currentAnimationIndex < newAnimationIndex.index)) {
352 // advancing with forward direction is the same as rewinding with backwards direction
353 d->advanceForwards(newAnimationIndex);
354 } else if (d->lastLoop > d->currentLoop
355 || (d->lastLoop == d->currentLoop && d->currentAnimationIndex > newAnimationIndex.index)) {
356 // rewinding with forward direction is the same as advancing with backwards direction
357 d->rewindForwards(newAnimationIndex);
358 }
359
360 d->setCurrentAnimation(newAnimationIndex.index);
361
362 const int newCurrentTime = currentTime - newAnimationIndex.timeOffset;
363
364 if (d->currentAnimation) {
365 d->currentAnimation->setCurrentTime(newCurrentTime);
366 if (d->atEnd()) {
367 //we make sure that we don't exceed the duration here
368 d->currentTime += QAbstractAnimationPrivate::get(d->currentAnimation)->totalCurrentTime - newCurrentTime;
369 stop();
370 }
371 } else {
372 //the only case where currentAnimation could be null
373 //is when all animations have been removed
374 Q_ASSERT(d->animations.isEmpty());
375 d->currentTime = 0;
376 stop();
377 }
378
379 d->lastLoop = d->currentLoop;
380}
381
382/*!
383 \reimp
384*/
385void QSequentialAnimationGroup::updateState(QAbstractAnimation::State newState,
386 QAbstractAnimation::State oldState)
387{
388 Q_D(QSequentialAnimationGroup);
389 QAnimationGroup::updateState(newState, oldState);
390
391 if (!d->currentAnimation)
392 return;
393
394 switch (newState) {
395 case Stopped:
396 d->currentAnimation->stop();
397 break;
398 case Paused:
399 if (oldState == d->currentAnimation->state()
400 && oldState == QSequentialAnimationGroup::Running) {
401 d->currentAnimation->pause();
402 }
403 else
404 d->restart();
405 break;
406 case Running:
407 if (oldState == d->currentAnimation->state()
408 && oldState == QSequentialAnimationGroup::Paused)
409 d->currentAnimation->start();
410 else
411 d->restart();
412 break;
413 }
414}
415
416/*!
417 \reimp
418*/
419void QSequentialAnimationGroup::updateDirection(QAbstractAnimation::Direction direction)
420{
421 Q_D(QSequentialAnimationGroup);
422 // we need to update the direction of the current animation
423 if (state() != Stopped && d->currentAnimation)
424 d->currentAnimation->setDirection(direction);
425}
426
427/*!
428 \reimp
429*/
430bool QSequentialAnimationGroup::event(QEvent *event)
431{
432 return QAnimationGroup::event(event);
433}
434
435void QSequentialAnimationGroupPrivate::setCurrentAnimation(int index, bool intermediate)
436{
437 Q_Q(QSequentialAnimationGroup);
438
439 index = qMin(index, animations.count() - 1);
440
441 if (index == -1) {
442 Q_ASSERT(animations.isEmpty());
443 currentAnimationIndex = -1;
444 currentAnimation = 0;
445 return;
446 }
447
448 // need these two checks below because this func can be called after the current animation
449 // has been removed
450 if (index == currentAnimationIndex && animations.at(index) == currentAnimation)
451 return;
452
453 // stop the old current animation
454 if (currentAnimation)
455 currentAnimation->stop();
456
457 currentAnimation = animations.at(index);
458 currentAnimationIndex = index;
459
460 emit q->currentAnimationChanged(currentAnimation);
461
462 activateCurrentAnimation(intermediate);
463}
464
465void QSequentialAnimationGroupPrivate::activateCurrentAnimation(bool intermediate)
466{
467 Q_Q(QSequentialAnimationGroup);
468
469 if (!currentAnimation)
470 return;
471
472 if (state == QSequentialAnimationGroup::Stopped)
473 return;
474
475 currentAnimation->stop();
476
477 // we ensure the direction is consistent with the group's direction
478 currentAnimation->setDirection(direction);
479
480 // connects to the finish signal of uncontrolled animations
481 if (currentAnimation->totalDuration() == -1)
482 connectUncontrolledAnimation(currentAnimation);
483
484 currentAnimation->start();
485 if (!intermediate && state == QSequentialAnimationGroup::Paused)
486 currentAnimation->pause();
487}
488
489void QSequentialAnimationGroupPrivate::_q_uncontrolledAnimationFinished()
490{
491 Q_Q(QSequentialAnimationGroup);
492 Q_ASSERT(qobject_cast<QAbstractAnimation *>(q->sender()) == currentAnimation);
493
494 // we trust the duration returned by the animation
495 while (actualDuration.size() < (currentAnimationIndex + 1))
496 actualDuration.append(-1);
497 actualDuration[currentAnimationIndex] = currentAnimation->currentTime();
498
499 disconnectUncontrolledAnimation(currentAnimation);
500
501 if ((direction == QAbstractAnimation::Forward && currentAnimation == animations.last())
502 || (direction == QAbstractAnimation::Backward && currentAnimationIndex == 0)) {
503 // we don't handle looping of a group with undefined duration
504 q->stop();
505 } else if (direction == QAbstractAnimation::Forward) {
506 // set the current animation to be the next one
507 setCurrentAnimation(currentAnimationIndex + 1);
508 } else {
509 // set the current animation to be the previous one
510 setCurrentAnimation(currentAnimationIndex - 1);
511 }
512}
513
514/*!
515 \internal
516 This method is called whenever an animation is added to
517 the group at index \a index.
518 Note: We only support insertion after the current animation
519*/
520void QSequentialAnimationGroupPrivate::animationInsertedAt(int index)
521{
522 if (currentAnimation == 0)
523 setCurrentAnimation(0); // initialize the current animation
524
525 if (currentAnimationIndex == index
526 && currentAnimation->currentTime() == 0 && currentAnimation->currentLoop() == 0) {
527 //in this case we simply insert an animation before the current one has actually started
528 setCurrentAnimation(index);
529 }
530
531 //we update currentAnimationIndex in case it has changed (the animation pointer is still valid)
532 currentAnimationIndex = animations.indexOf(currentAnimation);
533
534 if (index < currentAnimationIndex || currentLoop != 0) {
535 qWarning("QSequentialGroup::insertAnimation only supports to add animations after the current one.");
536 return; //we're not affected because it is added after the current one
537 }
538}
539
540/*!
541 \internal
542 This method is called whenever an animation is removed from
543 the group at index \a index. The animation is no more listed when this
544 method is called.
545*/
546void QSequentialAnimationGroupPrivate::animationRemoved(int index, QAbstractAnimation *anim)
547{
548 Q_Q(QSequentialAnimationGroup);
549 QAnimationGroupPrivate::animationRemoved(index, anim);
550
551 Q_ASSERT(currentAnimation); // currentAnimation should always be set
552
553 if (actualDuration.size() > index)
554 actualDuration.removeAt(index);
555
556 const int currentIndex = animations.indexOf(currentAnimation);
557 if (currentIndex == -1) {
558 //we're removing the current animation
559
560 disconnectUncontrolledAnimation(currentAnimation);
561
562 if (index < animations.count())
563 setCurrentAnimation(index); //let's try to take the next one
564 else if (index > 0)
565 setCurrentAnimation(index - 1);
566 else// case all animations were removed
567 setCurrentAnimation(-1);
568 } else if (currentAnimationIndex > index) {
569 currentAnimationIndex--;
570 }
571
572 // duration of the previous animations up to the current animation
573 currentTime = 0;
574 for (int i = 0; i < currentAnimationIndex; ++i) {
575 const int current = animationActualTotalDuration(i);
576 currentTime += current;
577 }
578
579 if (currentIndex != -1) {
580 //the current animation is not the one being removed
581 //so we add its current time to the current time of this group
582 currentTime += QAbstractAnimationPrivate::get(currentAnimation)->totalCurrentTime;
583 }
584
585 //let's also update the total current time
586 totalCurrentTime = currentTime + loopCount * q->duration();
587}
588
589QT_END_NAMESPACE
590
591#include "moc_qsequentialanimationgroup.cpp"
592
593#endif //QT_NO_ANIMATION
Note: See TracBrowser for help on using the repository browser.