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 QPropertyAnimation
6 \inmodule QtCore
7 \brief The QPropertyAnimation class animates Qt properties.
8 \since 4.6
9
10 \ingroup animation
11
12 QPropertyAnimation interpolates over \l{Qt's Property System}{Qt
13 properties}. As property values are stored in \l{QVariant}s, the
14 class inherits QVariantAnimation, and supports animation of the
15 same \l{QMetaType::Type}{meta types} as its super class.
16
17 A class declaring properties must be a QObject. To make it
18 possible to animate a property, it must provide a setter (so that
19 QPropertyAnimation can set the property's value). Note that this
20 makes it possible to animate many of Qt's widgets. Let's look at
21 an example:
22
23 \snippet code/src_corelib_animation_qpropertyanimation.cpp 0
24
25 \note You can also control an animation's lifespan by choosing a
26 \l{QAbstractAnimation::DeletionPolicy}{delete policy} while starting the
27 animation.
28
29 The property name and the QObject instance of which property
30 should be animated are passed to the constructor. You can then
31 specify the start and end value of the property. The procedure is
32 equal for properties in classes you have implemented
33 yourself--just check with QVariantAnimation that your QVariant
34 type is supported.
35
36 The QVariantAnimation class description explains how to set up the
37 animation in detail. Note, however, that if a start value is not
38 set, the property will start at the value it had when the
39 QPropertyAnimation instance was created.
40
41 QPropertyAnimation works like a charm on its own. For complex
42 animations that, for instance, contain several objects,
43 QAnimationGroup is provided. An animation group is an animation
44 that can contain other animations, and that can manage when its
45 animations are played. Look at QParallelAnimationGroup for an
46 example.
47
48 \sa QVariantAnimation, QAnimationGroup, {The Animation Framework}
49*/
50
51#include "qpropertyanimation.h"
52#include "qanimationgroup.h"
53#include "qpropertyanimation_p.h"
54
55#include <QtCore/QMutex>
56#include <QtCore/QHash>
57#include <QtCore/private/qlocking_p.h>
58
59QT_BEGIN_NAMESPACE
60
61void QPropertyAnimationPrivate::updateMetaProperty()
62{
63 const QObject *target = targetObject.valueBypassingBindings();
64 if (!target || propertyName.value().isEmpty()) {
65 propertyType = QMetaType::UnknownType;
66 propertyIndex = -1;
67 return;
68 }
69
70 //propertyType will be set to a valid type only if there is a Q_PROPERTY
71 //otherwise it will be set to QVariant::Invalid at the end of this function
72 propertyType = target->property(name: propertyName.value()).userType();
73 propertyIndex = target->metaObject()->indexOfProperty(name: propertyName.value());
74
75 if (propertyType != QMetaType::UnknownType)
76 convertValues(t: propertyType);
77 if (propertyIndex == -1) {
78 //there is no Q_PROPERTY on the object
79 propertyType = QMetaType::UnknownType;
80 if (!target->dynamicPropertyNames().contains(t: propertyName))
81 qWarning(msg: "QPropertyAnimation: you're trying to animate a non-existing property %s of "
82 "your QObject",
83 propertyName.value().constData());
84 } else if (!target->metaObject()->property(index: propertyIndex).isWritable()) {
85 qWarning(msg: "QPropertyAnimation: you're trying to animate the non-writable property %s of "
86 "your QObject",
87 propertyName.value().constData());
88 }
89}
90
91void QPropertyAnimationPrivate::updateProperty(const QVariant &newValue)
92{
93 if (state == QAbstractAnimation::Stopped)
94 return;
95
96 if (!targetObject)
97 return;
98
99 if (newValue.userType() == propertyType) {
100 //no conversion is needed, we directly call the QMetaObject::metacall
101 //check QMetaProperty::write for an explanation of these
102 int status = -1;
103 int flags = 0;
104 void *argv[] = { const_cast<void *>(newValue.constData()), const_cast<QVariant *>(&newValue), &status, &flags };
105 QMetaObject::metacall(targetObject, QMetaObject::WriteProperty, propertyIndex, argv);
106 } else {
107 targetObject->setProperty(name: propertyName.value().constData(), value: newValue);
108 }
109}
110
111/*!
112 Construct a QPropertyAnimation object. \a parent is passed to QObject's
113 constructor.
114*/
115QPropertyAnimation::QPropertyAnimation(QObject *parent)
116 : QVariantAnimation(*new QPropertyAnimationPrivate, parent)
117{
118}
119
120/*!
121 Construct a QPropertyAnimation object. \a parent is passed to QObject's
122 constructor. The animation changes the property \a propertyName on \a
123 target. The default duration is 250ms.
124
125 \sa targetObject, propertyName
126*/
127QPropertyAnimation::QPropertyAnimation(QObject *target, const QByteArray &propertyName, QObject *parent)
128 : QVariantAnimation(*new QPropertyAnimationPrivate, parent)
129{
130 setTargetObject(target);
131 setPropertyName(propertyName);
132}
133
134/*!
135 Destroys the QPropertyAnimation instance.
136 */
137QPropertyAnimation::~QPropertyAnimation()
138{
139 stop();
140}
141
142/*!
143 \property QPropertyAnimation::targetObject
144 \brief the target QObject for this animation.
145
146 This property defines the target QObject for this animation.
147 */
148QObject *QPropertyAnimation::targetObject() const
149{
150 return d_func()->targetObject;
151}
152
153QBindable<QObject *> QPropertyAnimation::bindableTargetObject()
154{
155 return &d_func()->targetObject;
156}
157
158void QPropertyAnimation::setTargetObject(QObject *target)
159{
160 Q_D(QPropertyAnimation);
161 if (d->state != QAbstractAnimation::Stopped) {
162 qWarning(msg: "QPropertyAnimation::setTargetObject: you can't change the target of a running animation");
163 return;
164 }
165
166 d->targetObject.removeBindingUnlessInWrapper();
167 const QObject *oldTarget = d->targetObject.valueBypassingBindings();
168 if (oldTarget == target)
169 return;
170
171 if (oldTarget != nullptr)
172 QObject::disconnect(sender: oldTarget, signal: &QObject::destroyed, receiver: this, zero: nullptr);
173 d->targetObject.setValueBypassingBindings(target);
174
175 if (target != nullptr) {
176 QObject::connect(sender: target, signal: &QObject::destroyed, context: this,
177 slot: [d] { d->targetObjectDestroyed(); });
178 }
179 d->updateMetaProperty();
180 d->targetObject.notify();
181}
182
183/*!
184 \property QPropertyAnimation::propertyName
185 \brief the target property name for this animation
186
187 This property defines the target property name for this animation. The
188 property name is required for the animation to operate.
189 */
190QByteArray QPropertyAnimation::propertyName() const
191{
192 Q_D(const QPropertyAnimation);
193 return d->propertyName;
194}
195
196void QPropertyAnimation::setPropertyName(const QByteArray &propertyName)
197{
198 Q_D(QPropertyAnimation);
199 if (d->state != QAbstractAnimation::Stopped) {
200 qWarning(msg: "QPropertyAnimation::setPropertyName: you can't change the property name of a running animation");
201 return;
202 }
203
204 d->propertyName.removeBindingUnlessInWrapper();
205
206 if (d->propertyName.valueBypassingBindings() == propertyName)
207 return;
208
209 d->propertyName.setValueBypassingBindings(propertyName);
210 d->updateMetaProperty();
211 d->propertyName.notify();
212}
213
214QBindable<QByteArray> QPropertyAnimation::bindablePropertyName()
215{
216 return &d_func()->propertyName;
217}
218
219/*!
220 \reimp
221 */
222bool QPropertyAnimation::event(QEvent *event)
223{
224 return QVariantAnimation::event(event);
225}
226
227/*!
228 This virtual function is called by QVariantAnimation whenever the current value
229 changes. \a value is the new, updated value. It updates the current value
230 of the property on the target object.
231
232 \sa currentValue, currentTime
233 */
234void QPropertyAnimation::updateCurrentValue(const QVariant &value)
235{
236 Q_D(QPropertyAnimation);
237 d->updateProperty(newValue: value);
238}
239
240/*!
241 \reimp
242
243 If the startValue is not defined when the state of the animation changes from Stopped to Running,
244 the current property value is used as the initial value for the animation.
245*/
246void QPropertyAnimation::updateState(QAbstractAnimation::State newState,
247 QAbstractAnimation::State oldState)
248{
249 Q_D(QPropertyAnimation);
250
251 if (!d->targetObject && oldState == Stopped) {
252 qWarning(msg: "QPropertyAnimation::updateState (%s): Changing state of an animation without "
253 "target",
254 d->propertyName.value().constData());
255 return;
256 }
257
258 QVariantAnimation::updateState(newState, oldState);
259
260 QPropertyAnimation *animToStop = nullptr;
261 {
262 Q_CONSTINIT static QBasicMutex mutex;
263 auto locker = qt_unique_lock(mutex);
264 typedef QPair<QObject *, QByteArray> QPropertyAnimationPair;
265 typedef QHash<QPropertyAnimationPair, QPropertyAnimation*> QPropertyAnimationHash;
266 Q_CONSTINIT static QPropertyAnimationHash hash;
267
268 // in case the targetObject gets deleted, the following happens:
269 // 1. targetObject's destroyed signal calls our targetObjectDestroyed.
270 // 2. targetObjectDestroyed calls stop()
271 // 3. QAbstractAnimation::stop() calls setState(Stopped)
272 // 4. setState(Stopped) calls updateState(newState, oldState)
273 // 5. we arrive here. d->targetObject is not yet set to nullptr, we can safely use it.
274 Q_ASSERT(d->targetObject);
275
276 QPropertyAnimationPair key(d->targetObject, d->propertyName);
277 if (newState == Running) {
278 d->updateMetaProperty();
279 animToStop = hash.value(key, defaultValue: nullptr);
280 hash.insert(key, value: this);
281 locker.unlock();
282 // update the default start value
283 if (oldState == Stopped) {
284 d->setDefaultStartEndValue(
285 d->targetObject->property(name: d->propertyName.value().constData()));
286 //let's check if we have a start value and an end value
287 const char *what = nullptr;
288 if (!startValue().isValid() && (d->direction == Backward || !d->defaultStartEndValue.isValid())) {
289 what = "start";
290 }
291 if (!endValue().isValid() && (d->direction == Forward || !d->defaultStartEndValue.isValid())) {
292 if (what)
293 what = "start and end";
294 else
295 what = "end";
296 }
297 if (Q_UNLIKELY(what)) {
298 qWarning(msg: "QPropertyAnimation::updateState (%s, %s, %ls): starting an animation "
299 "without %s value",
300 d->propertyName.value().constData(),
301 d->targetObject->metaObject()->className(),
302 qUtf16Printable(d->targetObject->objectName()), what);
303 }
304 }
305 } else if (hash.value(key) == this) {
306 hash.remove(key);
307 }
308 }
309
310 //we need to do that after the mutex was unlocked
311 if (animToStop) {
312 // try to stop the top level group
313 QAbstractAnimation *current = animToStop;
314 while (current->group() && current->state() != Stopped)
315 current = current->group();
316 current->stop();
317 }
318}
319
320QT_END_NAMESPACE
321
322#include "moc_qpropertyanimation.cpp"
323

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