1/****************************************************************************
2**
3** Copyright (C) 2016 Ford Motor Company
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "signaltransition.h"
41
42#include <QStateMachine>
43#include <QMetaProperty>
44#include <QQmlInfo>
45#include <QQmlEngine>
46#include <QQmlContext>
47#include <QQmlExpression>
48
49#include <private/qv4qobjectwrapper_p.h>
50#include <private/qjsvalue_p.h>
51#include <private/qv4scopedvalue_p.h>
52#include <private/qqmlcontext_p.h>
53#include <private/qqmlboundsignal_p.h>
54
55SignalTransition::SignalTransition(QState *parent)
56 : QSignalTransition(this, SIGNAL(invokeYourself()), parent), m_complete(false), m_signalExpression(nullptr)
57{
58 connect(asender: this, SIGNAL(signalChanged()), SIGNAL(qmlSignalChanged()));
59}
60
61bool SignalTransition::eventTest(QEvent *event)
62{
63 Q_ASSERT(event);
64 if (!QSignalTransition::eventTest(event))
65 return false;
66
67 if (m_guard.isEmpty())
68 return true;
69
70 QQmlContext *outerContext = QQmlEngine::contextForObject(this);
71 QQmlContext context(outerContext);
72 QQmlContextData::get(context: outerContext)->imports->addref();
73 QQmlContextData::get(context: &context)->imports = QQmlContextData::get(context: outerContext)->imports;
74
75 QStateMachine::SignalEvent *e = static_cast<QStateMachine::SignalEvent*>(event);
76
77 // Set arguments as context properties
78 int count = e->arguments().count();
79 QMetaMethod metaMethod = e->sender()->metaObject()->method(index: e->signalIndex());
80 const auto parameterNames = metaMethod.parameterNames();
81 for (int i = 0; i < count; i++)
82 context.setContextProperty(parameterNames[i], QVariant::fromValue(value: e->arguments().at(i)));
83
84 QQmlExpression expr(m_guard, &context, this);
85 QVariant result = expr.evaluate();
86
87 return result.toBool();
88}
89
90void SignalTransition::onTransition(QEvent *event)
91{
92 if (m_signalExpression) {
93 QStateMachine::SignalEvent *e = static_cast<QStateMachine::SignalEvent*>(event);
94 m_signalExpression->evaluate(args: e->arguments());
95 }
96 QSignalTransition::onTransition(event);
97}
98
99const QJSValue& SignalTransition::signal()
100{
101 return m_signal;
102}
103
104void SignalTransition::setSignal(const QJSValue &signal)
105{
106 if (m_signal.strictlyEquals(other: signal))
107 return;
108
109 m_signal = signal;
110
111 QV4::ExecutionEngine *jsEngine = QQmlEngine::contextForObject(this)->engine()->handle();
112 QV4::Scope scope(jsEngine);
113
114 QObject *sender;
115 QMetaMethod signalMethod;
116
117 QV4::ScopedValue value(scope, QJSValuePrivate::convertedToValue(e: jsEngine, jsval: m_signal));
118
119 // Did we get the "slot" that can be used to invoke the signal?
120 if (QV4::QObjectMethod *signalSlot = value->as<QV4::QObjectMethod>()) {
121 sender = signalSlot->object();
122 Q_ASSERT(sender);
123 signalMethod = sender->metaObject()->method(index: signalSlot->methodIndex());
124 } else if (QV4::QmlSignalHandler *signalObject = value->as<QV4::QmlSignalHandler>()) { // or did we get the signal object (the one with the connect()/disconnect() functions) ?
125 sender = signalObject->object();
126 Q_ASSERT(sender);
127 signalMethod = sender->metaObject()->method(index: signalObject->signalIndex());
128 } else {
129 qmlWarning(me: this) << tr(s: "Specified signal does not exist.");
130 return;
131 }
132
133 QSignalTransition::setSenderObject(sender);
134 QSignalTransition::setSignal(signalMethod.methodSignature());
135
136 connectTriggered();
137}
138
139QQmlScriptString SignalTransition::guard() const
140{
141 return m_guard;
142}
143
144void SignalTransition::setGuard(const QQmlScriptString &guard)
145{
146 if (m_guard == guard)
147 return;
148
149 m_guard = guard;
150 emit guardChanged();
151}
152
153void SignalTransition::invoke()
154{
155 emit invokeYourself();
156}
157
158void SignalTransition::connectTriggered()
159{
160 if (!m_complete || !m_compilationUnit)
161 return;
162
163 QObject *target = senderObject();
164 QQmlData *ddata = QQmlData::get(object: this);
165 QQmlContextData *ctxtdata = ddata ? ddata->outerContext : nullptr;
166
167 Q_ASSERT(m_bindings.count() == 1);
168 const QV4::CompiledData::Binding *binding = m_bindings.at(i: 0);
169 Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Script);
170
171 QV4::ExecutionEngine *jsEngine = QQmlEngine::contextForObject(this)->engine()->handle();
172 QV4::Scope scope(jsEngine);
173 QV4::Scoped<QV4::QObjectMethod> qobjectSignal(scope, QJSValuePrivate::convertedToValue(e: jsEngine, jsval: m_signal));
174 Q_ASSERT(qobjectSignal);
175 QMetaMethod metaMethod = target->metaObject()->method(index: qobjectSignal->methodIndex());
176 int signalIndex = QMetaObjectPrivate::signalIndex(m: metaMethod);
177
178 auto f = m_compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
179 if (ctxtdata) {
180 QQmlBoundSignalExpression *expression =
181 new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f);
182 expression->setNotifyOnValueChanged(false);
183 m_signalExpression.take(expression);
184 } else {
185 m_signalExpression.take(nullptr);
186 }
187}
188
189void SignalTransitionParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props)
190{
191 for (int ii = 0; ii < props.count(); ++ii) {
192 const QV4::CompiledData::Binding *binding = props.at(i: ii);
193
194 QString propName = compilationUnit->stringAt(index: binding->propertyNameIndex);
195
196 if (propName != QLatin1String("onTriggered")) {
197 error(binding: props.at(i: ii), description: SignalTransition::tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName));
198 return;
199 }
200
201 if (binding->type != QV4::CompiledData::Binding::Type_Script) {
202 error(binding, description: SignalTransition::tr(s: "SignalTransition: script expected"));
203 return;
204 }
205 }
206}
207
208void SignalTransitionParser::applyBindings(
209 QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
210 const QList<const QV4::CompiledData::Binding *> &bindings)
211{
212 SignalTransition *st = qobject_cast<SignalTransition*>(object);
213 st->m_compilationUnit = compilationUnit;
214 st->m_bindings = bindings;
215}
216
217/*!
218 \qmltype QAbstractTransition
219 \inqmlmodule QtQml.StateMachine
220 \omit
221 \ingroup statemachine-qmltypes
222 \endomit
223 \since 5.4
224
225 \brief The QAbstractTransition type is the base type of transitions between QAbstractState objects.
226
227 The QAbstractTransition type is the abstract base type of transitions
228 between states (QAbstractState objects) of a StateMachine.
229 QAbstractTransition is part of \l{The Declarative State Machine Framework}.
230
231 The sourceState() property has the source of the transition. The
232 targetState and targetStates properties return the target(s) of the
233 transition.
234
235 The triggered() signal is emitted when the transition has been triggered.
236
237 Do not use QAbstractTransition directly; use SignalTransition or
238 TimeoutTransition instead.
239
240 \sa SignalTransition, TimeoutTransition
241*/
242
243/*!
244 \qmlproperty bool QAbstractTransition::sourceState
245 \readonly sourceState
246
247 \brief The source state (parent) of this transition.
248*/
249
250/*!
251 \qmlproperty QAbstractState QAbstractTransition::targetState
252
253 \brief The target state of this transition.
254
255 If a transition has no target state, the transition may still be
256 triggered, but this will not cause the state machine's configuration to
257 change (i.e. the current state will not be exited and re-entered).
258*/
259
260/*!
261 \qmlproperty list<QAbstractState> QAbstractTransition::targetStates
262
263 \brief The target states of this transition.
264
265 If multiple states are specified, they all must be descendants of the
266 same parallel group state.
267*/
268
269/*!
270 \qmlsignal QAbstractTransition::triggered()
271
272 This signal is emitted when the transition has been triggered.
273*/
274
275/*!
276 \qmltype QSignalTransition
277 \inqmlmodule QtQml.StateMachine
278 \inherits QAbstractTransition
279 \omit
280 \ingroup statemachine-qmltypes
281 \endomit
282 \since 5.4
283
284 \brief The QSignalTransition type provides a transition based on a Qt signal.
285
286 Do not use QSignalTransition directly; use SignalTransition or
287 TimeoutTransition instead.
288
289 \sa SignalTransition, TimeoutTransition
290*/
291
292/*!
293 \qmlproperty string QSignalTransition::signal
294
295 \brief The signal which is associated with this signal transition.
296*/
297
298/*!
299 \qmlproperty QObject QSignalTransition::senderObject
300
301 \brief The sender object which is associated with this signal transition.
302*/
303
304
305/*!
306 \qmltype SignalTransition
307 \inqmlmodule QtQml.StateMachine
308 \inherits QSignalTransition
309 \ingroup statemachine-qmltypes
310 \since 5.4
311
312 \brief The SignalTransition type provides a transition based on a Qt signal.
313
314 SignalTransition is part of \l{The Declarative State Machine Framework}.
315
316 \section1 Example Usage
317
318 \snippet qml/statemachine/signaltransition.qml document
319
320 \clearfloat
321
322 \sa StateMachine, FinalState, TimeoutTransition
323*/
324
325/*!
326 \qmlproperty signal SignalTransition::signal
327
328 \brief The signal which is associated with this signal transition.
329
330 \snippet qml/statemachine/signaltransitionsignal.qml document
331*/
332
333/*!
334 \qmlproperty bool SignalTransition::guard
335
336 Guard conditions affect the behavior of a state machine by enabling
337 transitions only when they evaluate to true and disabling them when
338 they evaluate to false.
339
340 When the signal associated with this signal transition is emitted the
341 guard condition is evaluated. In the guard condition the arguments
342 of the signal can be used as demonstrated in the example below.
343
344 \snippet qml/statemachine/guardcondition.qml document
345
346 \sa signal
347*/
348
349#include "moc_signaltransition.cpp"
350

source code of qtdeclarative/src/imports/statemachine/signaltransition.cpp