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 test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtCore/qpropertyanimation.h>
31#include <QtCore/qvariantanimation.h>
32#include <private/qabstractanimation_p.h>
33#include <QtGui/qtouchdevice.h>
34#include <QtWidgets/qwidget.h>
35
36Q_DECLARE_METATYPE(QAbstractAnimation::State)
37
38class UncontrolledAnimation : public QPropertyAnimation
39{
40 Q_OBJECT
41public:
42 int duration() const { return -1; /* not time driven */ }
43
44protected:
45 void updateCurrentTime(int currentTime)
46 {
47 QPropertyAnimation::updateCurrentTime(currentTime);
48 if (currentTime >= QPropertyAnimation::duration() || currentLoop() >= 1)
49 stop();
50 }
51};
52
53class MyObject : public QObject
54{
55 Q_OBJECT
56 Q_PROPERTY(qreal x READ x WRITE setX)
57public:
58 MyObject() : m_x(0) { }
59 qreal x() const { return m_x; }
60 void setX(qreal x) { m_x = x; }
61private:
62 qreal m_x;
63};
64
65class DummyPropertyAnimation : public QPropertyAnimation
66{
67public:
68 DummyPropertyAnimation(QObject *parent = 0) : QPropertyAnimation(parent)
69 {
70 setTargetObject(&o);
71 this->setPropertyName("x");
72 setEndValue(100);
73 }
74
75 MyObject o;
76};
77
78class TestAnimationDriver : public QAnimationDriver
79{
80public:
81 TestAnimationDriver()
82 : QAnimationDriver()
83 , m_elapsed(0)
84 {
85 QUnifiedTimer::instance()->installAnimationDriver(driver: this);
86 }
87
88 ~TestAnimationDriver()
89 {
90 // This is to ensure that running animations are removed from the list of actual running
91 // animations.
92 QCoreApplication::sendPostedEvents();
93 QUnifiedTimer::instance()->uninstallAnimationDriver(driver: this);
94 }
95
96 void wait(qint64 ms)
97 {
98 /*
99 * When QAbstractAnimation::start() is called it will end up calling
100 * QAnimationTimer::registerAnimation(). This will do
101 *
102 * QMetaObject::invokeMethod(inst, "startAnimations", Qt::QueuedConnection); // typeof(inst) == QAnimationTimer
103 *
104 * startAnimations() will again fire a queued connection to actually add the animation
105 * to the list of running animations:
106 *
107 * QMetaObject::invokeMethod(inst, "startTimers", Qt::QueuedConnection); // typeof(inst) == QUnifiedTimer
108 *
109 * We therefore have to call QCoreApplication::sendPostedEvents() twice here.
110 */
111 QCoreApplication::sendPostedEvents();
112 QCoreApplication::sendPostedEvents();
113
114 // Simulates the ideal animation update freqency (approx. 60Hz)
115 static const int interval = 1000/60;
116 qint64 until = m_elapsed + ms;
117 while (m_elapsed < until) {
118 advanceAnimation(timeStep: m_elapsed);
119 m_elapsed += interval;
120 }
121 advanceAnimation(timeStep: m_elapsed);
122 // This is to make sure that animations that were started with DeleteWhenStopped
123 // will actually delete themselves within the test function.
124 // Normally, they won't be deleted until the main event loop is processed.
125 // Therefore, have to explicitly say that we want to process DeferredDelete events. Same
126 // trick is used by QTest::qWait().
127 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
128 }
129
130 qint64 elapsed() const override
131 {
132 return m_elapsed;
133 }
134
135 void start() override
136 {
137 d_func()->running = true;
138 m_elapsed = 0;
139 emit started();
140 }
141
142 void stop() override
143 {
144 d_func()->running = false;
145 emit stopped();
146 }
147
148private:
149 qint64 m_elapsed;
150 Q_DECLARE_PRIVATE(QAnimationDriver)
151};
152
153class tst_QPropertyAnimation : public QObject
154{
155 Q_OBJECT
156public Q_SLOTS:
157 void initTestCase();
158
159private slots:
160 void construction();
161 void setCurrentTime_data();
162 void setCurrentTime();
163 void statesAndSignals_data();
164 void statesAndSignals();
165 void deletion1();
166 void deletion2();
167 void deletion3();
168 void duration0();
169 void noStartValue();
170 void noStartValueWithLoop();
171 void startWhenAnotherIsRunning();
172 void easingcurve_data();
173 void easingcurve();
174 void startWithoutStartValue();
175 void startBackwardWithoutEndValue();
176 void playForwardBackward();
177 void interpolated();
178 void setStartEndValues_data();
179 void setStartEndValues();
180 void zeroDurationStart();
181 void zeroDurationForwardBackward();
182 void operationsInStates_data();
183 void operationsInStates();
184 void oneKeyValue();
185 void updateOnSetKeyValues();
186 void restart();
187 void valueChanged();
188 void twoAnimations();
189 void deletedInUpdateCurrentTime();
190 void totalDuration();
191 void zeroLoopCount();
192 void recursiveAnimations();
193};
194
195void tst_QPropertyAnimation::initTestCase()
196{
197 qRegisterMetaType<QAbstractAnimation::State>(typeName: "QAbstractAnimation::State");
198 qRegisterMetaType<QAbstractAnimation::DeletionPolicy>(typeName: "QAbstractAnimation::DeletionPolicy");
199}
200
201class AnimationObject : public QObject
202{
203 Q_OBJECT
204 Q_PROPERTY(int value READ value WRITE setValue)
205 Q_PROPERTY(qreal realValue READ realValue WRITE setRealValue)
206public:
207 AnimationObject(int startValue = 0)
208 : v(startValue), rv(startValue)
209 { }
210
211 int value() const { return v; }
212 void setValue(int value) { v = value; }
213
214 qreal realValue() const { return rv; }
215 void setRealValue(qreal value) { rv = value; }
216
217 int v;
218 qreal rv;
219};
220
221
222void tst_QPropertyAnimation::construction()
223{
224 QPropertyAnimation panimation;
225}
226
227void tst_QPropertyAnimation::setCurrentTime_data()
228{
229 QTest::addColumn<int>(name: "duration");
230 QTest::addColumn<int>(name: "loopCount");
231 QTest::addColumn<int>(name: "currentTime");
232 QTest::addColumn<int>(name: "testCurrentTime");
233 QTest::addColumn<int>(name: "testCurrentLoop");
234
235 QTest::newRow(dataTag: "-1") << -1 << 1 << 0 << 0 << 0;
236 QTest::newRow(dataTag: "0") << 0 << 1 << 0 << 0 << 0;
237 QTest::newRow(dataTag: "1") << 0 << 1 << 1 << 0 << 0;
238 QTest::newRow(dataTag: "2") << 0 << 2 << 1 << 0 << 0;
239 QTest::newRow(dataTag: "3") << 1 << 1 << 0 << 0 << 0;
240 QTest::newRow(dataTag: "4") << 1 << 1 << 1 << 1 << 0;
241 QTest::newRow(dataTag: "5") << 1 << 2 << 1 << 0 << 1;
242 QTest::newRow(dataTag: "6") << 1 << 2 << 2 << 1 << 1;
243 QTest::newRow(dataTag: "7") << 1 << 2 << 3 << 1 << 1;
244 QTest::newRow(dataTag: "8") << 1 << 3 << 2 << 0 << 2;
245 QTest::newRow(dataTag: "9") << 1 << 3 << 3 << 1 << 2;
246 QTest::newRow(dataTag: "a") << 10 << 1 << 0 << 0 << 0;
247 QTest::newRow(dataTag: "b") << 10 << 1 << 1 << 1 << 0;
248 QTest::newRow(dataTag: "c") << 10 << 1 << 10 << 10 << 0;
249 QTest::newRow(dataTag: "d") << 10 << 2 << 10 << 0 << 1;
250 QTest::newRow(dataTag: "e") << 10 << 2 << 11 << 1 << 1;
251 QTest::newRow(dataTag: "f") << 10 << 2 << 20 << 10 << 1;
252 QTest::newRow(dataTag: "g") << 10 << 2 << 21 << 10 << 1;
253 QTest::newRow(dataTag: "negloop 0") << 10 << -1 << 0 << 0 << 0;
254 QTest::newRow(dataTag: "negloop 1") << 10 << -1 << 10 << 0 << 1;
255 QTest::newRow(dataTag: "negloop 2") << 10 << -1 << 15 << 5 << 1;
256 QTest::newRow(dataTag: "negloop 3") << 10 << -1 << 20 << 0 << 2;
257 QTest::newRow(dataTag: "negloop 4") << 10 << -1 << 30 << 0 << 3;
258}
259
260void tst_QPropertyAnimation::setCurrentTime()
261{
262 QFETCH(int, duration);
263 QFETCH(int, loopCount);
264 QFETCH(int, currentTime);
265 QFETCH(int, testCurrentTime);
266 QFETCH(int, testCurrentLoop);
267
268 QPropertyAnimation animation;
269 if (duration < 0)
270 QTest::ignoreMessage(type: QtWarningMsg, message: "QVariantAnimation::setDuration: cannot set a negative duration");
271 animation.setDuration(duration);
272 animation.setLoopCount(loopCount);
273 animation.setCurrentTime(currentTime);
274
275 QCOMPARE(animation.currentLoopTime(), testCurrentTime);
276 QCOMPARE(animation.currentLoop(), testCurrentLoop);
277}
278
279void tst_QPropertyAnimation::statesAndSignals_data()
280{
281 QTest::addColumn<bool>(name: "uncontrolled");
282 QTest::newRow(dataTag: "normal animation") << false;
283 QTest::newRow(dataTag: "animation with undefined duration") << true;
284}
285
286void tst_QPropertyAnimation::statesAndSignals()
287{
288 QFETCH(bool, uncontrolled);
289 std::unique_ptr<QPropertyAnimation> anim;
290 if (uncontrolled)
291 anim.reset(p: new UncontrolledAnimation);
292 else
293 anim.reset(p: new DummyPropertyAnimation);
294 anim->setDuration(100);
295
296 QSignalSpy finishedSpy(anim.get(), &QPropertyAnimation::finished);
297 QSignalSpy runningSpy(anim.get(), &QPropertyAnimation::stateChanged);
298 QSignalSpy currentLoopSpy(anim.get(), &QPropertyAnimation::currentLoopChanged);
299
300 QVERIFY(finishedSpy.isValid());
301 QVERIFY(runningSpy.isValid());
302 QVERIFY(currentLoopSpy.isValid());
303
304 anim->setCurrentTime(1);
305 anim->setCurrentTime(100);
306 QCOMPARE(finishedSpy.count(), 0);
307 QCOMPARE(runningSpy.count(), 0);
308 QCOMPARE(currentLoopSpy.count(), 0);
309 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
310
311 anim->setLoopCount(3);
312 anim->setCurrentTime(101);
313
314 if (uncontrolled)
315 QSKIP("Uncontrolled animations don't handle looping");
316
317 QCOMPARE(currentLoopSpy.count(), 1);
318 QCOMPARE(anim->currentLoop(), 1);
319
320 anim->setCurrentTime(0);
321 QCOMPARE(currentLoopSpy.count(), 2);
322 QCOMPARE(anim->currentLoop(), 0);
323
324 anim->start();
325 QCOMPARE(anim->state(), QAnimationGroup::Running);
326 QCOMPARE(runningSpy.count(), 1); //anim must have started
327 QCOMPARE(anim->currentLoop(), 0);
328 runningSpy.clear();
329
330 anim->stop();
331 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
332 QCOMPARE(runningSpy.count(), 1); //anim must have stopped
333 QCOMPARE(finishedSpy.count(), 0);
334 QCOMPARE(anim->currentLoopTime(), 0);
335 QCOMPARE(anim->currentLoop(), 0);
336 QCOMPARE(currentLoopSpy.count(), 2);
337 runningSpy.clear();
338
339 {
340 TestAnimationDriver timeDriver;
341 anim->start();
342 timeDriver.wait(ms: 1000);
343 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
344 QCOMPARE(runningSpy.count(), 2); //started and stopped again
345 runningSpy.clear();
346 QCOMPARE(finishedSpy.count(), 1);
347 QCOMPARE(anim->currentLoopTime(), 100);
348 QCOMPARE(anim->currentLoop(), 2);
349 QCOMPARE(currentLoopSpy.count(), 4);
350
351 anim->start(); // auto-rewinds
352 QCOMPARE(anim->state(), QAnimationGroup::Running);
353 QCOMPARE(anim->currentTime(), 0);
354 QCOMPARE(anim->currentLoop(), 0);
355 QCOMPARE(currentLoopSpy.count(), 5);
356 QCOMPARE(runningSpy.count(), 1); // anim has started
357 QCOMPARE(finishedSpy.count(), 1);
358 QCOMPARE(anim->currentLoop(), 0);
359 runningSpy.clear();
360
361 timeDriver.wait(ms: 1000);
362
363 QCOMPARE(currentLoopSpy.count(), 7);
364 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
365 QCOMPARE(anim->currentLoop(), 2);
366 QCOMPARE(runningSpy.count(), 1); // anim has stopped
367 QCOMPARE(finishedSpy.count(), 2);
368 QCOMPARE(anim->currentLoopTime(), 100);
369 }
370}
371
372void tst_QPropertyAnimation::deletion1()
373{
374 TestAnimationDriver timeDriver;
375 QObject *object = new QWidget;
376 QPointer<QPropertyAnimation> anim = new QPropertyAnimation(object, "minimumWidth");
377
378 //test that the animation is deleted correctly depending of the deletion flag passed in start()
379 QSignalSpy runningSpy(anim.data(), &QPropertyAnimation::stateChanged);
380 QSignalSpy finishedSpy(anim.data(), &QPropertyAnimation::finished);
381 QVERIFY(runningSpy.isValid());
382 QVERIFY(finishedSpy.isValid());
383 anim->setStartValue(10);
384 anim->setEndValue(20);
385 anim->setDuration(200);
386 anim->start();
387 QCOMPARE(runningSpy.count(), 1);
388 QCOMPARE(finishedSpy.count(), 0);
389
390 QVERIFY(anim);
391 QCOMPARE(anim->state(), QAnimationGroup::Running);
392 timeDriver.wait(ms: 100);
393 QVERIFY(anim);
394 QCOMPARE(anim->state(), QAnimationGroup::Running);
395 timeDriver.wait(ms: 150);
396 QVERIFY(anim); //The animation should not have been deleted
397 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
398 QCOMPARE(runningSpy.count(), 2);
399 QCOMPARE(finishedSpy.count(), 1);
400
401 anim->start(policy: QVariantAnimation::DeleteWhenStopped);
402 QVERIFY(anim);
403 QCOMPARE(anim->state(), QAnimationGroup::Running);
404 timeDriver.wait(ms: 100);
405 QVERIFY(anim);
406 QCOMPARE(anim->state(), QAnimationGroup::Running);
407 timeDriver.wait(ms: 150);
408 QCOMPARE(runningSpy.count(), 4);
409 QCOMPARE(finishedSpy.count(), 2);
410 QVERIFY(!anim); //The animation must have been deleted
411 delete object;
412}
413
414void tst_QPropertyAnimation::deletion2()
415{
416 TestAnimationDriver timeDriver;
417 // test that the animation does not get deleted if the object is deleted
418 QObject *object = new QWidget;
419 QPointer<QPropertyAnimation> anim = new QPropertyAnimation(object,"minimumWidth");
420 QVERIFY(anim->parent() != object);
421 anim->setStartValue(10);
422 anim->setEndValue(20);
423 anim->setDuration(200);
424
425 QSignalSpy runningSpy(anim.data(), &QPropertyAnimation::stateChanged);
426 QSignalSpy finishedSpy(anim.data(), &QPropertyAnimation::finished);
427
428 QVERIFY(runningSpy.isValid());
429 QVERIFY(finishedSpy.isValid());
430
431 anim->setStartValue(10);
432 anim->setEndValue(20);
433 anim->setDuration(200);
434 anim->start();
435
436 timeDriver.wait(ms: 50);
437 QVERIFY(anim);
438 QCOMPARE(anim->state(), QAnimationGroup::Running);
439
440 QCOMPARE(runningSpy.count(), 1);
441 QCOMPARE(finishedSpy.count(), 0);
442
443 //we can't call deletaLater directly because the delete would only happen in the next loop of _this_ event loop
444 QTimer::singleShot(msec: 0, receiver: object, SLOT(deleteLater()));
445 timeDriver.wait(ms: 50);
446
447 QVERIFY(anim);
448 QVERIFY(!anim->targetObject());
449
450 delete anim;
451}
452
453void tst_QPropertyAnimation::deletion3()
454{
455 //test that the stopped signal is emit when the animation is destroyed
456 TestAnimationDriver timeDriver;
457 QWidget w;
458 QObject *object = &w;
459 QPropertyAnimation *anim = new QPropertyAnimation(object,"minimumWidth");
460 anim->setStartValue(10);
461 anim->setEndValue(20);
462 anim->setDuration(200);
463
464 QSignalSpy runningSpy(anim, &QPropertyAnimation::stateChanged);
465 QSignalSpy finishedSpy(anim, &QPropertyAnimation::finished);
466
467 QVERIFY(runningSpy.isValid());
468 QVERIFY(finishedSpy.isValid());
469
470 anim->start();
471
472 timeDriver.wait(ms: 50);
473 QCOMPARE(anim->state(), QAnimationGroup::Running);
474 QCOMPARE(runningSpy.count(), 1);
475 QCOMPARE(finishedSpy.count(), 0);
476 delete anim;
477 QCOMPARE(runningSpy.count(), 2);
478 QCOMPARE(finishedSpy.count(), 0);
479}
480
481void tst_QPropertyAnimation::duration0()
482{
483 QObject o;
484 o.setProperty(name: "ole", value: 42);
485 QCOMPARE(o.property("ole").toInt(), 42);
486
487 QPropertyAnimation animation(&o, "ole");
488 animation.setEndValue(43);
489 QVERIFY(!animation.currentValue().isValid());
490 QCOMPARE(animation.currentValue().toInt(), 0);
491 animation.setStartValue(42);
492 QVERIFY(animation.currentValue().isValid());
493 QCOMPARE(animation.currentValue().toInt(), 42);
494
495 QCOMPARE(o.property("ole").toInt(), 42);
496 animation.setDuration(0);
497 QCOMPARE(animation.currentValue().toInt(), 43); //it is at the end
498 animation.start();
499 QCOMPARE(animation.state(), QAnimationGroup::Stopped);
500 QCOMPARE(animation.currentTime(), 0);
501 QCOMPARE(o.property("ole").toInt(), 43);
502}
503
504class StartValueTester : public QObject
505{
506 Q_OBJECT
507 Q_PROPERTY(int ole READ ole WRITE setOle)
508public:
509 StartValueTester() : o(0) { }
510 int ole() const { return o; }
511 void setOle(int v) { o = v; values << v; }
512
513 int o;
514 QVector<int> values;
515};
516
517void tst_QPropertyAnimation::noStartValue()
518{
519 TestAnimationDriver timeDriver;
520 StartValueTester o;
521 o.setProperty(name: "ole", value: 42);
522 o.values.clear();
523
524 QPropertyAnimation a(&o, "ole");
525 a.setEndValue(420);
526 a.setDuration(250);
527 a.start();
528
529 timeDriver.wait(ms: a.duration());
530 QCOMPARE(o.values.value(o.values.size() - 1, -1), 420);
531 QCOMPARE(o.values.first(), 42);
532}
533
534void tst_QPropertyAnimation::noStartValueWithLoop()
535{
536 StartValueTester o;
537 o.setProperty(name: "ole", value: 42);
538 o.values.clear();
539
540 QPropertyAnimation a(&o, "ole");
541 a.setEndValue(420);
542 a.setDuration(250);
543 a.setLoopCount(2);
544 a.start();
545
546 a.setCurrentTime(250);
547 QCOMPARE(o.values.first(), 42);
548 QCOMPARE(a.currentValue().toInt(), 42);
549 QCOMPARE(o.values.last(), 42);
550
551 a.setCurrentTime(500);
552 QCOMPARE(a.currentValue().toInt(), 420);
553}
554
555void tst_QPropertyAnimation::startWhenAnotherIsRunning()
556{
557 StartValueTester o;
558 o.setProperty(name: "ole", value: 42);
559 o.values.clear();
560 TestAnimationDriver timeDriver;
561
562 {
563 //normal case: the animation finishes and is deleted
564 QPointer<QVariantAnimation> anim = new QPropertyAnimation(&o, "ole");
565 anim->setEndValue(100);
566 QSignalSpy runningSpy(anim.data(), &QVariantAnimation::stateChanged);
567 QVERIFY(runningSpy.isValid());
568 anim->start(policy: QVariantAnimation::DeleteWhenStopped);
569 timeDriver.wait(ms: anim->duration());
570 QCOMPARE(runningSpy.count(), 2); //started and then stopped
571 QVERIFY(!anim);
572 }
573 {
574 QPointer<QVariantAnimation> anim = new QPropertyAnimation(&o, "ole");
575 anim->setEndValue(100);
576 QSignalSpy runningSpy(anim.data(), &QVariantAnimation::stateChanged);
577 QVERIFY(runningSpy.isValid());
578 anim->start(policy: QVariantAnimation::DeleteWhenStopped);
579 timeDriver.wait(ms: anim->duration()/2);
580 QPointer<QVariantAnimation> anim2 = new QPropertyAnimation(&o, "ole");
581 anim2->setEndValue(100);
582 QCOMPARE(runningSpy.count(), 1);
583 QCOMPARE(anim->state(), QVariantAnimation::Running);
584
585 //anim2 will interrupt anim1
586 QMetaObject::invokeMethod(obj: anim2, member: "start", type: Qt::QueuedConnection, Q_ARG(QAbstractAnimation::DeletionPolicy, QVariantAnimation::DeleteWhenStopped));
587 timeDriver.wait(ms: 50);
588 QVERIFY(!anim); //anim should have been deleted
589 QVERIFY(anim2);
590 timeDriver.wait(ms: anim2->duration());
591 QVERIFY(!anim2); //anim2 is finished: it should have been deleted by now
592 QVERIFY(!anim);
593 }
594
595}
596
597// copy from easing.cpp in case that function changes definition
598static qreal easeInOutBack(qreal t)
599{
600 qreal s = 1.70158;
601 qreal t_adj = 2.0f * (qreal)t;
602 if (t_adj < 1) {
603 s *= 1.525f;
604 return 1.0/2*(t_adj*t_adj*((s+1)*t_adj - s));
605 } else {
606 t_adj -= 2;
607 s *= 1.525f;
608 return 1.0/2*(t_adj*t_adj*((s+1)*t_adj + s) + 2);
609 }
610}
611
612void tst_QPropertyAnimation::easingcurve_data()
613{
614 QTest::addColumn<int>(name: "currentTime");
615 QTest::addColumn<int>(name: "expectedvalue");
616
617 QTest::newRow(dataTag: "interpolation1") << 0 << 0;
618 QTest::newRow(dataTag: "interpolation2") << 1000 << 1000;
619 QTest::newRow(dataTag: "extrapolationbelow") << 250 << -99;
620 QTest::newRow(dataTag: "extrapolationabove") << 750 << 1099;
621}
622
623void tst_QPropertyAnimation::easingcurve()
624{
625 QFETCH(int, currentTime);
626 QFETCH(int, expectedvalue);
627 QObject o;
628 o.setProperty(name: "ole", value: 42);
629 QCOMPARE(o.property("ole").toInt(), 42);
630
631 QPropertyAnimation pAnimation(&o, "ole");
632 pAnimation.setStartValue(0);
633 pAnimation.setEndValue(1000);
634 pAnimation.setDuration(1000);
635
636 // this easingcurve assumes that we extrapolate before startValue and after endValue
637 QEasingCurve easingCurve;
638 easingCurve.setCustomType(easeInOutBack);
639 pAnimation.setEasingCurve(easingCurve);
640 pAnimation.start();
641 pAnimation.pause();
642 pAnimation.setCurrentTime(currentTime);
643 QCOMPARE(o.property("ole").toInt(), expectedvalue);
644}
645
646void tst_QPropertyAnimation::startWithoutStartValue()
647{
648 TestAnimationDriver timeDriver;
649 QObject o;
650 o.setProperty(name: "ole", value: 42);
651 QCOMPARE(o.property("ole").toInt(), 42);
652
653 QPropertyAnimation anim(&o, "ole");
654 anim.setEndValue(100);
655
656 anim.start();
657
658 timeDriver.wait(ms: 100);
659 int current = anim.currentValue().toInt();
660 //it is somewhere in the animation
661 QVERIFY(current > 42);
662 QVERIFY(current < 100);
663
664 timeDriver.wait(ms: 200);
665 QCOMPARE(anim.state(), QVariantAnimation::Stopped);
666 current = anim.currentValue().toInt();
667 QCOMPARE(current, 100);
668 QCOMPARE(o.property("ole").toInt(), current);
669
670 anim.setEndValue(110);
671 anim.start();
672 current = anim.currentValue().toInt();
673 // the default start value will reevaluate the current property
674 // and set it to the end value of the last iteration
675 QCOMPARE(current, 100);
676 timeDriver.wait(ms: 100);
677 current = anim.currentValue().toInt();
678 //it is somewhere in the animation
679 QVERIFY(current >= 100);
680 QVERIFY(current <= 110);
681}
682
683void tst_QPropertyAnimation::startBackwardWithoutEndValue()
684{
685 TestAnimationDriver timeDriver;
686 QObject o;
687 o.setProperty(name: "ole", value: 42);
688 QCOMPARE(o.property("ole").toInt(), 42);
689
690 QPropertyAnimation anim(&o, "ole");
691 anim.setStartValue(100);
692 anim.setDirection(QAbstractAnimation::Backward);
693
694 //we start without an end value
695 anim.start();
696 QCOMPARE(anim.state(), QAbstractAnimation::Running);
697 QCOMPARE(o.property("ole").toInt(), 42); //the initial value
698
699 timeDriver.wait(ms: 100);
700 int current = anim.currentValue().toInt();
701 //it is somewhere in the animation
702 QVERIFY(current > 42);
703 QVERIFY(current < 100);
704
705 timeDriver.wait(ms: 200);
706 QCOMPARE(anim.state(), QVariantAnimation::Stopped);
707 current = anim.currentValue().toInt();
708 QCOMPARE(current, 100);
709 QCOMPARE(o.property("ole").toInt(), current);
710
711 anim.setStartValue(110);
712 anim.start();
713 current = anim.currentValue().toInt();
714 // the default start value will reevaluate the current property
715 // and set it to the end value of the last iteration
716 QCOMPARE(current, 100);
717 timeDriver.wait(ms: 100);
718 current = anim.currentValue().toInt();
719 //it is somewhere in the animation
720 QVERIFY(current >= 100);
721 QVERIFY(current <= 110);
722}
723
724
725void tst_QPropertyAnimation::playForwardBackward()
726{
727 TestAnimationDriver timeDriver;
728 QObject o;
729 o.setProperty(name: "ole", value: 0);
730 QCOMPARE(o.property("ole").toInt(), 0);
731
732 QPropertyAnimation anim(&o, "ole");
733 anim.setStartValue(0);
734 anim.setEndValue(100);
735 anim.start();
736 timeDriver.wait(ms: anim.duration());
737 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
738 QCOMPARE(anim.currentTime(), anim.duration());
739
740 //the animation is at the end
741 anim.setDirection(QVariantAnimation::Backward);
742 anim.start();
743 QCOMPARE(anim.state(), QAbstractAnimation::Running);
744 timeDriver.wait(ms: anim.duration());
745 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
746 QCOMPARE(anim.currentTime(), 0);
747
748 //the direction is backward
749 //restarting should jump to the end
750 anim.start();
751 QCOMPARE(anim.state(), QAbstractAnimation::Running);
752 QCOMPARE(anim.currentTime(), anim.duration());
753 timeDriver.wait(ms: anim.duration());
754 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
755 QCOMPARE(anim.currentTime(), 0);
756}
757
758struct Number
759{
760 Number() {}
761 Number(int n)
762 : n(n) {}
763
764 bool operator==(const Number &other) const {
765 return n == other.n;
766 }
767
768 int n;
769};
770QT_BEGIN_NAMESPACE
771Q_DECLARE_TYPEINFO(Number, Q_PRIMITIVE_TYPE);
772QT_END_NAMESPACE
773
774Q_DECLARE_METATYPE(Number)
775
776QVariant numberInterpolator(const Number &f, const Number &t, qreal progress)
777{
778 return QVariant::fromValue<Number>(value: Number(f.n + (t.n - f.n)*progress));
779}
780
781QVariant xaxisQPointInterpolator(const QPointF &f, const QPointF &t, qreal progress)
782{
783 return QPointF(f.x() + (t.x() - f.x())*progress, f.y());
784}
785
786void tst_QPropertyAnimation::interpolated()
787{
788 QObject o;
789 o.setProperty(name: "point", value: QPointF()); //this will avoid warnings
790 o.setProperty(name: "number", value: QVariant::fromValue<Number>(value: Number(42)));
791 QCOMPARE(qvariant_cast<Number>(o.property("number")), Number(42));
792 {
793 qRegisterAnimationInterpolator<Number>(func: numberInterpolator);
794 QPropertyAnimation anim(&o, "number");
795 anim.setStartValue(QVariant::fromValue<Number>(value: Number(0)));
796 anim.setEndValue(QVariant::fromValue<Number>(value: Number(100)));
797 anim.setDuration(1000);
798 anim.start();
799 anim.pause();
800 anim.setCurrentTime(100);
801 Number t(qvariant_cast<Number>(v: o.property(name: "number")));
802 QCOMPARE(t, Number(10));
803 anim.setCurrentTime(500);
804 QCOMPARE(qvariant_cast<Number>(o.property("number")), Number(50));
805 }
806 {
807 qRegisterAnimationInterpolator<QPointF>(func: xaxisQPointInterpolator);
808 QPropertyAnimation anim(&o, "point");
809 anim.setStartValue(QPointF(0,0));
810 anim.setEndValue(QPointF(100, 100));
811 anim.setDuration(1000);
812 anim.start();
813 anim.pause();
814 anim.setCurrentTime(100);
815 QCOMPARE(o.property("point"), QVariant(QPointF(10, 0)));
816 anim.setCurrentTime(500);
817 QCOMPARE(o.property("point"), QVariant(QPointF(50, 0)));
818 }
819 {
820 // unregister it and see if we get back the default behaviour
821 qRegisterAnimationInterpolator<QPointF>(func: 0);
822 QPropertyAnimation anim(&o, "point");
823 anim.setStartValue(QPointF(0,0));
824 anim.setEndValue(QPointF(100, 100));
825 anim.setDuration(1000);
826 anim.start();
827 anim.pause();
828 anim.setCurrentTime(100);
829 QCOMPARE(o.property("point").toPointF(), QPointF(10, 10));
830 anim.setCurrentTime(500);
831 QCOMPARE(o.property("point").toPointF(), QPointF(50, 50));
832 }
833
834 {
835 // Interpolate a qreal property with a int interpolator
836 AnimationObject o1;
837 o1.setRealValue(42.42);
838 QPropertyAnimation anim(&o1, "realValue");
839 anim.setStartValue(0);
840 anim.setEndValue(100);
841 anim.start();
842 QCOMPARE(o1.realValue(), qreal(0));
843 anim.setCurrentTime(250);
844 QCOMPARE(o1.realValue(), qreal(100));
845 }
846}
847
848
849void tst_QPropertyAnimation::setStartEndValues_data()
850{
851 QTest::addColumn<QByteArray>(name: "propertyName");
852 QTest::addColumn<QVariant>(name: "initialValue");
853 QTest::addColumn<QVariant>(name: "startValue");
854 QTest::addColumn<QVariant>(name: "endValue");
855
856 QTest::newRow(dataTag: "dynamic property") << QByteArray("ole") << QVariant(42) << QVariant(0) << QVariant(10);
857 QTest::newRow(dataTag: "real property, with unmatching types") << QByteArray("x") << QVariant(42.) << QVariant(0) << QVariant(10.);
858}
859
860void tst_QPropertyAnimation::setStartEndValues()
861{
862 MyObject object;
863 QFETCH(QByteArray, propertyName);
864 QFETCH(QVariant, initialValue);
865 QFETCH(QVariant, startValue);
866 QFETCH(QVariant, endValue);
867
868 //this tests the start value, end value and default start value
869 object.setProperty(name: propertyName, value: initialValue);
870 QPropertyAnimation anim(&object, propertyName);
871 QVariantAnimation::KeyValues values;
872 QCOMPARE(anim.keyValues(), values);
873
874 //let's add a start value
875 anim.setStartValue(startValue);
876 values << QVariantAnimation::KeyValue(0, startValue);
877 QCOMPARE(anim.keyValues(), values);
878
879 anim.setEndValue(endValue);
880 values << QVariantAnimation::KeyValue(1, endValue);
881 QCOMPARE(anim.keyValues(), values);
882
883 //now we can play with objects
884 QCOMPARE(object.property(propertyName).toDouble(), initialValue.toDouble());
885 anim.start();
886 QVERIFY(anim.startValue().isValid());
887 QCOMPARE(object.property(propertyName), anim.startValue());
888 anim.setCurrentTime(anim.duration()/2);
889 QCOMPARE(object.property(propertyName).toDouble(), (startValue.toDouble() + endValue.toDouble())/2 ); //just in the middle of the animation
890 anim.setCurrentTime(anim.duration()); //we go to the end of the animation
891 QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped
892 QVERIFY(anim.endValue().isValid());
893 QCOMPARE(object.property(propertyName), anim.endValue()); //end of the animations
894
895 //now we remove the explicit start value and test the implicit one
896 anim.stop();
897 object.setProperty(name: propertyName, value: initialValue);
898
899 //let's reset the start value
900 values.remove(i: 0);
901 anim.setStartValue(QVariant());
902 QCOMPARE(anim.keyValues(), values);
903 QVERIFY(!anim.startValue().isValid());
904
905 anim.start();
906 QCOMPARE(object.property(propertyName), initialValue);
907 anim.setCurrentTime(anim.duration()/2);
908 QCOMPARE(object.property(propertyName).toDouble(), (initialValue.toDouble() + endValue.toDouble())/2 ); //just in the middle of the animation
909 anim.setCurrentTime(anim.duration()); //we go to the end of the animation
910 QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped
911 QVERIFY(anim.endValue().isValid());
912 QCOMPARE(object.property(propertyName), anim.endValue()); //end of the animations
913
914 //now we set back the startValue
915 anim.setStartValue(startValue);
916 QVERIFY(anim.startValue().isValid());
917 anim.start();
918 QCOMPARE(object.property(propertyName), startValue);
919}
920
921void tst_QPropertyAnimation::zeroDurationStart()
922{
923 DummyPropertyAnimation anim;
924 QSignalSpy spy(&anim, &DummyPropertyAnimation::stateChanged);
925 QVERIFY(spy.isValid());
926 anim.setDuration(0);
927 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
928 anim.start();
929 //the animation stops immediately
930 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
931 QCOMPARE(spy.count(), 2);
932
933 //let's check the first state change
934 const QVariantList firstChange = spy.first();
935 //old state
936 QCOMPARE(qvariant_cast<QAbstractAnimation::State>(firstChange.last()), QAbstractAnimation::Stopped);
937 //new state
938 QCOMPARE(qvariant_cast<QAbstractAnimation::State>(firstChange.first()), QAbstractAnimation::Running);
939
940 //let's check the first state change
941 const QVariantList secondChange = spy.last();
942 //old state
943 QCOMPARE(qvariant_cast<QAbstractAnimation::State>(secondChange.last()), QAbstractAnimation::Running);
944 //new state
945 QCOMPARE(qvariant_cast<QAbstractAnimation::State>(secondChange.first()), QAbstractAnimation::Stopped);
946}
947
948void tst_QPropertyAnimation::zeroDurationForwardBackward()
949{
950 QObject o; o.setProperty(name: "test", value: 1);
951 QObject o2; o2.setProperty(name: "test", value: 2);
952 QObject o3; o3.setProperty(name: "test", value: 3);
953 QObject o4; o4.setProperty(name: "test", value: 4);
954 QPropertyAnimation prop(&o, "test"); prop.setDuration(0); prop.setStartValue(1); prop.setEndValue(2);
955
956 prop.start();
957 QCOMPARE(o.property("test").toInt(), 2);
958 prop.setDirection(QAbstractAnimation::Backward);
959 prop.start();
960 QCOMPARE(o.property("test").toInt(), 1);
961
962 prop.setDirection(QAbstractAnimation::Forward);
963 QPropertyAnimation prop2(&o2, "test"); prop2.setDuration(0); prop2.setStartValue(2); prop2.setEndValue(3);
964 QPropertyAnimation prop3(&o3, "test"); prop3.setDuration(0); prop3.setStartValue(3); prop3.setEndValue(4);
965 QPropertyAnimation prop4(&o4, "test"); prop4.setDuration(0); prop4.setStartValue(4); prop4.setEndValue(5);
966 QSequentialAnimationGroup group;
967 group.addAnimation(animation: &prop);
968 group.addAnimation(animation: &prop2);
969 group.addAnimation(animation: &prop3);
970 group.addAnimation(animation: &prop4);
971 group.start();
972
973 QCOMPARE(o.property("test").toInt(), 2);
974 QCOMPARE(o2.property("test").toInt(), 3);
975 QCOMPARE(o3.property("test").toInt(), 4);
976 QCOMPARE(o4.property("test").toInt(), 5);
977
978 group.setDirection(QAbstractAnimation::Backward);
979 group.start();
980
981 QCOMPARE(o.property("test").toInt(), 1);
982 QCOMPARE(o2.property("test").toInt(), 2);
983 QCOMPARE(o3.property("test").toInt(), 3);
984 QCOMPARE(o4.property("test").toInt(), 4);
985
986 group.removeAnimation(animation: &prop);
987 group.removeAnimation(animation: &prop2);
988 group.removeAnimation(animation: &prop3);
989 group.removeAnimation(animation: &prop4);
990}
991
992#define Pause 1
993#define Start 2
994#define Resume 3
995#define Stop 4
996
997void tst_QPropertyAnimation::operationsInStates_data()
998{
999 QTest::addColumn<QAbstractAnimation::State>(name: "originState");
1000 QTest::addColumn<int>(name: "operation");
1001 QTest::addColumn<QString>(name: "expectedWarning");
1002 QTest::addColumn<QAbstractAnimation::State>(name: "expectedState");
1003
1004 QString pauseWarn(QLatin1String("QAbstractAnimation::pause: Cannot pause a stopped animation"));
1005 QString resumeWarn(QLatin1String("QAbstractAnimation::resume: Cannot resume an animation that is not paused"));
1006
1007 QTest::newRow(dataTag: "S-pause") << QAbstractAnimation::Stopped << Pause << pauseWarn << QAbstractAnimation::Stopped;
1008 QTest::newRow(dataTag: "S-start") << QAbstractAnimation::Stopped << Start << QString() << QAbstractAnimation::Running;
1009 QTest::newRow(dataTag: "S-resume") << QAbstractAnimation::Stopped << Resume << resumeWarn << QAbstractAnimation::Stopped;
1010 QTest::newRow(dataTag: "S-stop") << QAbstractAnimation::Stopped << Stop << QString() << QAbstractAnimation::Stopped;
1011
1012 QTest::newRow(dataTag: "P-pause") << QAbstractAnimation::Paused << Pause << QString() << QAbstractAnimation::Paused;
1013 QTest::newRow(dataTag: "P-start") << QAbstractAnimation::Paused << Start << QString() << QAbstractAnimation::Running;
1014 QTest::newRow(dataTag: "P-resume") << QAbstractAnimation::Paused << Resume << QString() << QAbstractAnimation::Running;
1015 QTest::newRow(dataTag: "P-stop") << QAbstractAnimation::Paused << Stop << QString() << QAbstractAnimation::Stopped;
1016
1017 QTest::newRow(dataTag: "R-pause") << QAbstractAnimation::Running << Pause << QString() << QAbstractAnimation::Paused;
1018 QTest::newRow(dataTag: "R-start") << QAbstractAnimation::Running << Start << QString() << QAbstractAnimation::Running;
1019 QTest::newRow(dataTag: "R-resume") << QAbstractAnimation::Running << Resume << resumeWarn << QAbstractAnimation::Running;
1020 QTest::newRow(dataTag: "R-stop") << QAbstractAnimation::Running << Stop << QString() << QAbstractAnimation::Stopped;
1021}
1022
1023void tst_QPropertyAnimation::operationsInStates()
1024{
1025/**
1026 * | pause() |start() |resume() |stop()
1027 * ----------+------------+-----------+-----------+-------------------+
1028 * Stopped | Stopped |Running |Stopped |Stopped |
1029 * _| qWarning |restart |qWarning | |
1030 * Paused | Paused |Running |Running |Stopped |
1031 * _| | | | |
1032 * Running | Paused |Running |Running |Stopped |
1033 * | |restart |qWarning | |
1034 * ----------+------------+-----------+-----------+-------------------+
1035**/
1036
1037 QFETCH(QAbstractAnimation::State, originState);
1038 QFETCH(int, operation);
1039 QFETCH(QString, expectedWarning);
1040 QFETCH(QAbstractAnimation::State, expectedState);
1041
1042 QObject o;
1043 o.setProperty(name: "ole", value: 42);
1044 QPropertyAnimation anim(&o, "ole");
1045 anim.setEndValue(100);
1046 QSignalSpy spy(&anim, &QPropertyAnimation::stateChanged);
1047 QVERIFY(spy.isValid());
1048
1049 anim.stop();
1050 switch (originState) {
1051 case QAbstractAnimation::Stopped:
1052 break;
1053 case QAbstractAnimation::Paused:
1054 anim.start();
1055 anim.pause();
1056 break;
1057 case QAbstractAnimation::Running:
1058 anim.start();
1059 break;
1060 }
1061 if (!expectedWarning.isEmpty()) {
1062 QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning));
1063 }
1064 QCOMPARE(anim.state(), originState);
1065 switch (operation) {
1066 case Pause:
1067 anim.pause();
1068 break;
1069 case Start:
1070 anim.start();
1071 break;
1072 case Resume:
1073 anim.resume();
1074 break;
1075 case Stop:
1076 anim.stop();
1077 break;
1078 }
1079
1080 QCOMPARE(anim.state(), expectedState);
1081}
1082#undef Pause
1083#undef Start
1084#undef Resume
1085#undef Stop
1086
1087void tst_QPropertyAnimation::oneKeyValue()
1088{
1089 QObject o;
1090 o.setProperty(name: "ole", value: 42);
1091 QCOMPARE(o.property("ole").toInt(), 42);
1092
1093 QPropertyAnimation animation(&o, "ole");
1094 animation.setStartValue(43);
1095 animation.setEndValue(44);
1096 animation.setDuration(100);
1097
1098 animation.setCurrentTime(0);
1099
1100 QVERIFY(animation.currentValue().isValid());
1101 QCOMPARE(animation.currentValue().toInt(), 43);
1102 QCOMPARE(o.property("ole").toInt(), 42);
1103
1104 // remove the last key value
1105 animation.setKeyValueAt(step: 1.0, value: QVariant());
1106
1107 // we will neither interpolate, nor update the current value
1108 // since there is only one 1 key value defined
1109 animation.setCurrentTime(100);
1110
1111 // the animation should not have been modified
1112 QVERIFY(animation.currentValue().isValid());
1113 QCOMPARE(animation.currentValue().toInt(), 43);
1114 QCOMPARE(o.property("ole").toInt(), 42);
1115}
1116
1117void tst_QPropertyAnimation::updateOnSetKeyValues()
1118{
1119 QObject o;
1120 o.setProperty(name: "ole", value: 100);
1121 QCOMPARE(o.property("ole").toInt(), 100);
1122
1123 QPropertyAnimation animation(&o, "ole");
1124 animation.setStartValue(100);
1125 animation.setEndValue(200);
1126 animation.setDuration(100);
1127
1128 animation.setCurrentTime(50);
1129 QCOMPARE(animation.currentValue().toInt(), 150);
1130 animation.setKeyValueAt(step: 0.0, value: 300);
1131 QCOMPARE(animation.currentValue().toInt(), 250);
1132
1133 o.setProperty(name: "ole", value: 100);
1134 QPropertyAnimation animation2(&o, "ole");
1135 QVariantAnimation::KeyValues kValues;
1136 kValues << QVariantAnimation::KeyValue(0.0, 100) << QVariantAnimation::KeyValue(1.0, 200);
1137 animation2.setKeyValues(kValues);
1138 animation2.setDuration(100);
1139 animation2.setCurrentTime(50);
1140 QCOMPARE(animation2.currentValue().toInt(), 150);
1141
1142 kValues.clear();
1143 kValues << QVariantAnimation::KeyValue(0.0, 300) << QVariantAnimation::KeyValue(1.0, 200);
1144 animation2.setKeyValues(kValues);
1145
1146 QCOMPARE(animation2.currentValue().toInt(), animation.currentValue().toInt());
1147}
1148
1149
1150//this class will 'throw' an error in the test lib
1151// if the property ole is set to ErrorValue
1152class MyErrorObject : public QObject
1153{
1154 Q_OBJECT
1155 Q_PROPERTY(int ole READ ole WRITE setOle)
1156public:
1157
1158 static const int ErrorValue = 10000;
1159
1160 MyErrorObject() : m_ole(0) { }
1161 int ole() const { return m_ole; }
1162 void setOle(int o)
1163 {
1164 QVERIFY(o != ErrorValue);
1165 m_ole = o;
1166 }
1167
1168private:
1169 int m_ole;
1170
1171
1172};
1173
1174void tst_QPropertyAnimation::restart()
1175{
1176 //here we check that be restarting an animation
1177 //it doesn't get an bogus intermediate value (end value)
1178 //because the time is not yet reset to 0
1179 MyErrorObject o;
1180 o.setOle(100);
1181 QCOMPARE(o.property("ole").toInt(), 100);
1182
1183 QPropertyAnimation anim(&o, "ole");
1184 anim.setEndValue(200);
1185 anim.start();
1186 anim.setCurrentTime(anim.duration());
1187 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
1188 QCOMPARE(o.property("ole").toInt(), 200);
1189
1190 //we'll check that the animation never gets a wrong value when starting it
1191 //after having changed the end value
1192 anim.setEndValue(MyErrorObject::ErrorValue);
1193 anim.start();
1194}
1195
1196void tst_QPropertyAnimation::valueChanged()
1197{
1198 TestAnimationDriver timeDriver;
1199 //we check that we receive the valueChanged signal
1200 MyErrorObject o;
1201 o.setOle(0);
1202 QCOMPARE(o.property("ole").toInt(), 0);
1203 QPropertyAnimation anim(&o, "ole");
1204 anim.setEndValue(5);
1205 anim.setDuration(200);
1206 QSignalSpy spy(&anim, &QPropertyAnimation::valueChanged);
1207 QVERIFY(spy.isValid());
1208 anim.start();
1209 // Drive animation forward to its end
1210 timeDriver.wait(ms: anim.duration());
1211
1212 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
1213 QCOMPARE(anim.currentTime(), anim.duration());
1214
1215 //let's check that the values go forward
1216 QCOMPARE(spy.count(), 6); //we should have got everything from 0 to 5
1217 for (int i = 0; i < spy.count(); ++i) {
1218 QCOMPARE(qvariant_cast<QVariant>(spy.at(i).first()).toInt(), i);
1219 }
1220}
1221
1222//this class will help us make sure that 2 animations started
1223//at the same time also end at the same time
1224class MySyncObject : public MyErrorObject
1225{
1226 Q_OBJECT
1227public:
1228 MySyncObject() : anim(this, "ole")
1229 {
1230 anim.setEndValue(1000);
1231 }
1232public slots:
1233 void checkAnimationFinished()
1234 {
1235 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
1236 QCOMPARE(ole(), 1000);
1237 }
1238
1239public:
1240 QPropertyAnimation anim;
1241};
1242
1243void tst_QPropertyAnimation::twoAnimations()
1244{
1245 TestAnimationDriver timeDriver;
1246 MySyncObject o1, o2;
1247 o1.setOle(0);
1248 o2.setOle(0);
1249
1250 //when the animation in o1 is finished
1251 //the animation in o2 should stop around the same time
1252 //We use a queued connection to check just after the tick from the common timer
1253 //the other way is true too
1254 QObject::connect(sender: &o1.anim, SIGNAL(finished()),
1255 receiver: &o2, SLOT(checkAnimationFinished()), Qt::QueuedConnection);
1256 QObject::connect(sender: &o2.anim, SIGNAL(finished()),
1257 receiver: &o1, SLOT(checkAnimationFinished()), Qt::QueuedConnection);
1258
1259 o1.anim.start();
1260 o2.anim.start();
1261
1262 timeDriver.wait(ms: o1.anim.duration());
1263 QCOMPARE(o1.anim.state(), QAbstractAnimation::Stopped);
1264 QCOMPARE(o2.anim.state(), QAbstractAnimation::Stopped);
1265
1266 QCOMPARE(o1.ole(), 1000);
1267 QCOMPARE(o2.ole(), 1000);
1268}
1269
1270class MyComposedAnimation : public QPropertyAnimation
1271{
1272 Q_OBJECT
1273public:
1274 MyComposedAnimation(QObject *target, const QByteArray &propertyName, const QByteArray &innerPropertyName)
1275 : QPropertyAnimation(target, propertyName)
1276 {
1277 innerAnim = new QPropertyAnimation(target, innerPropertyName);
1278 this->setEndValue(1000);
1279 innerAnim->setEndValue(1000);
1280 innerAnim->setDuration(duration() + 100);
1281 }
1282
1283 void start()
1284 {
1285 QPropertyAnimation::start();
1286 innerAnim->start();
1287 }
1288
1289 void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
1290 {
1291 QPropertyAnimation::updateState(newState, oldState);
1292 if (newState == QAbstractAnimation::Stopped)
1293 delete innerAnim;
1294 }
1295
1296public:
1297 QPropertyAnimation *innerAnim;
1298};
1299
1300void tst_QPropertyAnimation::deletedInUpdateCurrentTime()
1301{
1302 TestAnimationDriver timeDriver;
1303 // this test case reproduces an animation being deleted in the updateCurrentTime of
1304 // another animation(was causing segfault).
1305 // the deleted animation must have been started after the animation that is deleting.
1306 AnimationObject o;
1307 o.setValue(0);
1308 o.setRealValue(0.0);
1309
1310 MyComposedAnimation composedAnimation(&o, "value", "realValue");
1311 composedAnimation.start();
1312 QCOMPARE(composedAnimation.state(), QAbstractAnimation::Running);
1313 timeDriver.wait(ms: composedAnimation.duration());
1314
1315 QCOMPARE(composedAnimation.state(), QAbstractAnimation::Stopped);
1316 QCOMPARE(o.value(), 1000);
1317}
1318
1319void tst_QPropertyAnimation::totalDuration()
1320{
1321 QPropertyAnimation anim;
1322 QCOMPARE(anim.totalDuration(), 250);
1323 anim.setLoopCount(2);
1324 QCOMPARE(anim.totalDuration(), 2*250);
1325 anim.setLoopCount(-1);
1326 QCOMPARE(anim.totalDuration(), -1);
1327 anim.setDuration(0);
1328 QCOMPARE(anim.totalDuration(), 0);
1329}
1330
1331void tst_QPropertyAnimation::zeroLoopCount()
1332{
1333 DummyPropertyAnimation animation;
1334 auto *anim = &animation;
1335 anim->setStartValue(0);
1336 anim->setDuration(20);
1337 anim->setLoopCount(0);
1338
1339 QSignalSpy runningSpy(anim, &QPropertyAnimation::stateChanged);
1340 QSignalSpy finishedSpy(anim, &QPropertyAnimation::finished);
1341
1342 QVERIFY(runningSpy.isValid());
1343 QVERIFY(finishedSpy.isValid());
1344
1345 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
1346 QCOMPARE(anim->currentValue().toInt(), 0);
1347 QCOMPARE(runningSpy.count(), 0);
1348 QCOMPARE(finishedSpy.count(), 0);
1349
1350 anim->start();
1351
1352 QCOMPARE(anim->state(), QAnimationGroup::Stopped);
1353 QCOMPARE(anim->currentValue().toInt(), 0);
1354 QCOMPARE(runningSpy.count(), 0);
1355 QCOMPARE(finishedSpy.count(), 0);
1356}
1357
1358
1359class RecursiveObject : public QObject
1360{
1361 Q_OBJECT
1362 Q_PROPERTY(qreal x READ x WRITE setX)
1363 Q_PROPERTY(qreal y READ y WRITE setY)
1364public:
1365 RecursiveObject() : m_x(0), m_y(0) {
1366 animation.setTargetObject(this);
1367 animation.setPropertyName("y");
1368 animation.setDuration(30);
1369 }
1370 qreal x() const { return m_x; }
1371 void setX(qreal x) {
1372 m_x = x;
1373 animation.setEndValue(x);
1374 animation.start();
1375 }
1376 qreal y() const { return m_y; }
1377 void setY(qreal y) { m_y = y; }
1378
1379 qreal m_x;
1380 qreal m_y;
1381 QPropertyAnimation animation;
1382};
1383
1384
1385void tst_QPropertyAnimation::recursiveAnimations()
1386{
1387 TestAnimationDriver timeDriver;
1388 RecursiveObject o;
1389 QPropertyAnimation anim;
1390 anim.setTargetObject(&o);
1391 anim.setPropertyName("x");
1392 anim.setDuration(30);
1393
1394 anim.setEndValue(4000);
1395 anim.start();
1396 timeDriver.wait(ms: anim.duration() + o.animation.duration());
1397 QCOMPARE(anim.state(), QAbstractAnimation::Stopped);
1398 QCOMPARE(o.animation.state(), QAbstractAnimation::Stopped);
1399 QCOMPARE(o.y(), qreal(4000));
1400}
1401
1402
1403QTEST_MAIN(tst_QPropertyAnimation)
1404#include "tst_qpropertyanimation.moc"
1405

source code of qtbase/tests/auto/corelib/animation/qpropertyanimation/tst_qpropertyanimation.cpp