1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtCore 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 "qstatemachine.h" |
41 | #include "qstate.h" |
42 | #include "qstate_p.h" |
43 | #include "qstatemachine_p.h" |
44 | #include "qabstracttransition.h" |
45 | #include "qabstracttransition_p.h" |
46 | #include "qsignaltransition.h" |
47 | #include "qsignaltransition_p.h" |
48 | #include "qsignaleventgenerator_p.h" |
49 | #include "qabstractstate.h" |
50 | #include "qabstractstate_p.h" |
51 | #include "qfinalstate.h" |
52 | #include "qhistorystate.h" |
53 | #include "qhistorystate_p.h" |
54 | #include "private/qobject_p.h" |
55 | #include "private/qthread_p.h" |
56 | |
57 | #if QT_CONFIG(qeventtransition) |
58 | #include "qeventtransition.h" |
59 | #include "qeventtransition_p.h" |
60 | #endif |
61 | |
62 | #if QT_CONFIG(animation) |
63 | #include "qpropertyanimation.h" |
64 | #include "qanimationgroup.h" |
65 | #include <private/qvariantanimation_p.h> |
66 | #endif |
67 | |
68 | #include <QtCore/qmetaobject.h> |
69 | #include <qdebug.h> |
70 | |
71 | #include <algorithm> |
72 | |
73 | QT_BEGIN_NAMESPACE |
74 | |
75 | /*! |
76 | \class QStateMachine |
77 | \inmodule QtCore |
78 | \reentrant |
79 | |
80 | \brief The QStateMachine class provides a hierarchical finite state machine. |
81 | |
82 | \since 4.6 |
83 | \ingroup statemachine |
84 | |
85 | QStateMachine is based on the concepts and notation of |
86 | \l{http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf}{Statecharts}. |
87 | QStateMachine is part of \l{The State Machine Framework}. |
88 | |
89 | A state machine manages a set of states (classes that inherit from |
90 | QAbstractState) and transitions (descendants of |
91 | QAbstractTransition) between those states; these states and |
92 | transitions define a state graph. Once a state graph has been |
93 | built, the state machine can execute it. QStateMachine's |
94 | execution algorithm is based on the \l{http://www.w3.org/TR/scxml/}{State Chart XML (SCXML)} |
95 | algorithm. The framework's \l{The State Machine |
96 | Framework}{overview} gives several state graphs and the code to |
97 | build them. |
98 | |
99 | Use the addState() function to add a top-level state to the state machine. |
100 | States are removed with the removeState() function. Removing states while |
101 | the machine is running is discouraged. |
102 | |
103 | Before the machine can be started, the \l{initialState}{initial |
104 | state} must be set. The initial state is the state that the |
105 | machine enters when started. You can then start() the state |
106 | machine. The started() signal is emitted when the initial state is |
107 | entered. |
108 | |
109 | The machine is event driven and keeps its own event loop. Events |
110 | are posted to the machine through postEvent(). Note that this |
111 | means that it executes asynchronously, and that it will not |
112 | progress without a running event loop. You will normally not have |
113 | to post events to the machine directly as Qt's transitions, e.g., |
114 | QEventTransition and its subclasses, handle this. But for custom |
115 | transitions triggered by events, postEvent() is useful. |
116 | |
117 | The state machine processes events and takes transitions until a |
118 | top-level final state is entered; the state machine then emits the |
119 | finished() signal. You can also stop() the state machine |
120 | explicitly. The stopped() signal is emitted in this case. |
121 | |
122 | The following snippet shows a state machine that will finish when a button |
123 | is clicked: |
124 | |
125 | \snippet code/src_corelib_statemachine_qstatemachine.cpp simple state machine |
126 | |
127 | This code example uses QState, which inherits QAbstractState. The |
128 | QState class provides a state that you can use to set properties |
129 | and invoke methods on \l{QObject}s when the state is entered or |
130 | exited. It also contains convenience functions for adding |
131 | transitions, e.g., \l{QSignalTransition}s as in this example. See |
132 | the QState class description for further details. |
133 | |
134 | If an error is encountered, the machine will look for an |
135 | \l{errorState}{error state}, and if one is available, it will |
136 | enter this state. The types of errors possible are described by the |
137 | \l{QStateMachine::}{Error} enum. After the error state is entered, |
138 | the type of the error can be retrieved with error(). The execution |
139 | of the state graph will not stop when the error state is entered. If |
140 | no error state applies to the erroneous state, the machine will stop |
141 | executing and an error message will be printed to the console. |
142 | |
143 | \note Important: setting the \l{ChildMode} of a state machine to parallel (\l{ParallelStates}) |
144 | results in an invalid state machine. It can only be set to (or kept as) |
145 | \l{ExclusiveStates}. |
146 | |
147 | \sa QAbstractState, QAbstractTransition, QState, {The State Machine Framework} |
148 | */ |
149 | |
150 | /*! |
151 | \property QStateMachine::errorString |
152 | |
153 | \brief the error string of this state machine |
154 | */ |
155 | |
156 | /*! |
157 | \property QStateMachine::globalRestorePolicy |
158 | |
159 | \brief the restore policy for states of this state machine. |
160 | |
161 | The default value of this property is |
162 | QState::DontRestoreProperties. |
163 | */ |
164 | |
165 | /*! |
166 | \property QStateMachine::running |
167 | \since 5.4 |
168 | |
169 | \brief the running state of this state machine |
170 | |
171 | \sa start(), stop(), started(), stopped(), runningChanged() |
172 | */ |
173 | |
174 | #if QT_CONFIG(animation) |
175 | /*! |
176 | \property QStateMachine::animated |
177 | |
178 | \brief whether animations are enabled |
179 | |
180 | The default value of this property is \c true. |
181 | |
182 | \sa QAbstractTransition::addAnimation() |
183 | */ |
184 | #endif |
185 | |
186 | // #define QSTATEMACHINE_DEBUG |
187 | // #define QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
188 | |
189 | struct CalculationCache { |
190 | struct TransitionInfo { |
191 | QList<QAbstractState*> effectiveTargetStates; |
192 | QSet<QAbstractState*> exitSet; |
193 | QAbstractState *transitionDomain; |
194 | |
195 | bool effectiveTargetStatesIsKnown: 1; |
196 | bool exitSetIsKnown : 1; |
197 | bool transitionDomainIsKnown : 1; |
198 | |
199 | TransitionInfo() |
200 | : transitionDomain(nullptr) |
201 | , effectiveTargetStatesIsKnown(false) |
202 | , exitSetIsKnown(false) |
203 | , transitionDomainIsKnown(false) |
204 | {} |
205 | }; |
206 | |
207 | typedef QHash<QAbstractTransition *, TransitionInfo> TransitionInfoCache; |
208 | TransitionInfoCache cache; |
209 | |
210 | bool effectiveTargetStates(QAbstractTransition *t, QList<QAbstractState *> *targets) const |
211 | { |
212 | Q_ASSERT(targets); |
213 | |
214 | TransitionInfoCache::const_iterator cacheIt = cache.find(akey: t); |
215 | if (cacheIt == cache.end() || !cacheIt->effectiveTargetStatesIsKnown) |
216 | return false; |
217 | |
218 | *targets = cacheIt->effectiveTargetStates; |
219 | return true; |
220 | } |
221 | |
222 | void insert(QAbstractTransition *t, const QList<QAbstractState *> &targets) |
223 | { |
224 | TransitionInfoCache::iterator cacheIt = cache.find(akey: t); |
225 | TransitionInfo &ti = cacheIt == cache.end() |
226 | ? *cache.insert(akey: t, avalue: TransitionInfo()) |
227 | : *cacheIt; |
228 | |
229 | Q_ASSERT(!ti.effectiveTargetStatesIsKnown); |
230 | ti.effectiveTargetStates = targets; |
231 | ti.effectiveTargetStatesIsKnown = true; |
232 | } |
233 | |
234 | bool exitSet(QAbstractTransition *t, QSet<QAbstractState *> *exits) const |
235 | { |
236 | Q_ASSERT(exits); |
237 | |
238 | TransitionInfoCache::const_iterator cacheIt = cache.find(akey: t); |
239 | if (cacheIt == cache.end() || !cacheIt->exitSetIsKnown) |
240 | return false; |
241 | |
242 | *exits = cacheIt->exitSet; |
243 | return true; |
244 | } |
245 | |
246 | void insert(QAbstractTransition *t, const QSet<QAbstractState *> &exits) |
247 | { |
248 | TransitionInfoCache::iterator cacheIt = cache.find(akey: t); |
249 | TransitionInfo &ti = cacheIt == cache.end() |
250 | ? *cache.insert(akey: t, avalue: TransitionInfo()) |
251 | : *cacheIt; |
252 | |
253 | Q_ASSERT(!ti.exitSetIsKnown); |
254 | ti.exitSet = exits; |
255 | ti.exitSetIsKnown = true; |
256 | } |
257 | |
258 | bool transitionDomain(QAbstractTransition *t, QAbstractState **domain) const |
259 | { |
260 | Q_ASSERT(domain); |
261 | |
262 | TransitionInfoCache::const_iterator cacheIt = cache.find(akey: t); |
263 | if (cacheIt == cache.end() || !cacheIt->transitionDomainIsKnown) |
264 | return false; |
265 | |
266 | *domain = cacheIt->transitionDomain; |
267 | return true; |
268 | } |
269 | |
270 | void insert(QAbstractTransition *t, QAbstractState *domain) |
271 | { |
272 | TransitionInfoCache::iterator cacheIt = cache.find(akey: t); |
273 | TransitionInfo &ti = cacheIt == cache.end() |
274 | ? *cache.insert(akey: t, avalue: TransitionInfo()) |
275 | : *cacheIt; |
276 | |
277 | Q_ASSERT(!ti.transitionDomainIsKnown); |
278 | ti.transitionDomain = domain; |
279 | ti.transitionDomainIsKnown = true; |
280 | } |
281 | }; |
282 | |
283 | /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
284 | |
285 | function isDescendant(state1, state2) |
286 | |
287 | Returns 'true' if state1 is a descendant of state2 (a child, or a child of a child, or a child of a |
288 | child of a child, etc.) Otherwise returns 'false'. |
289 | */ |
290 | static inline bool isDescendant(const QAbstractState *state1, const QAbstractState *state2) |
291 | { |
292 | Q_ASSERT(state1 != nullptr); |
293 | |
294 | for (QAbstractState *it = state1->parentState(); it != nullptr; it = it->parentState()) { |
295 | if (it == state2) |
296 | return true; |
297 | } |
298 | |
299 | return false; |
300 | } |
301 | |
302 | static bool containsDecendantOf(const QSet<QAbstractState *> &states, const QAbstractState *node) |
303 | { |
304 | for (QAbstractState *s : states) |
305 | if (isDescendant(state1: s, state2: node)) |
306 | return true; |
307 | |
308 | return false; |
309 | } |
310 | |
311 | static int descendantDepth(const QAbstractState *state, const QAbstractState *ancestor) |
312 | { |
313 | int depth = 0; |
314 | for (const QAbstractState *it = state; it != nullptr; it = it->parentState()) { |
315 | if (it == ancestor) |
316 | break; |
317 | ++depth; |
318 | } |
319 | return depth; |
320 | } |
321 | |
322 | /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
323 | |
324 | function getProperAncestors(state1, state2) |
325 | |
326 | If state2 is null, returns the set of all ancestors of state1 in ancestry order (state1's parent |
327 | followed by the parent's parent, etc. up to an including the <scxml> element). If state2 is |
328 | non-null, returns in ancestry order the set of all ancestors of state1, up to but not including |
329 | state2. (A "proper ancestor" of a state is its parent, or the parent's parent, or the parent's |
330 | parent's parent, etc.))If state2 is state1's parent, or equal to state1, or a descendant of state1, |
331 | this returns the empty set. |
332 | */ |
333 | static QVector<QState*> getProperAncestors(const QAbstractState *state, const QAbstractState *upperBound) |
334 | { |
335 | Q_ASSERT(state != nullptr); |
336 | QVector<QState*> result; |
337 | result.reserve(asize: 16); |
338 | for (QState *it = state->parentState(); it && it != upperBound; it = it->parentState()) { |
339 | result.append(t: it); |
340 | } |
341 | return result; |
342 | } |
343 | |
344 | /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
345 | |
346 | function getEffectiveTargetStates(transition) |
347 | |
348 | Returns the states that will be the target when 'transition' is taken, dereferencing any history states. |
349 | |
350 | function getEffectiveTargetStates(transition) |
351 | targets = new OrderedSet() |
352 | for s in transition.target |
353 | if isHistoryState(s): |
354 | if historyValue[s.id]: |
355 | targets.union(historyValue[s.id]) |
356 | else: |
357 | targets.union(getEffectiveTargetStates(s.transition)) |
358 | else: |
359 | targets.add(s) |
360 | return targets |
361 | */ |
362 | static QList<QAbstractState *> getEffectiveTargetStates(QAbstractTransition *transition, CalculationCache *cache) |
363 | { |
364 | Q_ASSERT(cache); |
365 | |
366 | QList<QAbstractState *> targetsList; |
367 | if (cache->effectiveTargetStates(t: transition, targets: &targetsList)) |
368 | return targetsList; |
369 | |
370 | QSet<QAbstractState *> targets; |
371 | const auto targetStates = transition->targetStates(); |
372 | for (QAbstractState *s : targetStates) { |
373 | if (QHistoryState *historyState = QStateMachinePrivate::toHistoryState(state: s)) { |
374 | QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(q: historyState)->configuration; |
375 | if (!historyConfiguration.isEmpty()) { |
376 | // There is a saved history, so apply that. |
377 | targets.unite(other: QSet<QAbstractState *>(historyConfiguration.constBegin(), historyConfiguration.constEnd())); |
378 | } else if (QAbstractTransition *defaultTransition = historyState->defaultTransition()) { |
379 | // No saved history, take all default transition targets. |
380 | const auto &targetStates = defaultTransition->targetStates(); |
381 | targets.unite(other: QSet<QAbstractState *>(targetStates.constBegin(), targetStates.constEnd())); |
382 | } else { |
383 | // Woops, we found a history state without a default state. That's not valid! |
384 | QStateMachinePrivate *m = QStateMachinePrivate::get(q: historyState->machine()); |
385 | m->setError(error: QStateMachine::NoDefaultStateInHistoryStateError, currentContext: historyState); |
386 | } |
387 | } else { |
388 | targets.insert(value: s); |
389 | } |
390 | } |
391 | |
392 | targetsList = targets.values(); |
393 | cache->insert(t: transition, targets: targetsList); |
394 | return targetsList; |
395 | } |
396 | |
397 | QStateMachinePrivate::QStateMachinePrivate() |
398 | { |
399 | isMachine = true; |
400 | |
401 | state = NotRunning; |
402 | processing = false; |
403 | processingScheduled = false; |
404 | stop = false; |
405 | stopProcessingReason = EventQueueEmpty; |
406 | error = QStateMachine::NoError; |
407 | globalRestorePolicy = QState::DontRestoreProperties; |
408 | signalEventGenerator = nullptr; |
409 | #if QT_CONFIG(animation) |
410 | animated = true; |
411 | #endif |
412 | } |
413 | |
414 | QStateMachinePrivate::~QStateMachinePrivate() |
415 | { |
416 | qDeleteAll(c: internalEventQueue); |
417 | qDeleteAll(c: externalEventQueue); |
418 | |
419 | for (QHash<int, DelayedEvent>::const_iterator it = delayedEvents.cbegin(), eit = delayedEvents.cend(); it != eit; ++it) { |
420 | delete it.value().event; |
421 | } |
422 | } |
423 | |
424 | QState *QStateMachinePrivate::rootState() const |
425 | { |
426 | return const_cast<QStateMachine*>(q_func()); |
427 | } |
428 | |
429 | static QEvent *cloneEvent(QEvent *e) |
430 | { |
431 | switch (e->type()) { |
432 | case QEvent::None: |
433 | return new QEvent(*e); |
434 | case QEvent::Timer: |
435 | return new QTimerEvent(*static_cast<QTimerEvent*>(e)); |
436 | default: |
437 | Q_ASSERT_X(false, "cloneEvent()" , "not implemented" ); |
438 | break; |
439 | } |
440 | return nullptr; |
441 | } |
442 | |
443 | const QStateMachinePrivate::Handler qt_kernel_statemachine_handler = { |
444 | .cloneEvent: cloneEvent |
445 | }; |
446 | |
447 | const QStateMachinePrivate::Handler *QStateMachinePrivate::handler = &qt_kernel_statemachine_handler; |
448 | |
449 | Q_CORE_EXPORT const QStateMachinePrivate::Handler *qcoreStateMachineHandler() |
450 | { |
451 | return &qt_kernel_statemachine_handler; |
452 | } |
453 | |
454 | static int indexOfDescendant(QState *s, QAbstractState *desc) |
455 | { |
456 | QList<QAbstractState*> childStates = QStatePrivate::get(q: s)->childStates(); |
457 | for (int i = 0; i < childStates.size(); ++i) { |
458 | QAbstractState *c = childStates.at(i); |
459 | if ((c == desc) || isDescendant(state1: desc, state2: c)) { |
460 | return i; |
461 | } |
462 | } |
463 | return -1; |
464 | } |
465 | |
466 | bool QStateMachinePrivate::transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2) |
467 | { |
468 | QState *s1 = t1->sourceState(), *s2 = t2->sourceState(); |
469 | if (s1 == s2) { |
470 | QList<QAbstractTransition*> transitions = QStatePrivate::get(q: s1)->transitions(); |
471 | return transitions.indexOf(t: t1) < transitions.indexOf(t: t2); |
472 | } else if (isDescendant(state1: s1, state2: s2)) { |
473 | return true; |
474 | } else if (isDescendant(state1: s2, state2: s1)) { |
475 | return false; |
476 | } else { |
477 | Q_ASSERT(s1->machine() != nullptr); |
478 | QStateMachinePrivate *mach = QStateMachinePrivate::get(q: s1->machine()); |
479 | QState *lca = mach->findLCA(states: QList<QAbstractState*>() << s1 << s2); |
480 | Q_ASSERT(lca != nullptr); |
481 | int s1Depth = descendantDepth(state: s1, ancestor: lca); |
482 | int s2Depth = descendantDepth(state: s2, ancestor: lca); |
483 | if (s1Depth == s2Depth) |
484 | return (indexOfDescendant(s: lca, desc: s1) < indexOfDescendant(s: lca, desc: s2)); |
485 | else |
486 | return s1Depth > s2Depth; |
487 | } |
488 | } |
489 | |
490 | bool QStateMachinePrivate::stateEntryLessThan(QAbstractState *s1, QAbstractState *s2) |
491 | { |
492 | if (s1->parent() == s2->parent()) { |
493 | return s1->parent()->children().indexOf(t: s1) |
494 | < s2->parent()->children().indexOf(t: s2); |
495 | } else if (isDescendant(state1: s1, state2: s2)) { |
496 | return false; |
497 | } else if (isDescendant(state1: s2, state2: s1)) { |
498 | return true; |
499 | } else { |
500 | Q_ASSERT(s1->machine() != nullptr); |
501 | QStateMachinePrivate *mach = QStateMachinePrivate::get(q: s1->machine()); |
502 | QState *lca = mach->findLCA(states: QList<QAbstractState*>() << s1 << s2); |
503 | Q_ASSERT(lca != nullptr); |
504 | return (indexOfDescendant(s: lca, desc: s1) < indexOfDescendant(s: lca, desc: s2)); |
505 | } |
506 | } |
507 | |
508 | bool QStateMachinePrivate::stateExitLessThan(QAbstractState *s1, QAbstractState *s2) |
509 | { |
510 | if (s1->parent() == s2->parent()) { |
511 | return s2->parent()->children().indexOf(t: s2) |
512 | < s1->parent()->children().indexOf(t: s1); |
513 | } else if (isDescendant(state1: s1, state2: s2)) { |
514 | return true; |
515 | } else if (isDescendant(state1: s2, state2: s1)) { |
516 | return false; |
517 | } else { |
518 | Q_ASSERT(s1->machine() != nullptr); |
519 | QStateMachinePrivate *mach = QStateMachinePrivate::get(q: s1->machine()); |
520 | QState *lca = mach->findLCA(states: QList<QAbstractState*>() << s1 << s2); |
521 | Q_ASSERT(lca != nullptr); |
522 | return (indexOfDescendant(s: lca, desc: s2) < indexOfDescendant(s: lca, desc: s1)); |
523 | } |
524 | } |
525 | |
526 | QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states, bool onlyCompound) |
527 | { |
528 | if (states.isEmpty()) |
529 | return nullptr; |
530 | QVector<QState*> ancestors = getProperAncestors(state: states.at(i: 0), upperBound: rootState()->parentState()); |
531 | for (int i = 0; i < ancestors.size(); ++i) { |
532 | QState *anc = ancestors.at(i); |
533 | if (onlyCompound && !isCompound(s: anc)) |
534 | continue; |
535 | |
536 | bool ok = true; |
537 | for (int j = states.size() - 1; (j > 0) && ok; --j) { |
538 | const QAbstractState *s = states.at(i: j); |
539 | if (!isDescendant(state1: s, state2: anc)) |
540 | ok = false; |
541 | } |
542 | if (ok) |
543 | return anc; |
544 | } |
545 | |
546 | // Oops, this should never happen! The state machine itself is a common ancestor of all states, |
547 | // no matter what. But, for the onlyCompound case: we probably have a state machine whose |
548 | // childMode is set to parallel, which is illegal. However, we're stuck with it (and with |
549 | // exposing this invalid/dangerous API to users), so recover in the least horrible way. |
550 | setError(error: QStateMachine::StateMachineChildModeSetToParallelError, currentContext: q_func()); |
551 | return q_func(); // make the statemachine the LCA/LCCA (which it should have been anyway) |
552 | } |
553 | |
554 | QState *QStateMachinePrivate::findLCCA(const QList<QAbstractState*> &states) |
555 | { |
556 | return findLCA(states, onlyCompound: true); |
557 | } |
558 | |
559 | QList<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event, CalculationCache *cache) |
560 | { |
561 | Q_ASSERT(cache); |
562 | Q_Q(const QStateMachine); |
563 | |
564 | QVarLengthArray<QAbstractState *> configuration_sorted; |
565 | for (QAbstractState *s : qAsConst(t&: configuration)) { |
566 | if (isAtomic(s)) |
567 | configuration_sorted.append(t: s); |
568 | } |
569 | std::sort(first: configuration_sorted.begin(), last: configuration_sorted.end(), comp: stateEntryLessThan); |
570 | |
571 | QList<QAbstractTransition*> enabledTransitions; |
572 | const_cast<QStateMachine*>(q)->beginSelectTransitions(event); |
573 | for (QAbstractState *state : qAsConst(t&: configuration_sorted)) { |
574 | QVector<QState*> lst = getProperAncestors(state, upperBound: nullptr); |
575 | if (QState *grp = toStandardState(state)) |
576 | lst.prepend(t: grp); |
577 | bool found = false; |
578 | for (int j = 0; (j < lst.size()) && !found; ++j) { |
579 | QState *s = lst.at(i: j); |
580 | QList<QAbstractTransition*> transitions = QStatePrivate::get(q: s)->transitions(); |
581 | for (int k = 0; k < transitions.size(); ++k) { |
582 | QAbstractTransition *t = transitions.at(i: k); |
583 | if (QAbstractTransitionPrivate::get(q: t)->callEventTest(e: event)) { |
584 | #ifdef QSTATEMACHINE_DEBUG |
585 | qDebug() << q << ": selecting transition" << t; |
586 | #endif |
587 | enabledTransitions.append(t); |
588 | found = true; |
589 | break; |
590 | } |
591 | } |
592 | } |
593 | } |
594 | |
595 | if (!enabledTransitions.isEmpty()) { |
596 | removeConflictingTransitions(enabledTransitions, cache); |
597 | #ifdef QSTATEMACHINE_DEBUG |
598 | qDebug() << q << ": enabled transitions after removing conflicts:" << enabledTransitions; |
599 | #endif |
600 | } |
601 | const_cast<QStateMachine*>(q)->endSelectTransitions(event); |
602 | return enabledTransitions; |
603 | } |
604 | |
605 | /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
606 | |
607 | function removeConflictingTransitions(enabledTransitions): |
608 | filteredTransitions = new OrderedSet() |
609 | // toList sorts the transitions in the order of the states that selected them |
610 | for t1 in enabledTransitions.toList(): |
611 | t1Preempted = false; |
612 | transitionsToRemove = new OrderedSet() |
613 | for t2 in filteredTransitions.toList(): |
614 | if computeExitSet([t1]).hasIntersection(computeExitSet([t2])): |
615 | if isDescendant(t1.source, t2.source): |
616 | transitionsToRemove.add(t2) |
617 | else: |
618 | t1Preempted = true |
619 | break |
620 | if not t1Preempted: |
621 | for t3 in transitionsToRemove.toList(): |
622 | filteredTransitions.delete(t3) |
623 | filteredTransitions.add(t1) |
624 | |
625 | return filteredTransitions |
626 | |
627 | Note: the implementation below does not build the transitionsToRemove, but removes them in-place. |
628 | */ |
629 | void QStateMachinePrivate::removeConflictingTransitions(QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache) |
630 | { |
631 | Q_ASSERT(cache); |
632 | |
633 | if (enabledTransitions.size() < 2) |
634 | return; // There is no transition to conflict with. |
635 | |
636 | QList<QAbstractTransition*> filteredTransitions; |
637 | filteredTransitions.reserve(alloc: enabledTransitions.size()); |
638 | std::sort(first: enabledTransitions.begin(), last: enabledTransitions.end(), comp: transitionStateEntryLessThan); |
639 | |
640 | for (QAbstractTransition *t1 : qAsConst(t&: enabledTransitions)) { |
641 | bool t1Preempted = false; |
642 | const QSet<QAbstractState*> exitSetT1 = computeExitSet_Unordered(t: t1, cache); |
643 | QList<QAbstractTransition*>::iterator t2It = filteredTransitions.begin(); |
644 | while (t2It != filteredTransitions.end()) { |
645 | QAbstractTransition *t2 = *t2It; |
646 | if (t1 == t2) { |
647 | // Special case: someone added the same transition object to a state twice. In this |
648 | // case, t2 (which is already in the list) "preempts" t1. |
649 | t1Preempted = true; |
650 | break; |
651 | } |
652 | |
653 | QSet<QAbstractState*> exitSetT2 = computeExitSet_Unordered(t: t2, cache); |
654 | if (!exitSetT1.intersects(other: exitSetT2)) { |
655 | // No conflict, no cry. Next patient please. |
656 | ++t2It; |
657 | } else { |
658 | // Houston, we have a conflict. Check which transition can be removed. |
659 | if (isDescendant(state1: t1->sourceState(), state2: t2->sourceState())) { |
660 | // t1 preempts t2, so we can remove t2 |
661 | t2It = filteredTransitions.erase(it: t2It); |
662 | } else { |
663 | // t2 preempts t1, so there's no use in looking further and we don't need to add |
664 | // t1 to the list. |
665 | t1Preempted = true; |
666 | break; |
667 | } |
668 | } |
669 | } |
670 | if (!t1Preempted) |
671 | filteredTransitions.append(t: t1); |
672 | } |
673 | |
674 | enabledTransitions = filteredTransitions; |
675 | } |
676 | |
677 | void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions, |
678 | CalculationCache *cache) |
679 | { |
680 | Q_ASSERT(cache); |
681 | |
682 | #ifdef QSTATEMACHINE_DEBUG |
683 | qDebug() << q_func() << ": begin microstep( enabledTransitions:" << enabledTransitions << ')'; |
684 | qDebug() << q_func() << ": configuration before exiting states:" << configuration; |
685 | #endif |
686 | QList<QAbstractState*> exitedStates = computeExitSet(enabledTransitions, cache); |
687 | QHash<RestorableId, QVariant> pendingRestorables = computePendingRestorables(statesToExit_sorted: exitedStates); |
688 | |
689 | QSet<QAbstractState*> statesForDefaultEntry; |
690 | QList<QAbstractState*> enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry, cache); |
691 | |
692 | #ifdef QSTATEMACHINE_DEBUG |
693 | qDebug() << q_func() << ": computed exit set:" << exitedStates; |
694 | qDebug() << q_func() << ": computed entry set:" << enteredStates; |
695 | #endif |
696 | |
697 | QHash<QAbstractState*, QVector<QPropertyAssignment> > assignmentsForEnteredStates = |
698 | computePropertyAssignments(statesToEnter_sorted: enteredStates, pendingRestorables); |
699 | if (!pendingRestorables.isEmpty()) { |
700 | // Add "implicit" assignments for restored properties to the first |
701 | // (outermost) entered state |
702 | Q_ASSERT(!enteredStates.isEmpty()); |
703 | QAbstractState *s = enteredStates.constFirst(); |
704 | assignmentsForEnteredStates[s] << restorablesToPropertyList(restorables: pendingRestorables); |
705 | } |
706 | |
707 | exitStates(event, statesToExit_sorted: exitedStates, assignmentsForEnteredStates); |
708 | #ifdef QSTATEMACHINE_DEBUG |
709 | qDebug() << q_func() << ": configuration after exiting states:" << configuration; |
710 | #endif |
711 | |
712 | executeTransitionContent(event, transitionList: enabledTransitions); |
713 | |
714 | #if QT_CONFIG(animation) |
715 | QList<QAbstractAnimation *> selectedAnimations = selectAnimations(transitionList: enabledTransitions); |
716 | #endif |
717 | |
718 | enterStates(event, exitedStates_sorted: exitedStates, statesToEnter_sorted: enteredStates, statesForDefaultEntry, propertyAssignmentsForState&: assignmentsForEnteredStates |
719 | #if QT_CONFIG(animation) |
720 | , selectedAnimations |
721 | #endif |
722 | ); |
723 | #ifdef QSTATEMACHINE_DEBUG |
724 | qDebug() << q_func() << ": configuration after entering states:" << configuration; |
725 | qDebug() << q_func() << ": end microstep" ; |
726 | #endif |
727 | } |
728 | |
729 | /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
730 | |
731 | procedure computeExitSet(enabledTransitions) |
732 | |
733 | For each transition t in enabledTransitions, if t is targetless then do nothing, else compute the |
734 | transition's domain. (This will be the source state in the case of internal transitions) or the |
735 | least common compound ancestor state of the source state and target states of t (in the case of |
736 | external transitions. Add to the statesToExit set all states in the configuration that are |
737 | descendants of the domain. |
738 | |
739 | function computeExitSet(transitions) |
740 | statesToExit = new OrderedSet |
741 | for t in transitions: |
742 | if (t.target): |
743 | domain = getTransitionDomain(t) |
744 | for s in configuration: |
745 | if isDescendant(s,domain): |
746 | statesToExit.add(s) |
747 | return statesToExit |
748 | */ |
749 | QList<QAbstractState*> QStateMachinePrivate::computeExitSet(const QList<QAbstractTransition*> &enabledTransitions, |
750 | CalculationCache *cache) |
751 | { |
752 | Q_ASSERT(cache); |
753 | |
754 | QList<QAbstractState*> statesToExit_sorted = computeExitSet_Unordered(enabledTransitions, cache).values(); |
755 | std::sort(first: statesToExit_sorted.begin(), last: statesToExit_sorted.end(), comp: stateExitLessThan); |
756 | return statesToExit_sorted; |
757 | } |
758 | |
759 | QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(const QList<QAbstractTransition*> &enabledTransitions, |
760 | CalculationCache *cache) |
761 | { |
762 | Q_ASSERT(cache); |
763 | |
764 | QSet<QAbstractState*> statesToExit; |
765 | for (QAbstractTransition *t : enabledTransitions) |
766 | statesToExit.unite(other: computeExitSet_Unordered(t, cache)); |
767 | return statesToExit; |
768 | } |
769 | |
770 | QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(QAbstractTransition *t, |
771 | CalculationCache *cache) |
772 | { |
773 | Q_ASSERT(cache); |
774 | |
775 | QSet<QAbstractState*> statesToExit; |
776 | if (cache->exitSet(t, exits: &statesToExit)) |
777 | return statesToExit; |
778 | |
779 | QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(transition: t, cache); |
780 | QAbstractState *domain = getTransitionDomain(t, effectiveTargetStates, cache); |
781 | if (domain == nullptr && !t->targetStates().isEmpty()) { |
782 | // So we didn't find the least common ancestor for the source and target states of the |
783 | // transition. If there were not target states, that would be fine: then the transition |
784 | // will fire any events or signals, but not exit the state. |
785 | // |
786 | // However, there are target states, so it's either a node without a parent (or parent's |
787 | // parent, etc), or the state belongs to a different state machine. Either way, this |
788 | // makes the state machine invalid. |
789 | if (error == QStateMachine::NoError) |
790 | setError(error: QStateMachine::NoCommonAncestorForTransitionError, currentContext: t->sourceState()); |
791 | QList<QAbstractState *> lst = pendingErrorStates.values(); |
792 | lst.prepend(t: t->sourceState()); |
793 | |
794 | domain = findLCCA(states: lst); |
795 | Q_ASSERT(domain != nullptr); |
796 | } |
797 | |
798 | for (QAbstractState* s : qAsConst(t&: configuration)) { |
799 | if (isDescendant(state1: s, state2: domain)) |
800 | statesToExit.insert(value: s); |
801 | } |
802 | |
803 | cache->insert(t, exits: statesToExit); |
804 | return statesToExit; |
805 | } |
806 | |
807 | void QStateMachinePrivate::exitStates(QEvent *event, const QList<QAbstractState*> &statesToExit_sorted, |
808 | const QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) |
809 | { |
810 | for (int i = 0; i < statesToExit_sorted.size(); ++i) { |
811 | QAbstractState *s = statesToExit_sorted.at(i); |
812 | if (QState *grp = toStandardState(state: s)) { |
813 | QList<QHistoryState*> hlst = QStatePrivate::get(q: grp)->historyStates(); |
814 | for (int j = 0; j < hlst.size(); ++j) { |
815 | QHistoryState *h = hlst.at(i: j); |
816 | QHistoryStatePrivate::get(q: h)->configuration.clear(); |
817 | QSet<QAbstractState*>::const_iterator it; |
818 | for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { |
819 | QAbstractState *s0 = *it; |
820 | if (QHistoryStatePrivate::get(q: h)->historyType == QHistoryState::DeepHistory) { |
821 | if (isAtomic(s: s0) && isDescendant(state1: s0, state2: s)) |
822 | QHistoryStatePrivate::get(q: h)->configuration.append(t: s0); |
823 | } else if (s0->parentState() == s) { |
824 | QHistoryStatePrivate::get(q: h)->configuration.append(t: s0); |
825 | } |
826 | } |
827 | #ifdef QSTATEMACHINE_DEBUG |
828 | qDebug() << q_func() << ": recorded" << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow" ) |
829 | << "history for" << s << "in" << h << ':' << QHistoryStatePrivate::get(h)->configuration; |
830 | #endif |
831 | } |
832 | } |
833 | } |
834 | for (int i = 0; i < statesToExit_sorted.size(); ++i) { |
835 | QAbstractState *s = statesToExit_sorted.at(i); |
836 | #ifdef QSTATEMACHINE_DEBUG |
837 | qDebug() << q_func() << ": exiting" << s; |
838 | #endif |
839 | QAbstractStatePrivate::get(q: s)->callOnExit(e: event); |
840 | |
841 | #if QT_CONFIG(animation) |
842 | terminateActiveAnimations(state: s, assignmentsForEnteredStates); |
843 | #else |
844 | Q_UNUSED(assignmentsForEnteredStates); |
845 | #endif |
846 | |
847 | configuration.remove(value: s); |
848 | QAbstractStatePrivate::get(q: s)->emitExited(); |
849 | } |
850 | } |
851 | |
852 | void QStateMachinePrivate::executeTransitionContent(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions) |
853 | { |
854 | for (int i = 0; i < enabledTransitions.size(); ++i) { |
855 | QAbstractTransition *t = enabledTransitions.at(i); |
856 | #ifdef QSTATEMACHINE_DEBUG |
857 | qDebug() << q_func() << ": triggering" << t; |
858 | #endif |
859 | QAbstractTransitionPrivate::get(q: t)->callOnTransition(e: event); |
860 | QAbstractTransitionPrivate::get(q: t)->emitTriggered(); |
861 | } |
862 | } |
863 | |
864 | QList<QAbstractState*> QStateMachinePrivate::computeEntrySet(const QList<QAbstractTransition *> &enabledTransitions, |
865 | QSet<QAbstractState *> &statesForDefaultEntry, |
866 | CalculationCache *cache) |
867 | { |
868 | Q_ASSERT(cache); |
869 | |
870 | QSet<QAbstractState*> statesToEnter; |
871 | if (pendingErrorStates.isEmpty()) { |
872 | for (QAbstractTransition *t : enabledTransitions) { |
873 | const auto targetStates = t->targetStates(); |
874 | for (QAbstractState *s : targetStates) |
875 | addDescendantStatesToEnter(state: s, statesToEnter, statesForDefaultEntry); |
876 | |
877 | const QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(transition: t, cache); |
878 | QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates, cache); |
879 | for (QAbstractState *s : effectiveTargetStates) |
880 | addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry); |
881 | } |
882 | } |
883 | |
884 | // Did an error occur while selecting transitions? Then we enter the error state. |
885 | if (!pendingErrorStates.isEmpty()) { |
886 | statesToEnter.clear(); |
887 | statesToEnter = pendingErrorStates; |
888 | statesForDefaultEntry = pendingErrorStatesForDefaultEntry; |
889 | pendingErrorStates.clear(); |
890 | pendingErrorStatesForDefaultEntry.clear(); |
891 | } |
892 | |
893 | QList<QAbstractState*> statesToEnter_sorted = statesToEnter.values(); |
894 | std::sort(first: statesToEnter_sorted.begin(), last: statesToEnter_sorted.end(), comp: stateEntryLessThan); |
895 | return statesToEnter_sorted; |
896 | } |
897 | |
898 | /* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
899 | |
900 | function getTransitionDomain(transition) |
901 | |
902 | Return the compound state such that 1) all states that are exited or entered as a result of taking |
903 | 'transition' are descendants of it 2) no descendant of it has this property. |
904 | |
905 | function getTransitionDomain(t) |
906 | tstates = getEffectiveTargetStates(t) |
907 | if not tstates: |
908 | return null |
909 | elif t.type == "internal" and isCompoundState(t.source) and tstates.every(lambda s: isDescendant(s,t.source)): |
910 | return t.source |
911 | else: |
912 | return findLCCA([t.source].append(tstates)) |
913 | */ |
914 | QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t, |
915 | const QList<QAbstractState *> &effectiveTargetStates, |
916 | CalculationCache *cache) |
917 | { |
918 | Q_ASSERT(cache); |
919 | |
920 | if (effectiveTargetStates.isEmpty()) |
921 | return nullptr; |
922 | |
923 | QAbstractState *domain = nullptr; |
924 | if (cache->transitionDomain(t, domain: &domain)) |
925 | return domain; |
926 | |
927 | if (t->transitionType() == QAbstractTransition::InternalTransition) { |
928 | if (QState *tSource = t->sourceState()) { |
929 | if (isCompound(s: tSource)) { |
930 | bool allDescendants = true; |
931 | for (QAbstractState *s : effectiveTargetStates) { |
932 | if (!isDescendant(state1: s, state2: tSource)) { |
933 | allDescendants = false; |
934 | break; |
935 | } |
936 | } |
937 | |
938 | if (allDescendants) |
939 | return tSource; |
940 | } |
941 | } |
942 | } |
943 | |
944 | QList<QAbstractState *> states(effectiveTargetStates); |
945 | if (QAbstractState *src = t->sourceState()) |
946 | states.prepend(t: src); |
947 | domain = findLCCA(states); |
948 | cache->insert(t, domain); |
949 | return domain; |
950 | } |
951 | |
952 | void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState*> &exitedStates_sorted, |
953 | const QList<QAbstractState*> &statesToEnter_sorted, |
954 | const QSet<QAbstractState*> &statesForDefaultEntry, |
955 | QHash<QAbstractState*, QVector<QPropertyAssignment> > &propertyAssignmentsForState |
956 | #if QT_CONFIG(animation) |
957 | , const QList<QAbstractAnimation *> &selectedAnimations |
958 | #endif |
959 | ) |
960 | { |
961 | #ifdef QSTATEMACHINE_DEBUG |
962 | Q_Q(QStateMachine); |
963 | #endif |
964 | for (int i = 0; i < statesToEnter_sorted.size(); ++i) { |
965 | QAbstractState *s = statesToEnter_sorted.at(i); |
966 | #ifdef QSTATEMACHINE_DEBUG |
967 | qDebug() << q << ": entering" << s; |
968 | #endif |
969 | configuration.insert(value: s); |
970 | registerTransitions(state: s); |
971 | |
972 | #if QT_CONFIG(animation) |
973 | initializeAnimations(state: s, selectedAnimations, exitedStates_sorted, assignmentsForEnteredStates&: propertyAssignmentsForState); |
974 | #endif |
975 | |
976 | // Immediately set the properties that are not animated. |
977 | { |
978 | QVector<QPropertyAssignment> assignments = propertyAssignmentsForState.value(akey: s); |
979 | for (int i = 0; i < assignments.size(); ++i) { |
980 | const QPropertyAssignment &assn = assignments.at(i); |
981 | if (globalRestorePolicy == QState::RestoreProperties) { |
982 | if (assn.explicitlySet) { |
983 | if (!hasRestorable(state: s, object: assn.object, propertyName: assn.propertyName)) { |
984 | QVariant value = savedValueForRestorable(exitedStates_sorted, object: assn.object, propertyName: assn.propertyName); |
985 | unregisterRestorables(states: exitedStates_sorted, object: assn.object, propertyName: assn.propertyName); |
986 | registerRestorable(state: s, object: assn.object, propertyName: assn.propertyName, value); |
987 | } |
988 | } else { |
989 | // The property is being restored, hence no need to |
990 | // save the current value. Discard any saved values in |
991 | // exited states, since those are now stale. |
992 | unregisterRestorables(states: exitedStates_sorted, object: assn.object, propertyName: assn.propertyName); |
993 | } |
994 | } |
995 | assn.write(); |
996 | } |
997 | } |
998 | |
999 | QAbstractStatePrivate::get(q: s)->callOnEntry(e: event); |
1000 | QAbstractStatePrivate::get(q: s)->emitEntered(); |
1001 | |
1002 | // FIXME: |
1003 | // See the "initial transitions" comment in addDescendantStatesToEnter first, then implement: |
1004 | // if (statesForDefaultEntry.contains(s)) { |
1005 | // // ### executeContent(s.initial.transition.children()) |
1006 | // } |
1007 | Q_UNUSED(statesForDefaultEntry); |
1008 | |
1009 | if (QHistoryState *h = toHistoryState(state: s)) |
1010 | QAbstractTransitionPrivate::get(q: h->defaultTransition())->callOnTransition(e: event); |
1011 | |
1012 | // Emit propertiesAssigned signal if the state has no animated properties. |
1013 | { |
1014 | QState *ss = toStandardState(state: s); |
1015 | if (ss |
1016 | #if QT_CONFIG(animation) |
1017 | && !animationsForState.contains(akey: s) |
1018 | #endif |
1019 | ) |
1020 | QStatePrivate::get(q: ss)->emitPropertiesAssigned(); |
1021 | } |
1022 | |
1023 | if (isFinal(s)) { |
1024 | QState *parent = s->parentState(); |
1025 | if (parent) { |
1026 | if (parent != rootState()) { |
1027 | QFinalState *finalState = qobject_cast<QFinalState *>(object: s); |
1028 | Q_ASSERT(finalState); |
1029 | emitStateFinished(forState: parent, guiltyState: finalState); |
1030 | } |
1031 | QState *grandparent = parent->parentState(); |
1032 | if (grandparent && isParallel(s: grandparent)) { |
1033 | bool allChildStatesFinal = true; |
1034 | QList<QAbstractState*> childStates = QStatePrivate::get(q: grandparent)->childStates(); |
1035 | for (int j = 0; j < childStates.size(); ++j) { |
1036 | QAbstractState *cs = childStates.at(i: j); |
1037 | if (!isInFinalState(s: cs)) { |
1038 | allChildStatesFinal = false; |
1039 | break; |
1040 | } |
1041 | } |
1042 | if (allChildStatesFinal && (grandparent != rootState())) { |
1043 | QFinalState *finalState = qobject_cast<QFinalState *>(object: s); |
1044 | Q_ASSERT(finalState); |
1045 | emitStateFinished(forState: grandparent, guiltyState: finalState); |
1046 | } |
1047 | } |
1048 | } |
1049 | } |
1050 | } |
1051 | { |
1052 | QSet<QAbstractState*>::const_iterator it; |
1053 | for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { |
1054 | if (isFinal(s: *it)) { |
1055 | QState *parent = (*it)->parentState(); |
1056 | if (((parent == rootState()) |
1057 | && (rootState()->childMode() == QState::ExclusiveStates)) |
1058 | || ((parent->parentState() == rootState()) |
1059 | && (rootState()->childMode() == QState::ParallelStates) |
1060 | && isInFinalState(s: rootState()))) { |
1061 | processing = false; |
1062 | stopProcessingReason = Finished; |
1063 | break; |
1064 | } |
1065 | } |
1066 | } |
1067 | } |
1068 | // qDebug() << "configuration:" << configuration.toList(); |
1069 | } |
1070 | |
1071 | /* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ has a bug. See |
1072 | * QTBUG-44963 for details. The algorithm here is as described in |
1073 | * http://www.w3.org/Voice/2013/scxml-irp/SCXML.htm as of Friday March 13, 2015. |
1074 | |
1075 | procedure addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry, defaultHistoryContent): |
1076 | if isHistoryState(state): |
1077 | if historyValue[state.id]: |
1078 | for s in historyValue[state.id]: |
1079 | addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) |
1080 | for s in historyValue[state.id]: |
1081 | addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) |
1082 | else: |
1083 | defaultHistoryContent[state.parent.id] = state.transition.content |
1084 | for s in state.transition.target: |
1085 | addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) |
1086 | for s in state.transition.target: |
1087 | addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) |
1088 | else: |
1089 | statesToEnter.add(state) |
1090 | if isCompoundState(state): |
1091 | statesForDefaultEntry.add(state) |
1092 | for s in state.initial.transition.target: |
1093 | addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) |
1094 | for s in state.initial.transition.target: |
1095 | addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent) |
1096 | else: |
1097 | if isParallelState(state): |
1098 | for child in getChildStates(state): |
1099 | if not statesToEnter.some(lambda s: isDescendant(s,child)): |
1100 | addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) |
1101 | */ |
1102 | void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *state, |
1103 | QSet<QAbstractState*> &statesToEnter, |
1104 | QSet<QAbstractState*> &statesForDefaultEntry) |
1105 | { |
1106 | if (QHistoryState *h = toHistoryState(state)) { |
1107 | const QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(q: h)->configuration; |
1108 | if (!historyConfiguration.isEmpty()) { |
1109 | for (QAbstractState *s : historyConfiguration) |
1110 | addDescendantStatesToEnter(state: s, statesToEnter, statesForDefaultEntry); |
1111 | for (QAbstractState *s : historyConfiguration) |
1112 | addAncestorStatesToEnter(s, ancestor: state->parentState(), statesToEnter, statesForDefaultEntry); |
1113 | |
1114 | #ifdef QSTATEMACHINE_DEBUG |
1115 | qDebug() << q_func() << ": restoring" |
1116 | << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow" ) |
1117 | << "history from" << state << ':' << historyConfiguration; |
1118 | #endif |
1119 | } else { |
1120 | QList<QAbstractState*> defaultHistoryContent; |
1121 | if (QAbstractTransition *t = QHistoryStatePrivate::get(q: h)->defaultTransition) |
1122 | defaultHistoryContent = t->targetStates(); |
1123 | |
1124 | if (defaultHistoryContent.isEmpty()) { |
1125 | setError(error: QStateMachine::NoDefaultStateInHistoryStateError, currentContext: h); |
1126 | } else { |
1127 | for (QAbstractState *s : qAsConst(t&: defaultHistoryContent)) |
1128 | addDescendantStatesToEnter(state: s, statesToEnter, statesForDefaultEntry); |
1129 | for (QAbstractState *s : qAsConst(t&: defaultHistoryContent)) |
1130 | addAncestorStatesToEnter(s, ancestor: state->parentState(), statesToEnter, statesForDefaultEntry); |
1131 | #ifdef QSTATEMACHINE_DEBUG |
1132 | qDebug() << q_func() << ": initial history targets for" << state << ':' << defaultHistoryContent; |
1133 | #endif |
1134 | } |
1135 | } |
1136 | } else { |
1137 | if (state == rootState()) { |
1138 | // Error has already been set by exitStates(). |
1139 | Q_ASSERT(error != QStateMachine::NoError); |
1140 | return; |
1141 | } |
1142 | statesToEnter.insert(value: state); |
1143 | if (isCompound(s: state)) { |
1144 | statesForDefaultEntry.insert(value: state); |
1145 | if (QAbstractState *initial = toStandardState(state)->initialState()) { |
1146 | Q_ASSERT(initial->machine() == q_func()); |
1147 | |
1148 | // FIXME: |
1149 | // Qt does not support initial transitions (which is a problem for parallel states). |
1150 | // The way it simulates this for other states, is by having a single initial state. |
1151 | // See also the FIXME in enterStates. |
1152 | statesForDefaultEntry.insert(value: initial); |
1153 | |
1154 | addDescendantStatesToEnter(state: initial, statesToEnter, statesForDefaultEntry); |
1155 | addAncestorStatesToEnter(s: initial, ancestor: state, statesToEnter, statesForDefaultEntry); |
1156 | } else { |
1157 | setError(error: QStateMachine::NoInitialStateError, currentContext: state); |
1158 | return; |
1159 | } |
1160 | } else if (isParallel(s: state)) { |
1161 | QState *grp = toStandardState(state); |
1162 | const auto childStates = QStatePrivate::get(q: grp)->childStates(); |
1163 | for (QAbstractState *child : childStates) { |
1164 | if (!containsDecendantOf(states: statesToEnter, node: child)) |
1165 | addDescendantStatesToEnter(state: child, statesToEnter, statesForDefaultEntry); |
1166 | } |
1167 | } |
1168 | } |
1169 | } |
1170 | |
1171 | |
1172 | /* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : |
1173 | |
1174 | procedure addAncestorStatesToEnter(state, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent) |
1175 | for anc in getProperAncestors(state,ancestor): |
1176 | statesToEnter.add(anc) |
1177 | if isParallelState(anc): |
1178 | for child in getChildStates(anc): |
1179 | if not statesToEnter.some(lambda s: isDescendant(s,child)): |
1180 | addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) |
1181 | */ |
1182 | void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QAbstractState *ancestor, |
1183 | QSet<QAbstractState*> &statesToEnter, |
1184 | QSet<QAbstractState*> &statesForDefaultEntry) |
1185 | { |
1186 | const auto properAncestors = getProperAncestors(state: s, upperBound: ancestor); |
1187 | for (QState *anc : properAncestors) { |
1188 | if (!anc->parentState()) |
1189 | continue; |
1190 | statesToEnter.insert(value: anc); |
1191 | if (isParallel(s: anc)) { |
1192 | const auto childStates = QStatePrivate::get(q: anc)->childStates(); |
1193 | for (QAbstractState *child : childStates) { |
1194 | if (!containsDecendantOf(states: statesToEnter, node: child)) |
1195 | addDescendantStatesToEnter(state: child, statesToEnter, statesForDefaultEntry); |
1196 | } |
1197 | } |
1198 | } |
1199 | } |
1200 | |
1201 | bool QStateMachinePrivate::isFinal(const QAbstractState *s) |
1202 | { |
1203 | return s && (QAbstractStatePrivate::get(q: s)->stateType == QAbstractStatePrivate::FinalState); |
1204 | } |
1205 | |
1206 | bool QStateMachinePrivate::isParallel(const QAbstractState *s) |
1207 | { |
1208 | const QState *ss = toStandardState(state: s); |
1209 | return ss && (QStatePrivate::get(q: ss)->childMode == QState::ParallelStates); |
1210 | } |
1211 | |
1212 | bool QStateMachinePrivate::isCompound(const QAbstractState *s) const |
1213 | { |
1214 | const QState *group = toStandardState(state: s); |
1215 | if (!group) |
1216 | return false; |
1217 | bool isMachine = QStatePrivate::get(q: group)->isMachine; |
1218 | // Don't treat the machine as compound if it's a sub-state of this machine |
1219 | if (isMachine && (group != rootState())) |
1220 | return false; |
1221 | return (!isParallel(s: group) && !QStatePrivate::get(q: group)->childStates().isEmpty()); |
1222 | } |
1223 | |
1224 | bool QStateMachinePrivate::isAtomic(const QAbstractState *s) const |
1225 | { |
1226 | const QState *ss = toStandardState(state: s); |
1227 | return (ss && QStatePrivate::get(q: ss)->childStates().isEmpty()) |
1228 | || isFinal(s) |
1229 | // Treat the machine as atomic if it's a sub-state of this machine |
1230 | || (ss && QStatePrivate::get(q: ss)->isMachine && (ss != rootState())); |
1231 | } |
1232 | |
1233 | QState *QStateMachinePrivate::toStandardState(QAbstractState *state) |
1234 | { |
1235 | if (state && (QAbstractStatePrivate::get(q: state)->stateType == QAbstractStatePrivate::StandardState)) |
1236 | return static_cast<QState*>(state); |
1237 | return nullptr; |
1238 | } |
1239 | |
1240 | const QState *QStateMachinePrivate::toStandardState(const QAbstractState *state) |
1241 | { |
1242 | if (state && (QAbstractStatePrivate::get(q: state)->stateType == QAbstractStatePrivate::StandardState)) |
1243 | return static_cast<const QState*>(state); |
1244 | return nullptr; |
1245 | } |
1246 | |
1247 | QFinalState *QStateMachinePrivate::toFinalState(QAbstractState *state) |
1248 | { |
1249 | if (state && (QAbstractStatePrivate::get(q: state)->stateType == QAbstractStatePrivate::FinalState)) |
1250 | return static_cast<QFinalState*>(state); |
1251 | return nullptr; |
1252 | } |
1253 | |
1254 | QHistoryState *QStateMachinePrivate::toHistoryState(QAbstractState *state) |
1255 | { |
1256 | if (state && (QAbstractStatePrivate::get(q: state)->stateType == QAbstractStatePrivate::HistoryState)) |
1257 | return static_cast<QHistoryState*>(state); |
1258 | return nullptr; |
1259 | } |
1260 | |
1261 | bool QStateMachinePrivate::isInFinalState(QAbstractState* s) const |
1262 | { |
1263 | if (isCompound(s)) { |
1264 | QState *grp = toStandardState(state: s); |
1265 | QList<QAbstractState*> lst = QStatePrivate::get(q: grp)->childStates(); |
1266 | for (int i = 0; i < lst.size(); ++i) { |
1267 | QAbstractState *cs = lst.at(i); |
1268 | if (isFinal(s: cs) && configuration.contains(value: cs)) |
1269 | return true; |
1270 | } |
1271 | return false; |
1272 | } else if (isParallel(s)) { |
1273 | QState *grp = toStandardState(state: s); |
1274 | QList<QAbstractState*> lst = QStatePrivate::get(q: grp)->childStates(); |
1275 | for (int i = 0; i < lst.size(); ++i) { |
1276 | QAbstractState *cs = lst.at(i); |
1277 | if (!isInFinalState(s: cs)) |
1278 | return false; |
1279 | } |
1280 | return true; |
1281 | } |
1282 | else |
1283 | return false; |
1284 | } |
1285 | |
1286 | #ifndef QT_NO_PROPERTIES |
1287 | |
1288 | /*! |
1289 | \internal |
1290 | Returns \c true if the given state has saved the value of the given property, |
1291 | otherwise returns \c false. |
1292 | */ |
1293 | bool QStateMachinePrivate::hasRestorable(QAbstractState *state, QObject *object, |
1294 | const QByteArray &propertyName) const |
1295 | { |
1296 | RestorableId id(object, propertyName); |
1297 | return registeredRestorablesForState.value(akey: state).contains(akey: id); |
1298 | } |
1299 | |
1300 | /*! |
1301 | \internal |
1302 | Returns the value to save for the property identified by \a id. |
1303 | If an exited state (member of \a exitedStates_sorted) has saved a value for |
1304 | the property, the saved value from the last (outermost) state that will be |
1305 | exited is returned (in practice carrying the saved value on to the next |
1306 | state). Otherwise, the current value of the property is returned. |
1307 | */ |
1308 | QVariant QStateMachinePrivate::savedValueForRestorable(const QList<QAbstractState*> &exitedStates_sorted, |
1309 | QObject *object, const QByteArray &propertyName) const |
1310 | { |
1311 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1312 | qDebug() << q_func() << ": savedValueForRestorable(" << exitedStates_sorted << object << propertyName << ')'; |
1313 | #endif |
1314 | for (int i = exitedStates_sorted.size() - 1; i >= 0; --i) { |
1315 | QAbstractState *s = exitedStates_sorted.at(i); |
1316 | QHash<RestorableId, QVariant> restorables = registeredRestorablesForState.value(akey: s); |
1317 | QHash<RestorableId, QVariant>::const_iterator it = restorables.constFind(akey: RestorableId(object, propertyName)); |
1318 | if (it != restorables.constEnd()) { |
1319 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1320 | qDebug() << q_func() << ": using" << it.value() << "from" << s; |
1321 | #endif |
1322 | return it.value(); |
1323 | } |
1324 | } |
1325 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1326 | qDebug() << q_func() << ": falling back to current value" ; |
1327 | #endif |
1328 | return object->property(name: propertyName); |
1329 | } |
1330 | |
1331 | void QStateMachinePrivate::registerRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName, |
1332 | const QVariant &value) |
1333 | { |
1334 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1335 | qDebug() << q_func() << ": registerRestorable(" << state << object << propertyName << value << ')'; |
1336 | #endif |
1337 | RestorableId id(object, propertyName); |
1338 | QHash<RestorableId, QVariant> &restorables = registeredRestorablesForState[state]; |
1339 | if (!restorables.contains(akey: id)) |
1340 | restorables.insert(akey: id, avalue: value); |
1341 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1342 | else |
1343 | qDebug() << q_func() << ": (already registered)" ; |
1344 | #endif |
1345 | } |
1346 | |
1347 | void QStateMachinePrivate::unregisterRestorables(const QList<QAbstractState *> &states, QObject *object, |
1348 | const QByteArray &propertyName) |
1349 | { |
1350 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1351 | qDebug() << q_func() << ": unregisterRestorables(" << states << object << propertyName << ')'; |
1352 | #endif |
1353 | RestorableId id(object, propertyName); |
1354 | for (int i = 0; i < states.size(); ++i) { |
1355 | QAbstractState *s = states.at(i); |
1356 | QHash<QAbstractState*, QHash<RestorableId, QVariant> >::iterator it; |
1357 | it = registeredRestorablesForState.find(akey: s); |
1358 | if (it == registeredRestorablesForState.end()) |
1359 | continue; |
1360 | QHash<RestorableId, QVariant> &restorables = it.value(); |
1361 | const auto it2 = restorables.constFind(akey: id); |
1362 | if (it2 == restorables.cend()) |
1363 | continue; |
1364 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1365 | qDebug() << q_func() << ": unregistered for" << s; |
1366 | #endif |
1367 | restorables.erase(it: it2); |
1368 | if (restorables.isEmpty()) |
1369 | registeredRestorablesForState.erase(it); |
1370 | } |
1371 | } |
1372 | |
1373 | QVector<QPropertyAssignment> QStateMachinePrivate::restorablesToPropertyList(const QHash<RestorableId, QVariant> &restorables) const |
1374 | { |
1375 | QVector<QPropertyAssignment> result; |
1376 | QHash<RestorableId, QVariant>::const_iterator it; |
1377 | for (it = restorables.constBegin(); it != restorables.constEnd(); ++it) { |
1378 | const RestorableId &id = it.key(); |
1379 | if (!id.object()) { |
1380 | // Property object was deleted |
1381 | continue; |
1382 | } |
1383 | #ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG |
1384 | qDebug() << q_func() << ": restoring" << id.object() << id.proertyName() << "to" << it.value(); |
1385 | #endif |
1386 | result.append(t: QPropertyAssignment(id.object(), id.propertyName(), it.value(), /*explicitlySet=*/false)); |
1387 | } |
1388 | return result; |
1389 | } |
1390 | |
1391 | /*! |
1392 | \internal |
1393 | Computes the set of properties whose values should be restored given that |
1394 | the states \a statesToExit_sorted will be exited. |
1395 | |
1396 | If a particular (object, propertyName) pair occurs more than once (i.e., |
1397 | because nested states are being exited), the value from the last (outermost) |
1398 | exited state takes precedence. |
1399 | |
1400 | The result of this function must be filtered according to the explicit |
1401 | property assignments (QState::assignProperty()) of the entered states |
1402 | before the property restoration is actually performed; i.e., if an entered |
1403 | state assigns to a property that would otherwise be restored, that property |
1404 | should not be restored after all, but the saved value from the exited state |
1405 | should be remembered by the entered state (see registerRestorable()). |
1406 | */ |
1407 | QHash<QStateMachinePrivate::RestorableId, QVariant> QStateMachinePrivate::computePendingRestorables( |
1408 | const QList<QAbstractState*> &statesToExit_sorted) const |
1409 | { |
1410 | QHash<QStateMachinePrivate::RestorableId, QVariant> restorables; |
1411 | for (int i = statesToExit_sorted.size() - 1; i >= 0; --i) { |
1412 | QAbstractState *s = statesToExit_sorted.at(i); |
1413 | QHash<QStateMachinePrivate::RestorableId, QVariant> rs = registeredRestorablesForState.value(akey: s); |
1414 | QHash<QStateMachinePrivate::RestorableId, QVariant>::const_iterator it; |
1415 | for (it = rs.constBegin(); it != rs.constEnd(); ++it) { |
1416 | if (!restorables.contains(akey: it.key())) |
1417 | restorables.insert(akey: it.key(), avalue: it.value()); |
1418 | } |
1419 | } |
1420 | return restorables; |
1421 | } |
1422 | |
1423 | /*! |
1424 | \internal |
1425 | Computes the ordered sets of property assignments for the states to be |
1426 | entered, \a statesToEnter_sorted. Also filters \a pendingRestorables (removes |
1427 | properties that should not be restored because they are assigned by an |
1428 | entered state). |
1429 | */ |
1430 | QHash<QAbstractState*, QVector<QPropertyAssignment> > QStateMachinePrivate::computePropertyAssignments( |
1431 | const QList<QAbstractState*> &statesToEnter_sorted, QHash<RestorableId, QVariant> &pendingRestorables) const |
1432 | { |
1433 | QHash<QAbstractState*, QVector<QPropertyAssignment> > assignmentsForState; |
1434 | for (int i = 0; i < statesToEnter_sorted.size(); ++i) { |
1435 | QState *s = toStandardState(state: statesToEnter_sorted.at(i)); |
1436 | if (!s) |
1437 | continue; |
1438 | |
1439 | QVector<QPropertyAssignment> &assignments = QStatePrivate::get(q: s)->propertyAssignments; |
1440 | for (int j = 0; j < assignments.size(); ++j) { |
1441 | const QPropertyAssignment &assn = assignments.at(i: j); |
1442 | if (assn.objectDeleted()) { |
1443 | assignments.removeAt(i: j--); |
1444 | } else { |
1445 | pendingRestorables.remove(akey: RestorableId(assn.object, assn.propertyName)); |
1446 | assignmentsForState[s].append(t: assn); |
1447 | } |
1448 | } |
1449 | } |
1450 | return assignmentsForState; |
1451 | } |
1452 | |
1453 | #endif // QT_NO_PROPERTIES |
1454 | |
1455 | QAbstractState *QStateMachinePrivate::findErrorState(QAbstractState *context) |
1456 | { |
1457 | // Find error state recursively in parent hierarchy if not set explicitly for context state |
1458 | QAbstractState *errorState = nullptr; |
1459 | if (context != nullptr) { |
1460 | QState *s = toStandardState(state: context); |
1461 | if (s != nullptr) |
1462 | errorState = s->errorState(); |
1463 | |
1464 | if (errorState == nullptr) |
1465 | errorState = findErrorState(context: context->parentState()); |
1466 | } |
1467 | |
1468 | return errorState; |
1469 | } |
1470 | |
1471 | void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractState *currentContext) |
1472 | { |
1473 | Q_Q(QStateMachine); |
1474 | |
1475 | error = errorCode; |
1476 | switch (errorCode) { |
1477 | case QStateMachine::NoInitialStateError: |
1478 | Q_ASSERT(currentContext != nullptr); |
1479 | |
1480 | errorString = QStateMachine::tr(s: "Missing initial state in compound state '%1'" ) |
1481 | .arg(a: currentContext->objectName()); |
1482 | |
1483 | break; |
1484 | case QStateMachine::NoDefaultStateInHistoryStateError: |
1485 | Q_ASSERT(currentContext != nullptr); |
1486 | |
1487 | errorString = QStateMachine::tr(s: "Missing default state in history state '%1'" ) |
1488 | .arg(a: currentContext->objectName()); |
1489 | break; |
1490 | |
1491 | case QStateMachine::NoCommonAncestorForTransitionError: |
1492 | Q_ASSERT(currentContext != nullptr); |
1493 | |
1494 | errorString = QStateMachine::tr(s: "No common ancestor for targets and source of transition from state '%1'" ) |
1495 | .arg(a: currentContext->objectName()); |
1496 | break; |
1497 | |
1498 | case QStateMachine::StateMachineChildModeSetToParallelError: |
1499 | Q_ASSERT(currentContext != nullptr); |
1500 | |
1501 | errorString = QStateMachine::tr(s: "Child mode of state machine '%1' is not 'ExclusiveStates'." ) |
1502 | .arg(a: currentContext->objectName()); |
1503 | break; |
1504 | |
1505 | default: |
1506 | errorString = QStateMachine::tr(s: "Unknown error" ); |
1507 | }; |
1508 | |
1509 | pendingErrorStates.clear(); |
1510 | pendingErrorStatesForDefaultEntry.clear(); |
1511 | |
1512 | QAbstractState *currentErrorState = findErrorState(context: currentContext); |
1513 | |
1514 | // Avoid infinite loop if the error state itself has an error |
1515 | if (currentContext == currentErrorState) |
1516 | currentErrorState = nullptr; |
1517 | |
1518 | Q_ASSERT(currentErrorState != rootState()); |
1519 | |
1520 | if (currentErrorState != nullptr) { |
1521 | #ifdef QSTATEMACHINE_DEBUG |
1522 | qDebug() << q << ": entering error state" << currentErrorState << "from" << currentContext; |
1523 | #endif |
1524 | pendingErrorStates.insert(value: currentErrorState); |
1525 | addDescendantStatesToEnter(state: currentErrorState, statesToEnter&: pendingErrorStates, statesForDefaultEntry&: pendingErrorStatesForDefaultEntry); |
1526 | addAncestorStatesToEnter(s: currentErrorState, ancestor: rootState(), statesToEnter&: pendingErrorStates, statesForDefaultEntry&: pendingErrorStatesForDefaultEntry); |
1527 | pendingErrorStates -= configuration; |
1528 | } else { |
1529 | qWarning(msg: "Unrecoverable error detected in running state machine: %ls" , |
1530 | qUtf16Printable(errorString)); |
1531 | q->stop(); |
1532 | } |
1533 | } |
1534 | |
1535 | #if QT_CONFIG(animation) |
1536 | |
1537 | QStateMachinePrivate::InitializeAnimationResult |
1538 | QStateMachinePrivate::initializeAnimation(QAbstractAnimation *abstractAnimation, |
1539 | const QPropertyAssignment &prop) |
1540 | { |
1541 | InitializeAnimationResult result; |
1542 | QAnimationGroup *group = qobject_cast<QAnimationGroup*>(object: abstractAnimation); |
1543 | if (group) { |
1544 | for (int i = 0; i < group->animationCount(); ++i) { |
1545 | QAbstractAnimation *animationChild = group->animationAt(index: i); |
1546 | const auto ret = initializeAnimation(abstractAnimation: animationChild, prop); |
1547 | result.handledAnimations << ret.handledAnimations; |
1548 | result.localResetEndValues << ret.localResetEndValues; |
1549 | } |
1550 | } else { |
1551 | QPropertyAnimation *animation = qobject_cast<QPropertyAnimation *>(object: abstractAnimation); |
1552 | if (animation != nullptr |
1553 | && prop.object == animation->targetObject() |
1554 | && prop.propertyName == animation->propertyName()) { |
1555 | |
1556 | // Only change end value if it is undefined |
1557 | if (!animation->endValue().isValid()) { |
1558 | animation->setEndValue(prop.value); |
1559 | result.localResetEndValues.append(t: animation); |
1560 | } |
1561 | result.handledAnimations.append(t: animation); |
1562 | } |
1563 | } |
1564 | return result; |
1565 | } |
1566 | |
1567 | void QStateMachinePrivate::_q_animationFinished() |
1568 | { |
1569 | Q_Q(QStateMachine); |
1570 | QAbstractAnimation *anim = qobject_cast<QAbstractAnimation*>(object: q->sender()); |
1571 | Q_ASSERT(anim != nullptr); |
1572 | QObject::disconnect(sender: anim, SIGNAL(finished()), receiver: q, SLOT(_q_animationFinished())); |
1573 | if (resetAnimationEndValues.contains(value: anim)) { |
1574 | qobject_cast<QVariantAnimation*>(object: anim)->setEndValue(QVariant()); // ### generalize |
1575 | resetAnimationEndValues.remove(value: anim); |
1576 | } |
1577 | |
1578 | QAbstractState *state = stateForAnimation.take(akey: anim); |
1579 | Q_ASSERT(state != nullptr); |
1580 | |
1581 | #ifndef QT_NO_PROPERTIES |
1582 | // Set the final property value. |
1583 | QPropertyAssignment assn = propertyForAnimation.take(akey: anim); |
1584 | assn.write(); |
1585 | if (!assn.explicitlySet) |
1586 | unregisterRestorables(states: QList<QAbstractState*>() << state, object: assn.object, propertyName: assn.propertyName); |
1587 | #endif |
1588 | |
1589 | QHash<QAbstractState*, QList<QAbstractAnimation*> >::iterator it; |
1590 | it = animationsForState.find(akey: state); |
1591 | Q_ASSERT(it != animationsForState.end()); |
1592 | QList<QAbstractAnimation*> &animations = it.value(); |
1593 | animations.removeOne(t: anim); |
1594 | if (animations.isEmpty()) { |
1595 | animationsForState.erase(it); |
1596 | QStatePrivate::get(q: toStandardState(state))->emitPropertiesAssigned(); |
1597 | } |
1598 | } |
1599 | |
1600 | QList<QAbstractAnimation *> QStateMachinePrivate::selectAnimations(const QList<QAbstractTransition *> &transitionList) const |
1601 | { |
1602 | QList<QAbstractAnimation *> selectedAnimations; |
1603 | if (animated) { |
1604 | for (int i = 0; i < transitionList.size(); ++i) { |
1605 | QAbstractTransition *transition = transitionList.at(i); |
1606 | |
1607 | selectedAnimations << transition->animations(); |
1608 | selectedAnimations << defaultAnimationsForSource.values(akey: transition->sourceState()); |
1609 | |
1610 | QList<QAbstractState *> targetStates = transition->targetStates(); |
1611 | for (int j=0; j<targetStates.size(); ++j) |
1612 | selectedAnimations << defaultAnimationsForTarget.values(akey: targetStates.at(i: j)); |
1613 | } |
1614 | selectedAnimations << defaultAnimations; |
1615 | } |
1616 | return selectedAnimations; |
1617 | } |
1618 | |
1619 | void QStateMachinePrivate::terminateActiveAnimations(QAbstractState *state, |
1620 | const QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) |
1621 | { |
1622 | Q_Q(QStateMachine); |
1623 | QList<QAbstractAnimation*> animations = animationsForState.take(akey: state); |
1624 | for (int i = 0; i < animations.size(); ++i) { |
1625 | QAbstractAnimation *anim = animations.at(i); |
1626 | QObject::disconnect(sender: anim, SIGNAL(finished()), receiver: q, SLOT(_q_animationFinished())); |
1627 | stateForAnimation.remove(akey: anim); |
1628 | |
1629 | // Stop the (top-level) animation. |
1630 | // ### Stopping nested animation has weird behavior. |
1631 | QAbstractAnimation *topLevelAnim = anim; |
1632 | while (QAnimationGroup *group = topLevelAnim->group()) |
1633 | topLevelAnim = group; |
1634 | topLevelAnim->stop(); |
1635 | |
1636 | if (resetAnimationEndValues.contains(value: anim)) { |
1637 | qobject_cast<QVariantAnimation*>(object: anim)->setEndValue(QVariant()); // ### generalize |
1638 | resetAnimationEndValues.remove(value: anim); |
1639 | } |
1640 | QPropertyAssignment assn = propertyForAnimation.take(akey: anim); |
1641 | Q_ASSERT(assn.object != nullptr); |
1642 | // If there is no property assignment that sets this property, |
1643 | // set the property to its target value. |
1644 | bool found = false; |
1645 | QHash<QAbstractState*, QVector<QPropertyAssignment> >::const_iterator it; |
1646 | for (it = assignmentsForEnteredStates.constBegin(); it != assignmentsForEnteredStates.constEnd(); ++it) { |
1647 | const QVector<QPropertyAssignment> &assignments = it.value(); |
1648 | for (int j = 0; j < assignments.size(); ++j) { |
1649 | if (assignments.at(i: j).hasTarget(o: assn.object, pn: assn.propertyName)) { |
1650 | found = true; |
1651 | break; |
1652 | } |
1653 | } |
1654 | } |
1655 | if (!found) { |
1656 | assn.write(); |
1657 | if (!assn.explicitlySet) |
1658 | unregisterRestorables(states: QList<QAbstractState*>() << state, object: assn.object, propertyName: assn.propertyName); |
1659 | } |
1660 | } |
1661 | } |
1662 | |
1663 | void QStateMachinePrivate::initializeAnimations(QAbstractState *state, const QList<QAbstractAnimation *> &selectedAnimations, |
1664 | const QList<QAbstractState*> &exitedStates_sorted, |
1665 | QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) |
1666 | { |
1667 | Q_Q(QStateMachine); |
1668 | if (!assignmentsForEnteredStates.contains(akey: state)) |
1669 | return; |
1670 | QVector<QPropertyAssignment> &assignments = assignmentsForEnteredStates[state]; |
1671 | for (int i = 0; i < selectedAnimations.size(); ++i) { |
1672 | QAbstractAnimation *anim = selectedAnimations.at(i); |
1673 | QVector<QPropertyAssignment>::iterator it; |
1674 | for (it = assignments.begin(); it != assignments.end(); ) { |
1675 | const QPropertyAssignment &assn = *it; |
1676 | const auto ret = initializeAnimation(abstractAnimation: anim, prop: assn); |
1677 | if (!ret.handledAnimations.isEmpty()) { |
1678 | for (int j = 0; j < ret.handledAnimations.size(); ++j) { |
1679 | QAbstractAnimation *a = ret.handledAnimations.at(i: j); |
1680 | propertyForAnimation.insert(akey: a, avalue: assn); |
1681 | stateForAnimation.insert(akey: a, avalue: state); |
1682 | animationsForState[state].append(t: a); |
1683 | // ### connect to just the top-level animation? |
1684 | QObject::connect(sender: a, SIGNAL(finished()), receiver: q, SLOT(_q_animationFinished()), Qt::UniqueConnection); |
1685 | } |
1686 | if ((globalRestorePolicy == QState::RestoreProperties) |
1687 | && !hasRestorable(state, object: assn.object, propertyName: assn.propertyName)) { |
1688 | QVariant value = savedValueForRestorable(exitedStates_sorted, object: assn.object, propertyName: assn.propertyName); |
1689 | unregisterRestorables(states: exitedStates_sorted, object: assn.object, propertyName: assn.propertyName); |
1690 | registerRestorable(state, object: assn.object, propertyName: assn.propertyName, value); |
1691 | } |
1692 | it = assignments.erase(pos: it); |
1693 | } else { |
1694 | ++it; |
1695 | } |
1696 | for (int j = 0; j < ret.localResetEndValues.size(); ++j) |
1697 | resetAnimationEndValues.insert(value: ret.localResetEndValues.at(i: j)); |
1698 | } |
1699 | // We require that at least one animation is valid. |
1700 | // ### generalize |
1701 | QList<QVariantAnimation*> variantAnims = anim->findChildren<QVariantAnimation*>(); |
1702 | if (QVariantAnimation *va = qobject_cast<QVariantAnimation*>(object: anim)) |
1703 | variantAnims.append(t: va); |
1704 | |
1705 | bool hasValidEndValue = false; |
1706 | for (int j = 0; j < variantAnims.size(); ++j) { |
1707 | if (variantAnims.at(i: j)->endValue().isValid()) { |
1708 | hasValidEndValue = true; |
1709 | break; |
1710 | } |
1711 | } |
1712 | |
1713 | if (hasValidEndValue) { |
1714 | if (anim->state() == QAbstractAnimation::Running) { |
1715 | // The animation is still running. This can happen if the |
1716 | // animation is a group, and one of its children just finished, |
1717 | // and that caused a state to emit its propertiesAssigned() signal, and |
1718 | // that triggered a transition in the machine. |
1719 | // Just stop the animation so it is correctly restarted again. |
1720 | anim->stop(); |
1721 | } |
1722 | anim->start(); |
1723 | } |
1724 | |
1725 | if (assignments.isEmpty()) { |
1726 | assignmentsForEnteredStates.remove(akey: state); |
1727 | break; |
1728 | } |
1729 | } |
1730 | } |
1731 | |
1732 | #endif // animation |
1733 | |
1734 | QAbstractTransition *QStateMachinePrivate::createInitialTransition() const |
1735 | { |
1736 | class InitialTransition : public QAbstractTransition |
1737 | { |
1738 | public: |
1739 | InitialTransition(const QList<QAbstractState *> &targets) |
1740 | : QAbstractTransition() |
1741 | { setTargetStates(targets); } |
1742 | protected: |
1743 | bool eventTest(QEvent *) override { return true; } |
1744 | void onTransition(QEvent *) override {} |
1745 | }; |
1746 | |
1747 | QState *root = rootState(); |
1748 | Q_ASSERT(root != nullptr); |
1749 | QList<QAbstractState *> targets; |
1750 | switch (root->childMode()) { |
1751 | case QState::ExclusiveStates: |
1752 | targets.append(t: root->initialState()); |
1753 | break; |
1754 | case QState::ParallelStates: |
1755 | targets = QStatePrivate::get(q: root)->childStates(); |
1756 | break; |
1757 | } |
1758 | return new InitialTransition(targets); |
1759 | } |
1760 | |
1761 | void QStateMachinePrivate::clearHistory() |
1762 | { |
1763 | Q_Q(QStateMachine); |
1764 | QList<QHistoryState*> historyStates = q->findChildren<QHistoryState*>(); |
1765 | for (int i = 0; i < historyStates.size(); ++i) { |
1766 | QHistoryState *h = historyStates.at(i); |
1767 | QHistoryStatePrivate::get(q: h)->configuration.clear(); |
1768 | } |
1769 | } |
1770 | |
1771 | /*! |
1772 | \internal |
1773 | |
1774 | Registers all signal transitions whose sender object lives in another thread. |
1775 | |
1776 | Normally, signal transitions are lazily registered (when a state becomes |
1777 | active). But if the sender is in a different thread, the transition must be |
1778 | registered early to keep the state machine from "dropping" signals; e.g., |
1779 | a second (transition-bound) signal could be emitted on the sender thread |
1780 | before the state machine gets to process the first signal. |
1781 | */ |
1782 | void QStateMachinePrivate::registerMultiThreadedSignalTransitions() |
1783 | { |
1784 | Q_Q(QStateMachine); |
1785 | QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>(); |
1786 | for (int i = 0; i < transitions.size(); ++i) { |
1787 | QSignalTransition *t = transitions.at(i); |
1788 | if ((t->machine() == q) && t->senderObject() && (t->senderObject()->thread() != q->thread())) |
1789 | registerSignalTransition(transition: t); |
1790 | } |
1791 | } |
1792 | |
1793 | void QStateMachinePrivate::_q_start() |
1794 | { |
1795 | Q_Q(QStateMachine); |
1796 | Q_ASSERT(state == Starting); |
1797 | // iterate over a copy, since we emit signals which may cause |
1798 | // 'configuration' to change, resulting in undefined behavior when |
1799 | // iterating at the same time: |
1800 | const auto config = configuration; |
1801 | for (QAbstractState *state : config) { |
1802 | QAbstractStatePrivate *abstractStatePrivate = QAbstractStatePrivate::get(q: state); |
1803 | abstractStatePrivate->active = false; |
1804 | emit state->activeChanged(active: false); |
1805 | } |
1806 | configuration.clear(); |
1807 | qDeleteAll(c: internalEventQueue); |
1808 | internalEventQueue.clear(); |
1809 | qDeleteAll(c: externalEventQueue); |
1810 | externalEventQueue.clear(); |
1811 | clearHistory(); |
1812 | |
1813 | registerMultiThreadedSignalTransitions(); |
1814 | |
1815 | startupHook(); |
1816 | |
1817 | #ifdef QSTATEMACHINE_DEBUG |
1818 | qDebug() << q << ": starting" ; |
1819 | #endif |
1820 | state = Running; |
1821 | processingScheduled = true; // we call _q_process() below |
1822 | |
1823 | QList<QAbstractTransition*> transitions; |
1824 | CalculationCache calculationCache; |
1825 | QAbstractTransition *initialTransition = createInitialTransition(); |
1826 | transitions.append(t: initialTransition); |
1827 | |
1828 | QEvent nullEvent(QEvent::None); |
1829 | executeTransitionContent(event: &nullEvent, enabledTransitions: transitions); |
1830 | QList<QAbstractState*> exitedStates = QList<QAbstractState*>(); |
1831 | QSet<QAbstractState*> statesForDefaultEntry; |
1832 | QList<QAbstractState*> enteredStates = computeEntrySet(enabledTransitions: transitions, statesForDefaultEntry, cache: &calculationCache); |
1833 | QHash<RestorableId, QVariant> pendingRestorables; |
1834 | QHash<QAbstractState*, QVector<QPropertyAssignment> > assignmentsForEnteredStates = |
1835 | computePropertyAssignments(statesToEnter_sorted: enteredStates, pendingRestorables); |
1836 | #if QT_CONFIG(animation) |
1837 | QList<QAbstractAnimation*> selectedAnimations = selectAnimations(transitionList: transitions); |
1838 | #endif |
1839 | // enterStates() will set stopProcessingReason to Finished if a final |
1840 | // state is entered. |
1841 | stopProcessingReason = EventQueueEmpty; |
1842 | enterStates(event: &nullEvent, exitedStates_sorted: exitedStates, statesToEnter_sorted: enteredStates, statesForDefaultEntry, |
1843 | propertyAssignmentsForState&: assignmentsForEnteredStates |
1844 | #if QT_CONFIG(animation) |
1845 | , selectedAnimations |
1846 | #endif |
1847 | ); |
1848 | delete initialTransition; |
1849 | |
1850 | #ifdef QSTATEMACHINE_DEBUG |
1851 | qDebug() << q << ": initial configuration:" << configuration; |
1852 | #endif |
1853 | |
1854 | emit q->started(QStateMachine::QPrivateSignal()); |
1855 | emit q->runningChanged(running: true); |
1856 | |
1857 | if (stopProcessingReason == Finished) { |
1858 | // The state machine immediately reached a final state. |
1859 | processingScheduled = false; |
1860 | state = NotRunning; |
1861 | unregisterAllTransitions(); |
1862 | emitFinished(); |
1863 | emit q->runningChanged(running: false); |
1864 | exitInterpreter(); |
1865 | } else { |
1866 | _q_process(); |
1867 | } |
1868 | } |
1869 | |
1870 | void QStateMachinePrivate::_q_process() |
1871 | { |
1872 | Q_Q(QStateMachine); |
1873 | Q_ASSERT(state == Running); |
1874 | Q_ASSERT(!processing); |
1875 | processing = true; |
1876 | processingScheduled = false; |
1877 | beginMacrostep(); |
1878 | #ifdef QSTATEMACHINE_DEBUG |
1879 | qDebug() << q << ": starting the event processing loop" ; |
1880 | #endif |
1881 | bool didChange = false; |
1882 | while (processing) { |
1883 | if (stop) { |
1884 | processing = false; |
1885 | break; |
1886 | } |
1887 | QList<QAbstractTransition*> enabledTransitions; |
1888 | CalculationCache calculationCache; |
1889 | |
1890 | QEvent *e = new QEvent(QEvent::None); |
1891 | enabledTransitions = selectTransitions(event: e, cache: &calculationCache); |
1892 | if (enabledTransitions.isEmpty()) { |
1893 | delete e; |
1894 | e = nullptr; |
1895 | } |
1896 | while (enabledTransitions.isEmpty() && ((e = dequeueInternalEvent()) != nullptr)) { |
1897 | #ifdef QSTATEMACHINE_DEBUG |
1898 | qDebug() << q << ": dequeued internal event" << e << "of type" << e->type(); |
1899 | #endif |
1900 | enabledTransitions = selectTransitions(event: e, cache: &calculationCache); |
1901 | if (enabledTransitions.isEmpty()) { |
1902 | delete e; |
1903 | e = nullptr; |
1904 | } |
1905 | } |
1906 | while (enabledTransitions.isEmpty() && ((e = dequeueExternalEvent()) != nullptr)) { |
1907 | #ifdef QSTATEMACHINE_DEBUG |
1908 | qDebug() << q << ": dequeued external event" << e << "of type" << e->type(); |
1909 | #endif |
1910 | enabledTransitions = selectTransitions(event: e, cache: &calculationCache); |
1911 | if (enabledTransitions.isEmpty()) { |
1912 | delete e; |
1913 | e = nullptr; |
1914 | } |
1915 | } |
1916 | if (enabledTransitions.isEmpty()) { |
1917 | if (isInternalEventQueueEmpty()) { |
1918 | processing = false; |
1919 | stopProcessingReason = EventQueueEmpty; |
1920 | noMicrostep(); |
1921 | #ifdef QSTATEMACHINE_DEBUG |
1922 | qDebug() << q << ": no transitions enabled" ; |
1923 | #endif |
1924 | } |
1925 | } else { |
1926 | didChange = true; |
1927 | q->beginMicrostep(event: e); |
1928 | microstep(event: e, enabledTransitions, cache: &calculationCache); |
1929 | q->endMicrostep(event: e); |
1930 | } |
1931 | delete e; |
1932 | } |
1933 | #ifdef QSTATEMACHINE_DEBUG |
1934 | qDebug() << q << ": finished the event processing loop" ; |
1935 | #endif |
1936 | if (stop) { |
1937 | stop = false; |
1938 | stopProcessingReason = Stopped; |
1939 | } |
1940 | |
1941 | switch (stopProcessingReason) { |
1942 | case EventQueueEmpty: |
1943 | processedPendingEvents(didChange); |
1944 | break; |
1945 | case Finished: |
1946 | state = NotRunning; |
1947 | cancelAllDelayedEvents(); |
1948 | unregisterAllTransitions(); |
1949 | emitFinished(); |
1950 | emit q->runningChanged(running: false); |
1951 | break; |
1952 | case Stopped: |
1953 | state = NotRunning; |
1954 | cancelAllDelayedEvents(); |
1955 | unregisterAllTransitions(); |
1956 | emit q->stopped(QStateMachine::QPrivateSignal()); |
1957 | emit q->runningChanged(running: false); |
1958 | break; |
1959 | } |
1960 | endMacrostep(didChange); |
1961 | if (stopProcessingReason == Finished) |
1962 | exitInterpreter(); |
1963 | } |
1964 | |
1965 | void QStateMachinePrivate::_q_startDelayedEventTimer(int id, int delay) |
1966 | { |
1967 | Q_Q(QStateMachine); |
1968 | QMutexLocker locker(&delayedEventsMutex); |
1969 | QHash<int, DelayedEvent>::iterator it = delayedEvents.find(akey: id); |
1970 | if (it != delayedEvents.end()) { |
1971 | DelayedEvent &e = it.value(); |
1972 | Q_ASSERT(!e.timerId); |
1973 | e.timerId = q->startTimer(interval: delay); |
1974 | if (!e.timerId) { |
1975 | qWarning(msg: "QStateMachine::postDelayedEvent: failed to start timer (id=%d, delay=%d)" , id, delay); |
1976 | delete e.event; |
1977 | delayedEvents.erase(it); |
1978 | delayedEventIdFreeList.release(id); |
1979 | } else { |
1980 | timerIdToDelayedEventId.insert(akey: e.timerId, avalue: id); |
1981 | } |
1982 | } else { |
1983 | // It's been cancelled already |
1984 | delayedEventIdFreeList.release(id); |
1985 | } |
1986 | } |
1987 | |
1988 | void QStateMachinePrivate::_q_killDelayedEventTimer(int id, int timerId) |
1989 | { |
1990 | Q_Q(QStateMachine); |
1991 | q->killTimer(id: timerId); |
1992 | QMutexLocker locker(&delayedEventsMutex); |
1993 | delayedEventIdFreeList.release(id); |
1994 | } |
1995 | |
1996 | void QStateMachinePrivate::postInternalEvent(QEvent *e) |
1997 | { |
1998 | QMutexLocker locker(&internalEventMutex); |
1999 | internalEventQueue.append(t: e); |
2000 | } |
2001 | |
2002 | void QStateMachinePrivate::postExternalEvent(QEvent *e) |
2003 | { |
2004 | QMutexLocker locker(&externalEventMutex); |
2005 | externalEventQueue.append(t: e); |
2006 | } |
2007 | |
2008 | QEvent *QStateMachinePrivate::dequeueInternalEvent() |
2009 | { |
2010 | QMutexLocker locker(&internalEventMutex); |
2011 | if (internalEventQueue.isEmpty()) |
2012 | return nullptr; |
2013 | return internalEventQueue.takeFirst(); |
2014 | } |
2015 | |
2016 | QEvent *QStateMachinePrivate::dequeueExternalEvent() |
2017 | { |
2018 | QMutexLocker locker(&externalEventMutex); |
2019 | if (externalEventQueue.isEmpty()) |
2020 | return nullptr; |
2021 | return externalEventQueue.takeFirst(); |
2022 | } |
2023 | |
2024 | bool QStateMachinePrivate::isInternalEventQueueEmpty() |
2025 | { |
2026 | QMutexLocker locker(&internalEventMutex); |
2027 | return internalEventQueue.isEmpty(); |
2028 | } |
2029 | |
2030 | bool QStateMachinePrivate::isExternalEventQueueEmpty() |
2031 | { |
2032 | QMutexLocker locker(&externalEventMutex); |
2033 | return externalEventQueue.isEmpty(); |
2034 | } |
2035 | |
2036 | void QStateMachinePrivate::processEvents(EventProcessingMode processingMode) |
2037 | { |
2038 | Q_Q(QStateMachine); |
2039 | if ((state != Running) || processing || processingScheduled) |
2040 | return; |
2041 | switch (processingMode) { |
2042 | case DirectProcessing: |
2043 | if (QThread::currentThread() == q->thread()) { |
2044 | _q_process(); |
2045 | break; |
2046 | } |
2047 | // processing must be done in the machine thread, so: |
2048 | Q_FALLTHROUGH(); |
2049 | case QueuedProcessing: |
2050 | processingScheduled = true; |
2051 | QMetaObject::invokeMethod(obj: q, member: "_q_process" , type: Qt::QueuedConnection); |
2052 | break; |
2053 | } |
2054 | } |
2055 | |
2056 | void QStateMachinePrivate::cancelAllDelayedEvents() |
2057 | { |
2058 | Q_Q(QStateMachine); |
2059 | QMutexLocker locker(&delayedEventsMutex); |
2060 | QHash<int, DelayedEvent>::const_iterator it; |
2061 | for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) { |
2062 | const DelayedEvent &e = it.value(); |
2063 | if (e.timerId) { |
2064 | timerIdToDelayedEventId.remove(akey: e.timerId); |
2065 | q->killTimer(id: e.timerId); |
2066 | delayedEventIdFreeList.release(id: it.key()); |
2067 | } else { |
2068 | // Cancellation will be detected in pending _q_startDelayedEventTimer() call |
2069 | } |
2070 | delete e.event; |
2071 | } |
2072 | delayedEvents.clear(); |
2073 | } |
2074 | |
2075 | /* |
2076 | This function is called when the state machine is performing no |
2077 | microstep because no transition is enabled (i.e. an event is ignored). |
2078 | |
2079 | The default implementation does nothing. |
2080 | */ |
2081 | void QStateMachinePrivate::noMicrostep() |
2082 | { } |
2083 | |
2084 | /* |
2085 | This function is called when the state machine has reached a stable |
2086 | state (no pending events), and has not finished yet. |
2087 | For each event the state machine receives it is guaranteed that |
2088 | 1) beginMacrostep is called |
2089 | 2) selectTransition is called at least once |
2090 | 3) begin/endMicrostep is called at least once or noMicrostep is called |
2091 | at least once (possibly both, but at least one) |
2092 | 4) the state machine either enters an infinite loop, or stops (runningChanged(false), |
2093 | and either finished or stopped are emitted), or processedPendingEvents() is called. |
2094 | 5) if the machine is not in an infinite loop endMacrostep is called |
2095 | 6) when the machine is finished and all processing (like signal emission) is done, |
2096 | exitInterpreter() is called. (This is the same name as the SCXML specification uses.) |
2097 | |
2098 | didChange is set to true if at least one microstep was performed, it is possible |
2099 | that the machine returned to exactly the same state as before, but some transitions |
2100 | were triggered. |
2101 | |
2102 | The default implementation does nothing. |
2103 | */ |
2104 | void QStateMachinePrivate::processedPendingEvents(bool didChange) |
2105 | { |
2106 | Q_UNUSED(didChange); |
2107 | } |
2108 | |
2109 | void QStateMachinePrivate::beginMacrostep() |
2110 | { } |
2111 | |
2112 | void QStateMachinePrivate::endMacrostep(bool didChange) |
2113 | { |
2114 | Q_UNUSED(didChange); |
2115 | } |
2116 | |
2117 | void QStateMachinePrivate::exitInterpreter() |
2118 | { |
2119 | } |
2120 | |
2121 | void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState) |
2122 | { |
2123 | Q_UNUSED(guiltyState); |
2124 | Q_ASSERT(guiltyState); |
2125 | |
2126 | #ifdef QSTATEMACHINE_DEBUG |
2127 | Q_Q(QStateMachine); |
2128 | qDebug() << q << ": emitting finished signal for" << forState; |
2129 | #endif |
2130 | |
2131 | QStatePrivate::get(q: forState)->emitFinished(); |
2132 | } |
2133 | |
2134 | void QStateMachinePrivate::startupHook() |
2135 | { |
2136 | } |
2137 | |
2138 | namespace _QStateMachine_Internal{ |
2139 | |
2140 | class GoToStateTransition : public QAbstractTransition |
2141 | { |
2142 | Q_OBJECT |
2143 | public: |
2144 | GoToStateTransition(QAbstractState *target) |
2145 | : QAbstractTransition() |
2146 | { setTargetState(target); } |
2147 | protected: |
2148 | void onTransition(QEvent *) override { deleteLater(); } |
2149 | bool eventTest(QEvent *) override { return true; } |
2150 | }; |
2151 | |
2152 | } // namespace |
2153 | // mingw compiler tries to export QObject::findChild<GoToStateTransition>(), |
2154 | // which doesn't work if its in an anonymous namespace. |
2155 | using namespace _QStateMachine_Internal; |
2156 | /*! |
2157 | \internal |
2158 | |
2159 | Causes this state machine to unconditionally transition to the given |
2160 | \a targetState. |
2161 | |
2162 | Provides a backdoor for using the state machine "imperatively"; i.e. rather |
2163 | than defining explicit transitions, you drive the machine's execution by |
2164 | calling this function. It breaks the whole integrity of the |
2165 | transition-driven model, but is provided for pragmatic reasons. |
2166 | */ |
2167 | void QStateMachinePrivate::goToState(QAbstractState *targetState) |
2168 | { |
2169 | if (!targetState) { |
2170 | qWarning(msg: "QStateMachine::goToState(): cannot go to null state" ); |
2171 | return; |
2172 | } |
2173 | |
2174 | if (configuration.contains(value: targetState)) |
2175 | return; |
2176 | |
2177 | Q_ASSERT(state == Running); |
2178 | QState *sourceState = nullptr; |
2179 | QSet<QAbstractState*>::const_iterator it; |
2180 | for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { |
2181 | sourceState = toStandardState(state: *it); |
2182 | if (sourceState != nullptr) |
2183 | break; |
2184 | } |
2185 | |
2186 | Q_ASSERT(sourceState != nullptr); |
2187 | // Reuse previous GoToStateTransition in case of several calls to |
2188 | // goToState() in a row. |
2189 | GoToStateTransition *trans = sourceState->findChild<GoToStateTransition*>(); |
2190 | if (!trans) { |
2191 | trans = new GoToStateTransition(targetState); |
2192 | sourceState->addTransition(transition: trans); |
2193 | } else { |
2194 | trans->setTargetState(targetState); |
2195 | } |
2196 | |
2197 | processEvents(processingMode: QueuedProcessing); |
2198 | } |
2199 | |
2200 | void QStateMachinePrivate::registerTransitions(QAbstractState *state) |
2201 | { |
2202 | QState *group = toStandardState(state); |
2203 | if (!group) |
2204 | return; |
2205 | QList<QAbstractTransition*> transitions = QStatePrivate::get(q: group)->transitions(); |
2206 | for (int i = 0; i < transitions.size(); ++i) { |
2207 | QAbstractTransition *t = transitions.at(i); |
2208 | registerTransition(transition: t); |
2209 | } |
2210 | } |
2211 | |
2212 | void QStateMachinePrivate::maybeRegisterTransition(QAbstractTransition *transition) |
2213 | { |
2214 | if (QSignalTransition *st = qobject_cast<QSignalTransition*>(object: transition)) { |
2215 | maybeRegisterSignalTransition(transition: st); |
2216 | } |
2217 | #if QT_CONFIG(qeventtransition) |
2218 | else if (QEventTransition *et = qobject_cast<QEventTransition*>(object: transition)) { |
2219 | maybeRegisterEventTransition(transition: et); |
2220 | } |
2221 | #endif |
2222 | } |
2223 | |
2224 | void QStateMachinePrivate::registerTransition(QAbstractTransition *transition) |
2225 | { |
2226 | if (QSignalTransition *st = qobject_cast<QSignalTransition*>(object: transition)) { |
2227 | registerSignalTransition(transition: st); |
2228 | } |
2229 | #if QT_CONFIG(qeventtransition) |
2230 | else if (QEventTransition *oet = qobject_cast<QEventTransition*>(object: transition)) { |
2231 | registerEventTransition(transition: oet); |
2232 | } |
2233 | #endif |
2234 | } |
2235 | |
2236 | void QStateMachinePrivate::unregisterTransition(QAbstractTransition *transition) |
2237 | { |
2238 | if (QSignalTransition *st = qobject_cast<QSignalTransition*>(object: transition)) { |
2239 | unregisterSignalTransition(transition: st); |
2240 | } |
2241 | #if QT_CONFIG(qeventtransition) |
2242 | else if (QEventTransition *oet = qobject_cast<QEventTransition*>(object: transition)) { |
2243 | unregisterEventTransition(transition: oet); |
2244 | } |
2245 | #endif |
2246 | } |
2247 | |
2248 | void QStateMachinePrivate::maybeRegisterSignalTransition(QSignalTransition *transition) |
2249 | { |
2250 | Q_Q(QStateMachine); |
2251 | if ((state == Running) && (configuration.contains(value: transition->sourceState()) |
2252 | || (transition->senderObject() && (transition->senderObject()->thread() != q->thread())))) { |
2253 | registerSignalTransition(transition); |
2254 | } |
2255 | } |
2256 | |
2257 | void QStateMachinePrivate::registerSignalTransition(QSignalTransition *transition) |
2258 | { |
2259 | Q_Q(QStateMachine); |
2260 | if (QSignalTransitionPrivate::get(q: transition)->signalIndex != -1) |
2261 | return; // already registered |
2262 | const QObject *sender = QSignalTransitionPrivate::get(q: transition)->sender; |
2263 | if (!sender) |
2264 | return; |
2265 | QByteArray signal = QSignalTransitionPrivate::get(q: transition)->signal; |
2266 | if (signal.isEmpty()) |
2267 | return; |
2268 | if (signal.startsWith(c: '0'+QSIGNAL_CODE)) |
2269 | signal.remove(index: 0, len: 1); |
2270 | const QMetaObject *meta = sender->metaObject(); |
2271 | int signalIndex = meta->indexOfSignal(signal); |
2272 | int originalSignalIndex = signalIndex; |
2273 | if (signalIndex == -1) { |
2274 | signalIndex = meta->indexOfSignal(signal: QMetaObject::normalizedSignature(method: signal)); |
2275 | if (signalIndex == -1) { |
2276 | qWarning(msg: "QSignalTransition: no such signal: %s::%s" , |
2277 | meta->className(), signal.constData()); |
2278 | return; |
2279 | } |
2280 | originalSignalIndex = signalIndex; |
2281 | } |
2282 | // The signal index we actually want to connect to is the one |
2283 | // that is going to be sent, i.e. the non-cloned original index. |
2284 | while (meta->method(index: signalIndex).attributes() & QMetaMethod::Cloned) |
2285 | --signalIndex; |
2286 | |
2287 | connectionsMutex.lock(); |
2288 | QVector<int> &connectedSignalIndexes = connections[sender]; |
2289 | if (connectedSignalIndexes.size() <= signalIndex) |
2290 | connectedSignalIndexes.resize(asize: signalIndex+1); |
2291 | if (connectedSignalIndexes.at(i: signalIndex) == 0) { |
2292 | if (!signalEventGenerator) |
2293 | signalEventGenerator = new QSignalEventGenerator(q); |
2294 | static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset(); |
2295 | bool ok = QMetaObject::connect(sender, signal_index: signalIndex, receiver: signalEventGenerator, method_index: generatorMethodOffset); |
2296 | if (!ok) { |
2297 | #ifdef QSTATEMACHINE_DEBUG |
2298 | qDebug() << q << ": FAILED to add signal transition from" << transition->sourceState() |
2299 | << ": ( sender =" << sender << ", signal =" << signal |
2300 | << ", targets =" << transition->targetStates() << ')'; |
2301 | #endif |
2302 | return; |
2303 | } |
2304 | } |
2305 | ++connectedSignalIndexes[signalIndex]; |
2306 | connectionsMutex.unlock(); |
2307 | |
2308 | QSignalTransitionPrivate::get(q: transition)->signalIndex = signalIndex; |
2309 | QSignalTransitionPrivate::get(q: transition)->originalSignalIndex = originalSignalIndex; |
2310 | #ifdef QSTATEMACHINE_DEBUG |
2311 | qDebug() << q << ": added signal transition from" << transition->sourceState() |
2312 | << ": ( sender =" << sender << ", signal =" << signal |
2313 | << ", targets =" << transition->targetStates() << ')'; |
2314 | #endif |
2315 | } |
2316 | |
2317 | void QStateMachinePrivate::unregisterSignalTransition(QSignalTransition *transition) |
2318 | { |
2319 | int signalIndex = QSignalTransitionPrivate::get(q: transition)->signalIndex; |
2320 | if (signalIndex == -1) |
2321 | return; // not registered |
2322 | const QObject *sender = QSignalTransitionPrivate::get(q: transition)->sender; |
2323 | QSignalTransitionPrivate::get(q: transition)->signalIndex = -1; |
2324 | |
2325 | connectionsMutex.lock(); |
2326 | QVector<int> &connectedSignalIndexes = connections[sender]; |
2327 | Q_ASSERT(connectedSignalIndexes.size() > signalIndex); |
2328 | Q_ASSERT(connectedSignalIndexes.at(signalIndex) != 0); |
2329 | if (--connectedSignalIndexes[signalIndex] == 0) { |
2330 | Q_ASSERT(signalEventGenerator != nullptr); |
2331 | static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset(); |
2332 | QMetaObject::disconnect(sender, signal_index: signalIndex, receiver: signalEventGenerator, method_index: generatorMethodOffset); |
2333 | int sum = 0; |
2334 | for (int i = 0; i < connectedSignalIndexes.size(); ++i) |
2335 | sum += connectedSignalIndexes.at(i); |
2336 | if (sum == 0) |
2337 | connections.remove(akey: sender); |
2338 | } |
2339 | connectionsMutex.unlock(); |
2340 | } |
2341 | |
2342 | void QStateMachinePrivate::unregisterAllTransitions() |
2343 | { |
2344 | Q_Q(QStateMachine); |
2345 | { |
2346 | QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>(); |
2347 | for (int i = 0; i < transitions.size(); ++i) { |
2348 | QSignalTransition *t = transitions.at(i); |
2349 | if (t->machine() == q) |
2350 | unregisterSignalTransition(transition: t); |
2351 | } |
2352 | } |
2353 | #if QT_CONFIG(qeventtransition) |
2354 | { |
2355 | QList<QEventTransition*> transitions = rootState()->findChildren<QEventTransition*>(); |
2356 | for (int i = 0; i < transitions.size(); ++i) { |
2357 | QEventTransition *t = transitions.at(i); |
2358 | if (t->machine() == q) |
2359 | unregisterEventTransition(transition: t); |
2360 | } |
2361 | } |
2362 | #endif |
2363 | } |
2364 | |
2365 | #if QT_CONFIG(qeventtransition) |
2366 | void QStateMachinePrivate::maybeRegisterEventTransition(QEventTransition *transition) |
2367 | { |
2368 | if ((state == Running) && configuration.contains(value: transition->sourceState())) |
2369 | registerEventTransition(transition); |
2370 | } |
2371 | |
2372 | void QStateMachinePrivate::registerEventTransition(QEventTransition *transition) |
2373 | { |
2374 | Q_Q(QStateMachine); |
2375 | if (QEventTransitionPrivate::get(q: transition)->registered) |
2376 | return; |
2377 | if (transition->eventType() >= QEvent::User) { |
2378 | qWarning(msg: "QObject event transitions are not supported for custom types" ); |
2379 | return; |
2380 | } |
2381 | QObject *object = QEventTransitionPrivate::get(q: transition)->object; |
2382 | if (!object) |
2383 | return; |
2384 | QObjectPrivate *od = QObjectPrivate::get(o: object); |
2385 | if (!od->extraData || !od->extraData->eventFilters.contains(t: q)) |
2386 | object->installEventFilter(filterObj: q); |
2387 | ++qobjectEvents[object][transition->eventType()]; |
2388 | QEventTransitionPrivate::get(q: transition)->registered = true; |
2389 | #ifdef QSTATEMACHINE_DEBUG |
2390 | qDebug() << q << ": added event transition from" << transition->sourceState() |
2391 | << ": ( object =" << object << ", event =" << transition->eventType() |
2392 | << ", targets =" << transition->targetStates() << ')'; |
2393 | #endif |
2394 | } |
2395 | |
2396 | void QStateMachinePrivate::unregisterEventTransition(QEventTransition *transition) |
2397 | { |
2398 | Q_Q(QStateMachine); |
2399 | if (!QEventTransitionPrivate::get(q: transition)->registered) |
2400 | return; |
2401 | QObject *object = QEventTransitionPrivate::get(q: transition)->object; |
2402 | QHash<QEvent::Type, int> &events = qobjectEvents[object]; |
2403 | Q_ASSERT(events.value(transition->eventType()) > 0); |
2404 | if (--events[transition->eventType()] == 0) { |
2405 | events.remove(akey: transition->eventType()); |
2406 | int sum = 0; |
2407 | QHash<QEvent::Type, int>::const_iterator it; |
2408 | for (it = events.constBegin(); it != events.constEnd(); ++it) |
2409 | sum += it.value(); |
2410 | if (sum == 0) { |
2411 | qobjectEvents.remove(akey: object); |
2412 | object->removeEventFilter(obj: q); |
2413 | } |
2414 | } |
2415 | QEventTransitionPrivate::get(q: transition)->registered = false; |
2416 | } |
2417 | |
2418 | void QStateMachinePrivate::handleFilteredEvent(QObject *watched, QEvent *event) |
2419 | { |
2420 | if (qobjectEvents.value(akey: watched).contains(akey: event->type())) { |
2421 | postInternalEvent(e: new QStateMachine::WrappedEvent(watched, handler->cloneEvent(event))); |
2422 | processEvents(processingMode: DirectProcessing); |
2423 | } |
2424 | } |
2425 | #endif |
2426 | |
2427 | void QStateMachinePrivate::handleTransitionSignal(QObject *sender, int signalIndex, |
2428 | void **argv) |
2429 | { |
2430 | #ifndef QT_NO_DEBUG |
2431 | connectionsMutex.lock(); |
2432 | Q_ASSERT(connections[sender].at(signalIndex) != 0); |
2433 | connectionsMutex.unlock(); |
2434 | #endif |
2435 | const QMetaObject *meta = sender->metaObject(); |
2436 | QMetaMethod method = meta->method(index: signalIndex); |
2437 | int argc = method.parameterCount(); |
2438 | QList<QVariant> vargs; |
2439 | vargs.reserve(alloc: argc); |
2440 | for (int i = 0; i < argc; ++i) { |
2441 | int type = method.parameterType(index: i); |
2442 | vargs.append(t: QVariant(type, argv[i+1])); |
2443 | } |
2444 | |
2445 | #ifdef QSTATEMACHINE_DEBUG |
2446 | qDebug() << q_func() << ": sending signal event ( sender =" << sender |
2447 | << ", signal =" << method.methodSignature().constData() << ')'; |
2448 | #endif |
2449 | postInternalEvent(e: new QStateMachine::SignalEvent(sender, signalIndex, vargs)); |
2450 | processEvents(processingMode: DirectProcessing); |
2451 | } |
2452 | |
2453 | /*! |
2454 | Constructs a new state machine with the given \a parent. |
2455 | */ |
2456 | QStateMachine::QStateMachine(QObject *parent) |
2457 | : QState(*new QStateMachinePrivate, /*parentState=*/nullptr) |
2458 | { |
2459 | // Can't pass the parent to the QState constructor, as it expects a QState |
2460 | // But this works as expected regardless of whether parent is a QState or not |
2461 | setParent(parent); |
2462 | } |
2463 | |
2464 | /*! |
2465 | \since 5.0 |
2466 | \deprecated |
2467 | |
2468 | Constructs a new state machine with the given \a childMode |
2469 | and \a parent. |
2470 | |
2471 | \warning Do not set the \a childMode to anything else than \l{ExclusiveStates}, otherwise the |
2472 | state machine is invalid, and might work incorrectly. |
2473 | */ |
2474 | QStateMachine::QStateMachine(QState::ChildMode childMode, QObject *parent) |
2475 | : QState(*new QStateMachinePrivate, /*parentState=*/nullptr) |
2476 | { |
2477 | Q_D(QStateMachine); |
2478 | d->childMode = childMode; |
2479 | setParent(parent); // See comment in constructor above |
2480 | |
2481 | if (childMode != ExclusiveStates) { |
2482 | //### FIXME for Qt6: remove this constructor completely, and hide the childMode property. |
2483 | // Yes, the StateMachine itself is conceptually a state, but it should only expose a limited |
2484 | // number of properties. The execution algorithm (in the URL below) treats a state machine |
2485 | // as a state, but from an API point of view, it's questionable if the QStateMachine should |
2486 | // inherit from QState. |
2487 | // |
2488 | // See function findLCCA in https://www.w3.org/TR/2014/WD-scxml-20140529/#AlgorithmforSCXMLInterpretation |
2489 | // to see where setting childMode to parallel will break down. |
2490 | qWarning() << "Invalid childMode for QStateMachine" << this; |
2491 | } |
2492 | } |
2493 | |
2494 | /*! |
2495 | \internal |
2496 | */ |
2497 | QStateMachine::QStateMachine(QStateMachinePrivate &dd, QObject *parent) |
2498 | : QState(dd, /*parentState=*/nullptr) |
2499 | { |
2500 | setParent(parent); |
2501 | } |
2502 | |
2503 | /*! |
2504 | Destroys this state machine. |
2505 | */ |
2506 | QStateMachine::~QStateMachine() |
2507 | { |
2508 | } |
2509 | |
2510 | /*! |
2511 | \enum QStateMachine::EventPriority |
2512 | |
2513 | This enum type specifies the priority of an event posted to the state |
2514 | machine using postEvent(). |
2515 | |
2516 | Events of high priority are processed before events of normal priority. |
2517 | |
2518 | \value NormalPriority The event has normal priority. |
2519 | \value HighPriority The event has high priority. |
2520 | */ |
2521 | |
2522 | /*! \enum QStateMachine::Error |
2523 | |
2524 | This enum type defines errors that can occur in the state machine at run time. When the state |
2525 | machine encounters an unrecoverable error at run time, it will set the error code returned |
2526 | by error(), the error message returned by errorString(), and enter an error state based on |
2527 | the context of the error. |
2528 | |
2529 | \value NoError No error has occurred. |
2530 | \value NoInitialStateError The machine has entered a QState with children which does not have an |
2531 | initial state set. The context of this error is the state which is missing an initial |
2532 | state. |
2533 | \value NoDefaultStateInHistoryStateError The machine has entered a QHistoryState which does not have |
2534 | a default state set. The context of this error is the QHistoryState which is missing a |
2535 | default state. |
2536 | \value NoCommonAncestorForTransitionError The machine has selected a transition whose source |
2537 | and targets are not part of the same tree of states, and thus are not part of the same |
2538 | state machine. Commonly, this could mean that one of the states has not been given |
2539 | any parent or added to any machine. The context of this error is the source state of |
2540 | the transition. |
2541 | \value StateMachineChildModeSetToParallelError The machine's \l childMode |
2542 | property was set to \l{QState::ParallelStates}. This is illegal. |
2543 | Only states may be declared as parallel, not the state machine |
2544 | itself. This enum value was added in Qt 5.14. |
2545 | |
2546 | \sa setErrorState() |
2547 | */ |
2548 | |
2549 | /*! |
2550 | Returns the error code of the last error that occurred in the state machine. |
2551 | */ |
2552 | QStateMachine::Error QStateMachine::error() const |
2553 | { |
2554 | Q_D(const QStateMachine); |
2555 | return d->error; |
2556 | } |
2557 | |
2558 | /*! |
2559 | Returns the error string of the last error that occurred in the state machine. |
2560 | */ |
2561 | QString QStateMachine::errorString() const |
2562 | { |
2563 | Q_D(const QStateMachine); |
2564 | return d->errorString; |
2565 | } |
2566 | |
2567 | /*! |
2568 | Clears the error string and error code of the state machine. |
2569 | */ |
2570 | void QStateMachine::clearError() |
2571 | { |
2572 | Q_D(QStateMachine); |
2573 | d->errorString.clear(); |
2574 | d->error = NoError; |
2575 | } |
2576 | |
2577 | /*! |
2578 | Returns the restore policy of the state machine. |
2579 | |
2580 | \sa setGlobalRestorePolicy() |
2581 | */ |
2582 | QState::RestorePolicy QStateMachine::globalRestorePolicy() const |
2583 | { |
2584 | Q_D(const QStateMachine); |
2585 | return d->globalRestorePolicy; |
2586 | } |
2587 | |
2588 | /*! |
2589 | Sets the restore policy of the state machine to \a restorePolicy. The default |
2590 | restore policy is QState::DontRestoreProperties. |
2591 | |
2592 | \sa globalRestorePolicy() |
2593 | */ |
2594 | void QStateMachine::setGlobalRestorePolicy(QState::RestorePolicy restorePolicy) |
2595 | { |
2596 | Q_D(QStateMachine); |
2597 | d->globalRestorePolicy = restorePolicy; |
2598 | } |
2599 | |
2600 | /*! |
2601 | Adds the given \a state to this state machine. The state becomes a top-level |
2602 | state and the state machine takes ownership of the state. |
2603 | |
2604 | If the state is already in a different machine, it will first be removed |
2605 | from its old machine, and then added to this machine. |
2606 | |
2607 | \sa removeState(), setInitialState() |
2608 | */ |
2609 | void QStateMachine::addState(QAbstractState *state) |
2610 | { |
2611 | if (!state) { |
2612 | qWarning(msg: "QStateMachine::addState: cannot add null state" ); |
2613 | return; |
2614 | } |
2615 | if (QAbstractStatePrivate::get(q: state)->machine() == this) { |
2616 | qWarning(msg: "QStateMachine::addState: state has already been added to this machine" ); |
2617 | return; |
2618 | } |
2619 | state->setParent(this); |
2620 | } |
2621 | |
2622 | /*! |
2623 | Removes the given \a state from this state machine. The state machine |
2624 | releases ownership of the state. |
2625 | |
2626 | \sa addState() |
2627 | */ |
2628 | void QStateMachine::removeState(QAbstractState *state) |
2629 | { |
2630 | if (!state) { |
2631 | qWarning(msg: "QStateMachine::removeState: cannot remove null state" ); |
2632 | return; |
2633 | } |
2634 | if (QAbstractStatePrivate::get(q: state)->machine() != this) { |
2635 | qWarning(msg: "QStateMachine::removeState: state %p's machine (%p)" |
2636 | " is different from this machine (%p)" , |
2637 | state, QAbstractStatePrivate::get(q: state)->machine(), this); |
2638 | return; |
2639 | } |
2640 | state->setParent(nullptr); |
2641 | } |
2642 | |
2643 | bool QStateMachine::isRunning() const |
2644 | { |
2645 | Q_D(const QStateMachine); |
2646 | return (d->state == QStateMachinePrivate::Running); |
2647 | } |
2648 | |
2649 | /*! |
2650 | Starts this state machine. The machine will reset its configuration and |
2651 | transition to the initial state. When a final top-level state (QFinalState) |
2652 | is entered, the machine will emit the finished() signal. |
2653 | |
2654 | \note A state machine will not run without a running event loop, such as |
2655 | the main application event loop started with QCoreApplication::exec() or |
2656 | QApplication::exec(). |
2657 | |
2658 | \sa started(), finished(), stop(), initialState(), setRunning() |
2659 | */ |
2660 | void QStateMachine::start() |
2661 | { |
2662 | Q_D(QStateMachine); |
2663 | |
2664 | if ((childMode() == QState::ExclusiveStates) && (initialState() == nullptr)) { |
2665 | qWarning(msg: "QStateMachine::start: No initial state set for machine. Refusing to start." ); |
2666 | return; |
2667 | } |
2668 | |
2669 | switch (d->state) { |
2670 | case QStateMachinePrivate::NotRunning: |
2671 | d->state = QStateMachinePrivate::Starting; |
2672 | QMetaObject::invokeMethod(obj: this, member: "_q_start" , type: Qt::QueuedConnection); |
2673 | break; |
2674 | case QStateMachinePrivate::Starting: |
2675 | break; |
2676 | case QStateMachinePrivate::Running: |
2677 | qWarning(msg: "QStateMachine::start(): already running" ); |
2678 | break; |
2679 | } |
2680 | } |
2681 | |
2682 | /*! |
2683 | Stops this state machine. The state machine will stop processing events and |
2684 | then emit the stopped() signal. |
2685 | |
2686 | \sa stopped(), start(), setRunning() |
2687 | */ |
2688 | void QStateMachine::stop() |
2689 | { |
2690 | Q_D(QStateMachine); |
2691 | switch (d->state) { |
2692 | case QStateMachinePrivate::NotRunning: |
2693 | break; |
2694 | case QStateMachinePrivate::Starting: |
2695 | // the machine will exit as soon as it enters the event processing loop |
2696 | d->stop = true; |
2697 | break; |
2698 | case QStateMachinePrivate::Running: |
2699 | d->stop = true; |
2700 | d->processEvents(processingMode: QStateMachinePrivate::QueuedProcessing); |
2701 | break; |
2702 | } |
2703 | } |
2704 | |
2705 | void QStateMachine::setRunning(bool running) |
2706 | { |
2707 | if (running) |
2708 | start(); |
2709 | else |
2710 | stop(); |
2711 | } |
2712 | |
2713 | /*! |
2714 | \threadsafe |
2715 | |
2716 | Posts the given \a event of the given \a priority for processing by this |
2717 | state machine. |
2718 | |
2719 | This function returns immediately. The event is added to the state machine's |
2720 | event queue. Events are processed in the order posted. The state machine |
2721 | takes ownership of the event and deletes it once it has been processed. |
2722 | |
2723 | You can only post events when the state machine is running or when it is starting up. |
2724 | |
2725 | \sa postDelayedEvent() |
2726 | */ |
2727 | void QStateMachine::postEvent(QEvent *event, EventPriority priority) |
2728 | { |
2729 | Q_D(QStateMachine); |
2730 | switch (d->state) { |
2731 | case QStateMachinePrivate::Running: |
2732 | case QStateMachinePrivate::Starting: |
2733 | break; |
2734 | default: |
2735 | qWarning(msg: "QStateMachine::postEvent: cannot post event when the state machine is not running" ); |
2736 | return; |
2737 | } |
2738 | if (!event) { |
2739 | qWarning(msg: "QStateMachine::postEvent: cannot post null event" ); |
2740 | return; |
2741 | } |
2742 | #ifdef QSTATEMACHINE_DEBUG |
2743 | qDebug() << this << ": posting event" << event; |
2744 | #endif |
2745 | switch (priority) { |
2746 | case NormalPriority: |
2747 | d->postExternalEvent(e: event); |
2748 | break; |
2749 | case HighPriority: |
2750 | d->postInternalEvent(e: event); |
2751 | break; |
2752 | } |
2753 | d->processEvents(processingMode: QStateMachinePrivate::QueuedProcessing); |
2754 | } |
2755 | |
2756 | /*! |
2757 | \threadsafe |
2758 | |
2759 | Posts the given \a event for processing by this state machine, with the |
2760 | given \a delay in milliseconds. Returns an identifier associated with the |
2761 | delayed event, or -1 if the event could not be posted. |
2762 | |
2763 | This function returns immediately. When the delay has expired, the event |
2764 | will be added to the state machine's event queue for processing. The state |
2765 | machine takes ownership of the event and deletes it once it has been |
2766 | processed. |
2767 | |
2768 | You can only post events when the state machine is running. |
2769 | |
2770 | \sa cancelDelayedEvent(), postEvent() |
2771 | */ |
2772 | int QStateMachine::postDelayedEvent(QEvent *event, int delay) |
2773 | { |
2774 | Q_D(QStateMachine); |
2775 | if (d->state != QStateMachinePrivate::Running) { |
2776 | qWarning(msg: "QStateMachine::postDelayedEvent: cannot post event when the state machine is not running" ); |
2777 | return -1; |
2778 | } |
2779 | if (!event) { |
2780 | qWarning(msg: "QStateMachine::postDelayedEvent: cannot post null event" ); |
2781 | return -1; |
2782 | } |
2783 | if (delay < 0) { |
2784 | qWarning(msg: "QStateMachine::postDelayedEvent: delay cannot be negative" ); |
2785 | return -1; |
2786 | } |
2787 | #ifdef QSTATEMACHINE_DEBUG |
2788 | qDebug() << this << ": posting event" << event << "with delay" << delay; |
2789 | #endif |
2790 | QMutexLocker locker(&d->delayedEventsMutex); |
2791 | int id = d->delayedEventIdFreeList.next(); |
2792 | bool inMachineThread = (QThread::currentThread() == thread()); |
2793 | int timerId = inMachineThread ? startTimer(interval: delay) : 0; |
2794 | if (inMachineThread && !timerId) { |
2795 | qWarning(msg: "QStateMachine::postDelayedEvent: failed to start timer with interval %d" , delay); |
2796 | d->delayedEventIdFreeList.release(id); |
2797 | return -1; |
2798 | } |
2799 | QStateMachinePrivate::DelayedEvent delayedEvent(event, timerId); |
2800 | d->delayedEvents.insert(akey: id, avalue: delayedEvent); |
2801 | if (timerId) { |
2802 | d->timerIdToDelayedEventId.insert(akey: timerId, avalue: id); |
2803 | } else { |
2804 | Q_ASSERT(!inMachineThread); |
2805 | QMetaObject::invokeMethod(obj: this, member: "_q_startDelayedEventTimer" , |
2806 | type: Qt::QueuedConnection, |
2807 | Q_ARG(int, id), |
2808 | Q_ARG(int, delay)); |
2809 | } |
2810 | return id; |
2811 | } |
2812 | |
2813 | /*! |
2814 | \threadsafe |
2815 | |
2816 | Cancels the delayed event identified by the given \a id. The id should be a |
2817 | value returned by a call to postDelayedEvent(). Returns \c true if the event |
2818 | was successfully cancelled, otherwise returns \c false. |
2819 | |
2820 | \sa postDelayedEvent() |
2821 | */ |
2822 | bool QStateMachine::cancelDelayedEvent(int id) |
2823 | { |
2824 | Q_D(QStateMachine); |
2825 | if (d->state != QStateMachinePrivate::Running) { |
2826 | qWarning(msg: "QStateMachine::cancelDelayedEvent: the machine is not running" ); |
2827 | return false; |
2828 | } |
2829 | QMutexLocker locker(&d->delayedEventsMutex); |
2830 | QStateMachinePrivate::DelayedEvent e = d->delayedEvents.take(akey: id); |
2831 | if (!e.event) |
2832 | return false; |
2833 | if (e.timerId) { |
2834 | d->timerIdToDelayedEventId.remove(akey: e.timerId); |
2835 | bool inMachineThread = (QThread::currentThread() == thread()); |
2836 | if (inMachineThread) { |
2837 | killTimer(id: e.timerId); |
2838 | d->delayedEventIdFreeList.release(id); |
2839 | } else { |
2840 | QMetaObject::invokeMethod(obj: this, member: "_q_killDelayedEventTimer" , |
2841 | type: Qt::QueuedConnection, |
2842 | Q_ARG(int, id), |
2843 | Q_ARG(int, e.timerId)); |
2844 | } |
2845 | } else { |
2846 | // Cancellation will be detected in pending _q_startDelayedEventTimer() call |
2847 | } |
2848 | delete e.event; |
2849 | return true; |
2850 | } |
2851 | |
2852 | /*! |
2853 | Returns the maximal consistent set of states (including parallel and final |
2854 | states) that this state machine is currently in. If a state \c s is in the |
2855 | configuration, it is always the case that the parent of \c s is also in |
2856 | c. Note, however, that the machine itself is not an explicit member of the |
2857 | configuration. |
2858 | */ |
2859 | QSet<QAbstractState*> QStateMachine::configuration() const |
2860 | { |
2861 | Q_D(const QStateMachine); |
2862 | return d->configuration; |
2863 | } |
2864 | |
2865 | /*! |
2866 | \fn QStateMachine::started() |
2867 | |
2868 | This signal is emitted when the state machine has entered its initial state |
2869 | (QStateMachine::initialState). |
2870 | |
2871 | \sa QStateMachine::finished(), QStateMachine::start() |
2872 | */ |
2873 | |
2874 | /*! |
2875 | \fn QStateMachine::stopped() |
2876 | |
2877 | This signal is emitted when the state machine has stopped. |
2878 | |
2879 | \sa QStateMachine::stop(), QStateMachine::finished() |
2880 | */ |
2881 | |
2882 | /*! |
2883 | \reimp |
2884 | */ |
2885 | bool QStateMachine::event(QEvent *e) |
2886 | { |
2887 | Q_D(QStateMachine); |
2888 | if (e->type() == QEvent::Timer) { |
2889 | QTimerEvent *te = static_cast<QTimerEvent*>(e); |
2890 | int tid = te->timerId(); |
2891 | if (d->state != QStateMachinePrivate::Running) { |
2892 | // This event has been cancelled already |
2893 | QMutexLocker locker(&d->delayedEventsMutex); |
2894 | Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid)); |
2895 | return true; |
2896 | } |
2897 | d->delayedEventsMutex.lock(); |
2898 | int id = d->timerIdToDelayedEventId.take(akey: tid); |
2899 | QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(akey: id); |
2900 | if (ee.event != nullptr) { |
2901 | Q_ASSERT(ee.timerId == tid); |
2902 | killTimer(id: tid); |
2903 | d->delayedEventIdFreeList.release(id); |
2904 | d->delayedEventsMutex.unlock(); |
2905 | d->postExternalEvent(e: ee.event); |
2906 | d->processEvents(processingMode: QStateMachinePrivate::DirectProcessing); |
2907 | return true; |
2908 | } else { |
2909 | d->delayedEventsMutex.unlock(); |
2910 | } |
2911 | } |
2912 | return QState::event(e); |
2913 | } |
2914 | |
2915 | #if QT_CONFIG(qeventtransition) |
2916 | /*! |
2917 | \reimp |
2918 | */ |
2919 | bool QStateMachine::eventFilter(QObject *watched, QEvent *event) |
2920 | { |
2921 | Q_D(QStateMachine); |
2922 | d->handleFilteredEvent(watched, event); |
2923 | return false; |
2924 | } |
2925 | #endif |
2926 | |
2927 | /*! |
2928 | \internal |
2929 | |
2930 | This function is called when the state machine is about to select |
2931 | transitions based on the given \a event. |
2932 | |
2933 | The default implementation does nothing. |
2934 | */ |
2935 | void QStateMachine::beginSelectTransitions(QEvent *event) |
2936 | { |
2937 | Q_UNUSED(event); |
2938 | } |
2939 | |
2940 | /*! |
2941 | \internal |
2942 | |
2943 | This function is called when the state machine has finished selecting |
2944 | transitions based on the given \a event. |
2945 | |
2946 | The default implementation does nothing. |
2947 | */ |
2948 | void QStateMachine::endSelectTransitions(QEvent *event) |
2949 | { |
2950 | Q_UNUSED(event); |
2951 | } |
2952 | |
2953 | /*! |
2954 | \internal |
2955 | |
2956 | This function is called when the state machine is about to do a microstep. |
2957 | |
2958 | The default implementation does nothing. |
2959 | */ |
2960 | void QStateMachine::beginMicrostep(QEvent *event) |
2961 | { |
2962 | Q_UNUSED(event); |
2963 | } |
2964 | |
2965 | /*! |
2966 | \internal |
2967 | |
2968 | This function is called when the state machine has finished doing a |
2969 | microstep. |
2970 | |
2971 | The default implementation does nothing. |
2972 | */ |
2973 | void QStateMachine::endMicrostep(QEvent *event) |
2974 | { |
2975 | Q_UNUSED(event); |
2976 | } |
2977 | |
2978 | /*! |
2979 | \reimp |
2980 | This function will call start() to start the state machine. |
2981 | */ |
2982 | void QStateMachine::onEntry(QEvent *event) |
2983 | { |
2984 | start(); |
2985 | QState::onEntry(event); |
2986 | } |
2987 | |
2988 | /*! |
2989 | \reimp |
2990 | This function will call stop() to stop the state machine and |
2991 | subsequently emit the stopped() signal. |
2992 | */ |
2993 | void QStateMachine::onExit(QEvent *event) |
2994 | { |
2995 | stop(); |
2996 | QState::onExit(event); |
2997 | } |
2998 | |
2999 | #if QT_CONFIG(animation) |
3000 | |
3001 | /*! |
3002 | Returns whether animations are enabled for this state machine. |
3003 | */ |
3004 | bool QStateMachine::isAnimated() const |
3005 | { |
3006 | Q_D(const QStateMachine); |
3007 | return d->animated; |
3008 | } |
3009 | |
3010 | /*! |
3011 | Sets whether animations are \a enabled for this state machine. |
3012 | */ |
3013 | void QStateMachine::setAnimated(bool enabled) |
3014 | { |
3015 | Q_D(QStateMachine); |
3016 | d->animated = enabled; |
3017 | } |
3018 | |
3019 | /*! |
3020 | Adds a default \a animation to be considered for any transition. |
3021 | */ |
3022 | void QStateMachine::addDefaultAnimation(QAbstractAnimation *animation) |
3023 | { |
3024 | Q_D(QStateMachine); |
3025 | d->defaultAnimations.append(t: animation); |
3026 | } |
3027 | |
3028 | /*! |
3029 | Returns the list of default animations that will be considered for any transition. |
3030 | */ |
3031 | QList<QAbstractAnimation*> QStateMachine::defaultAnimations() const |
3032 | { |
3033 | Q_D(const QStateMachine); |
3034 | return d->defaultAnimations; |
3035 | } |
3036 | |
3037 | /*! |
3038 | Removes \a animation from the list of default animations. |
3039 | */ |
3040 | void QStateMachine::removeDefaultAnimation(QAbstractAnimation *animation) |
3041 | { |
3042 | Q_D(QStateMachine); |
3043 | d->defaultAnimations.removeAll(t: animation); |
3044 | } |
3045 | |
3046 | #endif // animation |
3047 | |
3048 | |
3049 | // Begin moc-generated code -- modify carefully (check "HAND EDIT" parts)! |
3050 | struct qt_meta_stringdata_QSignalEventGenerator_t { |
3051 | QByteArrayData data[3]; |
3052 | char stringdata[32]; |
3053 | }; |
3054 | #define QT_MOC_LITERAL(idx, ofs, len) \ |
3055 | Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ |
3056 | offsetof(qt_meta_stringdata_QSignalEventGenerator_t, stringdata) + ofs \ |
3057 | - idx * sizeof(QByteArrayData) \ |
3058 | ) |
3059 | static const qt_meta_stringdata_QSignalEventGenerator_t qt_meta_stringdata_QSignalEventGenerator = { |
3060 | .data: { |
3061 | QT_MOC_LITERAL(0, 0, 21), |
3062 | QT_MOC_LITERAL(1, 22, 7), |
3063 | QT_MOC_LITERAL(2, 30, 0) |
3064 | }, |
3065 | .stringdata: "QSignalEventGenerator\0execute\0\0" |
3066 | }; |
3067 | #undef QT_MOC_LITERAL |
3068 | |
3069 | static const uint qt_meta_data_QSignalEventGenerator[] = { |
3070 | |
3071 | // content: |
3072 | 7, // revision |
3073 | 0, // classname |
3074 | 0, 0, // classinfo |
3075 | 1, 14, // methods |
3076 | 0, 0, // properties |
3077 | 0, 0, // enums/sets |
3078 | 0, 0, // constructors |
3079 | 0, // flags |
3080 | 0, // signalCount |
3081 | |
3082 | // slots: name, argc, parameters, tag, flags |
3083 | 1, 0, 19, 2, 0x0a, |
3084 | |
3085 | // slots: parameters |
3086 | QMetaType::Void, |
3087 | |
3088 | 0 // eod |
3089 | }; |
3090 | |
3091 | void QSignalEventGenerator::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) |
3092 | { |
3093 | if (_c == QMetaObject::InvokeMetaMethod) { |
3094 | Q_ASSERT(staticMetaObject.cast(_o)); |
3095 | QSignalEventGenerator *_t = static_cast<QSignalEventGenerator *>(_o); |
3096 | switch (_id) { |
3097 | case 0: _t->execute(_a); break; // HAND EDIT: add the _a parameter |
3098 | default: ; |
3099 | } |
3100 | } |
3101 | Q_UNUSED(_a); |
3102 | } |
3103 | |
3104 | const QMetaObject QSignalEventGenerator::staticMetaObject = { |
3105 | .d: { .superdata: &QObject::staticMetaObject, .stringdata: qt_meta_stringdata_QSignalEventGenerator.data, |
3106 | .data: qt_meta_data_QSignalEventGenerator, .static_metacall: qt_static_metacall, .relatedMetaObjects: nullptr, .extradata: nullptr } |
3107 | }; |
3108 | |
3109 | const QMetaObject *QSignalEventGenerator::metaObject() const |
3110 | { |
3111 | return &staticMetaObject; |
3112 | } |
3113 | |
3114 | void *QSignalEventGenerator::qt_metacast(const char *_clname) |
3115 | { |
3116 | if (!_clname) return nullptr; |
3117 | if (!strcmp(s1: _clname, s2: qt_meta_stringdata_QSignalEventGenerator.stringdata)) |
3118 | return static_cast<void*>(const_cast< QSignalEventGenerator*>(this)); |
3119 | return QObject::qt_metacast(_clname); |
3120 | } |
3121 | |
3122 | int QSignalEventGenerator::qt_metacall(QMetaObject::Call _c, int _id, void **_a) |
3123 | { |
3124 | _id = QObject::qt_metacall(_c, _id, _a); |
3125 | if (_id < 0) |
3126 | return _id; |
3127 | if (_c == QMetaObject::InvokeMetaMethod) { |
3128 | if (_id < 1) |
3129 | qt_static_metacall(o: this, _c, _id, _a); |
3130 | _id -= 1; |
3131 | } |
3132 | return _id; |
3133 | } |
3134 | // End moc-generated code |
3135 | |
3136 | void QSignalEventGenerator::execute(void **_a) |
3137 | { |
3138 | auto machinePrivate = QStateMachinePrivate::get(q: qobject_cast<QStateMachine*>(object: parent())); |
3139 | if (machinePrivate->state != QStateMachinePrivate::Running) |
3140 | return; |
3141 | int signalIndex = senderSignalIndex(); |
3142 | if (signalIndex == -1) { |
3143 | qWarning() << "StateMachine: Could not execute transition because originating object has been deleted" ; |
3144 | return; |
3145 | } |
3146 | machinePrivate->handleTransitionSignal(sender: sender(), signalIndex, argv: _a); |
3147 | } |
3148 | |
3149 | QSignalEventGenerator::QSignalEventGenerator(QStateMachine *parent) |
3150 | : QObject(parent) |
3151 | { |
3152 | } |
3153 | |
3154 | /*! |
3155 | \class QStateMachine::SignalEvent |
3156 | \inmodule QtCore |
3157 | |
3158 | \brief The SignalEvent class represents a Qt signal event. |
3159 | |
3160 | \since 4.6 |
3161 | \ingroup statemachine |
3162 | |
3163 | A signal event is generated by a QStateMachine in response to a Qt |
3164 | signal. The QSignalTransition class provides a transition associated with a |
3165 | signal event. QStateMachine::SignalEvent is part of \l{The State Machine Framework}. |
3166 | |
3167 | The sender() function returns the object that generated the signal. The |
3168 | signalIndex() function returns the index of the signal. The arguments() |
3169 | function returns the arguments of the signal. |
3170 | |
3171 | \sa QSignalTransition |
3172 | */ |
3173 | |
3174 | /*! |
3175 | \internal |
3176 | |
3177 | Constructs a new SignalEvent object with the given \a sender, \a |
3178 | signalIndex and \a arguments. |
3179 | */ |
3180 | QStateMachine::SignalEvent::SignalEvent(QObject *sender, int signalIndex, |
3181 | const QList<QVariant> &arguments) |
3182 | : QEvent(QEvent::StateMachineSignal), m_sender(sender), |
3183 | m_signalIndex(signalIndex), m_arguments(arguments) |
3184 | { |
3185 | } |
3186 | |
3187 | /*! |
3188 | Destroys this SignalEvent. |
3189 | */ |
3190 | QStateMachine::SignalEvent::~SignalEvent() |
3191 | { |
3192 | } |
3193 | |
3194 | /*! |
3195 | \fn QStateMachine::SignalEvent::sender() const |
3196 | |
3197 | Returns the object that emitted the signal. |
3198 | |
3199 | \sa QObject::sender() |
3200 | */ |
3201 | |
3202 | /*! |
3203 | \fn QStateMachine::SignalEvent::signalIndex() const |
3204 | |
3205 | Returns the index of the signal. |
3206 | |
3207 | \sa QMetaObject::indexOfSignal(), QMetaObject::method() |
3208 | */ |
3209 | |
3210 | /*! |
3211 | \fn QStateMachine::SignalEvent::arguments() const |
3212 | |
3213 | Returns the arguments of the signal. |
3214 | */ |
3215 | |
3216 | |
3217 | /*! |
3218 | \class QStateMachine::WrappedEvent |
3219 | \inmodule QtCore |
3220 | |
3221 | \brief The WrappedEvent class inherits QEvent and holds a clone of an event associated with a QObject. |
3222 | |
3223 | \since 4.6 |
3224 | \ingroup statemachine |
3225 | |
3226 | A wrapped event is generated by a QStateMachine in response to a Qt |
3227 | event. The QEventTransition class provides a transition associated with a |
3228 | such an event. QStateMachine::WrappedEvent is part of \l{The State Machine |
3229 | Framework}. |
3230 | |
3231 | The object() function returns the object that generated the event. The |
3232 | event() function returns a clone of the original event. |
3233 | |
3234 | \sa QEventTransition |
3235 | */ |
3236 | |
3237 | /*! |
3238 | \internal |
3239 | |
3240 | Constructs a new WrappedEvent object with the given \a object |
3241 | and \a event. |
3242 | |
3243 | The WrappedEvent object takes ownership of \a event. |
3244 | */ |
3245 | QStateMachine::WrappedEvent::WrappedEvent(QObject *object, QEvent *event) |
3246 | : QEvent(QEvent::StateMachineWrapped), m_object(object), m_event(event) |
3247 | { |
3248 | } |
3249 | |
3250 | /*! |
3251 | Destroys this WrappedEvent. |
3252 | */ |
3253 | QStateMachine::WrappedEvent::~WrappedEvent() |
3254 | { |
3255 | delete m_event; |
3256 | } |
3257 | |
3258 | /*! |
3259 | \fn QStateMachine::WrappedEvent::object() const |
3260 | |
3261 | Returns the object that the event is associated with. |
3262 | */ |
3263 | |
3264 | /*! |
3265 | \fn QStateMachine::WrappedEvent::event() const |
3266 | |
3267 | Returns a clone of the original event. |
3268 | */ |
3269 | |
3270 | /*! |
3271 | \fn QStateMachine::runningChanged(bool running) |
3272 | \since 5.4 |
3273 | |
3274 | This signal is emitted when the running property is changed with \a running as argument. |
3275 | |
3276 | \sa QStateMachine::running |
3277 | */ |
3278 | |
3279 | /*! |
3280 | \fn QStateMachine::postDelayedEvent(QEvent *event, std::chrono::milliseconds delay) |
3281 | \since 5.15 |
3282 | \overload |
3283 | \threadsafe |
3284 | |
3285 | Posts the given \a event for processing by this state machine, with the |
3286 | given \a delay in milliseconds. Returns an identifier associated with the |
3287 | delayed event, or -1 if the event could not be posted. |
3288 | |
3289 | This function returns immediately. When the delay has expired, the event |
3290 | will be added to the state machine's event queue for processing. The state |
3291 | machine takes ownership of the event and deletes it once it has been |
3292 | processed. |
3293 | |
3294 | You can only post events when the state machine is running. |
3295 | |
3296 | \sa cancelDelayedEvent(), postEvent() |
3297 | */ |
3298 | |
3299 | QT_END_NAMESPACE |
3300 | |
3301 | #include "qstatemachine.moc" |
3302 | #include "moc_qstatemachine.cpp" |
3303 | |