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 QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "lifecycle.h"
52#include "stickman.h"
53#include "node.h"
54#include "animation.h"
55#include "graphicsview.h"
56
57#include <QEventTransition>
58#include <QFile>
59#include <QParallelAnimationGroup>
60#include <QPropertyAnimation>
61#include <QRandomGenerator>
62#include <QSignalTransition>
63#include <QState>
64#include <QStateMachine>
65#include <QTimer>
66
67class KeyPressTransition: public QSignalTransition
68{
69public:
70 KeyPressTransition(GraphicsView *receiver, Qt::Key key)
71 : QSignalTransition(receiver, &GraphicsView::keyPressed), m_key(key)
72 {
73 }
74 KeyPressTransition(GraphicsView *receiver, Qt::Key key, QAbstractState *target)
75 : QSignalTransition(receiver, &GraphicsView::keyPressed), m_key(key)
76 {
77 setTargetState(target);
78 }
79
80 bool eventTest(QEvent *e) override
81 {
82 if (QSignalTransition::eventTest(event: e)) {
83 QVariant key = static_cast<QStateMachine::SignalEvent*>(e)->arguments().at(i: 0);
84 return (key.toInt() == int(m_key));
85 }
86
87 return false;
88 }
89private:
90 Qt::Key m_key;
91};
92
93//! [4]
94class LightningStrikesTransition: public QEventTransition
95{
96public:
97 LightningStrikesTransition(QAbstractState *target)
98 : QEventTransition(this, QEvent::Timer)
99 {
100 setTargetState(target);
101 startTimer(interval: 1000);
102 }
103
104 bool eventTest(QEvent *e) override
105 {
106 return QEventTransition::eventTest(event: e) && QRandomGenerator::global()->bounded(highest: 50) == 0;
107 }
108};
109//! [4]
110
111LifeCycle::LifeCycle(StickMan *stickMan, GraphicsView *keyReceiver)
112 : m_stickMan(stickMan), m_keyReceiver(keyReceiver)
113{
114 // Create animation group to be used for all transitions
115 m_animationGroup = new QParallelAnimationGroup();
116 const int stickManNodeCount = m_stickMan->nodeCount();
117 for (int i = 0; i < stickManNodeCount; ++i) {
118 QPropertyAnimation *pa = new QPropertyAnimation(m_stickMan->node(idx: i), "pos");
119 m_animationGroup->addAnimation(animation: pa);
120 }
121
122 // Set up initial state graph
123//! [3]
124 m_machine = new QStateMachine();
125 m_machine->addDefaultAnimation(animation: m_animationGroup);
126//! [3]
127
128 m_alive = new QState(m_machine);
129 m_alive->setObjectName("alive");
130
131 // Make it blink when lightning strikes before entering dead animation
132 QState *lightningBlink = new QState(m_machine);
133 lightningBlink->assignProperty(object: m_stickMan->scene(), name: "backgroundBrush", value: QColor(Qt::white));
134 lightningBlink->assignProperty(object: m_stickMan, name: "penColor", value: QColor(Qt::black));
135 lightningBlink->assignProperty(object: m_stickMan, name: "fillColor", value: QColor(Qt::white));
136 lightningBlink->assignProperty(object: m_stickMan, name: "isDead", value: true);
137
138//! [5]
139 QTimer *timer = new QTimer(lightningBlink);
140 timer->setSingleShot(true);
141 timer->setInterval(100);
142 QObject::connect(sender: lightningBlink, signal: &QAbstractState::entered,
143 receiver: timer, slot: QOverload<>::of(ptr: &QTimer::start));
144 QObject::connect(sender: lightningBlink, signal: &QAbstractState::exited,
145 receiver: timer, slot: &QTimer::stop);
146//! [5]
147
148 m_dead = new QState(m_machine);
149 m_dead->assignProperty(object: m_stickMan->scene(), name: "backgroundBrush", value: QColor(Qt::black));
150 m_dead->assignProperty(object: m_stickMan, name: "penColor", value: QColor(Qt::white));
151 m_dead->assignProperty(object: m_stickMan, name: "fillColor", value: QColor(Qt::black));
152 m_dead->setObjectName("dead");
153
154 // Idle state (sets no properties)
155 m_idle = new QState(m_alive);
156 m_idle->setObjectName("idle");
157
158 m_alive->setInitialState(m_idle);
159
160 // Lightning strikes at random
161 m_alive->addTransition(transition: new LightningStrikesTransition(lightningBlink));
162//! [0]
163 lightningBlink->addTransition(obj: timer, signal: &QTimer::timeout, target: m_dead);
164//! [0]
165
166 m_machine->setInitialState(m_alive);
167}
168
169void LifeCycle::setDeathAnimation(const QString &fileName)
170{
171 QState *deathAnimation = makeState(parentState: m_dead, animationFileName: fileName);
172 m_dead->setInitialState(deathAnimation);
173}
174
175void LifeCycle::start()
176{
177 m_machine->start();
178}
179
180void LifeCycle::addActivity(const QString &fileName, Qt::Key key, QObject *sender, const char *signal)
181{
182 QState *state = makeState(parentState: m_alive, animationFileName: fileName);
183 m_alive->addTransition(transition: new KeyPressTransition(m_keyReceiver, key, state));
184
185 if (sender && signal)
186 m_alive->addTransition(sender, signal, target: state);
187}
188
189QState *LifeCycle::makeState(QState *parentState, const QString &animationFileName)
190{
191 QState *topLevel = new QState(parentState);
192
193 Animation animation;
194 {
195 QFile file(animationFileName);
196 if (file.open(flags: QIODevice::ReadOnly))
197 animation.load(device: &file);
198 }
199
200 const int frameCount = animation.totalFrames();
201 QState *previousState = nullptr;
202 for (int i = 0; i < frameCount; ++i) {
203 animation.setCurrentFrame(i);
204
205//! [1]
206 QState *frameState = new QState(topLevel);
207 const int nodeCount = animation.nodeCount();
208 for (int j = 0; j < nodeCount; ++j)
209 frameState->assignProperty(object: m_stickMan->node(idx: j), name: "pos", value: animation.nodePos(idx: j));
210//! [1]
211
212 frameState->setObjectName(QString::fromLatin1(str: "frame %0").arg(a: i));
213 if (previousState == nullptr)
214 topLevel->setInitialState(frameState);
215 else
216//! [2]
217 previousState->addTransition(obj: previousState, signal: &QState::propertiesAssigned, target: frameState);
218//! [2]
219
220 previousState = frameState;
221 }
222
223 // Loop
224 previousState->addTransition(obj: previousState, signal: &QState::propertiesAssigned, target: topLevel->initialState());
225
226 return topLevel;
227
228}
229
230LifeCycle::~LifeCycle()
231{
232 delete m_machine;
233 delete m_animationGroup;
234}
235

source code of qtbase/examples/widgets/animation/stickman/lifecycle.cpp