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 | |
55 | SignalTransition::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 | |
61 | bool 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 | |
90 | void 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 | |
99 | const QJSValue& SignalTransition::signal() |
100 | { |
101 | return m_signal; |
102 | } |
103 | |
104 | void 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 | |
139 | QQmlScriptString SignalTransition::guard() const |
140 | { |
141 | return m_guard; |
142 | } |
143 | |
144 | void SignalTransition::setGuard(const QQmlScriptString &guard) |
145 | { |
146 | if (m_guard == guard) |
147 | return; |
148 | |
149 | m_guard = guard; |
150 | emit guardChanged(); |
151 | } |
152 | |
153 | void SignalTransition::invoke() |
154 | { |
155 | emit invokeYourself(); |
156 | } |
157 | |
158 | void 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 | |
189 | void 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 | |
208 | void 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 | |