summaryrefslogtreecommitdiffstats
path: root/src/corelib/statemachine/qstatemachine.cpp
diff options
context:
space:
mode:
authorKarsten Heimrich <karsten.heimrich@qt.io>2020-08-18 14:26:50 +0200
committerKarsten Heimrich <karsten.heimrich@qt.io>2020-08-24 20:10:25 +0200
commita735038376e1c229c293c36bd67800851323baf1 (patch)
tree4b0621b9a0b322ecb45e3843b3d7c3bacd90296f /src/corelib/statemachine/qstatemachine.cpp
parent43f01ec2e5bc52b290098d0fca1dd4ae40f2c6d3 (diff)
Move QStateMachine from QtCore to QtScxml
Task-number: QTBUG-80316 Change-Id: I2ee74110fd55e94d86321d3b3dc5bb8297424ed4 Reviewed-by: Maurice Kalinowski <maurice.kalinowski@qt.io>
Diffstat (limited to 'src/corelib/statemachine/qstatemachine.cpp')
-rw-r--r--src/corelib/statemachine/qstatemachine.cpp3208
1 files changed, 0 insertions, 3208 deletions
diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp
deleted file mode 100644
index 873892552f..0000000000
--- a/src/corelib/statemachine/qstatemachine.cpp
+++ /dev/null
@@ -1,3208 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qstatemachine.h"
-#include "qstate.h"
-#include "qstate_p.h"
-#include "qstatemachine_p.h"
-#include "qabstracttransition.h"
-#include "qabstracttransition_p.h"
-#include "qsignaltransition.h"
-#include "qsignaltransition_p.h"
-#include "qsignaleventgenerator_p.h"
-#include "qabstractstate.h"
-#include "qabstractstate_p.h"
-#include "qfinalstate.h"
-#include "qhistorystate.h"
-#include "qhistorystate_p.h"
-#include "private/qobject_p.h"
-#include "private/qthread_p.h"
-
-#if QT_CONFIG(qeventtransition)
-#include "qeventtransition.h"
-#include "qeventtransition_p.h"
-#endif
-
-#if QT_CONFIG(animation)
-#include "qpropertyanimation.h"
-#include "qanimationgroup.h"
-#include <private/qvariantanimation_p.h>
-#endif
-
-#include <QtCore/qmetaobject.h>
-#include <qdebug.h>
-
-#include <algorithm>
-
-QT_BEGIN_NAMESPACE
-
-/*!
- \class QStateMachine
- \inmodule QtCore
- \reentrant
-
- \brief The QStateMachine class provides a hierarchical finite state machine.
-
- \since 4.6
- \ingroup statemachine
-
- QStateMachine is based on the concepts and notation of
- \l{http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf}{Statecharts}.
- QStateMachine is part of \l{The State Machine Framework}.
-
- A state machine manages a set of states (classes that inherit from
- QAbstractState) and transitions (descendants of
- QAbstractTransition) between those states; these states and
- transitions define a state graph. Once a state graph has been
- built, the state machine can execute it. QStateMachine's
- execution algorithm is based on the \l{http://www.w3.org/TR/scxml/}{State Chart XML (SCXML)}
- algorithm. The framework's \l{The State Machine
- Framework}{overview} gives several state graphs and the code to
- build them.
-
- Use the addState() function to add a top-level state to the state machine.
- States are removed with the removeState() function. Removing states while
- the machine is running is discouraged.
-
- Before the machine can be started, the \l{initialState}{initial
- state} must be set. The initial state is the state that the
- machine enters when started. You can then start() the state
- machine. The started() signal is emitted when the initial state is
- entered.
-
- The machine is event driven and keeps its own event loop. Events
- are posted to the machine through postEvent(). Note that this
- means that it executes asynchronously, and that it will not
- progress without a running event loop. You will normally not have
- to post events to the machine directly as Qt's transitions, e.g.,
- QEventTransition and its subclasses, handle this. But for custom
- transitions triggered by events, postEvent() is useful.
-
- The state machine processes events and takes transitions until a
- top-level final state is entered; the state machine then emits the
- finished() signal. You can also stop() the state machine
- explicitly. The stopped() signal is emitted in this case.
-
- The following snippet shows a state machine that will finish when a button
- is clicked:
-
- \snippet code/src_corelib_statemachine_qstatemachine.cpp simple state machine
-
- This code example uses QState, which inherits QAbstractState. The
- QState class provides a state that you can use to set properties
- and invoke methods on \l{QObject}s when the state is entered or
- exited. It also contains convenience functions for adding
- transitions, e.g., \l{QSignalTransition}s as in this example. See
- the QState class description for further details.
-
- If an error is encountered, the machine will look for an
- \l{errorState}{error state}, and if one is available, it will
- enter this state. The types of errors possible are described by the
- \l{QStateMachine::}{Error} enum. After the error state is entered,
- the type of the error can be retrieved with error(). The execution
- of the state graph will not stop when the error state is entered. If
- no error state applies to the erroneous state, the machine will stop
- executing and an error message will be printed to the console.
-
- \note Important: setting the \l{ChildMode} of a state machine to parallel (\l{ParallelStates})
- results in an invalid state machine. It can only be set to (or kept as)
- \l{ExclusiveStates}.
-
- \sa QAbstractState, QAbstractTransition, QState, {The State Machine Framework}
-*/
-
-/*!
- \property QStateMachine::errorString
-
- \brief the error string of this state machine
-*/
-
-/*!
- \property QStateMachine::globalRestorePolicy
-
- \brief the restore policy for states of this state machine.
-
- The default value of this property is
- QState::DontRestoreProperties.
-*/
-
-/*!
- \property QStateMachine::running
- \since 5.4
-
- \brief the running state of this state machine
-
- \sa start(), stop(), started(), stopped(), runningChanged()
-*/
-
-#if QT_CONFIG(animation)
-/*!
- \property QStateMachine::animated
-
- \brief whether animations are enabled
-
- The default value of this property is \c true.
-
- \sa QAbstractTransition::addAnimation()
-*/
-#endif
-
-// #define QSTATEMACHINE_DEBUG
-// #define QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
-
-struct CalculationCache {
- struct TransitionInfo {
- QList<QAbstractState*> effectiveTargetStates;
- QSet<QAbstractState*> exitSet;
- QAbstractState *transitionDomain;
-
- bool effectiveTargetStatesIsKnown: 1;
- bool exitSetIsKnown : 1;
- bool transitionDomainIsKnown : 1;
-
- TransitionInfo()
- : transitionDomain(nullptr)
- , effectiveTargetStatesIsKnown(false)
- , exitSetIsKnown(false)
- , transitionDomainIsKnown(false)
- {}
- };
-
- typedef QHash<QAbstractTransition *, TransitionInfo> TransitionInfoCache;
- TransitionInfoCache cache;
-
- bool effectiveTargetStates(QAbstractTransition *t, QList<QAbstractState *> *targets) const
- {
- Q_ASSERT(targets);
-
- TransitionInfoCache::const_iterator cacheIt = cache.find(t);
- if (cacheIt == cache.end() || !cacheIt->effectiveTargetStatesIsKnown)
- return false;
-
- *targets = cacheIt->effectiveTargetStates;
- return true;
- }
-
- void insert(QAbstractTransition *t, const QList<QAbstractState *> &targets)
- {
- TransitionInfoCache::iterator cacheIt = cache.find(t);
- TransitionInfo &ti = cacheIt == cache.end()
- ? *cache.insert(t, TransitionInfo())
- : *cacheIt;
-
- Q_ASSERT(!ti.effectiveTargetStatesIsKnown);
- ti.effectiveTargetStates = targets;
- ti.effectiveTargetStatesIsKnown = true;
- }
-
- bool exitSet(QAbstractTransition *t, QSet<QAbstractState *> *exits) const
- {
- Q_ASSERT(exits);
-
- TransitionInfoCache::const_iterator cacheIt = cache.find(t);
- if (cacheIt == cache.end() || !cacheIt->exitSetIsKnown)
- return false;
-
- *exits = cacheIt->exitSet;
- return true;
- }
-
- void insert(QAbstractTransition *t, const QSet<QAbstractState *> &exits)
- {
- TransitionInfoCache::iterator cacheIt = cache.find(t);
- TransitionInfo &ti = cacheIt == cache.end()
- ? *cache.insert(t, TransitionInfo())
- : *cacheIt;
-
- Q_ASSERT(!ti.exitSetIsKnown);
- ti.exitSet = exits;
- ti.exitSetIsKnown = true;
- }
-
- bool transitionDomain(QAbstractTransition *t, QAbstractState **domain) const
- {
- Q_ASSERT(domain);
-
- TransitionInfoCache::const_iterator cacheIt = cache.find(t);
- if (cacheIt == cache.end() || !cacheIt->transitionDomainIsKnown)
- return false;
-
- *domain = cacheIt->transitionDomain;
- return true;
- }
-
- void insert(QAbstractTransition *t, QAbstractState *domain)
- {
- TransitionInfoCache::iterator cacheIt = cache.find(t);
- TransitionInfo &ti = cacheIt == cache.end()
- ? *cache.insert(t, TransitionInfo())
- : *cacheIt;
-
- Q_ASSERT(!ti.transitionDomainIsKnown);
- ti.transitionDomain = domain;
- ti.transitionDomainIsKnown = true;
- }
-};
-
-/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-function isDescendant(state1, state2)
-
-Returns 'true' if state1 is a descendant of state2 (a child, or a child of a child, or a child of a
-child of a child, etc.) Otherwise returns 'false'.
-*/
-static inline bool isDescendant(const QAbstractState *state1, const QAbstractState *state2)
-{
- Q_ASSERT(state1 != nullptr);
-
- for (QAbstractState *it = state1->parentState(); it != nullptr; it = it->parentState()) {
- if (it == state2)
- return true;
- }
-
- return false;
-}
-
-static bool containsDecendantOf(const QSet<QAbstractState *> &states, const QAbstractState *node)
-{
- for (QAbstractState *s : states)
- if (isDescendant(s, node))
- return true;
-
- return false;
-}
-
-static int descendantDepth(const QAbstractState *state, const QAbstractState *ancestor)
-{
- int depth = 0;
- for (const QAbstractState *it = state; it != nullptr; it = it->parentState()) {
- if (it == ancestor)
- break;
- ++depth;
- }
- return depth;
-}
-
-/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-function getProperAncestors(state1, state2)
-
-If state2 is null, returns the set of all ancestors of state1 in ancestry order (state1's parent
-followed by the parent's parent, etc. up to an including the <scxml> element). If state2 is
-non-null, returns in ancestry order the set of all ancestors of state1, up to but not including
-state2. (A "proper ancestor" of a state is its parent, or the parent's parent, or the parent's
-parent's parent, etc.))If state2 is state1's parent, or equal to state1, or a descendant of state1,
-this returns the empty set.
-*/
-static QList<QState *> getProperAncestors(const QAbstractState *state, const QAbstractState *upperBound)
-{
- Q_ASSERT(state != nullptr);
- QList<QState *> result;
- result.reserve(16);
- for (QState *it = state->parentState(); it && it != upperBound; it = it->parentState()) {
- result.append(it);
- }
- return result;
-}
-
-/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-function getEffectiveTargetStates(transition)
-
-Returns the states that will be the target when 'transition' is taken, dereferencing any history states.
-
-function getEffectiveTargetStates(transition)
- targets = new OrderedSet()
- for s in transition.target
- if isHistoryState(s):
- if historyValue[s.id]:
- targets.union(historyValue[s.id])
- else:
- targets.union(getEffectiveTargetStates(s.transition))
- else:
- targets.add(s)
- return targets
-*/
-static QList<QAbstractState *> getEffectiveTargetStates(QAbstractTransition *transition, CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- QList<QAbstractState *> targetsList;
- if (cache->effectiveTargetStates(transition, &targetsList))
- return targetsList;
-
- QSet<QAbstractState *> targets;
- const auto targetStates = transition->targetStates();
- for (QAbstractState *s : targetStates) {
- if (QHistoryState *historyState = QStateMachinePrivate::toHistoryState(s)) {
- QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(historyState)->configuration;
- if (!historyConfiguration.isEmpty()) {
- // There is a saved history, so apply that.
- targets.unite(QSet<QAbstractState *>(historyConfiguration.constBegin(), historyConfiguration.constEnd()));
- } else if (QAbstractTransition *defaultTransition = historyState->defaultTransition()) {
- // No saved history, take all default transition targets.
- const auto &targetStates = defaultTransition->targetStates();
- targets.unite(QSet<QAbstractState *>(targetStates.constBegin(), targetStates.constEnd()));
- } else {
- // Woops, we found a history state without a default state. That's not valid!
- QStateMachinePrivate *m = QStateMachinePrivate::get(historyState->machine());
- m->setError(QStateMachine::NoDefaultStateInHistoryStateError, historyState);
- }
- } else {
- targets.insert(s);
- }
- }
-
- targetsList = targets.values();
- cache->insert(transition, targetsList);
- return targetsList;
-}
-
-QStateMachinePrivate::QStateMachinePrivate()
-{
- isMachine = true;
-
- state = NotRunning;
- processing = false;
- processingScheduled = false;
- stop = false;
- stopProcessingReason = EventQueueEmpty;
- error = QStateMachine::NoError;
- globalRestorePolicy = QState::DontRestoreProperties;
- signalEventGenerator = nullptr;
-#if QT_CONFIG(animation)
- animated = true;
-#endif
-}
-
-QStateMachinePrivate::~QStateMachinePrivate()
-{
- qDeleteAll(internalEventQueue);
- qDeleteAll(externalEventQueue);
-
- for (QHash<int, DelayedEvent>::const_iterator it = delayedEvents.cbegin(), eit = delayedEvents.cend(); it != eit; ++it) {
- delete it.value().event;
- }
-}
-
-QState *QStateMachinePrivate::rootState() const
-{
- return const_cast<QStateMachine*>(q_func());
-}
-
-static QEvent *cloneEvent(QEvent *e)
-{
- switch (e->type()) {
- case QEvent::None:
- return new QEvent(*e);
- case QEvent::Timer:
- return new QTimerEvent(*static_cast<QTimerEvent*>(e));
- default:
- Q_ASSERT_X(false, "cloneEvent()", "not implemented");
- break;
- }
- return nullptr;
-}
-
-const QStateMachinePrivate::Handler qt_kernel_statemachine_handler = {
- cloneEvent
-};
-
-const QStateMachinePrivate::Handler *QStateMachinePrivate::handler = &qt_kernel_statemachine_handler;
-
-Q_CORE_EXPORT const QStateMachinePrivate::Handler *qcoreStateMachineHandler()
-{
- return &qt_kernel_statemachine_handler;
-}
-
-static int indexOfDescendant(QState *s, QAbstractState *desc)
-{
- QList<QAbstractState*> childStates = QStatePrivate::get(s)->childStates();
- for (int i = 0; i < childStates.size(); ++i) {
- QAbstractState *c = childStates.at(i);
- if ((c == desc) || isDescendant(desc, c)) {
- return i;
- }
- }
- return -1;
-}
-
-bool QStateMachinePrivate::transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2)
-{
- QState *s1 = t1->sourceState(), *s2 = t2->sourceState();
- if (s1 == s2) {
- QList<QAbstractTransition*> transitions = QStatePrivate::get(s1)->transitions();
- return transitions.indexOf(t1) < transitions.indexOf(t2);
- } else if (isDescendant(s1, s2)) {
- return true;
- } else if (isDescendant(s2, s1)) {
- return false;
- } else {
- Q_ASSERT(s1->machine() != nullptr);
- QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine());
- QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2);
- Q_ASSERT(lca != nullptr);
- int s1Depth = descendantDepth(s1, lca);
- int s2Depth = descendantDepth(s2, lca);
- if (s1Depth == s2Depth)
- return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2));
- else
- return s1Depth > s2Depth;
- }
-}
-
-bool QStateMachinePrivate::stateEntryLessThan(QAbstractState *s1, QAbstractState *s2)
-{
- if (s1->parent() == s2->parent()) {
- return s1->parent()->children().indexOf(s1)
- < s2->parent()->children().indexOf(s2);
- } else if (isDescendant(s1, s2)) {
- return false;
- } else if (isDescendant(s2, s1)) {
- return true;
- } else {
- Q_ASSERT(s1->machine() != nullptr);
- QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine());
- QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2);
- Q_ASSERT(lca != nullptr);
- return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2));
- }
-}
-
-bool QStateMachinePrivate::stateExitLessThan(QAbstractState *s1, QAbstractState *s2)
-{
- if (s1->parent() == s2->parent()) {
- return s2->parent()->children().indexOf(s2)
- < s1->parent()->children().indexOf(s1);
- } else if (isDescendant(s1, s2)) {
- return true;
- } else if (isDescendant(s2, s1)) {
- return false;
- } else {
- Q_ASSERT(s1->machine() != nullptr);
- QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine());
- QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2);
- Q_ASSERT(lca != nullptr);
- return (indexOfDescendant(lca, s2) < indexOfDescendant(lca, s1));
- }
-}
-
-QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states, bool onlyCompound)
-{
- if (states.isEmpty())
- return nullptr;
- QList<QState *> ancestors = getProperAncestors(states.at(0), rootState()->parentState());
- for (int i = 0; i < ancestors.size(); ++i) {
- QState *anc = ancestors.at(i);
- if (onlyCompound && !isCompound(anc))
- continue;
-
- bool ok = true;
- for (int j = states.size() - 1; (j > 0) && ok; --j) {
- const QAbstractState *s = states.at(j);
- if (!isDescendant(s, anc))
- ok = false;
- }
- if (ok)
- return anc;
- }
-
- // Oops, this should never happen! The state machine itself is a common ancestor of all states,
- // no matter what. But, for the onlyCompound case: we probably have a state machine whose
- // childMode is set to parallel, which is illegal. However, we're stuck with it (and with
- // exposing this invalid/dangerous API to users), so recover in the least horrible way.
- setError(QStateMachine::StateMachineChildModeSetToParallelError, q_func());
- return q_func(); // make the statemachine the LCA/LCCA (which it should have been anyway)
-}
-
-QState *QStateMachinePrivate::findLCCA(const QList<QAbstractState*> &states)
-{
- return findLCA(states, true);
-}
-
-QList<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event, CalculationCache *cache)
-{
- Q_ASSERT(cache);
- Q_Q(const QStateMachine);
-
- QVarLengthArray<QAbstractState *> configuration_sorted;
- for (QAbstractState *s : qAsConst(configuration)) {
- if (isAtomic(s))
- configuration_sorted.append(s);
- }
- std::sort(configuration_sorted.begin(), configuration_sorted.end(), stateEntryLessThan);
-
- QList<QAbstractTransition*> enabledTransitions;
- const_cast<QStateMachine *>(q)->beginSelectTransitions(event);
- for (QAbstractState *state : qAsConst(configuration_sorted)) {
- QList<QState *> lst = getProperAncestors(state, nullptr);
- if (QState *grp = toStandardState(state))
- lst.prepend(grp);
- bool found = false;
- for (int j = 0; (j < lst.size()) && !found; ++j) {
- QState *s = lst.at(j);
- QList<QAbstractTransition*> transitions = QStatePrivate::get(s)->transitions();
- for (int k = 0; k < transitions.size(); ++k) {
- QAbstractTransition *t = transitions.at(k);
- if (QAbstractTransitionPrivate::get(t)->callEventTest(event)) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": selecting transition" << t;
-#endif
- enabledTransitions.append(t);
- found = true;
- break;
- }
- }
- }
- }
-
- if (!enabledTransitions.isEmpty()) {
- removeConflictingTransitions(enabledTransitions, cache);
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": enabled transitions after removing conflicts:" << enabledTransitions;
-#endif
- }
- const_cast<QStateMachine*>(q)->endSelectTransitions(event);
- return enabledTransitions;
-}
-
-/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-function removeConflictingTransitions(enabledTransitions):
- filteredTransitions = new OrderedSet()
- // toList sorts the transitions in the order of the states that selected them
- for t1 in enabledTransitions.toList():
- t1Preempted = false;
- transitionsToRemove = new OrderedSet()
- for t2 in filteredTransitions.toList():
- if computeExitSet([t1]).hasIntersection(computeExitSet([t2])):
- if isDescendant(t1.source, t2.source):
- transitionsToRemove.add(t2)
- else:
- t1Preempted = true
- break
- if not t1Preempted:
- for t3 in transitionsToRemove.toList():
- filteredTransitions.delete(t3)
- filteredTransitions.add(t1)
-
- return filteredTransitions
-
-Note: the implementation below does not build the transitionsToRemove, but removes them in-place.
-*/
-void QStateMachinePrivate::removeConflictingTransitions(QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- if (enabledTransitions.size() < 2)
- return; // There is no transition to conflict with.
-
- QList<QAbstractTransition*> filteredTransitions;
- filteredTransitions.reserve(enabledTransitions.size());
- std::sort(enabledTransitions.begin(), enabledTransitions.end(), transitionStateEntryLessThan);
-
- for (QAbstractTransition *t1 : qAsConst(enabledTransitions)) {
- bool t1Preempted = false;
- const QSet<QAbstractState*> exitSetT1 = computeExitSet_Unordered(t1, cache);
- QList<QAbstractTransition*>::iterator t2It = filteredTransitions.begin();
- while (t2It != filteredTransitions.end()) {
- QAbstractTransition *t2 = *t2It;
- if (t1 == t2) {
- // Special case: someone added the same transition object to a state twice. In this
- // case, t2 (which is already in the list) "preempts" t1.
- t1Preempted = true;
- break;
- }
-
- QSet<QAbstractState*> exitSetT2 = computeExitSet_Unordered(t2, cache);
- if (!exitSetT1.intersects(exitSetT2)) {
- // No conflict, no cry. Next patient please.
- ++t2It;
- } else {
- // Houston, we have a conflict. Check which transition can be removed.
- if (isDescendant(t1->sourceState(), t2->sourceState())) {
- // t1 preempts t2, so we can remove t2
- t2It = filteredTransitions.erase(t2It);
- } else {
- // t2 preempts t1, so there's no use in looking further and we don't need to add
- // t1 to the list.
- t1Preempted = true;
- break;
- }
- }
- }
- if (!t1Preempted)
- filteredTransitions.append(t1);
- }
-
- enabledTransitions = filteredTransitions;
-}
-
-void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions,
- CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": begin microstep( enabledTransitions:" << enabledTransitions << ')';
- qDebug() << q_func() << ": configuration before exiting states:" << configuration;
-#endif
- QList<QAbstractState*> exitedStates = computeExitSet(enabledTransitions, cache);
- QHash<RestorableId, QVariant> pendingRestorables = computePendingRestorables(exitedStates);
-
- QSet<QAbstractState*> statesForDefaultEntry;
- QList<QAbstractState*> enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry, cache);
-
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": computed exit set:" << exitedStates;
- qDebug() << q_func() << ": computed entry set:" << enteredStates;
-#endif
-
- QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForEnteredStates =
- computePropertyAssignments(enteredStates, pendingRestorables);
- if (!pendingRestorables.isEmpty()) {
- // Add "implicit" assignments for restored properties to the first
- // (outermost) entered state
- Q_ASSERT(!enteredStates.isEmpty());
- QAbstractState *s = enteredStates.constFirst();
- assignmentsForEnteredStates[s] << restorablesToPropertyList(pendingRestorables);
- }
-
- exitStates(event, exitedStates, assignmentsForEnteredStates);
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": configuration after exiting states:" << configuration;
-#endif
-
- executeTransitionContent(event, enabledTransitions);
-
-#if QT_CONFIG(animation)
- QList<QAbstractAnimation *> selectedAnimations = selectAnimations(enabledTransitions);
-#endif
-
- enterStates(event, exitedStates, enteredStates, statesForDefaultEntry, assignmentsForEnteredStates
-#if QT_CONFIG(animation)
- , selectedAnimations
-#endif
- );
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": configuration after entering states:" << configuration;
- qDebug() << q_func() << ": end microstep";
-#endif
-}
-
-/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-procedure computeExitSet(enabledTransitions)
-
-For each transition t in enabledTransitions, if t is targetless then do nothing, else compute the
-transition's domain. (This will be the source state in the case of internal transitions) or the
-least common compound ancestor state of the source state and target states of t (in the case of
-external transitions. Add to the statesToExit set all states in the configuration that are
-descendants of the domain.
-
-function computeExitSet(transitions)
- statesToExit = new OrderedSet
- for t in transitions:
- if (t.target):
- domain = getTransitionDomain(t)
- for s in configuration:
- if isDescendant(s,domain):
- statesToExit.add(s)
- return statesToExit
-*/
-QList<QAbstractState*> QStateMachinePrivate::computeExitSet(const QList<QAbstractTransition*> &enabledTransitions,
- CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- QList<QAbstractState*> statesToExit_sorted = computeExitSet_Unordered(enabledTransitions, cache).values();
- std::sort(statesToExit_sorted.begin(), statesToExit_sorted.end(), stateExitLessThan);
- return statesToExit_sorted;
-}
-
-QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(const QList<QAbstractTransition*> &enabledTransitions,
- CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- QSet<QAbstractState*> statesToExit;
- for (QAbstractTransition *t : enabledTransitions)
- statesToExit.unite(computeExitSet_Unordered(t, cache));
- return statesToExit;
-}
-
-QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(QAbstractTransition *t,
- CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- QSet<QAbstractState*> statesToExit;
- if (cache->exitSet(t, &statesToExit))
- return statesToExit;
-
- QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache);
- QAbstractState *domain = getTransitionDomain(t, effectiveTargetStates, cache);
- if (domain == nullptr && !t->targetStates().isEmpty()) {
- // So we didn't find the least common ancestor for the source and target states of the
- // transition. If there were not target states, that would be fine: then the transition
- // will fire any events or signals, but not exit the state.
- //
- // However, there are target states, so it's either a node without a parent (or parent's
- // parent, etc), or the state belongs to a different state machine. Either way, this
- // makes the state machine invalid.
- if (error == QStateMachine::NoError)
- setError(QStateMachine::NoCommonAncestorForTransitionError, t->sourceState());
- QList<QAbstractState *> lst = pendingErrorStates.values();
- lst.prepend(t->sourceState());
-
- domain = findLCCA(lst);
- Q_ASSERT(domain != nullptr);
- }
-
- for (QAbstractState* s : qAsConst(configuration)) {
- if (isDescendant(s, domain))
- statesToExit.insert(s);
- }
-
- cache->insert(t, statesToExit);
- return statesToExit;
-}
-
-void QStateMachinePrivate::exitStates(QEvent *event, const QList<QAbstractState *> &statesToExit_sorted,
- const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates)
-{
- for (int i = 0; i < statesToExit_sorted.size(); ++i) {
- QAbstractState *s = statesToExit_sorted.at(i);
- if (QState *grp = toStandardState(s)) {
- QList<QHistoryState*> hlst = QStatePrivate::get(grp)->historyStates();
- for (int j = 0; j < hlst.size(); ++j) {
- QHistoryState *h = hlst.at(j);
- QHistoryStatePrivate::get(h)->configuration.clear();
- QSet<QAbstractState*>::const_iterator it;
- for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
- QAbstractState *s0 = *it;
- if (QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) {
- if (isAtomic(s0) && isDescendant(s0, s))
- QHistoryStatePrivate::get(h)->configuration.append(s0);
- } else if (s0->parentState() == s) {
- QHistoryStatePrivate::get(h)->configuration.append(s0);
- }
- }
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": recorded" << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow")
- << "history for" << s << "in" << h << ':' << QHistoryStatePrivate::get(h)->configuration;
-#endif
- }
- }
- }
- for (int i = 0; i < statesToExit_sorted.size(); ++i) {
- QAbstractState *s = statesToExit_sorted.at(i);
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": exiting" << s;
-#endif
- QAbstractStatePrivate::get(s)->callOnExit(event);
-
-#if QT_CONFIG(animation)
- terminateActiveAnimations(s, assignmentsForEnteredStates);
-#else
- Q_UNUSED(assignmentsForEnteredStates);
-#endif
-
- configuration.remove(s);
- QAbstractStatePrivate::get(s)->emitExited();
- }
-}
-
-void QStateMachinePrivate::executeTransitionContent(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions)
-{
- for (int i = 0; i < enabledTransitions.size(); ++i) {
- QAbstractTransition *t = enabledTransitions.at(i);
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": triggering" << t;
-#endif
- QAbstractTransitionPrivate::get(t)->callOnTransition(event);
- QAbstractTransitionPrivate::get(t)->emitTriggered();
- }
-}
-
-QList<QAbstractState*> QStateMachinePrivate::computeEntrySet(const QList<QAbstractTransition *> &enabledTransitions,
- QSet<QAbstractState *> &statesForDefaultEntry,
- CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- QSet<QAbstractState*> statesToEnter;
- if (pendingErrorStates.isEmpty()) {
- for (QAbstractTransition *t : enabledTransitions) {
- const auto targetStates = t->targetStates();
- for (QAbstractState *s : targetStates)
- addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
-
- const QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache);
- QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates, cache);
- for (QAbstractState *s : effectiveTargetStates)
- addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry);
- }
- }
-
- // Did an error occur while selecting transitions? Then we enter the error state.
- if (!pendingErrorStates.isEmpty()) {
- statesToEnter.clear();
- statesToEnter = pendingErrorStates;
- statesForDefaultEntry = pendingErrorStatesForDefaultEntry;
- pendingErrorStates.clear();
- pendingErrorStatesForDefaultEntry.clear();
- }
-
- QList<QAbstractState*> statesToEnter_sorted = statesToEnter.values();
- std::sort(statesToEnter_sorted.begin(), statesToEnter_sorted.end(), stateEntryLessThan);
- return statesToEnter_sorted;
-}
-
-/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-function getTransitionDomain(transition)
-
-Return the compound state such that 1) all states that are exited or entered as a result of taking
-'transition' are descendants of it 2) no descendant of it has this property.
-
-function getTransitionDomain(t)
- tstates = getEffectiveTargetStates(t)
- if not tstates:
- return null
- elif t.type == "internal" and isCompoundState(t.source) and tstates.every(lambda s: isDescendant(s,t.source)):
- return t.source
- else:
- return findLCCA([t.source].append(tstates))
-*/
-QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t,
- const QList<QAbstractState *> &effectiveTargetStates,
- CalculationCache *cache)
-{
- Q_ASSERT(cache);
-
- if (effectiveTargetStates.isEmpty())
- return nullptr;
-
- QAbstractState *domain = nullptr;
- if (cache->transitionDomain(t, &domain))
- return domain;
-
- if (t->transitionType() == QAbstractTransition::InternalTransition) {
- if (QState *tSource = t->sourceState()) {
- if (isCompound(tSource)) {
- bool allDescendants = true;
- for (QAbstractState *s : effectiveTargetStates) {
- if (!isDescendant(s, tSource)) {
- allDescendants = false;
- break;
- }
- }
-
- if (allDescendants)
- return tSource;
- }
- }
- }
-
- QList<QAbstractState *> states(effectiveTargetStates);
- if (QAbstractState *src = t->sourceState())
- states.prepend(src);
- domain = findLCCA(states);
- cache->insert(t, domain);
- return domain;
-}
-
-void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState *> &exitedStates_sorted,
- const QList<QAbstractState *> &statesToEnter_sorted,
- const QSet<QAbstractState *> &statesForDefaultEntry,
- QHash<QAbstractState *, QList<QPropertyAssignment>> &propertyAssignmentsForState
-#if QT_CONFIG(animation)
- , const QList<QAbstractAnimation *> &selectedAnimations
-#endif
- )
-{
-#ifdef QSTATEMACHINE_DEBUG
- Q_Q(QStateMachine);
-#endif
- for (int i = 0; i < statesToEnter_sorted.size(); ++i) {
- QAbstractState *s = statesToEnter_sorted.at(i);
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": entering" << s;
-#endif
- configuration.insert(s);
- registerTransitions(s);
-
-#if QT_CONFIG(animation)
- initializeAnimations(s, selectedAnimations, exitedStates_sorted, propertyAssignmentsForState);
-#endif
-
- // Immediately set the properties that are not animated.
- {
- const auto assignments = propertyAssignmentsForState.value(s);
- for (const auto &assn : assignments) {
- if (globalRestorePolicy == QState::RestoreProperties) {
- if (assn.explicitlySet) {
- if (!hasRestorable(s, assn.object, assn.propertyName)) {
- QVariant value = savedValueForRestorable(exitedStates_sorted, assn.object, assn.propertyName);
- unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName);
- registerRestorable(s, assn.object, assn.propertyName, value);
- }
- } else {
- // The property is being restored, hence no need to
- // save the current value. Discard any saved values in
- // exited states, since those are now stale.
- unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName);
- }
- }
- assn.write();
- }
- }
-
- QAbstractStatePrivate::get(s)->callOnEntry(event);
- QAbstractStatePrivate::get(s)->emitEntered();
-
- // FIXME:
- // See the "initial transitions" comment in addDescendantStatesToEnter first, then implement:
-// if (statesForDefaultEntry.contains(s)) {
-// // ### executeContent(s.initial.transition.children())
-// }
- Q_UNUSED(statesForDefaultEntry);
-
- if (QHistoryState *h = toHistoryState(s))
- QAbstractTransitionPrivate::get(h->defaultTransition())->callOnTransition(event);
-
- // Emit propertiesAssigned signal if the state has no animated properties.
- {
- QState *ss = toStandardState(s);
- if (ss
- #if QT_CONFIG(animation)
- && !animationsForState.contains(s)
- #endif
- )
- QStatePrivate::get(ss)->emitPropertiesAssigned();
- }
-
- if (isFinal(s)) {
- QState *parent = s->parentState();
- if (parent) {
- if (parent != rootState()) {
- QFinalState *finalState = qobject_cast<QFinalState *>(s);
- Q_ASSERT(finalState);
- emitStateFinished(parent, finalState);
- }
- QState *grandparent = parent->parentState();
- if (grandparent && isParallel(grandparent)) {
- bool allChildStatesFinal = true;
- QList<QAbstractState*> childStates = QStatePrivate::get(grandparent)->childStates();
- for (int j = 0; j < childStates.size(); ++j) {
- QAbstractState *cs = childStates.at(j);
- if (!isInFinalState(cs)) {
- allChildStatesFinal = false;
- break;
- }
- }
- if (allChildStatesFinal && (grandparent != rootState())) {
- QFinalState *finalState = qobject_cast<QFinalState *>(s);
- Q_ASSERT(finalState);
- emitStateFinished(grandparent, finalState);
- }
- }
- }
- }
- }
- {
- QSet<QAbstractState*>::const_iterator it;
- for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
- if (isFinal(*it)) {
- QState *parent = (*it)->parentState();
- if (((parent == rootState())
- && (rootState()->childMode() == QState::ExclusiveStates))
- || ((parent->parentState() == rootState())
- && (rootState()->childMode() == QState::ParallelStates)
- && isInFinalState(rootState()))) {
- processing = false;
- stopProcessingReason = Finished;
- break;
- }
- }
- }
- }
-// qDebug() << "configuration:" << configuration.toList();
-}
-
-/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ has a bug. See
- * QTBUG-44963 for details. The algorithm here is as described in
- * http://www.w3.org/Voice/2013/scxml-irp/SCXML.htm as of Friday March 13, 2015.
-
-procedure addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry, defaultHistoryContent):
- if isHistoryState(state):
- if historyValue[state.id]:
- for s in historyValue[state.id]:
- addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
- for s in historyValue[state.id]:
- addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
- else:
- defaultHistoryContent[state.parent.id] = state.transition.content
- for s in state.transition.target:
- addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
- for s in state.transition.target:
- addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
- else:
- statesToEnter.add(state)
- if isCompoundState(state):
- statesForDefaultEntry.add(state)
- for s in state.initial.transition.target:
- addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
- for s in state.initial.transition.target:
- addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
- else:
- if isParallelState(state):
- for child in getChildStates(state):
- if not statesToEnter.some(lambda s: isDescendant(s,child)):
- addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
-*/
-void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *state,
- QSet<QAbstractState*> &statesToEnter,
- QSet<QAbstractState*> &statesForDefaultEntry)
-{
- if (QHistoryState *h = toHistoryState(state)) {
- const QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(h)->configuration;
- if (!historyConfiguration.isEmpty()) {
- for (QAbstractState *s : historyConfiguration)
- addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
- for (QAbstractState *s : historyConfiguration)
- addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry);
-
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": restoring"
- << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow")
- << "history from" << state << ':' << historyConfiguration;
-#endif
- } else {
- QList<QAbstractState*> defaultHistoryContent;
- if (QAbstractTransition *t = QHistoryStatePrivate::get(h)->defaultTransition)
- defaultHistoryContent = t->targetStates();
-
- if (defaultHistoryContent.isEmpty()) {
- setError(QStateMachine::NoDefaultStateInHistoryStateError, h);
- } else {
- for (QAbstractState *s : qAsConst(defaultHistoryContent))
- addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
- for (QAbstractState *s : qAsConst(defaultHistoryContent))
- addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry);
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": initial history targets for" << state << ':' << defaultHistoryContent;
-#endif
- }
- }
- } else {
- if (state == rootState()) {
- // Error has already been set by exitStates().
- Q_ASSERT(error != QStateMachine::NoError);
- return;
- }
- statesToEnter.insert(state);
- if (isCompound(state)) {
- statesForDefaultEntry.insert(state);
- if (QAbstractState *initial = toStandardState(state)->initialState()) {
- Q_ASSERT(initial->machine() == q_func());
-
- // FIXME:
- // Qt does not support initial transitions (which is a problem for parallel states).
- // The way it simulates this for other states, is by having a single initial state.
- // See also the FIXME in enterStates.
- statesForDefaultEntry.insert(initial);
-
- addDescendantStatesToEnter(initial, statesToEnter, statesForDefaultEntry);
- addAncestorStatesToEnter(initial, state, statesToEnter, statesForDefaultEntry);
- } else {
- setError(QStateMachine::NoInitialStateError, state);
- return;
- }
- } else if (isParallel(state)) {
- QState *grp = toStandardState(state);
- const auto childStates = QStatePrivate::get(grp)->childStates();
- for (QAbstractState *child : childStates) {
- if (!containsDecendantOf(statesToEnter, child))
- addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry);
- }
- }
- }
-}
-
-
-/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
-
-procedure addAncestorStatesToEnter(state, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
- for anc in getProperAncestors(state,ancestor):
- statesToEnter.add(anc)
- if isParallelState(anc):
- for child in getChildStates(anc):
- if not statesToEnter.some(lambda s: isDescendant(s,child)):
- addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
-*/
-void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QAbstractState *ancestor,
- QSet<QAbstractState*> &statesToEnter,
- QSet<QAbstractState*> &statesForDefaultEntry)
-{
- const auto properAncestors = getProperAncestors(s, ancestor);
- for (QState *anc : properAncestors) {
- if (!anc->parentState())
- continue;
- statesToEnter.insert(anc);
- if (isParallel(anc)) {
- const auto childStates = QStatePrivate::get(anc)->childStates();
- for (QAbstractState *child : childStates) {
- if (!containsDecendantOf(statesToEnter, child))
- addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry);
- }
- }
- }
-}
-
-bool QStateMachinePrivate::isFinal(const QAbstractState *s)
-{
- return s && (QAbstractStatePrivate::get(s)->stateType == QAbstractStatePrivate::FinalState);
-}
-
-bool QStateMachinePrivate::isParallel(const QAbstractState *s)
-{
- const QState *ss = toStandardState(s);
- return ss && (QStatePrivate::get(ss)->childMode == QState::ParallelStates);
-}
-
-bool QStateMachinePrivate::isCompound(const QAbstractState *s) const
-{
- const QState *group = toStandardState(s);
- if (!group)
- return false;
- bool isMachine = QStatePrivate::get(group)->isMachine;
- // Don't treat the machine as compound if it's a sub-state of this machine
- if (isMachine && (group != rootState()))
- return false;
- return (!isParallel(group) && !QStatePrivate::get(group)->childStates().isEmpty());
-}
-
-bool QStateMachinePrivate::isAtomic(const QAbstractState *s) const
-{
- const QState *ss = toStandardState(s);
- return (ss && QStatePrivate::get(ss)->childStates().isEmpty())
- || isFinal(s)
- // Treat the machine as atomic if it's a sub-state of this machine
- || (ss && QStatePrivate::get(ss)->isMachine && (ss != rootState()));
-}
-
-QState *QStateMachinePrivate::toStandardState(QAbstractState *state)
-{
- if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState))
- return static_cast<QState*>(state);
- return nullptr;
-}
-
-const QState *QStateMachinePrivate::toStandardState(const QAbstractState *state)
-{
- if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState))
- return static_cast<const QState*>(state);
- return nullptr;
-}
-
-QFinalState *QStateMachinePrivate::toFinalState(QAbstractState *state)
-{
- if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::FinalState))
- return static_cast<QFinalState*>(state);
- return nullptr;
-}
-
-QHistoryState *QStateMachinePrivate::toHistoryState(QAbstractState *state)
-{
- if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::HistoryState))
- return static_cast<QHistoryState*>(state);
- return nullptr;
-}
-
-bool QStateMachinePrivate::isInFinalState(QAbstractState* s) const
-{
- if (isCompound(s)) {
- QState *grp = toStandardState(s);
- QList<QAbstractState*> lst = QStatePrivate::get(grp)->childStates();
- for (int i = 0; i < lst.size(); ++i) {
- QAbstractState *cs = lst.at(i);
- if (isFinal(cs) && configuration.contains(cs))
- return true;
- }
- return false;
- } else if (isParallel(s)) {
- QState *grp = toStandardState(s);
- QList<QAbstractState*> lst = QStatePrivate::get(grp)->childStates();
- for (int i = 0; i < lst.size(); ++i) {
- QAbstractState *cs = lst.at(i);
- if (!isInFinalState(cs))
- return false;
- }
- return true;
- }
- else
- return false;
-}
-
-#ifndef QT_NO_PROPERTIES
-
-/*!
- \internal
- Returns \c true if the given state has saved the value of the given property,
- otherwise returns \c false.
-*/
-bool QStateMachinePrivate::hasRestorable(QAbstractState *state, QObject *object,
- const QByteArray &propertyName) const
-{
- RestorableId id(object, propertyName);
- return registeredRestorablesForState.value(state).contains(id);
-}
-
-/*!
- \internal
- Returns the value to save for the property identified by \a id.
- If an exited state (member of \a exitedStates_sorted) has saved a value for
- the property, the saved value from the last (outermost) state that will be
- exited is returned (in practice carrying the saved value on to the next
- state). Otherwise, the current value of the property is returned.
-*/
-QVariant QStateMachinePrivate::savedValueForRestorable(const QList<QAbstractState*> &exitedStates_sorted,
- QObject *object, const QByteArray &propertyName) const
-{
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": savedValueForRestorable(" << exitedStates_sorted << object << propertyName << ')';
-#endif
- for (int i = exitedStates_sorted.size() - 1; i >= 0; --i) {
- QAbstractState *s = exitedStates_sorted.at(i);
- QHash<RestorableId, QVariant> restorables = registeredRestorablesForState.value(s);
- QHash<RestorableId, QVariant>::const_iterator it = restorables.constFind(RestorableId(object, propertyName));
- if (it != restorables.constEnd()) {
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": using" << it.value() << "from" << s;
-#endif
- return it.value();
- }
- }
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": falling back to current value";
-#endif
- return object->property(propertyName);
-}
-
-void QStateMachinePrivate::registerRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName,
- const QVariant &value)
-{
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": registerRestorable(" << state << object << propertyName << value << ')';
-#endif
- RestorableId id(object, propertyName);
- QHash<RestorableId, QVariant> &restorables = registeredRestorablesForState[state];
- if (!restorables.contains(id))
- restorables.insert(id, value);
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- else
- qDebug() << q_func() << ": (already registered)";
-#endif
-}
-
-void QStateMachinePrivate::unregisterRestorables(const QList<QAbstractState *> &states, QObject *object,
- const QByteArray &propertyName)
-{
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": unregisterRestorables(" << states << object << propertyName << ')';
-#endif
- RestorableId id(object, propertyName);
- for (int i = 0; i < states.size(); ++i) {
- QAbstractState *s = states.at(i);
- QHash<QAbstractState*, QHash<RestorableId, QVariant> >::iterator it;
- it = registeredRestorablesForState.find(s);
- if (it == registeredRestorablesForState.end())
- continue;
- QHash<RestorableId, QVariant> &restorables = it.value();
- const auto it2 = restorables.constFind(id);
- if (it2 == restorables.cend())
- continue;
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": unregistered for" << s;
-#endif
- restorables.erase(it2);
- if (restorables.isEmpty())
- registeredRestorablesForState.erase(it);
- }
-}
-
-QList<QPropertyAssignment> QStateMachinePrivate::restorablesToPropertyList(const QHash<RestorableId, QVariant> &restorables) const
-{
- QList<QPropertyAssignment> result;
- QHash<RestorableId, QVariant>::const_iterator it;
- for (it = restorables.constBegin(); it != restorables.constEnd(); ++it) {
- const RestorableId &id = it.key();
- if (!id.object()) {
- // Property object was deleted
- continue;
- }
-#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": restoring" << id.object() << id.proertyName() << "to" << it.value();
-#endif
- result.append(QPropertyAssignment(id.object(), id.propertyName(), it.value(), /*explicitlySet=*/false));
- }
- return result;
-}
-
-/*!
- \internal
- Computes the set of properties whose values should be restored given that
- the states \a statesToExit_sorted will be exited.
-
- If a particular (object, propertyName) pair occurs more than once (i.e.,
- because nested states are being exited), the value from the last (outermost)
- exited state takes precedence.
-
- The result of this function must be filtered according to the explicit
- property assignments (QState::assignProperty()) of the entered states
- before the property restoration is actually performed; i.e., if an entered
- state assigns to a property that would otherwise be restored, that property
- should not be restored after all, but the saved value from the exited state
- should be remembered by the entered state (see registerRestorable()).
-*/
-QHash<QStateMachinePrivate::RestorableId, QVariant> QStateMachinePrivate::computePendingRestorables(
- const QList<QAbstractState*> &statesToExit_sorted) const
-{
- QHash<QStateMachinePrivate::RestorableId, QVariant> restorables;
- for (int i = statesToExit_sorted.size() - 1; i >= 0; --i) {
- QAbstractState *s = statesToExit_sorted.at(i);
- QHash<QStateMachinePrivate::RestorableId, QVariant> rs = registeredRestorablesForState.value(s);
- QHash<QStateMachinePrivate::RestorableId, QVariant>::const_iterator it;
- for (it = rs.constBegin(); it != rs.constEnd(); ++it) {
- if (!restorables.contains(it.key()))
- restorables.insert(it.key(), it.value());
- }
- }
- return restorables;
-}
-
-/*!
- \internal
- Computes the ordered sets of property assignments for the states to be
- entered, \a statesToEnter_sorted. Also filters \a pendingRestorables (removes
- properties that should not be restored because they are assigned by an
- entered state).
-*/
-QHash<QAbstractState *, QList<QPropertyAssignment>> QStateMachinePrivate::computePropertyAssignments(
- const QList<QAbstractState*> &statesToEnter_sorted, QHash<RestorableId, QVariant> &pendingRestorables) const
-{
- QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForState;
- for (int i = 0; i < statesToEnter_sorted.size(); ++i) {
- QState *s = toStandardState(statesToEnter_sorted.at(i));
- if (!s)
- continue;
-
- QList<QPropertyAssignment> &assignments = QStatePrivate::get(s)->propertyAssignments;
- for (int j = 0; j < assignments.size(); ++j) {
- const QPropertyAssignment &assn = assignments.at(j);
- if (assn.objectDeleted()) {
- assignments.removeAt(j--);
- } else {
- pendingRestorables.remove(RestorableId(assn.object, assn.propertyName));
- assignmentsForState[s].append(assn);
- }
- }
- }
- return assignmentsForState;
-}
-
-#endif // QT_NO_PROPERTIES
-
-QAbstractState *QStateMachinePrivate::findErrorState(QAbstractState *context)
-{
- // Find error state recursively in parent hierarchy if not set explicitly for context state
- QAbstractState *errorState = nullptr;
- if (context != nullptr) {
- QState *s = toStandardState(context);
- if (s != nullptr)
- errorState = s->errorState();
-
- if (errorState == nullptr)
- errorState = findErrorState(context->parentState());
- }
-
- return errorState;
-}
-
-void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractState *currentContext)
-{
- Q_Q(QStateMachine);
-
- error = errorCode;
- switch (errorCode) {
- case QStateMachine::NoInitialStateError:
- Q_ASSERT(currentContext != nullptr);
-
- errorString = QStateMachine::tr("Missing initial state in compound state '%1'")
- .arg(currentContext->objectName());
-
- break;
- case QStateMachine::NoDefaultStateInHistoryStateError:
- Q_ASSERT(currentContext != nullptr);
-
- errorString = QStateMachine::tr("Missing default state in history state '%1'")
- .arg(currentContext->objectName());
- break;
-
- case QStateMachine::NoCommonAncestorForTransitionError:
- Q_ASSERT(currentContext != nullptr);
-
- errorString = QStateMachine::tr("No common ancestor for targets and source of transition from state '%1'")
- .arg(currentContext->objectName());
- break;
-
- case QStateMachine::StateMachineChildModeSetToParallelError:
- Q_ASSERT(currentContext != nullptr);
-
- errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'.")
- .arg(currentContext->objectName());
- break;
-
- default:
- errorString = QStateMachine::tr("Unknown error");
- };
-
- pendingErrorStates.clear();
- pendingErrorStatesForDefaultEntry.clear();
-
- QAbstractState *currentErrorState = findErrorState(currentContext);
-
- // Avoid infinite loop if the error state itself has an error
- if (currentContext == currentErrorState)
- currentErrorState = nullptr;
-
- Q_ASSERT(currentErrorState != rootState());
-
- if (currentErrorState != nullptr) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": entering error state" << currentErrorState << "from" << currentContext;
-#endif
- pendingErrorStates.insert(currentErrorState);
- addDescendantStatesToEnter(currentErrorState, pendingErrorStates, pendingErrorStatesForDefaultEntry);
- addAncestorStatesToEnter(currentErrorState, rootState(), pendingErrorStates, pendingErrorStatesForDefaultEntry);
- pendingErrorStates -= configuration;
- } else {
- qWarning("Unrecoverable error detected in running state machine: %ls",
- qUtf16Printable(errorString));
- q->stop();
- }
-}
-
-#if QT_CONFIG(animation)
-
-QStateMachinePrivate::InitializeAnimationResult
-QStateMachinePrivate::initializeAnimation(QAbstractAnimation *abstractAnimation,
- const QPropertyAssignment &prop)
-{
- InitializeAnimationResult result;
- QAnimationGroup *group = qobject_cast<QAnimationGroup*>(abstractAnimation);
- if (group) {
- for (int i = 0; i < group->animationCount(); ++i) {
- QAbstractAnimation *animationChild = group->animationAt(i);
- const auto ret = initializeAnimation(animationChild, prop);
- result.handledAnimations << ret.handledAnimations;
- result.localResetEndValues << ret.localResetEndValues;
- }
- } else {
- QPropertyAnimation *animation = qobject_cast<QPropertyAnimation *>(abstractAnimation);
- if (animation != nullptr
- && prop.object == animation->targetObject()
- && prop.propertyName == animation->propertyName()) {
-
- // Only change end value if it is undefined
- if (!animation->endValue().isValid()) {
- animation->setEndValue(prop.value);
- result.localResetEndValues.append(animation);
- }
- result.handledAnimations.append(animation);
- }
- }
- return result;
-}
-
-void QStateMachinePrivate::_q_animationFinished()
-{
- Q_Q(QStateMachine);
- QAbstractAnimation *anim = qobject_cast<QAbstractAnimation*>(q->sender());
- Q_ASSERT(anim != nullptr);
- QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished()));
- if (resetAnimationEndValues.contains(anim)) {
- qobject_cast<QVariantAnimation*>(anim)->setEndValue(QVariant()); // ### generalize
- resetAnimationEndValues.remove(anim);
- }
-
- QAbstractState *state = stateForAnimation.take(anim);
- Q_ASSERT(state != nullptr);
-
-#ifndef QT_NO_PROPERTIES
- // Set the final property value.
- QPropertyAssignment assn = propertyForAnimation.take(anim);
- assn.write();
- if (!assn.explicitlySet)
- unregisterRestorables(QList<QAbstractState*>() << state, assn.object, assn.propertyName);
-#endif
-
- QHash<QAbstractState*, QList<QAbstractAnimation*> >::iterator it;
- it = animationsForState.find(state);
- Q_ASSERT(it != animationsForState.end());
- QList<QAbstractAnimation*> &animations = it.value();
- animations.removeOne(anim);
- if (animations.isEmpty()) {
- animationsForState.erase(it);
- QStatePrivate::get(toStandardState(state))->emitPropertiesAssigned();
- }
-}
-
-QList<QAbstractAnimation *> QStateMachinePrivate::selectAnimations(const QList<QAbstractTransition *> &transitionList) const
-{
- QList<QAbstractAnimation *> selectedAnimations;
- if (animated) {
- for (int i = 0; i < transitionList.size(); ++i) {
- QAbstractTransition *transition = transitionList.at(i);
-
- selectedAnimations << transition->animations();
- selectedAnimations << defaultAnimationsForSource.values(transition->sourceState());
-
- QList<QAbstractState *> targetStates = transition->targetStates();
- for (int j=0; j<targetStates.size(); ++j)
- selectedAnimations << defaultAnimationsForTarget.values(targetStates.at(j));
- }
- selectedAnimations << defaultAnimations;
- }
- return selectedAnimations;
-}
-
-void QStateMachinePrivate::terminateActiveAnimations(QAbstractState *state,
- const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates)
-{
- Q_Q(QStateMachine);
- QList<QAbstractAnimation*> animations = animationsForState.take(state);
- for (int i = 0; i < animations.size(); ++i) {
- QAbstractAnimation *anim = animations.at(i);
- QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished()));
- stateForAnimation.remove(anim);
-
- // Stop the (top-level) animation.
- // ### Stopping nested animation has weird behavior.
- QAbstractAnimation *topLevelAnim = anim;
- while (QAnimationGroup *group = topLevelAnim->group())
- topLevelAnim = group;
- topLevelAnim->stop();
-
- if (resetAnimationEndValues.contains(anim)) {
- qobject_cast<QVariantAnimation*>(anim)->setEndValue(QVariant()); // ### generalize
- resetAnimationEndValues.remove(anim);
- }
- QPropertyAssignment assn = propertyForAnimation.take(anim);
- Q_ASSERT(assn.object != nullptr);
- // If there is no property assignment that sets this property,
- // set the property to its target value.
- bool found = false;
- for (auto it = assignmentsForEnteredStates.constBegin(); it != assignmentsForEnteredStates.constEnd(); ++it) {
- const QList<QPropertyAssignment> &assignments = it.value();
- for (int j = 0; j < assignments.size(); ++j) {
- if (assignments.at(j).hasTarget(assn.object, assn.propertyName)) {
- found = true;
- break;
- }
- }
- }
- if (!found) {
- assn.write();
- if (!assn.explicitlySet)
- unregisterRestorables(QList<QAbstractState*>() << state, assn.object, assn.propertyName);
- }
- }
-}
-
-void QStateMachinePrivate::initializeAnimations(QAbstractState *state, const QList<QAbstractAnimation *> &selectedAnimations,
- const QList<QAbstractState*> &exitedStates_sorted,
- QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates)
-{
- Q_Q(QStateMachine);
- if (!assignmentsForEnteredStates.contains(state))
- return;
- QList<QPropertyAssignment> &assignments = assignmentsForEnteredStates[state];
- for (int i = 0; i < selectedAnimations.size(); ++i) {
- QAbstractAnimation *anim = selectedAnimations.at(i);
- for (auto it = assignments.begin(); it != assignments.end(); ) {
- const QPropertyAssignment &assn = *it;
- const auto ret = initializeAnimation(anim, assn);
- if (!ret.handledAnimations.isEmpty()) {
- for (int j = 0; j < ret.handledAnimations.size(); ++j) {
- QAbstractAnimation *a = ret.handledAnimations.at(j);
- propertyForAnimation.insert(a, assn);
- stateForAnimation.insert(a, state);
- animationsForState[state].append(a);
- // ### connect to just the top-level animation?
- QObject::connect(a, SIGNAL(finished()), q, SLOT(_q_animationFinished()), Qt::UniqueConnection);
- }
- if ((globalRestorePolicy == QState::RestoreProperties)
- && !hasRestorable(state, assn.object, assn.propertyName)) {
- QVariant value = savedValueForRestorable(exitedStates_sorted, assn.object, assn.propertyName);
- unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName);
- registerRestorable(state, assn.object, assn.propertyName, value);
- }
- it = assignments.erase(it);
- } else {
- ++it;
- }
- for (int j = 0; j < ret.localResetEndValues.size(); ++j)
- resetAnimationEndValues.insert(ret.localResetEndValues.at(j));
- }
- // We require that at least one animation is valid.
- // ### generalize
- QList<QVariantAnimation*> variantAnims = anim->findChildren<QVariantAnimation*>();
- if (QVariantAnimation *va = qobject_cast<QVariantAnimation*>(anim))
- variantAnims.append(va);
-
- bool hasValidEndValue = false;
- for (int j = 0; j < variantAnims.size(); ++j) {
- if (variantAnims.at(j)->endValue().isValid()) {
- hasValidEndValue = true;
- break;
- }
- }
-
- if (hasValidEndValue) {
- if (anim->state() == QAbstractAnimation::Running) {
- // The animation is still running. This can happen if the
- // animation is a group, and one of its children just finished,
- // and that caused a state to emit its propertiesAssigned() signal, and
- // that triggered a transition in the machine.
- // Just stop the animation so it is correctly restarted again.
- anim->stop();
- }
- anim->start();
- }
-
- if (assignments.isEmpty()) {
- assignmentsForEnteredStates.remove(state);
- break;
- }
- }
-}
-
-#endif // animation
-
-QAbstractTransition *QStateMachinePrivate::createInitialTransition() const
-{
- class InitialTransition : public QAbstractTransition
- {
- public:
- InitialTransition(const QList<QAbstractState *> &targets)
- : QAbstractTransition()
- { setTargetStates(targets); }
- protected:
- bool eventTest(QEvent *) override { return true; }
- void onTransition(QEvent *) override {}
- };
-
- QState *root = rootState();
- Q_ASSERT(root != nullptr);
- QList<QAbstractState *> targets;
- switch (root->childMode()) {
- case QState::ExclusiveStates:
- targets.append(root->initialState());
- break;
- case QState::ParallelStates:
- targets = QStatePrivate::get(root)->childStates();
- break;
- }
- return new InitialTransition(targets);
-}
-
-void QStateMachinePrivate::clearHistory()
-{
- Q_Q(QStateMachine);
- QList<QHistoryState*> historyStates = q->findChildren<QHistoryState*>();
- for (int i = 0; i < historyStates.size(); ++i) {
- QHistoryState *h = historyStates.at(i);
- QHistoryStatePrivate::get(h)->configuration.clear();
- }
-}
-
-/*!
- \internal
-
- Registers all signal transitions whose sender object lives in another thread.
-
- Normally, signal transitions are lazily registered (when a state becomes
- active). But if the sender is in a different thread, the transition must be
- registered early to keep the state machine from "dropping" signals; e.g.,
- a second (transition-bound) signal could be emitted on the sender thread
- before the state machine gets to process the first signal.
-*/
-void QStateMachinePrivate::registerMultiThreadedSignalTransitions()
-{
- Q_Q(QStateMachine);
- QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>();
- for (int i = 0; i < transitions.size(); ++i) {
- QSignalTransition *t = transitions.at(i);
- if ((t->machine() == q) && t->senderObject() && (t->senderObject()->thread() != q->thread()))
- registerSignalTransition(t);
- }
-}
-
-void QStateMachinePrivate::_q_start()
-{
- Q_Q(QStateMachine);
- Q_ASSERT(state == Starting);
- // iterate over a copy, since we emit signals which may cause
- // 'configuration' to change, resulting in undefined behavior when
- // iterating at the same time:
- const auto config = configuration;
- for (QAbstractState *state : config) {
- QAbstractStatePrivate *abstractStatePrivate = QAbstractStatePrivate::get(state);
- abstractStatePrivate->active = false;
- emit state->activeChanged(false);
- }
- configuration.clear();
- qDeleteAll(internalEventQueue);
- internalEventQueue.clear();
- qDeleteAll(externalEventQueue);
- externalEventQueue.clear();
- clearHistory();
-
- registerMultiThreadedSignalTransitions();
-
- startupHook();
-
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": starting";
-#endif
- state = Running;
- processingScheduled = true; // we call _q_process() below
-
- QList<QAbstractTransition*> transitions;
- CalculationCache calculationCache;
- QAbstractTransition *initialTransition = createInitialTransition();
- transitions.append(initialTransition);
-
- QEvent nullEvent(QEvent::None);
- executeTransitionContent(&nullEvent, transitions);
- QList<QAbstractState*> exitedStates = QList<QAbstractState*>();
- QSet<QAbstractState*> statesForDefaultEntry;
- QList<QAbstractState*> enteredStates = computeEntrySet(transitions, statesForDefaultEntry, &calculationCache);
- QHash<RestorableId, QVariant> pendingRestorables;
- QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForEnteredStates =
- computePropertyAssignments(enteredStates, pendingRestorables);
-#if QT_CONFIG(animation)
- QList<QAbstractAnimation *> selectedAnimations = selectAnimations(transitions);
-#endif
- // enterStates() will set stopProcessingReason to Finished if a final
- // state is entered.
- stopProcessingReason = EventQueueEmpty;
- enterStates(&nullEvent, exitedStates, enteredStates, statesForDefaultEntry,
- assignmentsForEnteredStates
-#if QT_CONFIG(animation)
- , selectedAnimations
-#endif
- );
- delete initialTransition;
-
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": initial configuration:" << configuration;
-#endif
-
- emit q->started(QStateMachine::QPrivateSignal());
- emit q->runningChanged(true);
-
- if (stopProcessingReason == Finished) {
- // The state machine immediately reached a final state.
- processingScheduled = false;
- state = NotRunning;
- unregisterAllTransitions();
- emitFinished();
- emit q->runningChanged(false);
- exitInterpreter();
- } else {
- _q_process();
- }
-}
-
-void QStateMachinePrivate::_q_process()
-{
- Q_Q(QStateMachine);
- Q_ASSERT(state == Running);
- Q_ASSERT(!processing);
- processing = true;
- processingScheduled = false;
- beginMacrostep();
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": starting the event processing loop";
-#endif
- bool didChange = false;
- while (processing) {
- if (stop) {
- processing = false;
- break;
- }
- QList<QAbstractTransition*> enabledTransitions;
- CalculationCache calculationCache;
-
- QEvent *e = new QEvent(QEvent::None);
- enabledTransitions = selectTransitions(e, &calculationCache);
- if (enabledTransitions.isEmpty()) {
- delete e;
- e = nullptr;
- }
- while (enabledTransitions.isEmpty() && ((e = dequeueInternalEvent()) != nullptr)) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": dequeued internal event" << e << "of type" << e->type();
-#endif
- enabledTransitions = selectTransitions(e, &calculationCache);
- if (enabledTransitions.isEmpty()) {
- delete e;
- e = nullptr;
- }
- }
- while (enabledTransitions.isEmpty() && ((e = dequeueExternalEvent()) != nullptr)) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": dequeued external event" << e << "of type" << e->type();
-#endif
- enabledTransitions = selectTransitions(e, &calculationCache);
- if (enabledTransitions.isEmpty()) {
- delete e;
- e = nullptr;
- }
- }
- if (enabledTransitions.isEmpty()) {
- if (isInternalEventQueueEmpty()) {
- processing = false;
- stopProcessingReason = EventQueueEmpty;
- noMicrostep();
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": no transitions enabled";
-#endif
- }
- } else {
- didChange = true;
- q->beginMicrostep(e);
- microstep(e, enabledTransitions, &calculationCache);
- q->endMicrostep(e);
- }
- delete e;
- }
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": finished the event processing loop";
-#endif
- if (stop) {
- stop = false;
- stopProcessingReason = Stopped;
- }
-
- switch (stopProcessingReason) {
- case EventQueueEmpty:
- processedPendingEvents(didChange);
- break;
- case Finished:
- state = NotRunning;
- cancelAllDelayedEvents();
- unregisterAllTransitions();
- emitFinished();
- emit q->runningChanged(false);
- break;
- case Stopped:
- state = NotRunning;
- cancelAllDelayedEvents();
- unregisterAllTransitions();
- emit q->stopped(QStateMachine::QPrivateSignal());
- emit q->runningChanged(false);
- break;
- }
- endMacrostep(didChange);
- if (stopProcessingReason == Finished)
- exitInterpreter();
-}
-
-void QStateMachinePrivate::_q_startDelayedEventTimer(int id, int delay)
-{
- Q_Q(QStateMachine);
- QMutexLocker locker(&delayedEventsMutex);
- QHash<int, DelayedEvent>::iterator it = delayedEvents.find(id);
- if (it != delayedEvents.end()) {
- DelayedEvent &e = it.value();
- Q_ASSERT(!e.timerId);
- e.timerId = q->startTimer(delay);
- if (!e.timerId) {
- qWarning("QStateMachine::postDelayedEvent: failed to start timer (id=%d, delay=%d)", id, delay);
- delete e.event;
- delayedEvents.erase(it);
- delayedEventIdFreeList.release(id);
- } else {
- timerIdToDelayedEventId.insert(e.timerId, id);
- }
- } else {
- // It's been cancelled already
- delayedEventIdFreeList.release(id);
- }
-}
-
-void QStateMachinePrivate::_q_killDelayedEventTimer(int id, int timerId)
-{
- Q_Q(QStateMachine);
- q->killTimer(timerId);
- QMutexLocker locker(&delayedEventsMutex);
- delayedEventIdFreeList.release(id);
-}
-
-void QStateMachinePrivate::postInternalEvent(QEvent *e)
-{
- QMutexLocker locker(&internalEventMutex);
- internalEventQueue.append(e);
-}
-
-void QStateMachinePrivate::postExternalEvent(QEvent *e)
-{
- QMutexLocker locker(&externalEventMutex);
- externalEventQueue.append(e);
-}
-
-QEvent *QStateMachinePrivate::dequeueInternalEvent()
-{
- QMutexLocker locker(&internalEventMutex);
- if (internalEventQueue.isEmpty())
- return nullptr;
- return internalEventQueue.takeFirst();
-}
-
-QEvent *QStateMachinePrivate::dequeueExternalEvent()
-{
- QMutexLocker locker(&externalEventMutex);
- if (externalEventQueue.isEmpty())
- return nullptr;
- return externalEventQueue.takeFirst();
-}
-
-bool QStateMachinePrivate::isInternalEventQueueEmpty()
-{
- QMutexLocker locker(&internalEventMutex);
- return internalEventQueue.isEmpty();
-}
-
-bool QStateMachinePrivate::isExternalEventQueueEmpty()
-{
- QMutexLocker locker(&externalEventMutex);
- return externalEventQueue.isEmpty();
-}
-
-void QStateMachinePrivate::processEvents(EventProcessingMode processingMode)
-{
- Q_Q(QStateMachine);
- if ((state != Running) || processing || processingScheduled)
- return;
- switch (processingMode) {
- case DirectProcessing:
- if (QThread::currentThread() == q->thread()) {
- _q_process();
- break;
- }
- // processing must be done in the machine thread, so:
- Q_FALLTHROUGH();
- case QueuedProcessing:
- processingScheduled = true;
- QMetaObject::invokeMethod(q, "_q_process", Qt::QueuedConnection);
- break;
- }
-}
-
-void QStateMachinePrivate::cancelAllDelayedEvents()
-{
- Q_Q(QStateMachine);
- QMutexLocker locker(&delayedEventsMutex);
- QHash<int, DelayedEvent>::const_iterator it;
- for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) {
- const DelayedEvent &e = it.value();
- if (e.timerId) {
- timerIdToDelayedEventId.remove(e.timerId);
- q->killTimer(e.timerId);
- delayedEventIdFreeList.release(it.key());
- } else {
- // Cancellation will be detected in pending _q_startDelayedEventTimer() call
- }
- delete e.event;
- }
- delayedEvents.clear();
-}
-
-/*
- This function is called when the state machine is performing no
- microstep because no transition is enabled (i.e. an event is ignored).
-
- The default implementation does nothing.
-*/
-void QStateMachinePrivate::noMicrostep()
-{ }
-
-/*
- This function is called when the state machine has reached a stable
- state (no pending events), and has not finished yet.
- For each event the state machine receives it is guaranteed that
- 1) beginMacrostep is called
- 2) selectTransition is called at least once
- 3) begin/endMicrostep is called at least once or noMicrostep is called
- at least once (possibly both, but at least one)
- 4) the state machine either enters an infinite loop, or stops (runningChanged(false),
- and either finished or stopped are emitted), or processedPendingEvents() is called.
- 5) if the machine is not in an infinite loop endMacrostep is called
- 6) when the machine is finished and all processing (like signal emission) is done,
- exitInterpreter() is called. (This is the same name as the SCXML specification uses.)
-
- didChange is set to true if at least one microstep was performed, it is possible
- that the machine returned to exactly the same state as before, but some transitions
- were triggered.
-
- The default implementation does nothing.
-*/
-void QStateMachinePrivate::processedPendingEvents(bool didChange)
-{
- Q_UNUSED(didChange);
-}
-
-void QStateMachinePrivate::beginMacrostep()
-{ }
-
-void QStateMachinePrivate::endMacrostep(bool didChange)
-{
- Q_UNUSED(didChange);
-}
-
-void QStateMachinePrivate::exitInterpreter()
-{
-}
-
-void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState)
-{
- Q_UNUSED(guiltyState);
- Q_ASSERT(guiltyState);
-
-#ifdef QSTATEMACHINE_DEBUG
- Q_Q(QStateMachine);
- qDebug() << q << ": emitting finished signal for" << forState;
-#endif
-
- QStatePrivate::get(forState)->emitFinished();
-}
-
-void QStateMachinePrivate::startupHook()
-{
-}
-
-namespace _QStateMachine_Internal{
-
-class GoToStateTransition : public QAbstractTransition
-{
- Q_OBJECT
-public:
- GoToStateTransition(QAbstractState *target)
- : QAbstractTransition()
- { setTargetState(target); }
-protected:
- void onTransition(QEvent *) override { deleteLater(); }
- bool eventTest(QEvent *) override { return true; }
-};
-
-} // namespace
-// mingw compiler tries to export QObject::findChild<GoToStateTransition>(),
-// which doesn't work if its in an anonymous namespace.
-using namespace _QStateMachine_Internal;
-/*!
- \internal
-
- Causes this state machine to unconditionally transition to the given
- \a targetState.
-
- Provides a backdoor for using the state machine "imperatively"; i.e. rather
- than defining explicit transitions, you drive the machine's execution by
- calling this function. It breaks the whole integrity of the
- transition-driven model, but is provided for pragmatic reasons.
-*/
-void QStateMachinePrivate::goToState(QAbstractState *targetState)
-{
- if (!targetState) {
- qWarning("QStateMachine::goToState(): cannot go to null state");
- return;
- }
-
- if (configuration.contains(targetState))
- return;
-
- Q_ASSERT(state == Running);
- QState *sourceState = nullptr;
- QSet<QAbstractState*>::const_iterator it;
- for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
- sourceState = toStandardState(*it);
- if (sourceState != nullptr)
- break;
- }
-
- Q_ASSERT(sourceState != nullptr);
- // Reuse previous GoToStateTransition in case of several calls to
- // goToState() in a row.
- GoToStateTransition *trans = sourceState->findChild<GoToStateTransition*>();
- if (!trans) {
- trans = new GoToStateTransition(targetState);
- sourceState->addTransition(trans);
- } else {
- trans->setTargetState(targetState);
- }
-
- processEvents(QueuedProcessing);
-}
-
-void QStateMachinePrivate::registerTransitions(QAbstractState *state)
-{
- QState *group = toStandardState(state);
- if (!group)
- return;
- QList<QAbstractTransition*> transitions = QStatePrivate::get(group)->transitions();
- for (int i = 0; i < transitions.size(); ++i) {
- QAbstractTransition *t = transitions.at(i);
- registerTransition(t);
- }
-}
-
-void QStateMachinePrivate::maybeRegisterTransition(QAbstractTransition *transition)
-{
- if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
- maybeRegisterSignalTransition(st);
- }
-#if QT_CONFIG(qeventtransition)
- else if (QEventTransition *et = qobject_cast<QEventTransition*>(transition)) {
- maybeRegisterEventTransition(et);
- }
-#endif
-}
-
-void QStateMachinePrivate::registerTransition(QAbstractTransition *transition)
-{
- if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
- registerSignalTransition(st);
- }
-#if QT_CONFIG(qeventtransition)
- else if (QEventTransition *oet = qobject_cast<QEventTransition*>(transition)) {
- registerEventTransition(oet);
- }
-#endif
-}
-
-void QStateMachinePrivate::unregisterTransition(QAbstractTransition *transition)
-{
- if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) {
- unregisterSignalTransition(st);
- }
-#if QT_CONFIG(qeventtransition)
- else if (QEventTransition *oet = qobject_cast<QEventTransition*>(transition)) {
- unregisterEventTransition(oet);
- }
-#endif
-}
-
-void QStateMachinePrivate::maybeRegisterSignalTransition(QSignalTransition *transition)
-{
- Q_Q(QStateMachine);
- if ((state == Running) && (configuration.contains(transition->sourceState())
- || (transition->senderObject() && (transition->senderObject()->thread() != q->thread())))) {
- registerSignalTransition(transition);
- }
-}
-
-void QStateMachinePrivate::registerSignalTransition(QSignalTransition *transition)
-{
- Q_Q(QStateMachine);
- if (QSignalTransitionPrivate::get(transition)->signalIndex != -1)
- return; // already registered
- const QObject *sender = QSignalTransitionPrivate::get(transition)->sender;
- if (!sender)
- return;
- QByteArray signal = QSignalTransitionPrivate::get(transition)->signal;
- if (signal.isEmpty())
- return;
- if (signal.startsWith('0'+QSIGNAL_CODE))
- signal.remove(0, 1);
- const QMetaObject *meta = sender->metaObject();
- int signalIndex = meta->indexOfSignal(signal);
- int originalSignalIndex = signalIndex;
- if (signalIndex == -1) {
- signalIndex = meta->indexOfSignal(QMetaObject::normalizedSignature(signal));
- if (signalIndex == -1) {
- qWarning("QSignalTransition: no such signal: %s::%s",
- meta->className(), signal.constData());
- return;
- }
- originalSignalIndex = signalIndex;
- }
- // The signal index we actually want to connect to is the one
- // that is going to be sent, i.e. the non-cloned original index.
- while (meta->method(signalIndex).attributes() & QMetaMethod::Cloned)
- --signalIndex;
-
- connectionsMutex.lock();
- QList<int> &connectedSignalIndexes = connections[sender];
- if (connectedSignalIndexes.size() <= signalIndex)
- connectedSignalIndexes.resize(signalIndex+1);
- if (connectedSignalIndexes.at(signalIndex) == 0) {
- if (!signalEventGenerator)
- signalEventGenerator = new QSignalEventGenerator(q);
- static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset();
- bool ok = QMetaObject::connect(sender, signalIndex, signalEventGenerator, generatorMethodOffset);
- if (!ok) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": FAILED to add signal transition from" << transition->sourceState()
- << ": ( sender =" << sender << ", signal =" << signal
- << ", targets =" << transition->targetStates() << ')';
-#endif
- return;
- }
- }
- ++connectedSignalIndexes[signalIndex];
- connectionsMutex.unlock();
-
- QSignalTransitionPrivate::get(transition)->signalIndex = signalIndex;
- QSignalTransitionPrivate::get(transition)->originalSignalIndex = originalSignalIndex;
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": added signal transition from" << transition->sourceState()
- << ": ( sender =" << sender << ", signal =" << signal
- << ", targets =" << transition->targetStates() << ')';
-#endif
-}
-
-void QStateMachinePrivate::unregisterSignalTransition(QSignalTransition *transition)
-{
- int signalIndex = QSignalTransitionPrivate::get(transition)->signalIndex;
- if (signalIndex == -1)
- return; // not registered
- const QObject *sender = QSignalTransitionPrivate::get(transition)->sender;
- QSignalTransitionPrivate::get(transition)->signalIndex = -1;
-
- connectionsMutex.lock();
- QList<int> &connectedSignalIndexes = connections[sender];
- Q_ASSERT(connectedSignalIndexes.size() > signalIndex);
- Q_ASSERT(connectedSignalIndexes.at(signalIndex) != 0);
- if (--connectedSignalIndexes[signalIndex] == 0) {
- Q_ASSERT(signalEventGenerator != nullptr);
- static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset();
- QMetaObject::disconnect(sender, signalIndex, signalEventGenerator, generatorMethodOffset);
- int sum = 0;
- for (int i = 0; i < connectedSignalIndexes.size(); ++i)
- sum += connectedSignalIndexes.at(i);
- if (sum == 0)
- connections.remove(sender);
- }
- connectionsMutex.unlock();
-}
-
-void QStateMachinePrivate::unregisterAllTransitions()
-{
- Q_Q(QStateMachine);
- {
- QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>();
- for (int i = 0; i < transitions.size(); ++i) {
- QSignalTransition *t = transitions.at(i);
- if (t->machine() == q)
- unregisterSignalTransition(t);
- }
- }
-#if QT_CONFIG(qeventtransition)
- {
- QList<QEventTransition*> transitions = rootState()->findChildren<QEventTransition*>();
- for (int i = 0; i < transitions.size(); ++i) {
- QEventTransition *t = transitions.at(i);
- if (t->machine() == q)
- unregisterEventTransition(t);
- }
- }
-#endif
-}
-
-#if QT_CONFIG(qeventtransition)
-void QStateMachinePrivate::maybeRegisterEventTransition(QEventTransition *transition)
-{
- if ((state == Running) && configuration.contains(transition->sourceState()))
- registerEventTransition(transition);
-}
-
-void QStateMachinePrivate::registerEventTransition(QEventTransition *transition)
-{
- Q_Q(QStateMachine);
- if (QEventTransitionPrivate::get(transition)->registered)
- return;
- if (transition->eventType() >= QEvent::User) {
- qWarning("QObject event transitions are not supported for custom types");
- return;
- }
- QObject *object = QEventTransitionPrivate::get(transition)->object;
- if (!object)
- return;
- QObjectPrivate *od = QObjectPrivate::get(object);
- if (!od->extraData || !od->extraData->eventFilters.contains(q))
- object->installEventFilter(q);
- ++qobjectEvents[object][transition->eventType()];
- QEventTransitionPrivate::get(transition)->registered = true;
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": added event transition from" << transition->sourceState()
- << ": ( object =" << object << ", event =" << transition->eventType()
- << ", targets =" << transition->targetStates() << ')';
-#endif
-}
-
-void QStateMachinePrivate::unregisterEventTransition(QEventTransition *transition)
-{
- Q_Q(QStateMachine);
- if (!QEventTransitionPrivate::get(transition)->registered)
- return;
- QObject *object = QEventTransitionPrivate::get(transition)->object;
- QHash<QEvent::Type, int> &events = qobjectEvents[object];
- Q_ASSERT(events.value(transition->eventType()) > 0);
- if (--events[transition->eventType()] == 0) {
- events.remove(transition->eventType());
- int sum = 0;
- QHash<QEvent::Type, int>::const_iterator it;
- for (it = events.constBegin(); it != events.constEnd(); ++it)
- sum += it.value();
- if (sum == 0) {
- qobjectEvents.remove(object);
- object->removeEventFilter(q);
- }
- }
- QEventTransitionPrivate::get(transition)->registered = false;
-}
-
-void QStateMachinePrivate::handleFilteredEvent(QObject *watched, QEvent *event)
-{
- if (qobjectEvents.value(watched).contains(event->type())) {
- postInternalEvent(new QStateMachine::WrappedEvent(watched, handler->cloneEvent(event)));
- processEvents(DirectProcessing);
- }
-}
-#endif
-
-void QStateMachinePrivate::handleTransitionSignal(QObject *sender, int signalIndex,
- void **argv)
-{
-#ifndef QT_NO_DEBUG
- connectionsMutex.lock();
- Q_ASSERT(connections[sender].at(signalIndex) != 0);
- connectionsMutex.unlock();
-#endif
- const QMetaObject *meta = sender->metaObject();
- QMetaMethod method = meta->method(signalIndex);
- int argc = method.parameterCount();
- QList<QVariant> vargs;
- vargs.reserve(argc);
- for (int i = 0; i < argc; ++i) {
- auto type = method.parameterMetaType(i);
- vargs.append(QVariant(type, argv[i+1]));
- }
-
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ": sending signal event ( sender =" << sender
- << ", signal =" << method.methodSignature().constData() << ')';
-#endif
- postInternalEvent(new QStateMachine::SignalEvent(sender, signalIndex, vargs));
- processEvents(DirectProcessing);
-}
-
-/*!
- Constructs a new state machine with the given \a parent.
-*/
-QStateMachine::QStateMachine(QObject *parent)
- : QState(*new QStateMachinePrivate, /*parentState=*/nullptr)
-{
- // Can't pass the parent to the QState constructor, as it expects a QState
- // But this works as expected regardless of whether parent is a QState or not
- setParent(parent);
-}
-
-/*!
- \since 5.0
- \deprecated
-
- Constructs a new state machine with the given \a childMode
- and \a parent.
-
- \warning Do not set the \a childMode to anything else than \l{ExclusiveStates}, otherwise the
- state machine is invalid, and might work incorrectly.
-*/
-QStateMachine::QStateMachine(QState::ChildMode childMode, QObject *parent)
- : QState(*new QStateMachinePrivate, /*parentState=*/nullptr)
-{
- Q_D(QStateMachine);
- d->childMode = childMode;
- setParent(parent); // See comment in constructor above
-
- if (childMode != ExclusiveStates) {
- //### FIXME for Qt6: remove this constructor completely, and hide the childMode property.
- // Yes, the StateMachine itself is conceptually a state, but it should only expose a limited
- // number of properties. The execution algorithm (in the URL below) treats a state machine
- // as a state, but from an API point of view, it's questionable if the QStateMachine should
- // inherit from QState.
- //
- // See function findLCCA in https://www.w3.org/TR/2014/WD-scxml-20140529/#AlgorithmforSCXMLInterpretation
- // to see where setting childMode to parallel will break down.
- qWarning() << "Invalid childMode for QStateMachine" << this;
- }
-}
-
-/*!
- \internal
-*/
-QStateMachine::QStateMachine(QStateMachinePrivate &dd, QObject *parent)
- : QState(dd, /*parentState=*/nullptr)
-{
- setParent(parent);
-}
-
-/*!
- Destroys this state machine.
-*/
-QStateMachine::~QStateMachine()
-{
-}
-
-/*!
- \enum QStateMachine::EventPriority
-
- This enum type specifies the priority of an event posted to the state
- machine using postEvent().
-
- Events of high priority are processed before events of normal priority.
-
- \value NormalPriority The event has normal priority.
- \value HighPriority The event has high priority.
-*/
-
-/*! \enum QStateMachine::Error
-
- This enum type defines errors that can occur in the state machine at run time. When the state
- machine encounters an unrecoverable error at run time, it will set the error code returned
- by error(), the error message returned by errorString(), and enter an error state based on
- the context of the error.
-
- \value NoError No error has occurred.
- \value NoInitialStateError The machine has entered a QState with children which does not have an
- initial state set. The context of this error is the state which is missing an initial
- state.
- \value NoDefaultStateInHistoryStateError The machine has entered a QHistoryState which does not have
- a default state set. The context of this error is the QHistoryState which is missing a
- default state.
- \value NoCommonAncestorForTransitionError The machine has selected a transition whose source
- and targets are not part of the same tree of states, and thus are not part of the same
- state machine. Commonly, this could mean that one of the states has not been given
- any parent or added to any machine. The context of this error is the source state of
- the transition.
- \value StateMachineChildModeSetToParallelError The machine's \l childMode
- property was set to \l{QState::ParallelStates}. This is illegal.
- Only states may be declared as parallel, not the state machine
- itself. This enum value was added in Qt 5.14.
-
- \sa setErrorState()
-*/
-
-/*!
- Returns the error code of the last error that occurred in the state machine.
-*/
-QStateMachine::Error QStateMachine::error() const
-{
- Q_D(const QStateMachine);
- return d->error;
-}
-
-/*!
- Returns the error string of the last error that occurred in the state machine.
-*/
-QString QStateMachine::errorString() const
-{
- Q_D(const QStateMachine);
- return d->errorString;
-}
-
-/*!
- Clears the error string and error code of the state machine.
-*/
-void QStateMachine::clearError()
-{
- Q_D(QStateMachine);
- d->errorString.clear();
- d->error = NoError;
-}
-
-/*!
- Returns the restore policy of the state machine.
-
- \sa setGlobalRestorePolicy()
-*/
-QState::RestorePolicy QStateMachine::globalRestorePolicy() const
-{
- Q_D(const QStateMachine);
- return d->globalRestorePolicy;
-}
-
-/*!
- Sets the restore policy of the state machine to \a restorePolicy. The default
- restore policy is QState::DontRestoreProperties.
-
- \sa globalRestorePolicy()
-*/
-void QStateMachine::setGlobalRestorePolicy(QState::RestorePolicy restorePolicy)
-{
- Q_D(QStateMachine);
- d->globalRestorePolicy = restorePolicy;
-}
-
-/*!
- Adds the given \a state to this state machine. The state becomes a top-level
- state and the state machine takes ownership of the state.
-
- If the state is already in a different machine, it will first be removed
- from its old machine, and then added to this machine.
-
- \sa removeState(), setInitialState()
-*/
-void QStateMachine::addState(QAbstractState *state)
-{
- if (!state) {
- qWarning("QStateMachine::addState: cannot add null state");
- return;
- }
- if (QAbstractStatePrivate::get(state)->machine() == this) {
- qWarning("QStateMachine::addState: state has already been added to this machine");
- return;
- }
- state->setParent(this);
-}
-
-/*!
- Removes the given \a state from this state machine. The state machine
- releases ownership of the state.
-
- \sa addState()
-*/
-void QStateMachine::removeState(QAbstractState *state)
-{
- if (!state) {
- qWarning("QStateMachine::removeState: cannot remove null state");
- return;
- }
- if (QAbstractStatePrivate::get(state)->machine() != this) {
- qWarning("QStateMachine::removeState: state %p's machine (%p)"
- " is different from this machine (%p)",
- state, QAbstractStatePrivate::get(state)->machine(), this);
- return;
- }
- state->setParent(nullptr);
-}
-
-bool QStateMachine::isRunning() const
-{
- Q_D(const QStateMachine);
- return (d->state == QStateMachinePrivate::Running);
-}
-
-/*!
- Starts this state machine. The machine will reset its configuration and
- transition to the initial state. When a final top-level state (QFinalState)
- is entered, the machine will emit the finished() signal.
-
- \note A state machine will not run without a running event loop, such as
- the main application event loop started with QCoreApplication::exec() or
- QApplication::exec().
-
- \sa started(), finished(), stop(), initialState(), setRunning()
-*/
-void QStateMachine::start()
-{
- Q_D(QStateMachine);
-
- if ((childMode() == QState::ExclusiveStates) && (initialState() == nullptr)) {
- qWarning("QStateMachine::start: No initial state set for machine. Refusing to start.");
- return;
- }
-
- switch (d->state) {
- case QStateMachinePrivate::NotRunning:
- d->state = QStateMachinePrivate::Starting;
- QMetaObject::invokeMethod(this, "_q_start", Qt::QueuedConnection);
- break;
- case QStateMachinePrivate::Starting:
- break;
- case QStateMachinePrivate::Running:
- qWarning("QStateMachine::start(): already running");
- break;
- }
-}
-
-/*!
- Stops this state machine. The state machine will stop processing events and
- then emit the stopped() signal.
-
- \sa stopped(), start(), setRunning()
-*/
-void QStateMachine::stop()
-{
- Q_D(QStateMachine);
- switch (d->state) {
- case QStateMachinePrivate::NotRunning:
- break;
- case QStateMachinePrivate::Starting:
- // the machine will exit as soon as it enters the event processing loop
- d->stop = true;
- break;
- case QStateMachinePrivate::Running:
- d->stop = true;
- d->processEvents(QStateMachinePrivate::QueuedProcessing);
- break;
- }
-}
-
-void QStateMachine::setRunning(bool running)
-{
- if (running)
- start();
- else
- stop();
-}
-
-/*!
- \threadsafe
-
- Posts the given \a event of the given \a priority for processing by this
- state machine.
-
- This function returns immediately. The event is added to the state machine's
- event queue. Events are processed in the order posted. The state machine
- takes ownership of the event and deletes it once it has been processed.
-
- You can only post events when the state machine is running or when it is starting up.
-
- \sa postDelayedEvent()
-*/
-void QStateMachine::postEvent(QEvent *event, EventPriority priority)
-{
- Q_D(QStateMachine);
- switch (d->state) {
- case QStateMachinePrivate::Running:
- case QStateMachinePrivate::Starting:
- break;
- default:
- qWarning("QStateMachine::postEvent: cannot post event when the state machine is not running");
- return;
- }
- if (!event) {
- qWarning("QStateMachine::postEvent: cannot post null event");
- return;
- }
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << this << ": posting event" << event;
-#endif
- switch (priority) {
- case NormalPriority:
- d->postExternalEvent(event);
- break;
- case HighPriority:
- d->postInternalEvent(event);
- break;
- }
- d->processEvents(QStateMachinePrivate::QueuedProcessing);
-}
-
-/*!
- \threadsafe
-
- Posts the given \a event for processing by this state machine, with the
- given \a delay in milliseconds. Returns an identifier associated with the
- delayed event, or -1 if the event could not be posted.
-
- This function returns immediately. When the delay has expired, the event
- will be added to the state machine's event queue for processing. The state
- machine takes ownership of the event and deletes it once it has been
- processed.
-
- You can only post events when the state machine is running.
-
- \sa cancelDelayedEvent(), postEvent()
-*/
-int QStateMachine::postDelayedEvent(QEvent *event, int delay)
-{
- Q_D(QStateMachine);
- if (d->state != QStateMachinePrivate::Running) {
- qWarning("QStateMachine::postDelayedEvent: cannot post event when the state machine is not running");
- return -1;
- }
- if (!event) {
- qWarning("QStateMachine::postDelayedEvent: cannot post null event");
- return -1;
- }
- if (delay < 0) {
- qWarning("QStateMachine::postDelayedEvent: delay cannot be negative");
- return -1;
- }
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << this << ": posting event" << event << "with delay" << delay;
-#endif
- QMutexLocker locker(&d->delayedEventsMutex);
- int id = d->delayedEventIdFreeList.next();
- bool inMachineThread = (QThread::currentThread() == thread());
- int timerId = inMachineThread ? startTimer(delay) : 0;
- if (inMachineThread && !timerId) {
- qWarning("QStateMachine::postDelayedEvent: failed to start timer with interval %d", delay);
- d->delayedEventIdFreeList.release(id);
- return -1;
- }
- QStateMachinePrivate::DelayedEvent delayedEvent(event, timerId);
- d->delayedEvents.insert(id, delayedEvent);
- if (timerId) {
- d->timerIdToDelayedEventId.insert(timerId, id);
- } else {
- Q_ASSERT(!inMachineThread);
- QMetaObject::invokeMethod(this, "_q_startDelayedEventTimer",
- Qt::QueuedConnection,
- Q_ARG(int, id),
- Q_ARG(int, delay));
- }
- return id;
-}
-
-/*!
- \threadsafe
-
- Cancels the delayed event identified by the given \a id. The id should be a
- value returned by a call to postDelayedEvent(). Returns \c true if the event
- was successfully cancelled, otherwise returns \c false.
-
- \sa postDelayedEvent()
-*/
-bool QStateMachine::cancelDelayedEvent(int id)
-{
- Q_D(QStateMachine);
- if (d->state != QStateMachinePrivate::Running) {
- qWarning("QStateMachine::cancelDelayedEvent: the machine is not running");
- return false;
- }
- QMutexLocker locker(&d->delayedEventsMutex);
- QStateMachinePrivate::DelayedEvent e = d->delayedEvents.take(id);
- if (!e.event)
- return false;
- if (e.timerId) {
- d->timerIdToDelayedEventId.remove(e.timerId);
- bool inMachineThread = (QThread::currentThread() == thread());
- if (inMachineThread) {
- killTimer(e.timerId);
- d->delayedEventIdFreeList.release(id);
- } else {
- QMetaObject::invokeMethod(this, "_q_killDelayedEventTimer",
- Qt::QueuedConnection,
- Q_ARG(int, id),
- Q_ARG(int, e.timerId));
- }
- } else {
- // Cancellation will be detected in pending _q_startDelayedEventTimer() call
- }
- delete e.event;
- return true;
-}
-
-/*!
- Returns the maximal consistent set of states (including parallel and final
- states) that this state machine is currently in. If a state \c s is in the
- configuration, it is always the case that the parent of \c s is also in
- c. Note, however, that the machine itself is not an explicit member of the
- configuration.
-*/
-QSet<QAbstractState*> QStateMachine::configuration() const
-{
- Q_D(const QStateMachine);
- return d->configuration;
-}
-
-/*!
- \fn QStateMachine::started()
-
- This signal is emitted when the state machine has entered its initial state
- (QStateMachine::initialState).
-
- \sa QStateMachine::finished(), QStateMachine::start()
-*/
-
-/*!
- \fn QStateMachine::stopped()
-
- This signal is emitted when the state machine has stopped.
-
- \sa QStateMachine::stop(), QStateMachine::finished()
-*/
-
-/*!
- \reimp
-*/
-bool QStateMachine::event(QEvent *e)
-{
- Q_D(QStateMachine);
- if (e->type() == QEvent::Timer) {
- QTimerEvent *te = static_cast<QTimerEvent*>(e);
- int tid = te->timerId();
- if (d->state != QStateMachinePrivate::Running) {
- // This event has been cancelled already
- QMutexLocker locker(&d->delayedEventsMutex);
- Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid));
- return true;
- }
- d->delayedEventsMutex.lock();
- int id = d->timerIdToDelayedEventId.take(tid);
- QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(id);
- if (ee.event != nullptr) {
- Q_ASSERT(ee.timerId == tid);
- killTimer(tid);
- d->delayedEventIdFreeList.release(id);
- d->delayedEventsMutex.unlock();
- d->postExternalEvent(ee.event);
- d->processEvents(QStateMachinePrivate::DirectProcessing);
- return true;
- } else {
- d->delayedEventsMutex.unlock();
- }
- }
- return QState::event(e);
-}
-
-#if QT_CONFIG(qeventtransition)
-/*!
- \reimp
-*/
-bool QStateMachine::eventFilter(QObject *watched, QEvent *event)
-{
- Q_D(QStateMachine);
- d->handleFilteredEvent(watched, event);
- return false;
-}
-#endif
-
-/*!
- \internal
-
- This function is called when the state machine is about to select
- transitions based on the given \a event.
-
- The default implementation does nothing.
-*/
-void QStateMachine::beginSelectTransitions(QEvent *event)
-{
- Q_UNUSED(event);
-}
-
-/*!
- \internal
-
- This function is called when the state machine has finished selecting
- transitions based on the given \a event.
-
- The default implementation does nothing.
-*/
-void QStateMachine::endSelectTransitions(QEvent *event)
-{
- Q_UNUSED(event);
-}
-
-/*!
- \internal
-
- This function is called when the state machine is about to do a microstep.
-
- The default implementation does nothing.
-*/
-void QStateMachine::beginMicrostep(QEvent *event)
-{
- Q_UNUSED(event);
-}
-
-/*!
- \internal
-
- This function is called when the state machine has finished doing a
- microstep.
-
- The default implementation does nothing.
-*/
-void QStateMachine::endMicrostep(QEvent *event)
-{
- Q_UNUSED(event);
-}
-
-/*!
- \reimp
- This function will call start() to start the state machine.
-*/
-void QStateMachine::onEntry(QEvent *event)
-{
- start();
- QState::onEntry(event);
-}
-
-/*!
- \reimp
- This function will call stop() to stop the state machine and
- subsequently emit the stopped() signal.
-*/
-void QStateMachine::onExit(QEvent *event)
-{
- stop();
- QState::onExit(event);
-}
-
-#if QT_CONFIG(animation)
-
-/*!
- Returns whether animations are enabled for this state machine.
-*/
-bool QStateMachine::isAnimated() const
-{
- Q_D(const QStateMachine);
- return d->animated;
-}
-
-/*!
- Sets whether animations are \a enabled for this state machine.
-*/
-void QStateMachine::setAnimated(bool enabled)
-{
- Q_D(QStateMachine);
- d->animated = enabled;
-}
-
-/*!
- Adds a default \a animation to be considered for any transition.
-*/
-void QStateMachine::addDefaultAnimation(QAbstractAnimation *animation)
-{
- Q_D(QStateMachine);
- d->defaultAnimations.append(animation);
-}
-
-/*!
- Returns the list of default animations that will be considered for any transition.
-*/
-QList<QAbstractAnimation*> QStateMachine::defaultAnimations() const
-{
- Q_D(const QStateMachine);
- return d->defaultAnimations;
-}
-
-/*!
- Removes \a animation from the list of default animations.
-*/
-void QStateMachine::removeDefaultAnimation(QAbstractAnimation *animation)
-{
- Q_D(QStateMachine);
- d->defaultAnimations.removeAll(animation);
-}
-
-#endif // animation
-
-void QSignalEventGenerator::execute(QMethodRawArguments a)
-{
- auto machinePrivate = QStateMachinePrivate::get(qobject_cast<QStateMachine*>(parent()));
- if (machinePrivate->state != QStateMachinePrivate::Running)
- return;
- int signalIndex = senderSignalIndex();
- Q_ASSERT(signalIndex != -1);
- machinePrivate->handleTransitionSignal(sender(), signalIndex, a.arguments);
-}
-
-QSignalEventGenerator::QSignalEventGenerator(QStateMachine *parent)
- : QObject(parent)
-{
-}
-
-/*!
- \class QStateMachine::SignalEvent
- \inmodule QtCore
-
- \brief The SignalEvent class represents a Qt signal event.
-
- \since 4.6
- \ingroup statemachine
-
- A signal event is generated by a QStateMachine in response to a Qt
- signal. The QSignalTransition class provides a transition associated with a
- signal event. QStateMachine::SignalEvent is part of \l{The State Machine Framework}.
-
- The sender() function returns the object that generated the signal. The
- signalIndex() function returns the index of the signal. The arguments()
- function returns the arguments of the signal.
-
- \sa QSignalTransition
-*/
-
-/*!
- \internal
-
- Constructs a new SignalEvent object with the given \a sender, \a
- signalIndex and \a arguments.
-*/
-QStateMachine::SignalEvent::SignalEvent(QObject *sender, int signalIndex,
- const QList<QVariant> &arguments)
- : QEvent(QEvent::StateMachineSignal), m_sender(sender),
- m_signalIndex(signalIndex), m_arguments(arguments)
-{
-}
-
-/*!
- Destroys this SignalEvent.
-*/
-QStateMachine::SignalEvent::~SignalEvent()
-{
-}
-
-/*!
- \fn QStateMachine::SignalEvent::sender() const
-
- Returns the object that emitted the signal.
-
- \sa QObject::sender()
-*/
-
-/*!
- \fn QStateMachine::SignalEvent::signalIndex() const
-
- Returns the index of the signal.
-
- \sa QMetaObject::indexOfSignal(), QMetaObject::method()
-*/
-
-/*!
- \fn QStateMachine::SignalEvent::arguments() const
-
- Returns the arguments of the signal.
-*/
-
-
-/*!
- \class QStateMachine::WrappedEvent
- \inmodule QtCore
-
- \brief The WrappedEvent class inherits QEvent and holds a clone of an event associated with a QObject.
-
- \since 4.6
- \ingroup statemachine
-
- A wrapped event is generated by a QStateMachine in response to a Qt
- event. The QEventTransition class provides a transition associated with a
- such an event. QStateMachine::WrappedEvent is part of \l{The State Machine
- Framework}.
-
- The object() function returns the object that generated the event. The
- event() function returns a clone of the original event.
-
- \sa QEventTransition
-*/
-
-/*!
- \internal
-
- Constructs a new WrappedEvent object with the given \a object
- and \a event.
-
- The WrappedEvent object takes ownership of \a event.
-*/
-QStateMachine::WrappedEvent::WrappedEvent(QObject *object, QEvent *event)
- : QEvent(QEvent::StateMachineWrapped), m_object(object), m_event(event)
-{
-}
-
-/*!
- Destroys this WrappedEvent.
-*/
-QStateMachine::WrappedEvent::~WrappedEvent()
-{
- delete m_event;
-}
-
-/*!
- \fn QStateMachine::WrappedEvent::object() const
-
- Returns the object that the event is associated with.
-*/
-
-/*!
- \fn QStateMachine::WrappedEvent::event() const
-
- Returns a clone of the original event.
-*/
-
-/*!
- \fn QStateMachine::runningChanged(bool running)
- \since 5.4
-
- This signal is emitted when the running property is changed with \a running as argument.
-
- \sa QStateMachine::running
-*/
-
-/*!
- \fn QStateMachine::postDelayedEvent(QEvent *event, std::chrono::milliseconds delay)
- \since 5.15
- \overload
- \threadsafe
-
- Posts the given \a event for processing by this state machine, with the
- given \a delay in milliseconds. Returns an identifier associated with the
- delayed event, or -1 if the event could not be posted.
-
- This function returns immediately. When the delay has expired, the event
- will be added to the state machine's event queue for processing. The state
- machine takes ownership of the event and deletes it once it has been
- processed.
-
- You can only post events when the state machine is running.
-
- \sa cancelDelayedEvent(), postEvent()
-*/
-
-QT_END_NAMESPACE
-
-#include "qstatemachine.moc"
-#include "moc_qstatemachine.cpp"