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

source code of qtscxml/src/statemachine/qstatemachine.cpp