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

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