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 | |
18 | SignalTransition::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 | |
24 | bool 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 | |
52 | void 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 | |
85 | const QJSValue& SignalTransition::signal() |
86 | { |
87 | return m_signal; |
88 | } |
89 | |
90 | void 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 | |
127 | QBindable<QJSValue> SignalTransition::bindableSignal() |
128 | { |
129 | return &m_signal; |
130 | } |
131 | |
132 | QQmlScriptString SignalTransition::guard() const |
133 | { |
134 | return m_guard; |
135 | } |
136 | |
137 | void SignalTransition::setGuard(const QQmlScriptString &guard) |
138 | { |
139 | m_guard = guard; |
140 | } |
141 | |
142 | QBindable<QQmlScriptString> SignalTransition::bindableGuard() |
143 | { |
144 | return &m_guard; |
145 | } |
146 | |
147 | void SignalTransition::invoke() |
148 | { |
149 | emit invokeYourself(); |
150 | } |
151 | |
152 | void 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 | |
189 | void 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 | |
210 | void 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 | |