1// Copyright (C) 2016 Ford Motor Company
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "signaltransition_p.h"
5
6#include <QStateMachine>
7#include <QMetaProperty>
8#include <QQmlInfo>
9#include <QQmlEngine>
10#include <QQmlContext>
11#include <QQmlExpression>
12
13#include <private/qv4qobjectwrapper_p.h>
14#include <private/qjsvalue_p.h>
15#include <private/qv4scopedvalue_p.h>
16#include <private/qqmlcontext_p.h>
17
18SignalTransition::SignalTransition(QState *parent)
19 : QSignalTransition(this, SIGNAL(invokeYourself()), parent), m_complete(false), m_signalExpression(nullptr)
20{
21 connect(sender: this, signal: &SignalTransition::signalChanged, slot: [this](){ m_signal.notify(); });
22}
23
24bool SignalTransition::eventTest(QEvent *event)
25{
26 Q_ASSERT(event);
27 if (!QSignalTransition::eventTest(event))
28 return false;
29
30 if (m_guard.value().isEmpty())
31 return true;
32
33 QQmlContext *outerContext = QQmlEngine::contextForObject(this);
34 QQmlContext context(outerContext);
35 QQmlContextData::get(context: &context)->setImports(QQmlContextData::get(context: outerContext)->imports());
36
37 QStateMachine::SignalEvent *e = static_cast<QStateMachine::SignalEvent*>(event);
38
39 // Set arguments as context properties
40 int count = e->arguments().size();
41 QMetaMethod metaMethod = e->sender()->metaObject()->method(index: e->signalIndex());
42 const auto parameterNames = metaMethod.parameterNames();
43 for (int i = 0; i < count; i++)
44 context.setContextProperty(QString::fromUtf8(ba: parameterNames[i]), QVariant::fromValue(value: e->arguments().at(i)));
45
46 QQmlExpression expr(m_guard.value(), &context, this);
47 QVariant result = expr.evaluate();
48
49 return result.toBool();
50}
51
52void SignalTransition::onTransition(QEvent *event)
53{
54 if (QQmlEnginePrivate *engine = m_signalExpression
55 ? QQmlEnginePrivate::get(e: m_signalExpression->engine())
56 : nullptr) {
57
58 QStateMachine::SignalEvent *e = static_cast<QStateMachine::SignalEvent*>(event);
59
60 QVarLengthArray<void *, 2> argValues;
61 QVarLengthArray<QMetaType, 2> argTypes;
62
63 QVariantList eventArguments = e->arguments();
64 const int argCount = eventArguments.size();
65 argValues.reserve(sz: argCount + 1);
66 argTypes.reserve(sz: argCount + 1);
67
68 // We're not interested in the return value
69 argValues.append(t: nullptr);
70 argTypes.append(t: QMetaType());
71
72 for (QVariant &arg : eventArguments) {
73 argValues.append(t: arg.data());
74 argTypes.append(t: arg.metaType());
75 }
76
77 engine->referenceScarceResources();
78 m_signalExpression->QQmlJavaScriptExpression::evaluate(
79 a: argValues.data(), types: argTypes.constData(), argc: argCount);
80 engine->dereferenceScarceResources();
81 }
82 QSignalTransition::onTransition(event);
83}
84
85const QJSValue& SignalTransition::signal()
86{
87 return m_signal;
88}
89
90void SignalTransition::setSignal(const QJSValue &signal)
91{
92 if (m_signal.value().strictlyEquals(other: signal)) {
93 m_signal.removeBindingUnlessInWrapper();
94 return;
95 }
96
97 QV4::ExecutionEngine *jsEngine = QQmlEngine::contextForObject(this)->engine()->handle();
98 QV4::Scope scope(jsEngine);
99
100 QObject *sender;
101 QMetaMethod signalMethod;
102
103 m_signal = signal;
104 QV4::ScopedValue value(scope, QJSValuePrivate::asReturnedValue(jsval: &signal));
105
106 // Did we get the "slot" that can be used to invoke the signal?
107 if (QV4::QObjectMethod *signalSlot = value->as<QV4::QObjectMethod>()) {
108 sender = signalSlot->object();
109 Q_ASSERT(sender);
110 signalMethod = sender->metaObject()->method(index: signalSlot->methodIndex());
111 } else if (QV4::QmlSignalHandler *signalObject = value->as<QV4::QmlSignalHandler>()) {
112 // or did we get the signal object (the one with the connect()/disconnect() functions) ?
113 sender = signalObject->object();
114 Q_ASSERT(sender);
115 signalMethod = sender->metaObject()->method(index: signalObject->signalIndex());
116 } else {
117 qmlWarning(me: this) << tr(s: "Specified signal does not exist.");
118 return;
119 }
120
121 QSignalTransition::setSenderObject(sender);
122 // the call below will emit change signal, and the interceptor lambda in ctor will notify()
123 QSignalTransition::setSignal(signalMethod.methodSignature());
124
125 connectTriggered();
126}
127
128QBindable<QJSValue> SignalTransition::bindableSignal()
129{
130 return &m_signal;
131}
132
133QQmlScriptString SignalTransition::guard() const
134{
135 return m_guard;
136}
137
138void SignalTransition::setGuard(const QQmlScriptString &guard)
139{
140 m_guard = guard;
141}
142
143QBindable<QQmlScriptString> SignalTransition::bindableGuard()
144{
145 return &m_guard;
146}
147
148void SignalTransition::invoke()
149{
150 emit invokeYourself();
151}
152
153void SignalTransition::connectTriggered()
154{
155 if (!m_complete || !m_compilationUnit)
156 return;
157
158 const QObject *target = senderObject();
159 QQmlData *ddata = QQmlData::get(object: this);
160 QQmlRefPointer<QQmlContextData> ctxtdata = ddata ? ddata->outerContext : nullptr;
161
162 Q_ASSERT(m_bindings.size() == 1);
163 const QV4::CompiledData::Binding *binding = m_bindings.at(i: 0);
164 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
165
166 QV4::ExecutionEngine *jsEngine = QQmlEngine::contextForObject(this)->engine()->handle();
167 QV4::Scope scope(jsEngine);
168 QV4::Scoped<QV4::QObjectMethod> qobjectSignal(
169 scope, QJSValuePrivate::asReturnedValue(jsval: &m_signal.value()));
170 if (!qobjectSignal) {
171 m_signalExpression.adopt(nullptr);
172 return;
173 }
174
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 QQmlRefPointer<QQmlBoundSignalExpression> expression(
181 new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f),
182 QQmlRefPointer<QQmlBoundSignalExpression>::Adopt);
183 expression->setNotifyOnValueChanged(false);
184 m_signalExpression = expression;
185 } else {
186 m_signalExpression.adopt(nullptr);
187 }
188}
189
190void SignalTransitionParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props)
191{
192 for (int ii = 0; ii < props.size(); ++ii) {
193 const QV4::CompiledData::Binding *binding = props.at(i: ii);
194
195 QString propName = compilationUnit->stringAt(index: binding->propertyNameIndex);
196
197 if (propName != QLatin1String("onTriggered")) {
198 error(binding: props.at(i: ii), description: SignalTransition::tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName));
199 return;
200 }
201
202 if (binding->type() != QV4::CompiledData::Binding::Type_Script) {
203 error(binding, description: SignalTransition::tr(s: "SignalTransition: script expected"));
204 return;
205 }
206 }
207}
208
209void SignalTransitionParser::applyBindings(
210 QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
211 const QList<const QV4::CompiledData::Binding *> &bindings)
212{
213 SignalTransition *st = qobject_cast<SignalTransition*>(object);
214 st->m_compilationUnit = compilationUnit;
215 st->m_bindings = bindings;
216}
217
218/*!
219 \qmltype QAbstractTransition
220 \inqmlmodule QtQml.StateMachine
221 \ingroup statemachine-qmltypes
222 \since 5.4
223
224 \brief The QAbstractTransition type is the base type of transitions between QAbstractState objects.
225
226 The QAbstractTransition type is the abstract base type of transitions
227 between states (QAbstractState objects) of a StateMachine.
228 QAbstractTransition is part of \l{Qt State Machine QML Guide}{Qt State Machine QML API}
229
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 \ingroup statemachine-qmltypes
280 \since 5.4
281
282 \brief The QSignalTransition type provides a transition based on a Qt signal.
283
284 Do not use QSignalTransition directly; use SignalTransition or
285 TimeoutTransition instead.
286
287 \sa SignalTransition, TimeoutTransition
288*/
289
290/*!
291 \qmlproperty string QSignalTransition::signal
292
293 \brief The signal which is associated with this signal transition.
294*/
295
296/*!
297 \qmlproperty QObject QSignalTransition::senderObject
298
299 \brief The sender object which is associated with this signal transition.
300*/
301
302
303/*!
304 \qmltype SignalTransition
305 \inqmlmodule QtQml.StateMachine
306 \inherits QSignalTransition
307 \ingroup statemachine-qmltypes
308 \since 5.4
309
310 \brief The SignalTransition type provides a transition based on a Qt signal.
311
312 SignalTransition is part of \l{Qt State Machine QML Guide}{Qt State Machine QML API}.
313
314 \section1 Example Usage
315
316 \snippet qml/statemachine/signaltransition.qml document
317
318 \clearfloat
319
320 \sa StateMachine, FinalState, TimeoutTransition
321*/
322
323/*!
324 \qmlproperty signal SignalTransition::signal
325
326 \brief The signal which is associated with this signal transition.
327
328 \snippet qml/statemachine/signaltransitionsignal.qml document
329*/
330
331/*!
332 \qmlproperty bool SignalTransition::guard
333
334 Guard conditions affect the behavior of a state machine by enabling
335 transitions only when they evaluate to true and disabling them when
336 they evaluate to false.
337
338 When the signal associated with this signal transition is emitted the
339 guard condition is evaluated. In the guard condition the arguments
340 of the signal can be used as demonstrated in the example below.
341
342 \snippet qml/statemachine/guardcondition.qml document
343
344 \sa signal
345*/
346
347#include "moc_signaltransition_p.cpp"
348

source code of qtscxml/src/statemachineqml/signaltransition.cpp