/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include "qstatemachine.h" #include "qstate.h" #include "qhistorystate.h" #include "qkeyeventtransition.h" #include "qmouseeventtransition.h" #include "private/qstate_p.h" #include "private/qstatemachine_p.h" static int globalTick; // Run exec for a maximum of TIMEOUT msecs #define QCOREAPPLICATION_EXEC(TIMEOUT) \ { \ QTimer timer; \ timer.setSingleShot(true); \ timer.setInterval(TIMEOUT); \ timer.start(); \ connect(&timer, SIGNAL(timeout()), QCoreApplication::instance(), SLOT(quit())); \ QCoreApplication::exec(); \ } class SignalEmitter : public QObject { Q_OBJECT public: SignalEmitter(QObject *parent = 0) : QObject(parent) {} void emitSignalWithNoArg() { emit signalWithNoArg(); } void emitSignalWithIntArg(int arg) { emit signalWithIntArg(arg); } void emitSignalWithStringArg(const QString &arg) { emit signalWithStringArg(arg); } void emitSignalWithDefaultArg() { emit signalWithDefaultArg(); } Q_SIGNALS: void signalWithNoArg(); void signalWithIntArg(int); void signalWithStringArg(const QString &); void signalWithDefaultArg(int i = 42); }; class tst_QStateMachine : public QObject { Q_OBJECT private slots: void rootState(); void machineWithParent(); void addAndRemoveState(); void stateEntryAndExit(); void assignProperty(); void assignPropertyWithAnimation(); void postEvent(); void cancelDelayedEvent(); void postDelayedEventAndStop(); void stopAndPostEvent(); void stateFinished(); void parallelStates(); void parallelRootState(); void allSourceToTargetConfigurations(); void signalTransitions(); void eventTransitions(); void graphicsSceneEventTransitions(); void historyStates(); void startAndStop(); void targetStateWithNoParent(); void targetStateDeleted(); void transitionToRootState(); void transitionFromRootState(); void transitionEntersParent(); void defaultErrorState(); void customGlobalErrorState(); void customLocalErrorStateInBrokenState(); void customLocalErrorStateInOtherState(); void customLocalErrorStateInParentOfBrokenState(); void customLocalErrorStateOverridesParent(); void errorStateHasChildren(); void errorStateHasErrors(); void errorStateIsRootState(); void errorStateEntersParentFirst(); void customErrorStateIsNull(); void clearError(); void historyStateHasNowhereToGo(); void historyStateAsInitialState(); void historyStateAfterRestart(); void brokenStateIsNeverEntered(); void customErrorStateNotInGraph(); void transitionToStateNotInGraph(); void restoreProperties(); void defaultGlobalRestorePolicy(); void globalRestorePolicySetToRestore(); void globalRestorePolicySetToDontRestore(); void noInitialStateForInitialState(); void transitionWithParent(); void transitionsFromParallelStateWithNoChildren(); void parallelStateTransition(); void parallelStateAssignmentsDone(); void nestedRestoreProperties(); void nestedRestoreProperties2(); void simpleAnimation(); void twoAnimations(); void twoAnimatedTransitions(); void playAnimationTwice(); void nestedTargetStateForAnimation(); void propertiesAssignedSignalTransitionsReuseAnimationGroup(); void animatedGlobalRestoreProperty(); void specificTargetValueOfAnimation(); void addDefaultAnimation(); void addDefaultAnimationWithUnusedAnimation(); void removeDefaultAnimation(); void overrideDefaultAnimationWithSpecific(); void nestedStateMachines(); void goToState(); void goToStateFromSourceWithTransition(); void clonedSignals(); void postEventFromOtherThread(); void eventFilterForApplication(); void eventClassesExported(); void stopInTransitionToFinalState(); void stopInEventTest_data(); void stopInEventTest(); }; class TestState : public QState { public: enum Event { Entry, Exit }; TestState(QState *parent) : QState(parent) {} QList > events; protected: virtual void onEntry(QEvent *) { events.append(qMakePair(globalTick++, Entry)); } virtual void onExit(QEvent *) { events.append(qMakePair(globalTick++, Exit)); } }; class TestTransition : public QAbstractTransition { public: TestTransition(QAbstractState *target) : QAbstractTransition() { setTargetState(target); } QList triggers; protected: virtual bool eventTest(QEvent *) { return true; } virtual void onTransition(QEvent *) { triggers.append(globalTick++); } }; class EventTransition : public QAbstractTransition { public: EventTransition(QEvent::Type type, QAbstractState *target, QState *parent = 0) : QAbstractTransition(parent), m_type(type) { setTargetState(target); } protected: virtual bool eventTest(QEvent *e) { return (e->type() == m_type); } virtual void onTransition(QEvent *) {} private: QEvent::Type m_type; }; void tst_QStateMachine::transitionToRootState() { QStateMachine machine; machine.setObjectName("machine"); QState *initialState = new QState(); initialState->setObjectName("initial"); machine.addState(initialState); machine.setInitialState(initialState); QAbstractTransition *trans = new EventTransition(QEvent::User, &machine); initialState->addTransition(trans); QCOMPARE(trans->sourceState(), initialState); QCOMPARE(trans->targetState(), static_cast(&machine)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(initialState)); machine.postEvent(new QEvent(QEvent::User)); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 'initial'"); QCoreApplication::processEvents(); QVERIFY(machine.configuration().isEmpty()); QVERIFY(!machine.isRunning()); } void tst_QStateMachine::transitionFromRootState() { QStateMachine machine; QState *root = &machine; QState *s1 = new QState(root); EventTransition *trans = new EventTransition(QEvent::User, s1); root->addTransition(trans); QCOMPARE(trans->sourceState(), root); QCOMPARE(trans->targetState(), static_cast(s1)); } void tst_QStateMachine::transitionEntersParent() { QStateMachine machine; QObject *entryController = new QObject(&machine); entryController->setObjectName("entryController"); entryController->setProperty("greatGrandParentEntered", false); entryController->setProperty("grandParentEntered", false); entryController->setProperty("parentEntered", false); entryController->setProperty("stateEntered", false); QState *greatGrandParent = new QState(); greatGrandParent->setObjectName("grandParent"); greatGrandParent->assignProperty(entryController, "greatGrandParentEntered", true); machine.addState(greatGrandParent); machine.setInitialState(greatGrandParent); QState *grandParent = new QState(greatGrandParent); grandParent->setObjectName("grandParent"); grandParent->assignProperty(entryController, "grandParentEntered", true); QState *parent = new QState(grandParent); parent->setObjectName("parent"); parent->assignProperty(entryController, "parentEntered", true); QState *state = new QState(parent); state->setObjectName("state"); state->assignProperty(entryController, "stateEntered", true); QState *initialStateOfGreatGrandParent = new QState(greatGrandParent); initialStateOfGreatGrandParent->setObjectName("initialStateOfGreatGrandParent"); greatGrandParent->setInitialState(initialStateOfGreatGrandParent); initialStateOfGreatGrandParent->addTransition(new EventTransition(QEvent::User, state)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), true); QCOMPARE(entryController->property("grandParentEntered").toBool(), false); QCOMPARE(entryController->property("parentEntered").toBool(), false); QCOMPARE(entryController->property("stateEntered").toBool(), false); QCOMPARE(machine.configuration().count(), 2); QVERIFY(machine.configuration().contains(greatGrandParent)); QVERIFY(machine.configuration().contains(initialStateOfGreatGrandParent)); entryController->setProperty("greatGrandParentEntered", false); entryController->setProperty("grandParentEntered", false); entryController->setProperty("parentEntered", false); entryController->setProperty("stateEntered", false); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), false); QCOMPARE(entryController->property("grandParentEntered").toBool(), true); QCOMPARE(entryController->property("parentEntered").toBool(), true); QCOMPARE(entryController->property("stateEntered").toBool(), true); QCOMPARE(machine.configuration().count(), 4); QVERIFY(machine.configuration().contains(greatGrandParent)); QVERIFY(machine.configuration().contains(grandParent)); QVERIFY(machine.configuration().contains(parent)); QVERIFY(machine.configuration().contains(state)); } void tst_QStateMachine::defaultErrorState() { QStateMachine machine; QCOMPARE(machine.errorState(), reinterpret_cast(0)); QState *brokenState = new QState(); brokenState->setObjectName("MyInitialState"); machine.addState(brokenState); machine.setInitialState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'MyInitialState'"); // initialState has no initial state machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'MyInitialState'")); QCOMPARE(machine.isRunning(), false); } class CustomErrorState: public QState { public: CustomErrorState(QStateMachine *machine, QState *parent = 0) : QState(parent), error(QStateMachine::NoError), m_machine(machine) { } void onEntry(QEvent *) { error = m_machine->error(); errorString = m_machine->errorString(); } QStateMachine::Error error; QString errorString; private: QStateMachine *m_machine; }; void tst_QStateMachine::customGlobalErrorState() { QStateMachine machine; CustomErrorState *customErrorState = new CustomErrorState(&machine); customErrorState->setObjectName("customErrorState"); machine.addState(customErrorState); machine.setErrorState(customErrorState); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); brokenState->setObjectName("brokenState"); machine.addState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.errorState(), static_cast(customErrorState)); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(initialState)); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(initialState)); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), true); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(customErrorState)); QCOMPARE(customErrorState->error, QStateMachine::NoInitialStateError); QCOMPARE(customErrorState->errorString, QString::fromLatin1("Missing initial state in compound state 'brokenState'")); QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'")); } void tst_QStateMachine::customLocalErrorStateInBrokenState() { QStateMachine machine; CustomErrorState *customErrorState = new CustomErrorState(&machine); machine.addState(customErrorState); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); brokenState->setObjectName("brokenState"); machine.addState(brokenState); brokenState->setErrorState(customErrorState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), true); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(customErrorState)); QCOMPARE(customErrorState->error, QStateMachine::NoInitialStateError); } void tst_QStateMachine::customLocalErrorStateInOtherState() { QStateMachine machine; CustomErrorState *customErrorState = new CustomErrorState(&machine); machine.addState(customErrorState); QState *initialState = new QState(); initialState->setObjectName("initialState"); QTest::ignoreMessage(QtWarningMsg, "QState::setErrorState: error state cannot belong to a different state machine"); initialState->setErrorState(customErrorState); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); brokenState->setObjectName("brokenState"); machine.addState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'brokenState'"); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); } void tst_QStateMachine::customLocalErrorStateInParentOfBrokenState() { QStateMachine machine; CustomErrorState *customErrorState = new CustomErrorState(&machine); machine.addState(customErrorState); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *parentOfBrokenState = new QState(); machine.addState(parentOfBrokenState); parentOfBrokenState->setObjectName("parentOfBrokenState"); parentOfBrokenState->setErrorState(customErrorState); QState *brokenState = new QState(parentOfBrokenState); brokenState->setObjectName("brokenState"); parentOfBrokenState->setInitialState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), true); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(customErrorState)); } void tst_QStateMachine::customLocalErrorStateOverridesParent() { QStateMachine machine; CustomErrorState *customErrorStateForParent = new CustomErrorState(&machine); machine.addState(customErrorStateForParent); CustomErrorState *customErrorStateForBrokenState = new CustomErrorState(&machine); machine.addState(customErrorStateForBrokenState); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *parentOfBrokenState = new QState(); machine.addState(parentOfBrokenState); parentOfBrokenState->setObjectName("parentOfBrokenState"); parentOfBrokenState->setErrorState(customErrorStateForParent); QState *brokenState = new QState(parentOfBrokenState); brokenState->setObjectName("brokenState"); brokenState->setErrorState(customErrorStateForBrokenState); parentOfBrokenState->setInitialState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(customErrorStateForBrokenState)); QCOMPARE(customErrorStateForBrokenState->error, QStateMachine::NoInitialStateError); QCOMPARE(customErrorStateForParent->error, QStateMachine::NoError); } void tst_QStateMachine::errorStateHasChildren() { QStateMachine machine; CustomErrorState *customErrorState = new CustomErrorState(&machine); customErrorState->setObjectName("customErrorState"); machine.addState(customErrorState); machine.setErrorState(customErrorState); QState *childOfErrorState = new QState(customErrorState); childOfErrorState->setObjectName("childOfErrorState"); customErrorState->setInitialState(childOfErrorState); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); brokenState->setObjectName("brokenState"); machine.addState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), true); QCOMPARE(machine.configuration().count(), 2); QVERIFY(machine.configuration().contains(customErrorState)); QVERIFY(machine.configuration().contains(childOfErrorState)); } void tst_QStateMachine::errorStateHasErrors() { QStateMachine machine; CustomErrorState *customErrorState = new CustomErrorState(&machine); customErrorState->setObjectName("customErrorState"); machine.addState(customErrorState); machine.setErrorState(customErrorState); QState *childOfErrorState = new QState(customErrorState); childOfErrorState->setObjectName("childOfErrorState"); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); brokenState->setObjectName("brokenState"); machine.addState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'customErrorState'"); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'customErrorState'")); } void tst_QStateMachine::errorStateIsRootState() { QStateMachine machine; QTest::ignoreMessage(QtWarningMsg, "QStateMachine::setErrorState: root state cannot be error state"); machine.setErrorState(&machine); QState *initialState = new QState(); initialState->setObjectName("initialState"); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); brokenState->setObjectName("brokenState"); machine.addState(brokenState); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'brokenState'"); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); } void tst_QStateMachine::errorStateEntersParentFirst() { QStateMachine machine; QObject *entryController = new QObject(&machine); entryController->setObjectName("entryController"); entryController->setProperty("greatGrandParentEntered", false); entryController->setProperty("grandParentEntered", false); entryController->setProperty("parentEntered", false); entryController->setProperty("errorStateEntered", false); QState *greatGrandParent = new QState(); greatGrandParent->setObjectName("greatGrandParent"); greatGrandParent->assignProperty(entryController, "greatGrandParentEntered", true); machine.addState(greatGrandParent); machine.setInitialState(greatGrandParent); QState *grandParent = new QState(greatGrandParent); grandParent->setObjectName("grandParent"); grandParent->assignProperty(entryController, "grandParentEntered", true); QState *parent = new QState(grandParent); parent->setObjectName("parent"); parent->assignProperty(entryController, "parentEntered", true); QState *errorState = new QState(parent); errorState->setObjectName("errorState"); errorState->assignProperty(entryController, "errorStateEntered", true); machine.setErrorState(errorState); QState *initialStateOfGreatGrandParent = new QState(greatGrandParent); initialStateOfGreatGrandParent->setObjectName("initialStateOfGreatGrandParent"); greatGrandParent->setInitialState(initialStateOfGreatGrandParent); QState *brokenState = new QState(greatGrandParent); brokenState->setObjectName("brokenState"); QState *childState = new QState(brokenState); childState->setObjectName("childState"); initialStateOfGreatGrandParent->addTransition(new EventTransition(QEvent::User, brokenState)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), true); QCOMPARE(entryController->property("grandParentEntered").toBool(), false); QCOMPARE(entryController->property("parentEntered").toBool(), false); QCOMPARE(entryController->property("errorStateEntered").toBool(), false); QCOMPARE(machine.configuration().count(), 2); QVERIFY(machine.configuration().contains(greatGrandParent)); QVERIFY(machine.configuration().contains(initialStateOfGreatGrandParent)); entryController->setProperty("greatGrandParentEntered", false); entryController->setProperty("grandParentEntered", false); entryController->setProperty("parentEntered", false); entryController->setProperty("errorStateEntered", false); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), false); QCOMPARE(entryController->property("grandParentEntered").toBool(), true); QCOMPARE(entryController->property("parentEntered").toBool(), true); QCOMPARE(entryController->property("errorStateEntered").toBool(), true); QCOMPARE(machine.configuration().count(), 4); QVERIFY(machine.configuration().contains(greatGrandParent)); QVERIFY(machine.configuration().contains(grandParent)); QVERIFY(machine.configuration().contains(parent)); QVERIFY(machine.configuration().contains(errorState)); } void tst_QStateMachine::customErrorStateIsNull() { QStateMachine machine; machine.setErrorState(0); QState *initialState = new QState(); machine.addState(initialState); machine.setInitialState(initialState); QState *brokenState = new QState(); machine.addState(brokenState); new QState(brokenState); initialState->addTransition(new EventTransition(QEvent::User, brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state ''"); QCoreApplication::processEvents(); QCOMPARE(machine.errorState(), reinterpret_cast(0)); QCOMPARE(machine.isRunning(), false); } void tst_QStateMachine::clearError() { QStateMachine machine; machine.setErrorState(new QState(&machine)); // avoid warnings QState *brokenState = new QState(&machine); brokenState->setObjectName("brokenState"); machine.setInitialState(brokenState); new QState(brokenState); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), true); QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'")); machine.clearError(); QCOMPARE(machine.error(), QStateMachine::NoError); QVERIFY(machine.errorString().isEmpty()); } void tst_QStateMachine::historyStateAsInitialState() { QStateMachine machine; QHistoryState *hs = new QHistoryState(&machine); machine.setInitialState(hs); QState *s1 = new QState(&machine); hs->setDefaultState(s1); QState *s2 = new QState(&machine); QHistoryState *s2h = new QHistoryState(s2); s2->setInitialState(s2h); QState *s21 = new QState(s2); s2h->setDefaultState(s21); s1->addTransition(new EventTransition(QEvent::User, s2)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s21)); } void tst_QStateMachine::historyStateHasNowhereToGo() { QStateMachine machine; QState *initialState = new QState(&machine); machine.setInitialState(initialState); machine.setErrorState(new QState(&machine)); // avoid warnings QState *brokenState = new QState(&machine); brokenState->setObjectName("brokenState"); brokenState->setInitialState(new QState(brokenState)); QHistoryState *historyState = new QHistoryState(brokenState); historyState->setObjectName("historyState"); initialState->addTransition(new EventTransition(QEvent::User, historyState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), true); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(machine.errorState())); QCOMPARE(machine.error(), QStateMachine::NoDefaultStateInHistoryStateError); QCOMPARE(machine.errorString(), QString::fromLatin1("Missing default state in history state 'historyState'")); } void tst_QStateMachine::historyStateAfterRestart() { // QTBUG-8842 QStateMachine machine; QState *s1 = new QState(&machine); machine.setInitialState(s1); QState *s2 = new QState(&machine); QState *s21 = new QState(s2); QState *s22 = new QState(s2); QHistoryState *s2h = new QHistoryState(s2); s2h->setDefaultState(s21); s1->addTransition(new EventTransition(QEvent::User, s2h)); s21->addTransition(new EventTransition(QEvent::User, s22)); s2->addTransition(new EventTransition(QEvent::User, s1)); for (int x = 0; x < 2; ++x) { QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(s1)); // s1 -> s2h -> s21 (default state) machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 2); QVERIFY(machine.configuration().contains(s2)); // This used to fail on the 2nd run because the // history had not been cleared. QVERIFY(machine.configuration().contains(s21)); // s21 -> s22 machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s22)); // s2 -> s1 (s22 saved in s2h) machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(s1)); // s1 -> s2h -> s22 (saved state) machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().count(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s22)); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); machine.stop(); QTRY_COMPARE(stoppedSpy.count(), 1); } } void tst_QStateMachine::brokenStateIsNeverEntered() { QStateMachine machine; QObject *entryController = new QObject(&machine); entryController->setProperty("brokenStateEntered", false); entryController->setProperty("childStateEntered", false); entryController->setProperty("errorStateEntered", false); QState *initialState = new QState(&machine); machine.setInitialState(initialState); QState *errorState = new QState(&machine); errorState->assignProperty(entryController, "errorStateEntered", true); machine.setErrorState(errorState); QState *brokenState = new QState(&machine); brokenState->assignProperty(entryController, "brokenStateEntered", true); brokenState->setObjectName("brokenState"); QState *childState = new QState(brokenState); childState->assignProperty(entryController, "childStateEntered", true); initialState->addTransition(new EventTransition(QEvent::User, brokenState)); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(entryController->property("errorStateEntered").toBool(), true); QCOMPARE(entryController->property("brokenStateEntered").toBool(), false); QCOMPARE(entryController->property("childStateEntered").toBool(), false); } void tst_QStateMachine::transitionToStateNotInGraph() { QStateMachine machine; QState *initialState = new QState(&machine); initialState->setObjectName("initialState"); machine.setInitialState(initialState); QState independentState; independentState.setObjectName("independentState"); initialState->addTransition(&independentState); machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 'initialState'"); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); } void tst_QStateMachine::customErrorStateNotInGraph() { QStateMachine machine; QState errorState; errorState.setObjectName("errorState"); QTest::ignoreMessage(QtWarningMsg, "QState::setErrorState: error state cannot belong to a different state machine"); machine.setErrorState(&errorState); QCOMPARE(machine.errorState(), reinterpret_cast(0)); QState *initialBrokenState = new QState(&machine); initialBrokenState->setObjectName("initialBrokenState"); machine.setInitialState(initialBrokenState); new QState(initialBrokenState); machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'initialBrokenState'"); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); } void tst_QStateMachine::restoreProperties() { QStateMachine machine; QCOMPARE(machine.globalRestorePolicy(), QStateMachine::DontRestoreProperties); machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); QObject *object = new QObject(&machine); object->setProperty("a", 1); object->setProperty("b", 2); QState *S1 = new QState(); S1->setObjectName("S1"); S1->assignProperty(object, "a", 3); machine.addState(S1); QState *S2 = new QState(); S2->setObjectName("S2"); S2->assignProperty(object, "b", 5); machine.addState(S2); QState *S3 = new QState(); S3->setObjectName("S3"); machine.addState(S3); QFinalState *S4 = new QFinalState(); machine.addState(S4); S1->addTransition(new EventTransition(QEvent::User, S2)); S2->addTransition(new EventTransition(QEvent::User, S3)); S3->addTransition(S4); machine.setInitialState(S1); machine.start(); QCoreApplication::processEvents(); QCOMPARE(object->property("a").toInt(), 3); QCOMPARE(object->property("b").toInt(), 2); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(object->property("a").toInt(), 1); QCOMPARE(object->property("b").toInt(), 5); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(object->property("a").toInt(), 1); QCOMPARE(object->property("b").toInt(), 2); } void tst_QStateMachine::rootState() { QStateMachine machine; QCOMPARE(qobject_cast(machine.parentState()), (QState*)0); QCOMPARE(machine.machine(), (QStateMachine*)0); QState *s1 = new QState(&machine); QCOMPARE(s1->parentState(), static_cast(&machine)); QState *s2 = new QState(); s2->setParent(&machine); QCOMPARE(s2->parentState(), static_cast(&machine)); } void tst_QStateMachine::machineWithParent() { QObject object; QStateMachine *machine = new QStateMachine(&object); QCOMPARE(machine->parent(), &object); QCOMPARE(machine->parentState(), static_cast(0)); } void tst_QStateMachine::addAndRemoveState() { #ifdef QT_BUILD_INTERNAL QStateMachine machine; QStatePrivate *root_d = QStatePrivate::get(&machine); QCOMPARE(root_d->childStates().size(), 0); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: cannot add null state"); machine.addState(0); QState *s1 = new QState(); QCOMPARE(s1->parentState(), (QState*)0); QCOMPARE(s1->machine(), (QStateMachine*)0); machine.addState(s1); QCOMPARE(s1->machine(), static_cast(&machine)); QCOMPARE(s1->parentState(), static_cast(&machine)); QCOMPARE(root_d->childStates().size(), 1); QCOMPARE(root_d->childStates().at(0), (QAbstractState*)s1); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: state has already been added to this machine"); machine.addState(s1); QState *s2 = new QState(); QCOMPARE(s2->parentState(), (QState*)0); machine.addState(s2); QCOMPARE(s2->parentState(), static_cast(&machine)); QCOMPARE(root_d->childStates().size(), 2); QCOMPARE(root_d->childStates().at(0), (QAbstractState*)s1); QCOMPARE(root_d->childStates().at(1), (QAbstractState*)s2); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: state has already been added to this machine"); machine.addState(s2); machine.removeState(s1); QCOMPARE(s1->parentState(), (QState*)0); QCOMPARE(root_d->childStates().size(), 1); QCOMPARE(root_d->childStates().at(0), (QAbstractState*)s2); machine.removeState(s2); QCOMPARE(s2->parentState(), (QState*)0); QCOMPARE(root_d->childStates().size(), 0); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::removeState: cannot remove null state"); machine.removeState(0); { QStateMachine machine2; { QString warning; warning.sprintf("QStateMachine::removeState: state %p's machine (%p) is different from this machine (%p)", &machine2, (void*)0, &machine); QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); machine.removeState(&machine2); } // ### check this behavior machine.addState(&machine2); QCOMPARE(machine2.parent(), (QObject*)&machine); } delete s1; delete s2; // ### how to deal with this? // machine.removeState(machine.errorState()); #endif } void tst_QStateMachine::stateEntryAndExit() { // Two top-level states { QStateMachine machine; TestState *s1 = new TestState(&machine); QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add transition to null state"); s1->addTransition((QAbstractState*)0); QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add null transition"); s1->addTransition((QAbstractTransition*)0); QTest::ignoreMessage(QtWarningMsg, "QState::removeTransition: cannot remove null transition"); s1->removeTransition((QAbstractTransition*)0); TestState *s2 = new TestState(&machine); QFinalState *s3 = new QFinalState(&machine); TestTransition *t = new TestTransition(s2); QCOMPARE(t->machine(), (QStateMachine*)0); QCOMPARE(t->sourceState(), (QState*)0); QCOMPARE(t->targetState(), (QAbstractState*)s2); QCOMPARE(t->targetStates().size(), 1); QCOMPARE(t->targetStates().at(0), (QAbstractState*)s2); t->setTargetState(0); QCOMPARE(t->targetState(), (QAbstractState*)0); QVERIFY(t->targetStates().isEmpty()); t->setTargetState(s2); QCOMPARE(t->targetState(), (QAbstractState*)s2); QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::setTargetStates: target state(s) cannot be null"); t->setTargetStates(QList() << 0); QCOMPARE(t->targetState(), (QAbstractState*)s2); t->setTargetStates(QList() << s2); QCOMPARE(t->targetState(), (QAbstractState*)s2); QCOMPARE(t->targetStates().size(), 1); QCOMPARE(t->targetStates().at(0), (QAbstractState*)s2); s1->addTransition(t); QCOMPARE(t->sourceState(), (QState*)s1); QCOMPARE(t->machine(), &machine); { QAbstractTransition *trans = s2->addTransition(s3); QVERIFY(trans != 0); QCOMPARE(trans->sourceState(), (QState*)s2); QCOMPARE(trans->targetState(), (QAbstractState*)s3); { QString warning; warning.sprintf("QState::removeTransition: transition %p's source state (%p) is different from this state (%p)", trans, s2, s1); QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); s1->removeTransition(trans); } s2->removeTransition(trans); QCOMPARE(trans->sourceState(), (QState*)0); QCOMPARE(trans->targetState(), (QAbstractState*)s3); s2->addTransition(trans); QCOMPARE(trans->sourceState(), (QState*)s2); } QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s1); QCOMPARE(machine.initialState(), (QAbstractState*)s1); { QString warning; warning.sprintf("QState::setInitialState: state %p is not a child of this state (%p)", &machine, &machine); QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); machine.setInitialState(&machine); QCOMPARE(machine.initialState(), (QAbstractState*)s1); } QVERIFY(machine.configuration().isEmpty()); globalTick = 0; QVERIFY(!machine.isRunning()); QSignalSpy s1EnteredSpy(s1, SIGNAL(entered())); QSignalSpy s1ExitedSpy(s1, SIGNAL(exited())); QSignalSpy tTriggeredSpy(t, SIGNAL(triggered())); QSignalSpy s2EnteredSpy(s2, SIGNAL(entered())); QSignalSpy s2ExitedSpy(s2, SIGNAL(exited())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QTRY_COMPARE(finishedSpy.count(), 1); QTRY_COMPARE(stoppedSpy.count(), 0); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(s3)); // s1 is entered QCOMPARE(s1->events.count(), 2); QCOMPARE(s1->events.at(0).first, 0); QCOMPARE(s1->events.at(0).second, TestState::Entry); // s1 is exited QCOMPARE(s1->events.at(1).first, 1); QCOMPARE(s1->events.at(1).second, TestState::Exit); // t is triggered QCOMPARE(t->triggers.count(), 1); QCOMPARE(t->triggers.at(0), 2); // s2 is entered QCOMPARE(s2->events.count(), 2); QCOMPARE(s2->events.at(0).first, 3); QCOMPARE(s2->events.at(0).second, TestState::Entry); // s2 is exited QCOMPARE(s2->events.at(1).first, 4); QCOMPARE(s2->events.at(1).second, TestState::Exit); QCOMPARE(s1EnteredSpy.count(), 1); QCOMPARE(s1ExitedSpy.count(), 1); QCOMPARE(tTriggeredSpy.count(), 1); QCOMPARE(s2EnteredSpy.count(), 1); QCOMPARE(s2ExitedSpy.count(), 1); } // Two top-level states, one has two child states { QStateMachine machine; TestState *s1 = new TestState(&machine); TestState *s11 = new TestState(s1); TestState *s12 = new TestState(s1); TestState *s2 = new TestState(&machine); QFinalState *s3 = new QFinalState(&machine); s1->setInitialState(s11); TestTransition *t1 = new TestTransition(s12); s11->addTransition(t1); TestTransition *t2 = new TestTransition(s2); s12->addTransition(t2); s2->addTransition(s3); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s1); globalTick = 0; machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(s3)); // s1 is entered QCOMPARE(s1->events.count(), 2); QCOMPARE(s1->events.at(0).first, 0); QCOMPARE(s1->events.at(0).second, TestState::Entry); // s11 is entered QCOMPARE(s11->events.count(), 2); QCOMPARE(s11->events.at(0).first, 1); QCOMPARE(s11->events.at(0).second, TestState::Entry); // s11 is exited QCOMPARE(s11->events.at(1).first, 2); QCOMPARE(s11->events.at(1).second, TestState::Exit); // t1 is triggered QCOMPARE(t1->triggers.count(), 1); QCOMPARE(t1->triggers.at(0), 3); // s12 is entered QCOMPARE(s12->events.count(), 2); QCOMPARE(s12->events.at(0).first, 4); QCOMPARE(s12->events.at(0).second, TestState::Entry); // s12 is exited QCOMPARE(s12->events.at(1).first, 5); QCOMPARE(s12->events.at(1).second, TestState::Exit); // s1 is exited QCOMPARE(s1->events.at(1).first, 6); QCOMPARE(s1->events.at(1).second, TestState::Exit); // t2 is triggered QCOMPARE(t2->triggers.count(), 1); QCOMPARE(t2->triggers.at(0), 7); // s2 is entered QCOMPARE(s2->events.count(), 2); QCOMPARE(s2->events.at(0).first, 8); QCOMPARE(s2->events.at(0).second, TestState::Entry); // s2 is exited QCOMPARE(s2->events.at(1).first, 9); QCOMPARE(s2->events.at(1).second, TestState::Exit); } } void tst_QStateMachine::assignProperty() { QStateMachine machine; QState *s1 = new QState(&machine); QTest::ignoreMessage(QtWarningMsg, "QState::assignProperty: cannot assign property 'foo' of null object"); s1->assignProperty(0, "foo", QVariant()); s1->assignProperty(s1, "objectName", "s1"); QFinalState *s2 = new QFinalState(&machine); s1->addTransition(s2); machine.setInitialState(s1); machine.start(); QTRY_COMPARE(s1->objectName(), QString::fromLatin1("s1")); s1->assignProperty(s1, "objectName", "foo"); machine.start(); QTRY_COMPARE(s1->objectName(), QString::fromLatin1("foo")); s1->assignProperty(s1, "noSuchProperty", 123); machine.start(); QTRY_COMPARE(s1->dynamicPropertyNames().size(), 1); QCOMPARE(s1->dynamicPropertyNames().at(0), QByteArray("noSuchProperty")); QCOMPARE(s1->objectName(), QString::fromLatin1("foo")); { QSignalSpy propertiesAssignedSpy(s1, SIGNAL(propertiesAssigned())); machine.start(); QTRY_COMPARE(propertiesAssignedSpy.count(), 1); } // nested states { QState *s11 = new QState(s1); QString str = QString::fromLatin1("set by nested state"); s11->assignProperty(s11, "objectName", str); s1->setInitialState(s11); machine.start(); QTRY_COMPARE(s11->objectName(), str); } } void tst_QStateMachine::assignPropertyWithAnimation() { // Single animation { QStateMachine machine; QVERIFY(machine.isAnimated()); machine.setAnimated(false); QVERIFY(!machine.isAnimated()); machine.setAnimated(true); QVERIFY(machine.isAnimated()); QObject obj; obj.setProperty("foo", 321); obj.setProperty("bar", 654); QState *s1 = new QState(&machine); s1->assignProperty(&obj, "foo", 123); QState *s2 = new QState(&machine); s2->assignProperty(&obj, "foo", 456); s2->assignProperty(&obj, "bar", 789); QAbstractTransition *trans = s1->addTransition(s2); QVERIFY(trans->animations().isEmpty()); QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::addAnimation: cannot add null animation"); trans->addAnimation(0); QPropertyAnimation anim(&obj, "foo"); anim.setDuration(250); trans->addAnimation(&anim); QCOMPARE(trans->animations().size(), 1); QCOMPARE(trans->animations().at(0), (QAbstractAnimation*)&anim); QCOMPARE(anim.parent(), (QObject*)0); QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::removeAnimation: cannot remove null animation"); trans->removeAnimation(0); trans->removeAnimation(&anim); QVERIFY(trans->animations().isEmpty()); trans->addAnimation(&anim); QCOMPARE(trans->animations().size(), 1); QCOMPARE(trans->animations().at(0), (QAbstractAnimation*)&anim); QFinalState *s3 = new QFinalState(&machine); s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(obj.property("foo").toInt(), 456); QCOMPARE(obj.property("bar").toInt(), 789); } // Two animations { QStateMachine machine; QObject obj; obj.setProperty("foo", 321); obj.setProperty("bar", 654); QState *s1 = new QState(&machine); s1->assignProperty(&obj, "foo", 123); QState *s2 = new QState(&machine); s2->assignProperty(&obj, "foo", 456); s2->assignProperty(&obj, "bar", 789); QAbstractTransition *trans = s1->addTransition(s2); QPropertyAnimation anim(&obj, "foo"); anim.setDuration(150); trans->addAnimation(&anim); QPropertyAnimation anim2(&obj, "bar"); anim2.setDuration(150); trans->addAnimation(&anim2); QFinalState *s3 = new QFinalState(&machine); s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(obj.property("foo").toInt(), 456); QCOMPARE(obj.property("bar").toInt(), 789); } // Animation group { QStateMachine machine; QObject obj; obj.setProperty("foo", 321); obj.setProperty("bar", 654); QState *s1 = new QState(&machine); s1->assignProperty(&obj, "foo", 123); s1->assignProperty(&obj, "bar", 321); QState *s2 = new QState(&machine); s2->assignProperty(&obj, "foo", 456); s2->assignProperty(&obj, "bar", 654); s2->assignProperty(&obj, "baz", 789); QAbstractTransition *trans = s1->addTransition(s2); QSequentialAnimationGroup group; group.addAnimation(new QPropertyAnimation(&obj, "foo")); group.addAnimation(new QPropertyAnimation(&obj, "bar")); trans->addAnimation(&group); QFinalState *s3 = new QFinalState(&machine); s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(obj.property("foo").toInt(), 456); QCOMPARE(obj.property("bar").toInt(), 654); QCOMPARE(obj.property("baz").toInt(), 789); } // Nested states { QStateMachine machine; QObject obj; obj.setProperty("foo", 321); obj.setProperty("bar", 654); QState *s1 = new QState(&machine); QCOMPARE(s1->childMode(), QState::ExclusiveStates); s1->setChildMode(QState::ParallelStates); QCOMPARE(s1->childMode(), QState::ParallelStates); s1->setChildMode(QState::ExclusiveStates); QCOMPARE(s1->childMode(), QState::ExclusiveStates); QCOMPARE(s1->initialState(), (QAbstractState*)0); s1->setObjectName("s1"); s1->assignProperty(&obj, "foo", 123); s1->assignProperty(&obj, "bar", 456); QState *s2 = new QState(&machine); s2->setObjectName("s2"); s2->assignProperty(&obj, "foo", 321); QState *s21 = new QState(s2); s21->setObjectName("s21"); s21->assignProperty(&obj, "bar", 654); QState *s22 = new QState(s2); s22->setObjectName("s22"); s22->assignProperty(&obj, "bar", 789); s2->setInitialState(s21); QCOMPARE(s2->initialState(), (QAbstractState*)s21); QAbstractTransition *trans = s1->addTransition(s2); QPropertyAnimation anim(&obj, "foo"); anim.setDuration(500); trans->addAnimation(&anim); QPropertyAnimation anim2(&obj, "bar"); anim2.setDuration(250); trans->addAnimation(&anim2); s21->addTransition(s21, SIGNAL(propertiesAssigned()), s22); QFinalState *s3 = new QFinalState(&machine); s22->addTransition(s2, SIGNAL(propertiesAssigned()), s3); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(obj.property("foo").toInt(), 321); QCOMPARE(obj.property("bar").toInt(), 789); } // Aborted animation { QStateMachine machine; SignalEmitter emitter; QObject obj; obj.setProperty("foo", 321); obj.setProperty("bar", 654); QState *group = new QState(&machine); QState *s1 = new QState(group); group->setInitialState(s1); s1->assignProperty(&obj, "foo", 123); QState *s2 = new QState(group); s2->assignProperty(&obj, "foo", 456); s2->assignProperty(&obj, "bar", 789); QAbstractTransition *trans = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s2); QPropertyAnimation anim(&obj, "foo"); anim.setDuration(8000); trans->addAnimation(&anim); QPropertyAnimation anim2(&obj, "bar"); anim2.setDuration(8000); trans->addAnimation(&anim2); QState *s3 = new QState(group); s3->assignProperty(&obj, "foo", 911); s2->addTransition(&emitter, SIGNAL(signalWithNoArg()), s3); machine.setInitialState(group); machine.start(); QTRY_COMPARE(machine.configuration().contains(s1), true); QSignalSpy propertiesAssignedSpy(s2, SIGNAL(propertiesAssigned())); emitter.emitSignalWithNoArg(); QTRY_COMPARE(machine.configuration().contains(s2), true); QVERIFY(propertiesAssignedSpy.isEmpty()); emitter.emitSignalWithNoArg(); // will cause animations from s1-->s2 to abort QTRY_COMPARE(machine.configuration().contains(s3), true); QVERIFY(propertiesAssignedSpy.isEmpty()); QCOMPARE(obj.property("foo").toInt(), 911); QCOMPARE(obj.property("bar").toInt(), 789); } } struct StringEvent : public QEvent { public: StringEvent(const QString &val) : QEvent(QEvent::Type(QEvent::User+2)), value(val) {} QString value; }; class StringTransition : public QAbstractTransition { public: StringTransition(const QString &value, QAbstractState *target) : QAbstractTransition(), m_value(value) { setTargetState(target); } protected: virtual bool eventTest(QEvent *e) { if (e->type() != QEvent::Type(QEvent::User+2)) return false; StringEvent *se = static_cast(e); return (m_value == se->value) && (!m_cond.isValid() || (m_cond.indexIn(m_value) != -1)); } virtual void onTransition(QEvent *) {} private: QString m_value; QRegExp m_cond; }; class StringEventPoster : public QState { public: StringEventPoster(const QString &value, QState *parent = 0) : QState(parent), m_value(value), m_delay(-1) {} void setString(const QString &value) { m_value = value; } void setDelay(int delay) { m_delay = delay; } protected: virtual void onEntry(QEvent *) { if (m_delay == -1) machine()->postEvent(new StringEvent(m_value)); else machine()->postDelayedEvent(new StringEvent(m_value), m_delay); } virtual void onExit(QEvent *) {} private: QString m_value; int m_delay; }; void tst_QStateMachine::postEvent() { for (int x = 0; x < 2; ++x) { QStateMachine machine; { QEvent e(QEvent::None); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::postEvent: cannot post event when the state machine is not running"); machine.postEvent(&e); } StringEventPoster *s1 = new StringEventPoster("a"); if (x == 1) s1->setDelay(100); QFinalState *s2 = new QFinalState; s1->addTransition(new StringTransition("a", s2)); machine.addState(s1); machine.addState(s2); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); s1->setString("b"); QFinalState *s3 = new QFinalState(); machine.addState(s3); s1->addTransition(new StringTransition("b", s3)); finishedSpy.clear(); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s3)); } } void tst_QStateMachine::cancelDelayedEvent() { QStateMachine machine; QTest::ignoreMessage(QtWarningMsg, "QStateMachine::cancelDelayedEvent: the machine is not running"); QVERIFY(!machine.cancelDelayedEvent(-1)); QState *s1 = new QState(&machine); QFinalState *s2 = new QFinalState(&machine); s1->addTransition(new StringTransition("a", s2)); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); int id1 = machine.postDelayedEvent(new StringEvent("c"), 50000); QVERIFY(id1 != -1); int id2 = machine.postDelayedEvent(new StringEvent("b"), 25000); QVERIFY(id2 != -1); QVERIFY(id2 != id1); int id3 = machine.postDelayedEvent(new StringEvent("a"), 100); QVERIFY(id3 != -1); QVERIFY(id3 != id2); QVERIFY(machine.cancelDelayedEvent(id1)); QVERIFY(!machine.cancelDelayedEvent(id1)); QVERIFY(machine.cancelDelayedEvent(id2)); QVERIFY(!machine.cancelDelayedEvent(id2)); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } void tst_QStateMachine::postDelayedEventAndStop() { QStateMachine machine; QState *s1 = new QState(&machine); QFinalState *s2 = new QFinalState(&machine); s1->addTransition(new StringTransition("a", s2)); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); int id1 = machine.postDelayedEvent(new StringEvent("a"), 0); QVERIFY(id1 != -1); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); machine.stop(); QTRY_COMPARE(stoppedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); machine.start(); QTRY_COMPARE(startedSpy.count(), 2); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); int id2 = machine.postDelayedEvent(new StringEvent("a"), 1000); QVERIFY(id2 != -1); machine.stop(); QTRY_COMPARE(stoppedSpy.count(), 2); machine.start(); QTRY_COMPARE(startedSpy.count(), 3); QTestEventLoop::instance().enterLoop(2); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); } void tst_QStateMachine::stopAndPostEvent() { QStateMachine machine; QState *s1 = new QState(&machine); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); machine.stop(); QCOMPARE(stoppedSpy.count(), 0); machine.postEvent(new QEvent(QEvent::User)); QTRY_COMPARE(stoppedSpy.count(), 1); QCoreApplication::processEvents(); } void tst_QStateMachine::stateFinished() { QStateMachine machine; QState *s1 = new QState(&machine); QState *s1_1 = new QState(s1); QFinalState *s1_2 = new QFinalState(s1); s1_1->addTransition(s1_2); s1->setInitialState(s1_1); QFinalState *s2 = new QFinalState(&machine); s1->addTransition(s1, SIGNAL(finished()), s2); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } void tst_QStateMachine::parallelStates() { QStateMachine machine; QState *s1 = new QState(QState::ParallelStates); QCOMPARE(s1->childMode(), QState::ParallelStates); QState *s1_1 = new QState(s1); QState *s1_1_1 = new QState(s1_1); QFinalState *s1_1_f = new QFinalState(s1_1); s1_1_1->addTransition(s1_1_f); s1_1->setInitialState(s1_1_1); QState *s1_2 = new QState(s1); QState *s1_2_1 = new QState(s1_2); QFinalState *s1_2_f = new QFinalState(s1_2); s1_2_1->addTransition(s1_2_f); s1_2->setInitialState(s1_2_1); { QString warning; warning.sprintf("QState::setInitialState: ignoring attempt to set initial state of parallel state group %p", s1); QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); s1->setInitialState(0); } machine.addState(s1); QFinalState *s2 = new QFinalState(); machine.addState(s2); s1->addTransition(s1, SIGNAL(finished()), s2); machine.setInitialState(s1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } void tst_QStateMachine::parallelRootState() { QStateMachine machine; QState *root = &machine; QCOMPARE(root->childMode(), QState::ExclusiveStates); root->setChildMode(QState::ParallelStates); QCOMPARE(root->childMode(), QState::ParallelStates); QState *s1 = new QState(root); QFinalState *s1_f = new QFinalState(s1); s1->setInitialState(s1_f); QState *s2 = new QState(root); QFinalState *s2_f = new QFinalState(s2); s2->setInitialState(s2_f); QSignalSpy startedSpy(&machine, SIGNAL(started())); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start."); machine.start(); QCoreApplication::processEvents(); QEXPECT_FAIL("", "parallel root state is not supported (QTBUG-22931)", Continue); QCOMPARE(startedSpy.count(), 1); } void tst_QStateMachine::allSourceToTargetConfigurations() { QStateMachine machine; QState *s0 = new QState(&machine); s0->setObjectName("s0"); QState *s1 = new QState(s0); s1->setObjectName("s1"); QState *s11 = new QState(s1); s11->setObjectName("s11"); QState *s2 = new QState(s0); s2->setObjectName("s2"); QState *s21 = new QState(s2); s21->setObjectName("s21"); QState *s211 = new QState(s21); s211->setObjectName("s211"); QFinalState *f = new QFinalState(&machine); f->setObjectName("f"); s0->setInitialState(s1); s1->setInitialState(s11); s2->setInitialState(s21); s21->setInitialState(s211); s11->addTransition(new StringTransition("g", s211)); s1->addTransition(new StringTransition("a", s1)); s1->addTransition(new StringTransition("b", s11)); s1->addTransition(new StringTransition("c", s2)); s1->addTransition(new StringTransition("d", s0)); s1->addTransition(new StringTransition("f", s211)); s211->addTransition(new StringTransition("d", s21)); s211->addTransition(new StringTransition("g", s0)); s211->addTransition(new StringTransition("h", f)); s21->addTransition(new StringTransition("b", s211)); s2->addTransition(new StringTransition("c", s1)); s2->addTransition(new StringTransition("f", s11)); s0->addTransition(new StringTransition("e", s211)); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("a")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("b")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("c")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("d")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("e")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("f")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("g")); QCoreApplication::processEvents(); machine.postEvent(new StringEvent("h")); QCoreApplication::processEvents(); QTRY_COMPARE(finishedSpy.count(), 1); } class TestSignalTransition : public QSignalTransition { public: TestSignalTransition(QState *sourceState = 0) : QSignalTransition(sourceState), m_sender(0) {} TestSignalTransition(QObject *sender, const char *signal, QAbstractState *target) : QSignalTransition(sender, signal), m_sender(0) { setTargetState(target); } QObject *senderReceived() const { return m_sender; } int signalIndexReceived() const { return m_signalIndex; } QVariantList argumentsReceived() const { return m_args; } protected: bool eventTest(QEvent *e) { if (!QSignalTransition::eventTest(e)) return false; QStateMachine::SignalEvent *se = static_cast(e); m_sender = se->sender(); m_signalIndex = se->signalIndex(); m_args = se->arguments(); return true; } private: QObject *m_sender; int m_signalIndex; QVariantList m_args; }; void tst_QStateMachine::signalTransitions() { { QStateMachine machine; QState *s0 = new QState(&machine); QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: sender cannot be null"); QCOMPARE(s0->addTransition(0, SIGNAL(noSuchSignal()), 0), (QSignalTransition*)0); SignalEmitter emitter; QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: signal cannot be null"); QCOMPARE(s0->addTransition(&emitter, 0, 0), (QSignalTransition*)0); QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add transition to null state"); QCOMPARE(s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), 0), (QSignalTransition*)0); QFinalState *s1 = new QFinalState(&machine); QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: no such signal SignalEmitter::noSuchSignal()"); QCOMPARE(s0->addTransition(&emitter, SIGNAL(noSuchSignal()), s1), (QSignalTransition*)0); QSignalTransition *trans = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); QVERIFY(trans != 0); QCOMPARE(trans->sourceState(), s0); QCOMPARE(trans->targetState(), (QAbstractState*)s1); QCOMPARE(trans->senderObject(), (QObject*)&emitter); QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); emitter.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 1); emitter.emitSignalWithNoArg(); trans->setSignal(SIGNAL(signalWithIntArg(int))); QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithIntArg(int)))); machine.start(); QCoreApplication::processEvents(); emitter.emitSignalWithIntArg(123); QTRY_COMPARE(finishedSpy.count(), 2); machine.start(); QCoreApplication::processEvents(); trans->setSignal(SIGNAL(signalWithNoArg())); QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); emitter.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 3); SignalEmitter emitter2; machine.start(); QCoreApplication::processEvents(); trans->setSenderObject(&emitter2); emitter2.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 4); machine.start(); QCoreApplication::processEvents(); QTest::ignoreMessage(QtWarningMsg, "QSignalTransition: no such signal: SignalEmitter::noSuchSignal()"); trans->setSignal(SIGNAL(noSuchSignal())); QCOMPARE(trans->signal(), QByteArray(SIGNAL(noSuchSignal()))); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); SignalEmitter emitter; QSignalTransition *trans = s0->addTransition(&emitter, "signalWithNoArg()", s1); QVERIFY(trans != 0); QCOMPARE(trans->sourceState(), s0); QCOMPARE(trans->targetState(), (QAbstractState*)s1); QCOMPARE(trans->senderObject(), (QObject*)&emitter); QCOMPARE(trans->signal(), QByteArray("signalWithNoArg()")); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); emitter.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 1); trans->setSignal("signalWithIntArg(int)"); QCOMPARE(trans->signal(), QByteArray("signalWithIntArg(int)")); machine.start(); QCoreApplication::processEvents(); emitter.emitSignalWithIntArg(123); QTRY_COMPARE(finishedSpy.count(), 2); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); SignalEmitter emitter; TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithIntArg(int)), s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); emitter.emitSignalWithIntArg(123); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(trans->senderReceived(), (QObject*)&emitter); QCOMPARE(trans->signalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithIntArg(int)")); QCOMPARE(trans->argumentsReceived().size(), 1); QCOMPARE(trans->argumentsReceived().at(0).toInt(), 123); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); SignalEmitter emitter; TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithStringArg(QString)), s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QString testString = QString::fromLatin1("hello"); emitter.emitSignalWithStringArg(testString); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(trans->senderReceived(), (QObject*)&emitter); QCOMPARE(trans->signalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithStringArg(QString)")); QCOMPARE(trans->argumentsReceived().size(), 1); QCOMPARE(trans->argumentsReceived().at(0).toString(), testString); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); TestSignalTransition *trans = new TestSignalTransition(); QCOMPARE(trans->senderObject(), (QObject*)0); QCOMPARE(trans->signal(), QByteArray()); SignalEmitter emitter; trans->setSenderObject(&emitter); QCOMPARE(trans->senderObject(), (QObject*)&emitter); trans->setSignal(SIGNAL(signalWithNoArg())); QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); trans->setTargetState(s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); emitter.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 1); } // Multiple transitions for same (object,signal) { QStateMachine machine; SignalEmitter emitter; QState *s0 = new QState(&machine); QState *s1 = new QState(&machine); QSignalTransition *t0 = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); QSignalTransition *t1 = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s0); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); emitter.emitSignalWithNoArg(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); s0->removeTransition(t0); emitter.emitSignalWithNoArg(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); emitter.emitSignalWithNoArg(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); s1->removeTransition(t1); emitter.emitSignalWithNoArg(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); s0->addTransition(t0); s1->addTransition(t1); emitter.emitSignalWithNoArg(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); } // multiple signal transitions from same source { QStateMachine machine; SignalEmitter emitter; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); QFinalState *s2 = new QFinalState(&machine); s0->addTransition(&emitter, SIGNAL(signalWithIntArg(int)), s2); QFinalState *s3 = new QFinalState(&machine); s0->addTransition(&emitter, SIGNAL(signalWithStringArg(QString)), s3); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); emitter.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); machine.start(); QTRY_COMPARE(startedSpy.count(), 2); emitter.emitSignalWithIntArg(123); QTRY_COMPARE(finishedSpy.count(), 2); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); machine.start(); QTRY_COMPARE(startedSpy.count(), 3); emitter.emitSignalWithStringArg("hello"); QTRY_COMPARE(finishedSpy.count(), 3); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s3)); } // signature normalization { QStateMachine machine; SignalEmitter emitter; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QSignalTransition *t0 = s0->addTransition(&emitter, SIGNAL( signalWithNoArg( ) ), s1); QVERIFY(t0 != 0); QCOMPARE(t0->signal(), QByteArray(SIGNAL( signalWithNoArg( ) ))); QSignalTransition *t1 = s0->addTransition(&emitter, SIGNAL( signalWithStringArg( const QString & ) ), s1); QVERIFY(t1 != 0); QCOMPARE(t1->signal(), QByteArray(SIGNAL( signalWithStringArg( const QString & ) ))); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); emitter.emitSignalWithNoArg(); QTRY_COMPARE(finishedSpy.count(), 1); } } class TestEventTransition : public QEventTransition { public: TestEventTransition(QState *sourceState = 0) : QEventTransition(sourceState), m_eventSource(0), m_eventType(QEvent::None) {} TestEventTransition(QObject *object, QEvent::Type type, QAbstractState *target) : QEventTransition(object, type), m_eventSource(0), m_eventType(QEvent::None) { setTargetState(target); } QObject *eventSourceReceived() const { return m_eventSource; } QEvent::Type eventTypeReceived() const { return m_eventType; } protected: bool eventTest(QEvent *e) { if (!QEventTransition::eventTest(e)) return false; QStateMachine::WrappedEvent *we = static_cast(e); m_eventSource = we->object(); m_eventType = we->event()->type(); return true; } private: QObject *m_eventSource; QEvent::Type m_eventType; }; void tst_QStateMachine::eventTransitions() { QPushButton button; { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QMouseEventTransition *trans; trans = new QMouseEventTransition(&button, QEvent::MouseButtonPress, Qt::LeftButton); QCOMPARE(trans->targetState(), (QAbstractState*)0); trans->setTargetState(s1); QCOMPARE(trans->eventType(), QEvent::MouseButtonPress); QCOMPARE(trans->button(), Qt::LeftButton); QCOMPARE(trans->targetState(), (QAbstractState*)s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QTest::mousePress(&button, Qt::LeftButton); QTRY_COMPARE(finishedSpy.count(), 1); QTest::mousePress(&button, Qt::LeftButton); trans->setEventType(QEvent::MouseButtonRelease); QCOMPARE(trans->eventType(), QEvent::MouseButtonRelease); machine.start(); QCoreApplication::processEvents(); QTest::mouseRelease(&button, Qt::LeftButton); QTRY_COMPARE(finishedSpy.count(), 2); machine.start(); QCoreApplication::processEvents(); trans->setEventType(QEvent::MouseButtonPress); QTest::mousePress(&button, Qt::LeftButton); QTRY_COMPARE(finishedSpy.count(), 3); QPushButton button2; machine.start(); QCoreApplication::processEvents(); trans->setEventSource(&button2); QTest::mousePress(&button2, Qt::LeftButton); QTRY_COMPARE(finishedSpy.count(), 4); } for (int x = 0; x < 2; ++x) { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QEventTransition *trans; if (x == 0) { trans = new QEventTransition(); QCOMPARE(trans->eventSource(), (QObject*)0); QCOMPARE(trans->eventType(), QEvent::None); trans->setEventSource(&button); trans->setEventType(QEvent::MouseButtonPress); trans->setTargetState(s1); } else if (x == 1) { trans = new QEventTransition(&button, QEvent::MouseButtonPress); trans->setTargetState(s1); } QCOMPARE(trans->eventSource(), (QObject*)&button); QCOMPARE(trans->eventType(), QEvent::MouseButtonPress); QCOMPARE(trans->targetState(), (QAbstractState*)s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QTRY_COMPARE(finishedSpy.count(), 1); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QMouseEventTransition *trans = new QMouseEventTransition(); QCOMPARE(trans->eventSource(), (QObject*)0); QCOMPARE(trans->eventType(), QEvent::None); QCOMPARE(trans->button(), Qt::NoButton); trans->setEventSource(&button); trans->setEventType(QEvent::MouseButtonPress); trans->setButton(Qt::LeftButton); trans->setTargetState(s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QTRY_COMPARE(finishedSpy.count(), 1); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QKeyEventTransition *trans = new QKeyEventTransition(&button, QEvent::KeyPress, Qt::Key_A); QCOMPARE(trans->eventType(), QEvent::KeyPress); QCOMPARE(trans->key(), (int)Qt::Key_A); trans->setTargetState(s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QTest::keyPress(&button, Qt::Key_A); QCoreApplication::processEvents(); QTRY_COMPARE(finishedSpy.count(), 1); } { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QKeyEventTransition *trans = new QKeyEventTransition(); QCOMPARE(trans->eventSource(), (QObject*)0); QCOMPARE(trans->eventType(), QEvent::None); QCOMPARE(trans->key(), 0); trans->setEventSource(&button); trans->setEventType(QEvent::KeyPress); trans->setKey(Qt::Key_A); trans->setTargetState(s1); s0->addTransition(trans); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QTest::keyPress(&button, Qt::Key_A); QCoreApplication::processEvents(); QTRY_COMPARE(finishedSpy.count(), 1); } // Multiple transitions for same (object,event) { QStateMachine machine; QState *s0 = new QState(&machine); QState *s1 = new QState(&machine); QEventTransition *t0 = new QEventTransition(&button, QEvent::MouseButtonPress); t0->setTargetState(s1); s0->addTransition(t0); QEventTransition *t1 = new QEventTransition(&button, QEvent::MouseButtonPress); t1->setTargetState(s0); s1->addTransition(t1); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); s0->removeTransition(t0); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); s1->removeTransition(t1); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s0)); s0->addTransition(t0); s1->addTransition(t1); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); } // multiple event transitions from same source { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QFinalState *s2 = new QFinalState(&machine); QEventTransition *t0 = new QEventTransition(&button, QEvent::MouseButtonPress); t0->setTargetState(s1); s0->addTransition(t0); QEventTransition *t1 = new QEventTransition(&button, QEvent::MouseButtonRelease); t1->setTargetState(s2); s0->addTransition(t1); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QTest::mousePress(&button, Qt::LeftButton); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); machine.start(); QTRY_COMPARE(startedSpy.count(), 2); QTest::mouseRelease(&button, Qt::LeftButton); QTRY_COMPARE(finishedSpy.count(), 2); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } // custom event { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); QEventTransition *trans = new QEventTransition(&button, QEvent::Type(QEvent::User+1)); trans->setTargetState(s1); s0->addTransition(trans); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.setInitialState(s0); machine.start(); QTest::ignoreMessage(QtWarningMsg, "QObject event transitions are not supported for custom types"); QTRY_COMPARE(startedSpy.count(), 1); } // custom transition { QStateMachine machine; QState *s0 = new QState(&machine); QFinalState *s1 = new QFinalState(&machine); TestEventTransition *trans = new TestEventTransition(&button, QEvent::MouseButtonPress, s1); s0->addTransition(trans); QCOMPARE(trans->eventSourceReceived(), (QObject*)0); QCOMPARE(trans->eventTypeReceived(), QEvent::None); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.setInitialState(s0); machine.start(); QCoreApplication::processEvents(); QTest::mousePress(&button, Qt::LeftButton); QCoreApplication::processEvents(); QTRY_COMPARE(finishedSpy.count(), 1); QCOMPARE(trans->eventSourceReceived(), (QObject*)&button); QCOMPARE(trans->eventTypeReceived(), QEvent::MouseButtonPress); } } void tst_QStateMachine::graphicsSceneEventTransitions() { QGraphicsScene scene; QGraphicsTextItem *textItem = scene.addText("foo"); QStateMachine machine; QState *s1 = new QState(&machine); QFinalState *s2 = new QFinalState(&machine); QEventTransition *t = new QEventTransition(textItem, QEvent::GraphicsSceneMouseMove); t->setTargetState(s2); s1->addTransition(t); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QVERIFY(finishedSpy.count() == 0); QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); scene.sendEvent(textItem, &mouseEvent); QTRY_COMPARE(finishedSpy.count(), 1); } void tst_QStateMachine::historyStates() { for (int x = 0; x < 2; ++x) { QStateMachine machine; QState *root = &machine; QState *s0 = new QState(root); QState *s00 = new QState(s0); QState *s01 = new QState(s0); QHistoryState *s0h; if (x == 0) { s0h = new QHistoryState(s0); QCOMPARE(s0h->historyType(), QHistoryState::ShallowHistory); s0h->setHistoryType(QHistoryState::DeepHistory); } else { s0h = new QHistoryState(QHistoryState::DeepHistory, s0); } QCOMPARE(s0h->historyType(), QHistoryState::DeepHistory); s0h->setHistoryType(QHistoryState::ShallowHistory); QCOMPARE(s0h->historyType(), QHistoryState::ShallowHistory); QCOMPARE(s0h->defaultState(), (QAbstractState*)0); s0h->setDefaultState(s00); QCOMPARE(s0h->defaultState(), (QAbstractState*)s00); QString warning; warning.sprintf("QHistoryState::setDefaultState: state %p does not belong to this history state's group (%p)", s0, s0); QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); s0h->setDefaultState(s0); QState *s1 = new QState(root); QFinalState *s2 = new QFinalState(root); s00->addTransition(new StringTransition("a", s01)); s0->addTransition(new StringTransition("b", s1)); s1->addTransition(new StringTransition("c", s0h)); s0->addTransition(new StringTransition("d", s2)); root->setInitialState(s0); s0->setInitialState(s00); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s0)); QVERIFY(machine.configuration().contains(s00)); machine.postEvent(new StringEvent("a")); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s0)); QVERIFY(machine.configuration().contains(s01)); machine.postEvent(new StringEvent("b")); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); machine.postEvent(new StringEvent("c")); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s0)); QVERIFY(machine.configuration().contains(s01)); machine.postEvent(new StringEvent("d")); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); QTRY_COMPARE(finishedSpy.count(), 1); } } void tst_QStateMachine::startAndStop() { QStateMachine machine; QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); QVERIFY(!machine.isRunning()); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start."); machine.start(); QCOMPARE(startedSpy.count(), 0); QCOMPARE(stoppedSpy.count(), 0); QCOMPARE(finishedSpy.count(), 0); QVERIFY(!machine.isRunning()); machine.stop(); QCOMPARE(startedSpy.count(), 0); QCOMPARE(stoppedSpy.count(), 0); QCOMPARE(finishedSpy.count(), 0); QState *s1 = new QState(&machine); machine.setInitialState(s1); machine.start(); QTRY_COMPARE(machine.isRunning(), true); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(stoppedSpy.count(), 0); QCOMPARE(finishedSpy.count(), 0); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(s1)); QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start(): already running"); machine.start(); machine.stop(); QTRY_COMPARE(machine.isRunning(), false); QTRY_COMPARE(stoppedSpy.count(), 1); QCOMPARE(startedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); QCOMPARE(machine.configuration().count(), 1); QVERIFY(machine.configuration().contains(s1)); machine.start(); machine.stop(); QTRY_COMPARE(startedSpy.count(), 2); QCOMPARE(stoppedSpy.count(), 2); } void tst_QStateMachine::targetStateWithNoParent() { QStateMachine machine; QState *s1 = new QState(&machine); s1->setObjectName("s1"); QState s2; s1->addTransition(&s2); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 's1'"); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.isRunning(), false); QCOMPARE(stoppedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); QCOMPARE(machine.error(), QStateMachine::NoCommonAncestorForTransitionError); } void tst_QStateMachine::targetStateDeleted() { QStateMachine machine; QState *s1 = new QState(&machine); s1->setObjectName("s1"); QState *s2 = new QState(&machine); QAbstractTransition *trans = s1->addTransition(s2); delete s2; QCOMPARE(trans->targetState(), (QAbstractState*)0); QVERIFY(trans->targetStates().isEmpty()); } void tst_QStateMachine::defaultGlobalRestorePolicy() { QStateMachine machine; QObject *propertyHolder = new QObject(&machine); propertyHolder->setProperty("a", 1); propertyHolder->setProperty("b", 2); QState *s1 = new QState(&machine); s1->assignProperty(propertyHolder, "a", 3); QState *s2 = new QState(&machine); s2->assignProperty(propertyHolder, "b", 4); QState *s3 = new QState(&machine); s1->addTransition(new EventTransition(QEvent::User, s2)); s2->addTransition(new EventTransition(QEvent::User, s3)); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 2); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 4); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 4); } void tst_QStateMachine::noInitialStateForInitialState() { QStateMachine machine; QState *initialState = new QState(&machine); initialState->setObjectName("initialState"); machine.setInitialState(initialState); QState *childState = new QState(initialState); (void)childState; QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " "Missing initial state in compound state 'initialState'"); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); QCOMPARE(int(machine.error()), int(QStateMachine::NoInitialStateError)); } void tst_QStateMachine::globalRestorePolicySetToDontRestore() { QStateMachine machine; machine.setGlobalRestorePolicy(QStateMachine::DontRestoreProperties); QObject *propertyHolder = new QObject(&machine); propertyHolder->setProperty("a", 1); propertyHolder->setProperty("b", 2); QState *s1 = new QState(&machine); s1->assignProperty(propertyHolder, "a", 3); QState *s2 = new QState(&machine); s2->assignProperty(propertyHolder, "b", 4); QState *s3 = new QState(&machine); s1->addTransition(new EventTransition(QEvent::User, s2)); s2->addTransition(new EventTransition(QEvent::User, s3)); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 2); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 4); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 4); } void tst_QStateMachine::globalRestorePolicySetToRestore() { QStateMachine machine; machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); QObject *propertyHolder = new QObject(&machine); propertyHolder->setProperty("a", 1); propertyHolder->setProperty("b", 2); QState *s1 = new QState(&machine); s1->assignProperty(propertyHolder, "a", 3); QState *s2 = new QState(&machine); s2->assignProperty(propertyHolder, "b", 4); QState *s3 = new QState(&machine); s1->addTransition(new EventTransition(QEvent::User, s2)); s2->addTransition(new EventTransition(QEvent::User, s3)); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 3); QCOMPARE(propertyHolder->property("b").toInt(), 2); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 1); QCOMPARE(propertyHolder->property("b").toInt(), 4); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("a").toInt(), 1); QCOMPARE(propertyHolder->property("b").toInt(), 2); } void tst_QStateMachine::transitionWithParent() { QStateMachine machine; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); EventTransition *trans = new EventTransition(QEvent::User, s2, s1); QCOMPARE(trans->sourceState(), s1); QCOMPARE(trans->targetState(), (QAbstractState*)s2); QCOMPARE(trans->targetStates().size(), 1); QCOMPARE(trans->targetStates().at(0), (QAbstractState*)s2); } void tst_QStateMachine::simpleAnimation() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("fooBar", 1.0); QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "fooBar", 2.0); EventTransition *et = new EventTransition(QEvent::User, s2); QPropertyAnimation *animation = new QPropertyAnimation(object, "fooBar", s2); et->addAnimation(animation); s1->addTransition(et); QState *s3 = new QState(&machine); s2->addTransition(animation, SIGNAL(finished()), s3); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("fooBar").toDouble(), 2.0); } class SlotCalledCounter: public QObject { Q_OBJECT public: SlotCalledCounter() : counter(0) {} int counter; public slots: void slot() { counter++; } }; void tst_QStateMachine::twoAnimations() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); object->setProperty("bar", 3.0); QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); s2->assignProperty(object, "bar", 10.0); QPropertyAnimation *animationFoo = new QPropertyAnimation(object, "foo", s2); QPropertyAnimation *animationBar = new QPropertyAnimation(object, "bar", s2); animationBar->setDuration(900); SlotCalledCounter counter; connect(animationFoo, SIGNAL(finished()), &counter, SLOT(slot())); connect(animationBar, SIGNAL(finished()), &counter, SLOT(slot())); EventTransition *et = new EventTransition(QEvent::User, s2); et->addAnimation(animationFoo); et->addAnimation(animationBar); s1->addTransition(et); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 2.0); QCOMPARE(object->property("bar").toDouble(), 10.0); QCOMPARE(counter.counter, 2); } void tst_QStateMachine::twoAnimatedTransitions() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 5.0); QPropertyAnimation *fooAnimation = new QPropertyAnimation(object, "foo", s2); EventTransition *trans = new EventTransition(QEvent::User, s2); s1->addTransition(trans); trans->addAnimation(fooAnimation); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); s2->addTransition(fooAnimation, SIGNAL(finished()), s3); QState *s4 = new QState(&machine); s4->assignProperty(object, "foo", 2.0); QPropertyAnimation *fooAnimation2 = new QPropertyAnimation(object, "foo", s4); trans = new EventTransition(QEvent::User, s4); s3->addTransition(trans); trans->addAnimation(fooAnimation2); QState *s5 = new QState(&machine); QObject::connect(s5, SIGNAL(entered()), QApplication::instance(), SLOT(quit())); s4->addTransition(fooAnimation2, SIGNAL(finished()), s5); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 5.0); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s5)); QCOMPARE(object->property("foo").toDouble(), 2.0); } void tst_QStateMachine::playAnimationTwice() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 5.0); QPropertyAnimation *fooAnimation = new QPropertyAnimation(object, "foo", s2); EventTransition *trans = new EventTransition(QEvent::User, s2); s1->addTransition(trans); trans->addAnimation(fooAnimation); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); s2->addTransition(fooAnimation, SIGNAL(finished()), s3); QState *s4 = new QState(&machine); s4->assignProperty(object, "foo", 2.0); trans = new EventTransition(QEvent::User, s4); s3->addTransition(trans); trans->addAnimation(fooAnimation); QState *s5 = new QState(&machine); QObject::connect(s5, SIGNAL(entered()), QApplication::instance(), SLOT(quit())); s4->addTransition(fooAnimation, SIGNAL(finished()), s5); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 5.0); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s5)); QCOMPARE(object->property("foo").toDouble(), 2.0); } void tst_QStateMachine::nestedTargetStateForAnimation() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); object->setProperty("bar", 3.0); SlotCalledCounter counter; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); QState *s2Child = new QState(s2); s2Child->assignProperty(object, "bar", 10.0); s2->setInitialState(s2Child); QState *s2Child2 = new QState(s2); s2Child2->assignProperty(object, "bar", 11.0); QAbstractTransition *at = new EventTransition(QEvent::User, s2Child2); s2Child->addTransition(at); QPropertyAnimation *animation = new QPropertyAnimation(object, "bar", s2); animation->setDuration(2000); connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); at->addAnimation(animation); at = new EventTransition(QEvent::User, s2); s1->addTransition(at); animation = new QPropertyAnimation(object, "foo", s2); connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); at->addAnimation(animation); animation = new QPropertyAnimation(object, "bar", s2); connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); at->addAnimation(animation); QState *s3 = new QState(&machine); s2->addTransition(s2Child, SIGNAL(propertiesAssigned()), s3); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 2.0); QCOMPARE(object->property("bar").toDouble(), 10.0); QCOMPARE(counter.counter, 2); } void tst_QStateMachine::propertiesAssignedSignalTransitionsReuseAnimationGroup() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 0); QState *s1 = new QState(&machine); s1->assignProperty(object, "foo", 123); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 456); QState *s3 = new QState(&machine); s3->assignProperty(object, "foo", 789); QFinalState *s4 = new QFinalState(&machine); QParallelAnimationGroup animationGroup; animationGroup.addAnimation(new QPropertyAnimation(object, "foo")); QSignalSpy animationFinishedSpy(&animationGroup, SIGNAL(finished())); s1->addTransition(s1, SIGNAL(propertiesAssigned()), s2)->addAnimation(&animationGroup); s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3)->addAnimation(&animationGroup); s3->addTransition(s3, SIGNAL(propertiesAssigned()), s4); machine.setInitialState(s1); QSignalSpy machineFinishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(machineFinishedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s4)); QCOMPARE(object->property("foo").toInt(), 789); QCOMPARE(animationFinishedSpy.count(), 2); } void tst_QStateMachine::animatedGlobalRestoreProperty() { QStateMachine machine; machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); SlotCalledCounter counter; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); QState *s3 = new QState(&machine); QState *s4 = new QState(&machine); QObject::connect(s4, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); QAbstractTransition *at = new EventTransition(QEvent::User, s2); s1->addTransition(at); QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", s2); connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); at->addAnimation(pa); at = s2->addTransition(pa, SIGNAL(finished()), s3); pa = new QPropertyAnimation(object, "foo", s3); connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); at->addAnimation(pa); at = s3->addTransition(pa, SIGNAL(finished()), s4); pa = new QPropertyAnimation(object, "foo", s4); connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); at->addAnimation(pa); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s4)); QCOMPARE(object->property("foo").toDouble(), 1.0); QCOMPARE(counter.counter, 2); } void tst_QStateMachine::specificTargetValueOfAnimation() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); QPropertyAnimation *anim = new QPropertyAnimation(object, "foo"); anim->setEndValue(10.0); EventTransition *trans = new EventTransition(QEvent::User, s2); s1->addTransition(trans); trans->addAnimation(anim); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); s2->addTransition(anim, SIGNAL(finished()), s3); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 2.0); QCOMPARE(anim->endValue().toDouble(), 10.0); delete anim; } void tst_QStateMachine::addDefaultAnimation() { QStateMachine machine; QObject *object = new QObject(); object->setProperty("foo", 1.0); QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); s1->addTransition(new EventTransition(QEvent::User, s2)); QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); machine.addDefaultAnimation(pa); s2->addTransition(pa, SIGNAL(finished()), s3); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 2.0); delete object; } void tst_QStateMachine::addDefaultAnimationWithUnusedAnimation() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); object->setProperty("bar", 2.0); SlotCalledCounter counter; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); s1->addTransition(new EventTransition(QEvent::User, s2)); QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); machine.addDefaultAnimation(pa); s2->addTransition(pa, SIGNAL(finished()), s3); pa = new QPropertyAnimation(object, "bar", &machine); connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); machine.addDefaultAnimation(pa); machine.setInitialState(s1); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(object->property("foo").toDouble(), 2.0); QCOMPARE(counter.counter, 1); } void tst_QStateMachine::removeDefaultAnimation() { QStateMachine machine; QObject propertyHolder; propertyHolder.setProperty("foo", 0); QCOMPARE(machine.defaultAnimations().size(), 0); QPropertyAnimation *anim = new QPropertyAnimation(&propertyHolder, "foo"); machine.addDefaultAnimation(anim); QCOMPARE(machine.defaultAnimations().size(), 1); QVERIFY(machine.defaultAnimations().contains(anim)); machine.removeDefaultAnimation(anim); QCOMPARE(machine.defaultAnimations().size(), 0); machine.addDefaultAnimation(anim); QPropertyAnimation *anim2 = new QPropertyAnimation(&propertyHolder, "foo"); machine.addDefaultAnimation(anim2); QCOMPARE(machine.defaultAnimations().size(), 2); QVERIFY(machine.defaultAnimations().contains(anim)); QVERIFY(machine.defaultAnimations().contains(anim2)); machine.removeDefaultAnimation(anim); QCOMPARE(machine.defaultAnimations().size(), 1); QVERIFY(machine.defaultAnimations().contains(anim2)); machine.removeDefaultAnimation(anim2); QCOMPARE(machine.defaultAnimations().size(), 0); delete anim; delete anim2; } void tst_QStateMachine::overrideDefaultAnimationWithSpecific() { QStateMachine machine; QObject *object = new QObject(&machine); object->setProperty("foo", 1.0); SlotCalledCounter counter; QState *s1 = new QState(&machine); machine.setInitialState(s1); QState *s2 = new QState(&machine); s2->assignProperty(object, "foo", 2.0); QState *s3 = new QState(&machine); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); QAbstractTransition *at = new EventTransition(QEvent::User, s2); s1->addTransition(at); QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); machine.addDefaultAnimation(defaultAnimation); at->addAnimation(moreSpecificAnimation); machine.start(); QCoreApplication::processEvents(); machine.postEvent(new QEvent(QEvent::User)); QCOREAPPLICATION_EXEC(5000); QVERIFY(machine.configuration().contains(s3)); QCOMPARE(counter.counter, 2); // specific animation started and stopped delete defaultAnimation; delete moreSpecificAnimation; } void tst_QStateMachine::parallelStateAssignmentsDone() { QStateMachine machine; QObject *propertyHolder = new QObject(&machine); propertyHolder->setProperty("foo", 123); propertyHolder->setProperty("bar", 456); propertyHolder->setProperty("zoot", 789); QState *s1 = new QState(&machine); machine.setInitialState(s1); QState *parallelState = new QState(QState::ParallelStates, &machine); parallelState->assignProperty(propertyHolder, "foo", 321); QState *s2 = new QState(parallelState); s2->assignProperty(propertyHolder, "bar", 654); QState *s3 = new QState(parallelState); s3->assignProperty(propertyHolder, "zoot", 987); s1->addTransition(new EventTransition(QEvent::User, parallelState)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("foo").toInt(), 123); QCOMPARE(propertyHolder->property("bar").toInt(), 456); QCOMPARE(propertyHolder->property("zoot").toInt(), 789); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(propertyHolder->property("foo").toInt(), 321); QCOMPARE(propertyHolder->property("bar").toInt(), 654); QCOMPARE(propertyHolder->property("zoot").toInt(), 987); } void tst_QStateMachine::transitionsFromParallelStateWithNoChildren() { QStateMachine machine; QState *parallelState = new QState(QState::ParallelStates, &machine); machine.setInitialState(parallelState); QState *s1 = new QState(&machine); parallelState->addTransition(new EventTransition(QEvent::User, s1)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(1, machine.configuration().size()); QVERIFY(machine.configuration().contains(parallelState)); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(1, machine.configuration().size()); QVERIFY(machine.configuration().contains(s1)); } void tst_QStateMachine::parallelStateTransition() { QStateMachine machine; QState *parallelState = new QState(QState::ParallelStates, &machine); machine.setInitialState(parallelState); QState *s1 = new QState(parallelState); QState *s2 = new QState(parallelState); QState *s1InitialChild = new QState(s1); s1->setInitialState(s1InitialChild); QState *s2InitialChild = new QState(s2); s2->setInitialState(s2InitialChild); QState *s1OtherChild = new QState(s1); s1->addTransition(new EventTransition(QEvent::User, s1OtherChild)); machine.start(); QCoreApplication::processEvents(); QVERIFY(machine.configuration().contains(parallelState)); QVERIFY(machine.configuration().contains(s1)); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s1InitialChild)); QVERIFY(machine.configuration().contains(s2InitialChild)); QCOMPARE(machine.configuration().size(), 5); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QVERIFY(machine.configuration().contains(parallelState)); QVERIFY(machine.configuration().contains(s1)); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s1OtherChild)); QVERIFY(machine.configuration().contains(s2InitialChild)); QCOMPARE(machine.configuration().size(), 5); } void tst_QStateMachine::nestedRestoreProperties() { QStateMachine machine; machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); QObject *propertyHolder = new QObject(&machine); propertyHolder->setProperty("foo", 1); propertyHolder->setProperty("bar", 2); QState *s1 = new QState(&machine); machine.setInitialState(s1); QState *s2 = new QState(&machine); s2->assignProperty(propertyHolder, "foo", 3); QState *s21 = new QState(s2); s21->assignProperty(propertyHolder, "bar", 4); s2->setInitialState(s21); QState *s22 = new QState(s2); s22->assignProperty(propertyHolder, "bar", 5); s1->addTransition(new EventTransition(QEvent::User, s2)); s21->addTransition(new EventTransition(QEvent::User, s22)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); QCOMPARE(propertyHolder->property("foo").toInt(), 1); QCOMPARE(propertyHolder->property("bar").toInt(), 2); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s21)); QCOMPARE(propertyHolder->property("foo").toInt(), 3); QCOMPARE(propertyHolder->property("bar").toInt(), 4); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s22)); QCOMPARE(propertyHolder->property("foo").toInt(), 3); QCOMPARE(propertyHolder->property("bar").toInt(), 5); } void tst_QStateMachine::nestedRestoreProperties2() { QStateMachine machine; machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); QObject *propertyHolder = new QObject(&machine); propertyHolder->setProperty("foo", 1); propertyHolder->setProperty("bar", 2); QState *s1 = new QState(&machine); machine.setInitialState(s1); QState *s2 = new QState(&machine); s2->assignProperty(propertyHolder, "foo", 3); QState *s21 = new QState(s2); s21->assignProperty(propertyHolder, "bar", 4); s2->setInitialState(s21); QState *s22 = new QState(s2); s22->assignProperty(propertyHolder, "foo", 6); s22->assignProperty(propertyHolder, "bar", 5); s1->addTransition(new EventTransition(QEvent::User, s2)); s21->addTransition(new EventTransition(QEvent::User, s22)); s22->addTransition(new EventTransition(QEvent::User, s21)); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); QCOMPARE(propertyHolder->property("foo").toInt(), 1); QCOMPARE(propertyHolder->property("bar").toInt(), 2); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s21)); QCOMPARE(propertyHolder->property("foo").toInt(), 3); QCOMPARE(propertyHolder->property("bar").toInt(), 4); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s22)); QCOMPARE(propertyHolder->property("foo").toInt(), 6); QCOMPARE(propertyHolder->property("bar").toInt(), 5); machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s21)); QCOMPARE(propertyHolder->property("foo").toInt(), 3); QCOMPARE(propertyHolder->property("bar").toInt(), 4); } void tst_QStateMachine::nestedStateMachines() { QStateMachine machine; QState *group = new QState(&machine); group->setChildMode(QState::ParallelStates); QStateMachine *subMachines[3]; for (int i = 0; i < 3; ++i) { QState *subGroup = new QState(group); QStateMachine *subMachine = new QStateMachine(subGroup); { QState *initial = new QState(subMachine); QFinalState *done = new QFinalState(subMachine); initial->addTransition(new EventTransition(QEvent::User, done)); subMachine->setInitialState(initial); } QFinalState *subMachineDone = new QFinalState(subGroup); subMachine->addTransition(subMachine, SIGNAL(finished()), subMachineDone); subGroup->setInitialState(subMachine); subMachines[i] = subMachine; } QFinalState *final = new QFinalState(&machine); group->addTransition(group, SIGNAL(finished()), final); machine.setInitialState(group); QSignalSpy startedSpy(&machine, SIGNAL(started())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QTRY_COMPARE(machine.configuration().count(), 1+2*3); QVERIFY(machine.configuration().contains(group)); for (int i = 0; i < 3; ++i) QVERIFY(machine.configuration().contains(subMachines[i])); QCoreApplication::processEvents(); // starts the submachines for (int i = 0; i < 3; ++i) subMachines[i]->postEvent(new QEvent(QEvent::User)); QTRY_COMPARE(finishedSpy.count(), 1); } void tst_QStateMachine::goToState() { QStateMachine machine; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QStateMachinePrivate::get(&machine)->goToState(s2); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); QStateMachinePrivate::get(&machine)->goToState(s2); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); QStateMachinePrivate::get(&machine)->goToState(s1); QStateMachinePrivate::get(&machine)->goToState(s2); QStateMachinePrivate::get(&machine)->goToState(s1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); // go to state in group QState *s2_1 = new QState(s2); s2->setInitialState(s2_1); QStateMachinePrivate::get(&machine)->goToState(s2_1); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 2); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s2_1)); } void tst_QStateMachine::goToStateFromSourceWithTransition() { // QTBUG-21813 QStateMachine machine; QState *s1 = new QState(&machine); s1->addTransition(new QSignalTransition); QState *s2 = new QState(&machine); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QStateMachinePrivate::get(&machine)->goToState(s2); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } class CloneSignalTransition : public QSignalTransition { public: CloneSignalTransition(QObject *sender, const char *signal, QAbstractState *target) : QSignalTransition(sender, signal) { setTargetState(target); } void onTransition(QEvent *e) { QSignalTransition::onTransition(e); QStateMachine::SignalEvent *se = static_cast(e); eventSignalIndex = se->signalIndex(); } int eventSignalIndex; }; void tst_QStateMachine::clonedSignals() { SignalEmitter emitter; QStateMachine machine; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); CloneSignalTransition *t1 = new CloneSignalTransition(&emitter, SIGNAL(signalWithDefaultArg()), s2); s1->addTransition(t1); machine.setInitialState(s1); machine.start(); QTest::qWait(1); emitter.emitSignalWithDefaultArg(); QTest::qWait(1); QCOMPARE(t1->eventSignalIndex, emitter.metaObject()->indexOfSignal("signalWithDefaultArg()")); } class EventPosterThread : public QThread { Q_OBJECT public: EventPosterThread(QStateMachine *machine, QObject *parent = 0) : QThread(parent), m_machine(machine), m_count(0) { moveToThread(this); QObject::connect(m_machine, SIGNAL(started()), this, SLOT(postEvent())); } protected: virtual void run() { exec(); } private Q_SLOTS: void postEvent() { m_machine->postEvent(new QEvent(QEvent::User)); if (++m_count < 10000) QTimer::singleShot(0, this, SLOT(postEvent())); else quit(); } private: QStateMachine *m_machine; int m_count; }; void tst_QStateMachine::postEventFromOtherThread() { QStateMachine machine; EventPosterThread poster(&machine); StringEventPoster *s1 = new StringEventPoster("foo", &machine); s1->addTransition(new EventTransition(QEvent::User, s1)); QFinalState *f = new QFinalState(&machine); s1->addTransition(&poster, SIGNAL(finished()), f); machine.setInitialState(s1); poster.start(); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.start(); QTRY_COMPARE(finishedSpy.count(), 1); } void tst_QStateMachine::eventFilterForApplication() { QStateMachine machine; QState *s1 = new QState(&machine); { machine.setInitialState(s1); } QState *s2 = new QState(&machine); QEventTransition *transition = new QEventTransition(QCoreApplication::instance(), QEvent::ApplicationActivate); transition->setTargetState(s2); s1->addTransition(transition); machine.start(); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); QCoreApplication::postEvent(QCoreApplication::instance(), new QEvent(QEvent::ApplicationActivate)); QCoreApplication::processEvents(); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } void tst_QStateMachine::eventClassesExported() { // make sure this links QStateMachine::WrappedEvent *wrappedEvent = new QStateMachine::WrappedEvent(0, 0); Q_UNUSED(wrappedEvent); QStateMachine::SignalEvent *signalEvent = new QStateMachine::SignalEvent(0, 0, QList()); Q_UNUSED(signalEvent); } void tst_QStateMachine::stopInTransitionToFinalState() { QStateMachine machine; QState *s1 = new QState(&machine); QFinalState *s2 = new QFinalState(&machine); QAbstractTransition *t1 = s1->addTransition(s2); machine.setInitialState(s1); QObject::connect(t1, SIGNAL(triggered()), &machine, SLOT(stop())); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); QSignalSpy s2EnteredSpy(s2, SIGNAL(entered())); machine.start(); // Stopping should take precedence over finished. QTRY_COMPARE(stoppedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); QCOMPARE(s2EnteredSpy.count(), 1); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s2)); } class StopInEventTestTransition : public QAbstractTransition { public: bool eventTest(QEvent *e) { if (e->type() == QEvent::User) machine()->stop(); return false; } void onTransition(QEvent *) { } }; void tst_QStateMachine::stopInEventTest_data() { QTest::addColumn("eventPriority"); QTest::newRow("NormalPriority") << int(QStateMachine::NormalPriority); QTest::newRow("HighPriority") << int(QStateMachine::HighPriority); } void tst_QStateMachine::stopInEventTest() { QFETCH(int, eventPriority); QStateMachine machine; QState *s1 = new QState(&machine); s1->addTransition(new StopInEventTestTransition()); machine.setInitialState(s1); QSignalSpy startedSpy(&machine, SIGNAL(started())); machine.start(); QTRY_COMPARE(startedSpy.count(), 1); QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); QSignalSpy finishedSpy(&machine, SIGNAL(finished())); machine.postEvent(new QEvent(QEvent::User), QStateMachine::EventPriority(eventPriority)); QTRY_COMPARE(stoppedSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); QCOMPARE(machine.configuration().size(), 1); QVERIFY(machine.configuration().contains(s1)); } QTEST_MAIN(tst_QStateMachine) #include "tst_qstatemachine.moc"