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, context: this, 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 m_signal.removeBindingUnlessInWrapper();
93 if (m_signal.valueBypassingBindings().strictlyEquals(other: signal))
94 return;
95
96 QV4::ExecutionEngine *jsEngine = QQmlEngine::contextForObject(this)->engine()->handle();
97 QV4::Scope scope(jsEngine);
98
99 QObject *sender;
100 QMetaMethod signalMethod;
101
102 m_signal.setValueBypassingBindings(signal);
103 QV4::ScopedValue value(scope, QJSValuePrivate::asReturnedValue(jsval: &signal));
104
105 // Did we get the "slot" that can be used to invoke the signal?
106 if (QV4::QObjectMethod *signalSlot = value->as<QV4::QObjectMethod>()) {
107 sender = signalSlot->object();
108 Q_ASSERT(sender);
109 signalMethod = sender->metaObject()->method(index: signalSlot->methodIndex());
110 } else if (QV4::QmlSignalHandler *signalObject = value->as<QV4::QmlSignalHandler>()) {
111 // or did we get the signal object (the one with the connect()/disconnect() functions) ?
112 sender = signalObject->object();
113 Q_ASSERT(sender);
114 signalMethod = sender->metaObject()->method(index: signalObject->signalIndex());
115 } else {
116 qmlWarning(me: this) << tr(s: "Specified signal does not exist.");
117 return;
118 }
119
120 QSignalTransition::setSenderObject(sender);
121 // the call below will emit change signal, and the interceptor lambda in ctor will notify()
122 QSignalTransition::setSignal(signalMethod.methodSignature());
123
124 connectTriggered();
125}
126
127QBindable<QJSValue> SignalTransition::bindableSignal()
128{
129 return &m_signal;
130}
131
132QQmlScriptString SignalTransition::guard() const
133{
134 return m_guard;
135}
136
137void SignalTransition::setGuard(const QQmlScriptString &guard)
138{
139 m_guard = guard;
140}
141
142QBindable<QQmlScriptString> SignalTransition::bindableGuard()
143{
144 return &m_guard;
145}
146
147void SignalTransition::invoke()
148{
149 emit invokeYourself();
150}
151
152void SignalTransition::connectTriggered()
153{
154 if (!m_complete || !m_compilationUnit)
155 return;
156
157 const QObject *target = senderObject();
158 QQmlData *ddata = QQmlData::get(object: this);
159 QQmlRefPointer<QQmlContextData> ctxtdata = ddata ? ddata->outerContext : nullptr;
160
161 Q_ASSERT(m_bindings.size() == 1);
162 const QV4::CompiledData::Binding *binding = m_bindings.at(i: 0);
163 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
164
165 QV4::ExecutionEngine *jsEngine = QQmlEngine::contextForObject(this)->engine()->handle();
166 QV4::Scope scope(jsEngine);
167 QV4::Scoped<QV4::QObjectMethod> qobjectSignal(
168 scope, QJSValuePrivate::asReturnedValue(jsval: &m_signal.value()));
169 if (!qobjectSignal) {
170 m_signalExpression.adopt(other: nullptr);
171 return;
172 }
173
174 QMetaMethod metaMethod = target->metaObject()->method(index: qobjectSignal->methodIndex());
175 int signalIndex = QMetaObjectPrivate::signalIndex(m: metaMethod);
176
177 auto f = m_compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
178 if (ctxtdata) {
179 QQmlRefPointer<QQmlBoundSignalExpression> expression(
180 new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f),
181 QQmlRefPointer<QQmlBoundSignalExpression>::Adopt);
182 expression->setNotifyOnValueChanged(false);
183 m_signalExpression = expression;
184 } else {
185 m_signalExpression.adopt(other: nullptr);
186 }
187}
188
189void SignalTransitionParser::verifyBindings(
190 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
191 const QList<const QV4::CompiledData::Binding *> &props)
192{
193 for (int ii = 0; ii < props.size(); ++ii) {
194 const QV4::CompiledData::Binding *binding = props.at(i: ii);
195
196 QString propName = compilationUnit->stringAt(index: binding->propertyNameIndex);
197
198 if (propName != QLatin1String("onTriggered")) {
199 error(binding: props.at(i: ii), description: SignalTransition::tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName));
200 return;
201 }
202
203 if (binding->type() != QV4::CompiledData::Binding::Type_Script) {
204 error(binding, description: SignalTransition::tr(s: "SignalTransition: script expected"));
205 return;
206 }
207 }
208}
209
210void SignalTransitionParser::applyBindings(
211 QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
212 const QList<const QV4::CompiledData::Binding *> &bindings)
213{
214 SignalTransition *st = qobject_cast<SignalTransition*>(object);
215 st->m_compilationUnit = compilationUnit;
216 st->m_bindings = bindings;
217}
218
219/*!
220 \qmltype QAbstractTransition
221 \inqmlmodule QtQml.StateMachine
222 \ingroup statemachine-qmltypes
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{Qt State Machine QML Guide}{Qt State Machine QML API}
230
231
232 The sourceState() property has the source of the transition. The
233 targetState and targetStates properties return the target(s) of the
234 transition.
235
236 The triggered() signal is emitted when the transition has been triggered.
237
238 Do not use QAbstractTransition directly; use SignalTransition or
239 TimeoutTransition instead.
240
241 \sa SignalTransition, TimeoutTransition
242*/
243
244/*!
245 \qmlproperty bool QAbstractTransition::sourceState
246 \readonly sourceState
247
248 \brief The source state (parent) of this transition.
249*/
250
251/*!
252 \qmlproperty QAbstractState QAbstractTransition::targetState
253
254 \brief The target state of this transition.
255
256 If a transition has no target state, the transition may still be
257 triggered, but this will not cause the state machine's configuration to
258 change (i.e. the current state will not be exited and re-entered).
259*/
260
261/*!
262 \qmlproperty list<QAbstractState> QAbstractTransition::targetStates
263
264 \brief The target states of this transition.
265
266 If multiple states are specified, they all must be descendants of the
267 same parallel group state.
268*/
269
270/*!
271 \qmlsignal QAbstractTransition::triggered()
272
273 This signal is emitted when the transition has been triggered.
274*/
275
276/*!
277 \qmltype QSignalTransition
278 \inqmlmodule QtQml.StateMachine
279 \inherits QAbstractTransition
280 \ingroup statemachine-qmltypes
281 \since 5.4
282
283 \brief The QSignalTransition type provides a transition based on a Qt signal.
284
285 Do not use QSignalTransition directly; use SignalTransition or
286 TimeoutTransition instead.
287
288 \sa SignalTransition, TimeoutTransition
289*/
290
291/*!
292 \qmlproperty string QSignalTransition::signal
293
294 \brief The signal which is associated with this signal transition.
295*/
296
297/*!
298 \qmlproperty QObject QSignalTransition::senderObject
299
300 \brief The sender object which is associated with this signal transition.
301*/
302
303
304/*!
305 \qmltype SignalTransition
306 \inqmlmodule QtQml.StateMachine
307 \inherits QSignalTransition
308 \ingroup statemachine-qmltypes
309 \since 5.4
310
311 \brief The SignalTransition type provides a transition based on a Qt signal.
312
313 SignalTransition is part of \l{Qt State Machine QML Guide}{Qt State Machine QML API}.
314
315 \section1 Example Usage
316
317 \snippet qml/statemachine/signaltransition.qml document
318
319 \clearfloat
320
321 \sa StateMachine, FinalState, TimeoutTransition
322*/
323
324/*!
325 \qmlproperty signal SignalTransition::signal
326
327 \brief The signal which is associated with this signal transition.
328
329 \snippet qml/statemachine/signaltransitionsignal.qml document
330*/
331
332/*!
333 \qmlproperty bool SignalTransition::guard
334
335 Guard conditions affect the behavior of a state machine by enabling
336 transitions only when they evaluate to true and disabling them when
337 they evaluate to false.
338
339 When the signal associated with this signal transition is emitted the
340 guard condition is evaluated. In the guard condition the arguments
341 of the signal can be used as demonstrated in the example below.
342
343 \snippet qml/statemachine/guardcondition.qml document
344
345 \sa signal
346*/
347
348#include "moc_signaltransition_p.cpp"
349

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