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
61QPropertyAnimationPrivate::~QPropertyAnimationPrivate()
62 = default;
63
64void QPropertyAnimationPrivate::updateMetaProperty()
65{
66 const QObject *target = targetObject.valueBypassingBindings();
67 if (!target || propertyName.value().isEmpty()) {
68 propertyType = QMetaType::UnknownType;
69 propertyIndex = -1;
70 return;
71 }
72
73 //propertyType will be set to a valid type only if there is a Q_PROPERTY
74 //otherwise it will be set to QVariant::Invalid at the end of this function
75 propertyType = target->property(name: propertyName.value()).userType();
76 propertyIndex = target->metaObject()->indexOfProperty(name: propertyName.value());
77
78 if (propertyType != QMetaType::UnknownType)
79 convertValues(t: propertyType);
80 if (propertyIndex == -1) {
81 //there is no Q_PROPERTY on the object
82 propertyType = QMetaType::UnknownType;
83 if (!target->dynamicPropertyNames().contains(t: propertyName))
84 qWarning(msg: "QPropertyAnimation: you're trying to animate a non-existing property %s of "
85 "your QObject",
86 propertyName.value().constData());
87 } else if (!target->metaObject()->property(index: propertyIndex).isWritable()) {
88 qWarning(msg: "QPropertyAnimation: you're trying to animate the non-writable property %s of "
89 "your QObject",
90 propertyName.value().constData());
91 }
92}
93
94void QPropertyAnimationPrivate::updateProperty(const QVariant &newValue)
95{
96 if (state == QAbstractAnimation::Stopped)
97 return;
98
99 if (!targetObject)
100 return;
101
102 if (newValue.userType() == propertyType) {
103 //no conversion is needed, we directly call the QMetaObject::metacall
104 //check QMetaProperty::write for an explanation of these
105 int status = -1;
106 int flags = 0;
107 void *argv[] = { const_cast<void *>(newValue.constData()), const_cast<QVariant *>(&newValue), &status, &flags };
108 QMetaObject::metacall(targetObject, QMetaObject::WriteProperty, propertyIndex, argv);
109 } else {
110 targetObject->setProperty(name: propertyName.value().constData(), value: newValue);
111 }
112}
113
114/*!
115 Construct a QPropertyAnimation object. \a parent is passed to QObject's
116 constructor.
117*/
118QPropertyAnimation::QPropertyAnimation(QObject *parent)
119 : QVariantAnimation(*new QPropertyAnimationPrivate, parent)
120{
121}
122
123/*!
124 Construct a QPropertyAnimation object. \a parent is passed to QObject's
125 constructor. The animation changes the property \a propertyName on \a
126 target. The default duration is 250ms.
127
128 \sa targetObject, propertyName
129*/
130QPropertyAnimation::QPropertyAnimation(QObject *target, const QByteArray &propertyName, QObject *parent)
131 : QVariantAnimation(*new QPropertyAnimationPrivate, parent)
132{
133 setTargetObject(target);
134 setPropertyName(propertyName);
135}
136
137/*!
138 Destroys the QPropertyAnimation instance.
139 */
140QPropertyAnimation::~QPropertyAnimation()
141{
142 stop();
143}
144
145/*!
146 \property QPropertyAnimation::targetObject
147 \brief the target QObject for this animation.
148
149 This property defines the target QObject for this animation.
150 */
151QObject *QPropertyAnimation::targetObject() const
152{
153 return d_func()->targetObject;
154}
155
156QBindable<QObject *> QPropertyAnimation::bindableTargetObject()
157{
158 return &d_func()->targetObject;
159}
160
161void QPropertyAnimation::setTargetObject(QObject *target)
162{
163 Q_D(QPropertyAnimation);
164 if (d->state != QAbstractAnimation::Stopped) {
165 qWarning(msg: "QPropertyAnimation::setTargetObject: you can't change the target of a running animation");
166 return;
167 }
168
169 d->targetObject.removeBindingUnlessInWrapper();
170 const QObject *oldTarget = d->targetObject.valueBypassingBindings();
171 if (oldTarget == target)
172 return;
173
174 if (oldTarget != nullptr)
175 QObject::disconnect(sender: oldTarget, signal: &QObject::destroyed, receiver: this, zero: nullptr);
176 d->targetObject.setValueBypassingBindings(target);
177
178 if (target != nullptr) {
179 QObject::connect(sender: target, signal: &QObject::destroyed, context: this,
180 slot: [d] { d->targetObjectDestroyed(); });
181 }
182 d->updateMetaProperty();
183 d->targetObject.notify();
184}
185
186/*!
187 \property QPropertyAnimation::propertyName
188 \brief the target property name for this animation
189
190 This property defines the target property name for this animation. The
191 property name is required for the animation to operate.
192 */
193QByteArray QPropertyAnimation::propertyName() const
194{
195 Q_D(const QPropertyAnimation);
196 return d->propertyName;
197}
198
199void QPropertyAnimation::setPropertyName(const QByteArray &propertyName)
200{
201 Q_D(QPropertyAnimation);
202 if (d->state != QAbstractAnimation::Stopped) {
203 qWarning(msg: "QPropertyAnimation::setPropertyName: you can't change the property name of a running animation");
204 return;
205 }
206
207 d->propertyName.removeBindingUnlessInWrapper();
208
209 if (d->propertyName.valueBypassingBindings() == propertyName)
210 return;
211
212 d->propertyName.setValueBypassingBindings(propertyName);
213 d->updateMetaProperty();
214 d->propertyName.notify();
215}
216
217QBindable<QByteArray> QPropertyAnimation::bindablePropertyName()
218{
219 return &d_func()->propertyName;
220}
221
222/*!
223 \reimp
224 */
225bool QPropertyAnimation::event(QEvent *event)
226{
227 return QVariantAnimation::event(event);
228}
229
230/*!
231 This virtual function is called by QVariantAnimation whenever the current value
232 changes. \a value is the new, updated value. It updates the current value
233 of the property on the target object.
234
235 \sa currentValue, currentTime
236 */
237void QPropertyAnimation::updateCurrentValue(const QVariant &value)
238{
239 Q_D(QPropertyAnimation);
240 d->updateProperty(newValue: value);
241}
242
243/*!
244 \reimp
245
246 If the startValue is not defined when the state of the animation changes from Stopped to Running,
247 the current property value is used as the initial value for the animation.
248*/
249void QPropertyAnimation::updateState(QAbstractAnimation::State newState,
250 QAbstractAnimation::State oldState)
251{
252 Q_D(QPropertyAnimation);
253
254 if (!d->targetObject && oldState == Stopped) {
255 qWarning(msg: "QPropertyAnimation::updateState (%s): Changing state of an animation without "
256 "target",
257 d->propertyName.value().constData());
258 return;
259 }
260
261 QVariantAnimation::updateState(newState, oldState);
262
263 QPropertyAnimation *animToStop = nullptr;
264 {
265 Q_CONSTINIT static QBasicMutex mutex;
266 auto locker = qt_unique_lock(mutex);
267 using QPropertyAnimationPair = std::pair<QObject *, QByteArray>;
268 typedef QHash<QPropertyAnimationPair, QPropertyAnimation*> QPropertyAnimationHash;
269 Q_CONSTINIT static QPropertyAnimationHash hash;
270
271 // in case the targetObject gets deleted, the following happens:
272 // 1. targetObject's destroyed signal calls our targetObjectDestroyed.
273 // 2. targetObjectDestroyed calls stop()
274 // 3. QAbstractAnimation::stop() calls setState(Stopped)
275 // 4. setState(Stopped) calls updateState(newState, oldState)
276 // 5. we arrive here. d->targetObject is not yet set to nullptr, we can safely use it.
277 Q_ASSERT(d->targetObject);
278
279 QPropertyAnimationPair key(d->targetObject, d->propertyName);
280 if (newState == Running) {
281 d->updateMetaProperty();
282 animToStop = hash.value(key, defaultValue: nullptr);
283 hash.insert(key, value: this);
284 locker.unlock();
285 // update the default start value
286 if (oldState == Stopped) {
287 d->setDefaultStartEndValue(
288 d->targetObject->property(name: d->propertyName.value().constData()));
289 //let's check if we have a start value and an end value
290 const char *what = nullptr;
291 if (!startValue().isValid() && (d->direction == Backward || !d->defaultStartEndValue.isValid())) {
292 what = "start";
293 }
294 if (!endValue().isValid() && (d->direction == Forward || !d->defaultStartEndValue.isValid())) {
295 if (what)
296 what = "start and end";
297 else
298 what = "end";
299 }
300 if (Q_UNLIKELY(what)) {
301 qWarning(msg: "QPropertyAnimation::updateState (%s, %s, %ls): starting an animation "
302 "without %s value",
303 d->propertyName.value().constData(),
304 d->targetObject->metaObject()->className(),
305 qUtf16Printable(d->targetObject->objectName()), what);
306 }
307 }
308 } else if (hash.value(key) == this) {
309 hash.remove(key);
310 }
311 }
312
313 //we need to do that after the mutex was unlocked
314 if (animToStop) {
315 // try to stop the top level group
316 QAbstractAnimation *current = animToStop;
317 while (current->group() && current->state() != Stopped)
318 current = current->group();
319 current->stop();
320 }
321}
322
323QT_END_NAMESPACE
324
325#include "moc_qpropertyanimation.cpp"
326

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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