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