QStateMachine: add internal transitions.

The behavior of "external" and "internal" transitions is identical,
except in the case of a transition whose source state is a compound
state and whose target(s) is a descendant of the source. In such a case,
an internal transition will not exit and re-enter its source state,
while an external one will.

[ChangeLog][State machine] Added support for internal transitions.

Change-Id: I9efb1e7368ee52aa2544eb84709a00ae3d5350d3
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
This commit is contained in:
Erik Verbruggen 2015-04-13 13:54:20 +02:00
parent bd15b23987
commit c07f5b801b
5 changed files with 113 additions and 13 deletions

View File

@ -101,7 +101,35 @@ QT_BEGIN_NAMESPACE
parallel group state.
*/
/*!
\property QAbstractTransition::transitionType
\brief indicates whether this transition is an internal transition, or an external transition.
Internal and external transitions behave the same, except for the case of a transition whose
source state is a compound state and whose target(s) is a descendant of the source. In such a
case, an internal transition will not exit and re-enter its source state, while an external one
will.
By default, the type is an external transition.
*/
/*!
\enum QAbstractTransition::TransitionType
This enum specifies the kind of transition. By default, the type is an external transition.
\value ExternalTransition Any state that is the source state of a transition (which is not a
target-less transition) is left, and re-entered when necessary.
\value InternalTransition If the target state of a transition is a sub-state of a compound state,
and that compound state is the source state, an internal transition will
not leave the source state.
\sa QAbstractTransition::transitionType
*/
QAbstractTransitionPrivate::QAbstractTransitionPrivate()
: transitionType(QAbstractTransition::ExternalTransition)
{
}
@ -248,6 +276,24 @@ void QAbstractTransition::setTargetStates(const QList<QAbstractState*> &targets)
emit targetStatesChanged(QPrivateSignal());
}
/*!
Returns the type of the transition.
*/
QAbstractTransition::TransitionType QAbstractTransition::transitionType() const
{
Q_D(const QAbstractTransition);
return d->transitionType;
}
/*!
Sets the type of the transition to \a type.
*/
void QAbstractTransition::setTransitionType(TransitionType type)
{
Q_D(QAbstractTransition);
d->transitionType = type;
}
/*!
Returns the state machine that this transition is part of, or 0 if the
transition is not part of a state machine.

View File

@ -59,7 +59,14 @@ class Q_CORE_EXPORT QAbstractTransition : public QObject
Q_PROPERTY(QState* sourceState READ sourceState)
Q_PROPERTY(QAbstractState* targetState READ targetState WRITE setTargetState NOTIFY targetStateChanged)
Q_PROPERTY(QList<QAbstractState*> targetStates READ targetStates WRITE setTargetStates NOTIFY targetStatesChanged)
Q_PROPERTY(TransitionType transitionType READ transitionType WRITE setTransitionType)
public:
enum TransitionType {
ExternalTransition,
InternalTransition
};
Q_ENUM(TransitionType)
QAbstractTransition(QState *sourceState = 0);
virtual ~QAbstractTransition();
@ -69,6 +76,9 @@ public:
QList<QAbstractState*> targetStates() const;
void setTargetStates(const QList<QAbstractState*> &targets);
TransitionType transitionType() const;
void setTransitionType(TransitionType type);
QStateMachine *machine() const;
#ifndef QT_NO_ANIMATION

View File

@ -73,6 +73,7 @@ public:
void emitTriggered();
QList<QPointer<QAbstractState> > targetStates;
QAbstractTransition::TransitionType transitionType;
#ifndef QT_NO_ANIMATION
QList<QAbstractAnimation*> animations;

View File

@ -889,23 +889,22 @@ QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t
if (cache->transitionDomain(t, &domain))
return domain;
#if 0
// Qt only has external transitions, so skip the special case for the internal transitions
if (QState *tSource = t->sourceState()) {
if (isCompound(tSource)) {
bool allDescendants = true;
foreach (QAbstractState *s, effectiveTargetStates) {
if (!isDescendant(s, tSource)) {
allDescendants = false;
break;
if (t->transitionType() == QAbstractTransition::InternalTransition) {
if (QState *tSource = t->sourceState()) {
if (isCompound(tSource)) {
bool allDescendants = true;
foreach (QAbstractState *s, effectiveTargetStates) {
if (!isDescendant(s, tSource)) {
allDescendants = false;
break;
}
}
}
if (allDescendants)
return tSource;
if (allDescendants)
return tSource;
}
}
}
#endif
QList<QAbstractState *> states(effectiveTargetStates);
if (QAbstractState *src = t->sourceState())

View File

@ -247,6 +247,7 @@ private slots:
void qtbug_44963();
void qtbug_44783();
void internalTransition();
};
class TestState : public QState
@ -6338,5 +6339,48 @@ void tst_QStateMachine::qtbug_44783()
QVERIFY(machine.isRunning());
}
void tst_QStateMachine::internalTransition()
{
SignalEmitter emitter;
QStateMachine machine;
QState *s = new QState(&machine);
QState *s1 = new QState(s);
QState *s11 = new QState(s1);
DEFINE_ACTIVE_SPY(s);
DEFINE_ACTIVE_SPY(s1);
DEFINE_ACTIVE_SPY(s11);
machine.setInitialState(s);
s->setInitialState(s1);
s1->setInitialState(s11);
QSignalTransition *t = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s11);
t->setObjectName("s1->s11");
t->setTransitionType(QAbstractTransition::InternalTransition);
s->setObjectName("s");
s1->setObjectName("s1");
s11->setObjectName("s11");
machine.start();
QTRY_COMPARE(machine.configuration().contains(s), true);
QTRY_COMPARE(machine.configuration().contains(s1), true);
QTRY_COMPARE(machine.configuration().contains(s11), true);
TEST_ACTIVE_CHANGED(s, 1);
TEST_ACTIVE_CHANGED(s1, 1);
TEST_ACTIVE_CHANGED(s11, 1);
emitter.emitSignalWithNoArg();
QTRY_COMPARE(machine.configuration().contains(s), true);
QTRY_COMPARE(machine.configuration().contains(s1), true);
QTRY_COMPARE(machine.configuration().contains(s11), true);
TEST_ACTIVE_CHANGED(s11, 3);
TEST_ACTIVE_CHANGED(s1, 1); // external transitions will return 3, internal transitions should return 1.
TEST_ACTIVE_CHANGED(s, 1);
}
QTEST_MAIN(tst_QStateMachine)
#include "tst_qstatemachine.moc"