summaryrefslogtreecommitdiff
path: root/src/corelib/statemachine
diff options
context:
space:
mode:
authorErik Verbruggen <erik.verbruggen@digia.com>2015-06-18 16:45:32 +0200
committerErik Verbruggen <erik.verbruggen@theqtcompany.com>2015-07-23 13:29:46 +0000
commit5fd9fe02ff14438a2d0c93789be138a58583dbe8 (patch)
tree4532d254a6ae22834948c61b81c1876d8beb7719 /src/corelib/statemachine
parent5329d739eed83cc9f430f9d97652743558ccd146 (diff)
downloadqtbase-5fd9fe02ff14438a2d0c93789be138a58583dbe8.tar.gz
QStateMachine: add defaultTransition in QHistoryState
The history state had the limitation that it was hard (or impossible) to use when more than one default state had to be entered. For example, using it in a parallel state was impossible without ending up in an infinite loop. This patch changes the QHistoryState to only have an initial transition, and the state selection algorithm is changed accordingly. It also brings QStateMachine closer to the SCXML standard. The existing defaultState is implemented on top of the defaultTransition: when used, a new transition, with the default state as its target, is set as the defaultTransition. Task-number: QTBUG-46703 Change-Id: Ifbb44e4f0f26b72e365af4c94753e4483f9850e7 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
Diffstat (limited to 'src/corelib/statemachine')
-rw-r--r--src/corelib/statemachine/qabstractstate_p.h3
-rw-r--r--src/corelib/statemachine/qabstracttransition.cpp11
-rw-r--r--src/corelib/statemachine/qhistorystate.cpp65
-rw-r--r--src/corelib/statemachine/qhistorystate.h6
-rw-r--r--src/corelib/statemachine/qhistorystate_p.h22
-rw-r--r--src/corelib/statemachine/qstatemachine.cpp25
6 files changed, 111 insertions, 21 deletions
diff --git a/src/corelib/statemachine/qabstractstate_p.h b/src/corelib/statemachine/qabstractstate_p.h
index 660371b80c..1eb3cbd420 100644
--- a/src/corelib/statemachine/qabstractstate_p.h
+++ b/src/corelib/statemachine/qabstractstate_p.h
@@ -46,12 +46,13 @@
//
#include <private/qobject_p.h>
+#include <QtCore/qabstractstate.h>
QT_BEGIN_NAMESPACE
class QStateMachine;
-class QAbstractState;
+class QState;
class QAbstractStatePrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QAbstractState)
diff --git a/src/corelib/statemachine/qabstracttransition.cpp b/src/corelib/statemachine/qabstracttransition.cpp
index 92a5d373fe..5a7a95883b 100644
--- a/src/corelib/statemachine/qabstracttransition.cpp
+++ b/src/corelib/statemachine/qabstracttransition.cpp
@@ -37,6 +37,7 @@
#include "qabstracttransition_p.h"
#include "qabstractstate.h"
+#include "qhistorystate.h"
#include "qstate.h"
#include "qstatemachine.h"
@@ -135,10 +136,12 @@ QAbstractTransitionPrivate::QAbstractTransitionPrivate()
QStateMachine *QAbstractTransitionPrivate::machine() const
{
- QState *source = sourceState();
- if (!source)
- return 0;
- return source->machine();
+ if (QState *source = sourceState())
+ return source->machine();
+ Q_Q(const QAbstractTransition);
+ if (QHistoryState *parent = qobject_cast<QHistoryState *>(q->parent()))
+ return parent->machine();
+ return 0;
}
bool QAbstractTransitionPrivate::callEventTest(QEvent *e)
diff --git a/src/corelib/statemachine/qhistorystate.cpp b/src/corelib/statemachine/qhistorystate.cpp
index b6384a159a..a0ebb9d239 100644
--- a/src/corelib/statemachine/qhistorystate.cpp
+++ b/src/corelib/statemachine/qhistorystate.cpp
@@ -50,7 +50,7 @@ QT_BEGIN_NAMESPACE
A history state is a pseudo-state that represents the child state that the
parent state was in the last time the parent state was exited. A transition
- with a history state as its target is in fact a transition to one of the
+ with a history state as its target is in fact a transition to one or more
other child states of the parent state. QHistoryState is part of \l{The
State Machine Framework}.
@@ -79,11 +79,22 @@ QT_BEGIN_NAMESPACE
s1->addTransition(button, SIGNAL(clicked()), s1h);
\endcode
+ If more than one default state has to be entered, or if the transition to the default state(s)
+ has to be acted upon, the defaultTransition should be set instead. Note that the eventTest()
+ method of that transition will never be called: the selection and execution of the transition is
+ done automatically when entering the history state.
+
By default a history state is shallow, meaning that it won't remember nested
states. This can be configured through the historyType property.
*/
/*!
+ \property QHistoryState::defaultTransition
+
+ \brief the default transition of this history state
+*/
+
+/*!
\property QHistoryState::defaultState
\brief the default state of this history state
@@ -113,11 +124,19 @@ QT_BEGIN_NAMESPACE
*/
QHistoryStatePrivate::QHistoryStatePrivate()
- : QAbstractStatePrivate(HistoryState),
- defaultState(0), historyType(QHistoryState::ShallowHistory)
+ : QAbstractStatePrivate(HistoryState)
+ , defaultTransition(0)
+ , historyType(QHistoryState::ShallowHistory)
{
}
+DefaultStateTransition::DefaultStateTransition(QHistoryState *source, QAbstractState *target)
+ : QAbstractTransition()
+{
+ setParent(source);
+ setTargetState(target);
+}
+
/*!
Constructs a new shallow history state with the given \a parent state.
*/
@@ -144,13 +163,40 @@ QHistoryState::~QHistoryState()
}
/*!
+ Returns this history state's default transition. The default transition is
+ taken when the history state has never been entered before. The target states
+ of the default transition therefore make up the default state.
+*/
+QAbstractTransition *QHistoryState::defaultTransition() const
+{
+ Q_D(const QHistoryState);
+ return d->defaultTransition;
+}
+
+/*!
+ Sets this history state's default transition to be the given \a transition.
+ This will set the source state of the \a transition to the history state.
+
+ Note that the eventTest method of the \a transition will never be called.
+*/
+void QHistoryState::setDefaultTransition(QAbstractTransition *transition)
+{
+ Q_D(QHistoryState);
+ if (d->defaultTransition != transition) {
+ d->defaultTransition = transition;
+ transition->setParent(this);
+ emit defaultTransitionChanged(QHistoryState::QPrivateSignal());
+ }
+}
+
+/*!
Returns this history state's default state. The default state indicates the
state to transition to if the parent state has never been entered before.
*/
QAbstractState *QHistoryState::defaultState() const
{
Q_D(const QHistoryState);
- return d->defaultState;
+ return d->defaultTransition ? d->defaultTransition->targetState() : Q_NULLPTR;
}
/*!
@@ -168,8 +214,15 @@ void QHistoryState::setDefaultState(QAbstractState *state)
"to this history state's group (%p)", state, parentState());
return;
}
- if (d->defaultState != state) {
- d->defaultState = state;
+ if (!d->defaultTransition
+ || d->defaultTransition->targetStates().size() != 1
+ || d->defaultTransition->targetStates().first() != state) {
+ if (!d->defaultTransition || !qobject_cast<DefaultStateTransition*>(d->defaultTransition)) {
+ d->defaultTransition = new DefaultStateTransition(this, state);
+ emit defaultTransitionChanged(QHistoryState::QPrivateSignal());
+ } else {
+ d->defaultTransition->setTargetState(state);
+ }
emit defaultStateChanged(QHistoryState::QPrivateSignal());
}
}
diff --git a/src/corelib/statemachine/qhistorystate.h b/src/corelib/statemachine/qhistorystate.h
index 97ac376688..2f2081bf9c 100644
--- a/src/corelib/statemachine/qhistorystate.h
+++ b/src/corelib/statemachine/qhistorystate.h
@@ -41,11 +41,13 @@ QT_BEGIN_NAMESPACE
#ifndef QT_NO_STATEMACHINE
+class QAbstractTransition;
class QHistoryStatePrivate;
class Q_CORE_EXPORT QHistoryState : public QAbstractState
{
Q_OBJECT
Q_PROPERTY(QAbstractState* defaultState READ defaultState WRITE setDefaultState NOTIFY defaultStateChanged)
+ Q_PROPERTY(QAbstractTransition* defaultTransition READ defaultTransition WRITE setDefaultTransition NOTIFY defaultTransitionChanged)
Q_PROPERTY(HistoryType historyType READ historyType WRITE setHistoryType NOTIFY historyTypeChanged)
public:
enum HistoryType {
@@ -58,6 +60,9 @@ public:
QHistoryState(HistoryType type, QState *parent = Q_NULLPTR);
~QHistoryState();
+ QAbstractTransition *defaultTransition() const;
+ void setDefaultTransition(QAbstractTransition *transition);
+
QAbstractState *defaultState() const;
void setDefaultState(QAbstractState *state);
@@ -65,6 +70,7 @@ public:
void setHistoryType(HistoryType type);
Q_SIGNALS:
+ void defaultTransitionChanged(QPrivateSignal);
void defaultStateChanged(QPrivateSignal);
void historyTypeChanged(QPrivateSignal);
diff --git a/src/corelib/statemachine/qhistorystate_p.h b/src/corelib/statemachine/qhistorystate_p.h
index 57149e3f86..b0865f7f46 100644
--- a/src/corelib/statemachine/qhistorystate_p.h
+++ b/src/corelib/statemachine/qhistorystate_p.h
@@ -47,11 +47,12 @@
#include "private/qabstractstate_p.h"
+#include <QtCore/qabstracttransition.h>
+#include <QtCore/qhistorystate.h>
#include <QtCore/qlist.h>
QT_BEGIN_NAMESPACE
-class QHistoryState;
class QHistoryStatePrivate : public QAbstractStatePrivate
{
Q_DECLARE_PUBLIC(QHistoryState)
@@ -62,11 +63,28 @@ public:
static QHistoryStatePrivate *get(QHistoryState *q)
{ return q->d_func(); }
- QAbstractState *defaultState;
+ QAbstractTransition *defaultTransition;
QHistoryState::HistoryType historyType;
QList<QAbstractState*> configuration;
};
+class DefaultStateTransition: public QAbstractTransition
+{
+ Q_OBJECT
+
+public:
+ DefaultStateTransition(QHistoryState *source, QAbstractState *target);
+
+protected:
+ // It doesn't matter whether this transition matches any event or not. It is always associated
+ // with a QHistoryState, and as soon as the state-machine detects that it enters a history
+ // state, it will handle this transition as a special case. The history state itself is never
+ // entered either: either the stored configuration will be used, or the target(s) of this
+ // transition are used.
+ virtual bool eventTest(QEvent *event) { Q_UNUSED(event); return false; }
+ virtual void onTransition(QEvent *event) { Q_UNUSED(event); }
+};
+
QT_END_NAMESPACE
#endif
diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp
index 335610dac6..237b19bc0e 100644
--- a/src/corelib/statemachine/qstatemachine.cpp
+++ b/src/corelib/statemachine/qstatemachine.cpp
@@ -365,9 +365,9 @@ static QList<QAbstractState *> getEffectiveTargetStates(QAbstractTransition *tra
if (!historyConfiguration.isEmpty()) {
// There is a saved history, so apply that.
targets.unite(historyConfiguration.toSet());
- } else if (QAbstractState *defaultState = historyState->defaultState()) {
- // Qt does not support initial transitions, but uses the default state of the history state for this.
- targets.insert(defaultState);
+ } else if (QAbstractTransition *defaultTransition = historyState->defaultTransition()) {
+ // No saved history, take all default transition targets.
+ targets.unite(defaultTransition->targetStates().toSet());
} else {
// Woops, we found a history state without a default state. That's not valid!
QStateMachinePrivate *m = QStateMachinePrivate::get(historyState->machine());
@@ -978,9 +978,16 @@ void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState
QAbstractStatePrivate::get(s)->callOnEntry(event);
QAbstractStatePrivate::get(s)->emitEntered();
- if (statesForDefaultEntry.contains(s)) {
- // ### executeContent(s.initial.transition.children())
- }
+
+ // 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.
{
@@ -1091,8 +1098,8 @@ void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *state,
#endif
} else {
QList<QAbstractState*> defaultHistoryContent;
- if (QHistoryStatePrivate::get(h)->defaultState)
- defaultHistoryContent.append(QHistoryStatePrivate::get(h)->defaultState);
+ if (QAbstractTransition *t = QHistoryStatePrivate::get(h)->defaultTransition)
+ defaultHistoryContent = t->targetStates();
if (defaultHistoryContent.isEmpty()) {
setError(QStateMachine::NoDefaultStateInHistoryStateError, h);
@@ -1118,8 +1125,10 @@ void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *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);