| /**************************************************************************** |
| ** |
| ** 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 QVector<QState*> getProperAncestors(const QAbstractState *state, const QAbstractState *upperBound) |
| { |
| Q_ASSERT(state != nullptr); |
| QVector<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; |
| QVector<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)) { |
| QVector<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*, QVector<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*, QVector<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*, QVector<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. |
| { |
| QVector<QPropertyAssignment> assignments = propertyAssignmentsForState.value(s); |
| for (int i = 0; i < assignments.size(); ++i) { |
| const QPropertyAssignment &assn = assignments.at(i); |
| 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); |
| } |
| } |
| |
| QVector<QPropertyAssignment> QStateMachinePrivate::restorablesToPropertyList(const QHash<RestorableId, QVariant> &restorables) const |
| { |
| QVector<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*, QVector<QPropertyAssignment> > QStateMachinePrivate::computePropertyAssignments( |
| const QList<QAbstractState*> &statesToEnter_sorted, QHash<RestorableId, QVariant> &pendingRestorables) const |
| { |
| QHash<QAbstractState*, QVector<QPropertyAssignment> > assignmentsForState; |
| for (int i = 0; i < statesToEnter_sorted.size(); ++i) { |
| QState *s = toStandardState(statesToEnter_sorted.at(i)); |
| if (!s) |
| continue; |
| |
| QVector<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*, QVector<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; |
| QHash<QAbstractState*, QVector<QPropertyAssignment> >::const_iterator it; |
| for (it = assignmentsForEnteredStates.constBegin(); it != assignmentsForEnteredStates.constEnd(); ++it) { |
| const QVector<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*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) |
| { |
| Q_Q(QStateMachine); |
| if (!assignmentsForEnteredStates.contains(state)) |
| return; |
| QVector<QPropertyAssignment> &assignments = assignmentsForEnteredStates[state]; |
| for (int i = 0; i < selectedAnimations.size(); ++i) { |
| QAbstractAnimation *anim = selectedAnimations.at(i); |
| QVector<QPropertyAssignment>::iterator it; |
| for (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*, QVector<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(); |
| QVector<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(); |
| QVector<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) { |
| int type = method.parameterType(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 |
| |
| |
| // Begin moc-generated code -- modify carefully (check "HAND EDIT" parts)! |
| struct qt_meta_stringdata_QSignalEventGenerator_t { |
| QByteArrayData data[3]; |
| char stringdata[32]; |
| }; |
| #define QT_MOC_LITERAL(idx, ofs, len) \ |
| Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ |
| offsetof(qt_meta_stringdata_QSignalEventGenerator_t, stringdata) + ofs \ |
| - idx * sizeof(QByteArrayData) \ |
| ) |
| static const qt_meta_stringdata_QSignalEventGenerator_t qt_meta_stringdata_QSignalEventGenerator = { |
| { |
| QT_MOC_LITERAL(0, 0, 21), |
| QT_MOC_LITERAL(1, 22, 7), |
| QT_MOC_LITERAL(2, 30, 0) |
| }, |
| "QSignalEventGenerator\0execute\0\0" |
| }; |
| #undef QT_MOC_LITERAL |
| |
| static const uint qt_meta_data_QSignalEventGenerator[] = { |
| |
| // content: |
| 7, // revision |
| 0, // classname |
| 0, 0, // classinfo |
| 1, 14, // methods |
| 0, 0, // properties |
| 0, 0, // enums/sets |
| 0, 0, // constructors |
| 0, // flags |
| 0, // signalCount |
| |
| // slots: name, argc, parameters, tag, flags |
| 1, 0, 19, 2, 0x0a, |
| |
| // slots: parameters |
| QMetaType::Void, |
| |
| 0 // eod |
| }; |
| |
| void QSignalEventGenerator::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) |
| { |
| if (_c == QMetaObject::InvokeMetaMethod) { |
| Q_ASSERT(staticMetaObject.cast(_o)); |
| QSignalEventGenerator *_t = static_cast<QSignalEventGenerator *>(_o); |
| switch (_id) { |
| case 0: _t->execute(_a); break; // HAND EDIT: add the _a parameter |
| default: ; |
| } |
| } |
| Q_UNUSED(_a); |
| } |
| |
| const QMetaObject QSignalEventGenerator::staticMetaObject = { |
| { &QObject::staticMetaObject, qt_meta_stringdata_QSignalEventGenerator.data, |
| qt_meta_data_QSignalEventGenerator, qt_static_metacall, nullptr, nullptr } |
| }; |
| |
| const QMetaObject *QSignalEventGenerator::metaObject() const |
| { |
| return &staticMetaObject; |
| } |
| |
| void *QSignalEventGenerator::qt_metacast(const char *_clname) |
| { |
| if (!_clname) return nullptr; |
| if (!strcmp(_clname, qt_meta_stringdata_QSignalEventGenerator.stringdata)) |
| return static_cast<void*>(const_cast< QSignalEventGenerator*>(this)); |
| return QObject::qt_metacast(_clname); |
| } |
| |
| int QSignalEventGenerator::qt_metacall(QMetaObject::Call _c, int _id, void **_a) |
| { |
| _id = QObject::qt_metacall(_c, _id, _a); |
| if (_id < 0) |
| return _id; |
| if (_c == QMetaObject::InvokeMetaMethod) { |
| if (_id < 1) |
| qt_static_metacall(this, _c, _id, _a); |
| _id -= 1; |
| } |
| return _id; |
| } |
| // End moc-generated code |
| |
| void QSignalEventGenerator::execute(void **_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); |
| } |
| |
| 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" |