1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/*!
5 \class QAnimationGroup
6 \inmodule QtCore
7 \brief The QAnimationGroup class is an abstract base class for groups of animations.
8 \since 4.6
9 \ingroup animation
10
11 An animation group is a container for animations (subclasses of
12 QAbstractAnimation). A group is usually responsible for managing
13 the \l{QAbstractAnimation::State}{state} of its animations, i.e.,
14 it decides when to start, stop, resume, and pause them. Currently,
15 Qt provides two such groups: QParallelAnimationGroup and
16 QSequentialAnimationGroup. Look up their class descriptions for
17 details.
18
19 Since QAnimationGroup inherits from QAbstractAnimation, you can
20 combine groups, and easily construct complex animation graphs.
21 You can query QAbstractAnimation for the group it belongs to
22 (using the \l{QAbstractAnimation::}{group()} function).
23
24 To start a top-level animation group, you simply use the
25 \l{QAbstractAnimation::}{start()} function from
26 QAbstractAnimation. By a top-level animation group, we think of a
27 group that itself is not contained within another group. Starting
28 sub groups directly is not supported, and may lead to unexpected
29 behavior.
30
31 \omit OK, we'll put in a snippet on this here \endomit
32
33 QAnimationGroup provides methods for adding and retrieving
34 animations. Besides that, you can remove animations by calling
35 \l removeAnimation(), and clear the animation group by calling
36 clear(). You may keep track of changes in the group's
37 animations by listening to QEvent::ChildAdded and
38 QEvent::ChildRemoved events.
39
40 \omit OK, let's find a snippet here as well. \endomit
41
42 QAnimationGroup takes ownership of the animations it manages, and
43 ensures that they are deleted when the animation group is deleted.
44
45 \sa QAbstractAnimation, QVariantAnimation, {The Animation Framework}
46*/
47
48#include "qanimationgroup.h"
49#include <QtCore/qdebug.h>
50#include <QtCore/qcoreevent.h>
51#include "qanimationgroup_p.h"
52
53#include <algorithm>
54
55QT_BEGIN_NAMESPACE
56
57
58/*!
59 Constructs a QAnimationGroup.
60 \a parent is passed to QObject's constructor.
61*/
62QAnimationGroup::QAnimationGroup(QObject *parent)
63 : QAbstractAnimation(*new QAnimationGroupPrivate, parent)
64{
65}
66
67/*!
68 \internal
69*/
70QAnimationGroup::QAnimationGroup(QAnimationGroupPrivate &dd, QObject *parent)
71 : QAbstractAnimation(dd, parent)
72{
73}
74
75/*!
76 Destroys the animation group. It will also destroy all its animations.
77*/
78QAnimationGroup::~QAnimationGroup()
79{
80 Q_D(QAnimationGroup);
81 // We need to clear the animations now while we are still a valid QAnimationGroup.
82 // If we wait until ~QObject() the QAbstractAnimation's pointer back to us would
83 // point to a QObject, not a valid QAnimationGroup.
84 d->clear(onDestruction: true);
85}
86
87/*!
88 Returns a pointer to the animation at \a index in this group. This
89 function is useful when you need access to a particular animation. \a
90 index is between 0 and animationCount() - 1.
91
92 \sa animationCount(), indexOfAnimation()
93*/
94QAbstractAnimation *QAnimationGroup::animationAt(int index) const
95{
96 Q_D(const QAnimationGroup);
97
98 if (index < 0 || index >= d->animations.size()) {
99 qWarning(msg: "QAnimationGroup::animationAt: index is out of bounds");
100 return nullptr;
101 }
102
103 return d->animations.at(i: index);
104}
105
106
107/*!
108 Returns the number of animations managed by this group.
109
110 \sa indexOfAnimation(), addAnimation(), animationAt()
111*/
112int QAnimationGroup::animationCount() const
113{
114 Q_D(const QAnimationGroup);
115 return d->animations.size();
116}
117
118/*!
119 Returns the index of \a animation. The returned index can be passed
120 to the other functions that take an index as an argument.
121
122 \sa insertAnimation(), animationAt(), takeAnimation()
123*/
124int QAnimationGroup::indexOfAnimation(QAbstractAnimation *animation) const
125{
126 Q_D(const QAnimationGroup);
127 return d->animations.indexOf(t: animation);
128}
129
130/*!
131 Adds \a animation to this group. This will call insertAnimation with
132 index equals to animationCount().
133
134 \note The group takes ownership of the animation.
135
136 \sa removeAnimation()
137*/
138void QAnimationGroup::addAnimation(QAbstractAnimation *animation)
139{
140 Q_D(QAnimationGroup);
141 insertAnimation(index: d->animations.size(), animation);
142}
143
144/*!
145 Inserts \a animation into this animation group at \a index.
146 If \a index is 0 the animation is inserted at the beginning.
147 If \a index is animationCount(), the animation is inserted at the end.
148
149 \note The group takes ownership of the animation.
150
151 \sa takeAnimation(), addAnimation(), indexOfAnimation(), removeAnimation()
152*/
153void QAnimationGroup::insertAnimation(int index, QAbstractAnimation *animation)
154{
155 Q_D(QAnimationGroup);
156
157 if (index < 0 || index > d->animations.size()) {
158 qWarning(msg: "QAnimationGroup::insertAnimation: index is out of bounds");
159 return;
160 }
161
162 if (QAnimationGroup *oldGroup = animation->group()) {
163 oldGroup->removeAnimation(animation);
164 // ensure we don't insert out of bounds if oldGroup == this
165 index = qMin(a: index, b: d->animations.size());
166 }
167
168 d->animations.insert(i: index, t: animation);
169 QAbstractAnimationPrivate::get(q: animation)->group = this;
170 // this will make sure that ChildAdded event is sent to 'this'
171 animation->setParent(this);
172 d->animationInsertedAt(index);
173}
174
175/*!
176 Removes \a animation from this group. The ownership of \a animation is
177 transferred to the caller.
178
179 \sa takeAnimation(), insertAnimation(), addAnimation()
180*/
181void QAnimationGroup::removeAnimation(QAbstractAnimation *animation)
182{
183 Q_D(QAnimationGroup);
184
185 if (!animation) {
186 qWarning(msg: "QAnimationGroup::remove: cannot remove null animation");
187 return;
188 }
189 qsizetype index = d->animations.indexOf(t: animation);
190 if (index == -1) {
191 qWarning(msg: "QAnimationGroup::remove: animation is not part of this group");
192 return;
193 }
194
195 takeAnimation(index);
196}
197
198/*!
199 Returns the animation at \a index and removes it from the animation group.
200
201 \note The ownership of the animation is transferred to the caller.
202
203 \sa removeAnimation(), addAnimation(), insertAnimation(), indexOfAnimation()
204*/
205QAbstractAnimation *QAnimationGroup::takeAnimation(int index)
206{
207 Q_D(QAnimationGroup);
208 if (index < 0 || index >= d->animations.size()) {
209 qWarning(msg: "QAnimationGroup::takeAnimation: no animation at index %d", index);
210 return nullptr;
211 }
212 QAbstractAnimation *animation = d->animations.at(i: index);
213 QAbstractAnimationPrivate::get(q: animation)->group = nullptr;
214 // ### removing from list before doing setParent to avoid infinite recursion
215 // in ChildRemoved event
216 d->animations.removeAt(i: index);
217 animation->setParent(nullptr);
218 d->animationRemoved(index, animation);
219 return animation;
220}
221
222/*!
223 Removes and deletes all animations in this animation group, and resets the current
224 time to 0.
225
226 \sa addAnimation(), removeAnimation()
227*/
228void QAnimationGroup::clear()
229{
230 Q_D(QAnimationGroup);
231 d->clear(onDestruction: false);
232}
233
234/*!
235 \reimp
236*/
237bool QAnimationGroup::event(QEvent *event)
238{
239 Q_D(QAnimationGroup);
240 if (event->type() == QEvent::ChildAdded) {
241 QChildEvent *childEvent = static_cast<QChildEvent *>(event);
242 if (QAbstractAnimation *a = qobject_cast<QAbstractAnimation *>(object: childEvent->child())) {
243 if (a->group() != this)
244 addAnimation(animation: a);
245 }
246 } else if (event->type() == QEvent::ChildRemoved) {
247 QChildEvent *childEvent = static_cast<QChildEvent *>(event);
248 // You can only rely on the child being a QObject because in the QEvent::ChildRemoved
249 // case it might be called from the destructor. Casting down to QAbstractAnimation then
250 // entails undefined behavior, so compare items as QObjects (which std::find does internally):
251 const QList<QAbstractAnimation *>::const_iterator it
252 = std::find(first: d->animations.cbegin(), last: d->animations.cend(), val: childEvent->child());
253 if (it != d->animations.cend())
254 takeAnimation(index: it - d->animations.cbegin());
255 }
256 return QAbstractAnimation::event(event);
257}
258
259void QAnimationGroupPrivate::clear(bool onDestruction)
260{
261 const QList<QAbstractAnimation *> animationsCopy = animations; // taking a copy
262 animations.clear();
263 // Clearing backwards so the indices doesn't change while we remove animations.
264 for (qsizetype i = animationsCopy.size() - 1; i >= 0; --i) {
265 QAbstractAnimation *animation = animationsCopy.at(i);
266 animation->setParent(nullptr);
267 QAbstractAnimationPrivate::get(q: animation)->group = nullptr;
268 // If we are in ~QAnimationGroup() it is not safe to called the virtual
269 // animationRemoved method, which can still be a method in a
270 // QAnimationGroupPrivate derived class that assumes q_ptr is still
271 // a valid derived class of QAnimationGroup.
272 if (!onDestruction)
273 animationRemoved(i, animation);
274 delete animation;
275 }
276}
277
278void QAnimationGroupPrivate::animationRemoved(qsizetype index, QAbstractAnimation *)
279{
280 Q_Q(QAnimationGroup);
281 Q_UNUSED(index);
282 if (animations.isEmpty()) {
283 currentTime = 0;
284 q->stop();
285 }
286}
287
288QT_END_NAMESPACE
289
290#include "moc_qanimationgroup.cpp"
291

source code of qtbase/src/corelib/animation/qanimationgroup.cpp