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