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, 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 | 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 | |
128 | QBindable<QJSValue> SignalTransition::bindableSignal() |
129 | { |
130 | return &m_signal; |
131 | } |
132 | |
133 | QQmlScriptString SignalTransition::guard() const |
134 | { |
135 | return m_guard; |
136 | } |
137 | |
138 | void SignalTransition::setGuard(const QQmlScriptString &guard) |
139 | { |
140 | m_guard = guard; |
141 | } |
142 | |
143 | QBindable<QQmlScriptString> SignalTransition::bindableGuard() |
144 | { |
145 | return &m_guard; |
146 | } |
147 | |
148 | void SignalTransition::invoke() |
149 | { |
150 | emit invokeYourself(); |
151 | } |
152 | |
153 | void 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 | |
190 | void 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 | |
209 | void 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 | |