From 146a63c8025d4b5a20554e067d0246df9be3e68a Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 29 Apr 2009 16:00:35 +0200 Subject: SCXML defines an atomic state as a with no children or . However, in SCXML it makes no sense for a tag to be atomic, hence have no children, whereas in a dynamic state machine you might set an atomic state as parallel because this should govern the behavior if the state gets children later. We decided that the most intuitive definition is that a state is atomic if it has no children, regardless of whether it has the parallel child mode. With the old definition, transitions from empty parallel states will never be taken, as illustrated by the test. --- src/corelib/statemachine/qstatemachine.cpp | 5 ++--- tests/auto/qstatemachine/tst_qstatemachine.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 8dfb229..2f8a19e 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -772,9 +772,8 @@ bool QStateMachinePrivate::isCompound(const QAbstractState *s) bool QStateMachinePrivate::isAtomic(const QAbstractState *s) { const QState *ss = qobject_cast(s); - return (ss && (QStatePrivate::get(ss)->childMode != QState::ParallelStates) - && QStatePrivate::get(ss)->childStates().isEmpty()) - || isFinal(s); + return (ss && QStatePrivate::get(ss)->childStates().isEmpty()) + || isFinal(s); } diff --git a/tests/auto/qstatemachine/tst_qstatemachine.cpp b/tests/auto/qstatemachine/tst_qstatemachine.cpp index 5ce0f35..3acde49 100644 --- a/tests/auto/qstatemachine/tst_qstatemachine.cpp +++ b/tests/auto/qstatemachine/tst_qstatemachine.cpp @@ -135,6 +135,7 @@ private slots: //void restorePolicyOnChildState(); void transitionWithParent(); + void transitionsFromParallelStateWithNoChildren(); void parallelStateTransition(); void parallelStateAssignmentsDone(); @@ -2889,6 +2890,30 @@ void tst_QStateMachine::parallelStateAssignmentsDone() QCOMPARE(propertyHolder->property("zoot").toInt(), 987); } +void tst_QStateMachine::transitionsFromParallelStateWithNoChildren() +{ + QStateMachine machine; + + QState *parallelState = new QState(QState::ParallelGroup, machine.rootState()); + machine.setInitialState(parallelState); + + QState *s1 = new QState(machine.rootState()); + 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; -- cgit v0.12 From 085c0bcc85cea0d44a2a63fd7f8ebf6c4bfcaf4c Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 29 Apr 2009 16:36:19 +0200 Subject: Kill gameOver signal as it was only used when you clicked the stop action --- examples/statemachine/errorstate/mainwindow.cpp | 14 ++++++-------- examples/statemachine/errorstate/mainwindow.h | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/statemachine/errorstate/mainwindow.cpp b/examples/statemachine/errorstate/mainwindow.cpp index f5edf60..8a15e8b 100644 --- a/examples/statemachine/errorstate/mainwindow.cpp +++ b/examples/statemachine/errorstate/mainwindow.cpp @@ -110,44 +110,41 @@ void MainWindow::init() QAction *quitAction = menuBar()->addAction("&Quit"); connect(addTankAction, SIGNAL(triggered()), this, SLOT(addTank())); - connect(stopGameAction, SIGNAL(triggered()), this, SIGNAL(gameOver())); connect(quitAction, SIGNAL(triggered()), this, SLOT(close())); m_machine = new QStateMachine(this); m_machine->setGlobalRestorePolicy(QStateMachine::RestoreProperties); - QState *stoppedState = new QState(m_machine->rootState()); + QState *stoppedState = new QState(m_machine->rootState()); stoppedState->setObjectName("stoppedState"); - stoppedState->assignProperty(runGameAction, "enabled", true); stoppedState->assignProperty(stopGameAction, "enabled", false); stoppedState->assignProperty(this, "started", false); m_machine->setInitialState(stoppedState); QState *spawnsAvailable = new QState(stoppedState); - spawnsAvailable->assignProperty(addTankAction, "enabled", true); spawnsAvailable->setObjectName("spawnsAvailable"); + spawnsAvailable->assignProperty(addTankAction, "enabled", true); QState *noSpawnsAvailable = new QState(stoppedState); + noSpawnsAvailable->setObjectName("noSpawnsAvailable"); noSpawnsAvailable->assignProperty(addTankAction, "enabled", false); spawnsAvailable->addTransition(this, SIGNAL(mapFull()), noSpawnsAvailable); QHistoryState *hs = new QHistoryState(stoppedState); - hs->setObjectName("hs"); hs->setDefaultState(spawnsAvailable); stoppedState->setInitialState(hs); m_runningState = new QState(QState::ParallelGroup, m_machine->rootState()); + m_runningState->setObjectName("runningState"); m_runningState->assignProperty(addTankAction, "enabled", false); m_runningState->assignProperty(runGameAction, "enabled", false); m_runningState->assignProperty(stopGameAction, "enabled", true); stoppedState->addTransition(runGameAction, SIGNAL(triggered()), m_runningState); - m_runningState->addTransition(this, SIGNAL(gameOver()), stoppedState); - - m_machine->start(); + m_runningState->addTransition(stopGameAction, SIGNAL(triggered()), stoppedState); QTimer *timer = new QTimer(this); timer->setInterval(100); @@ -155,6 +152,7 @@ void MainWindow::init() connect(m_runningState, SIGNAL(entered()), timer, SLOT(start())); connect(m_runningState, SIGNAL(exited()), timer, SLOT(stop())); + m_machine->start(); m_time.start(); } diff --git a/examples/statemachine/errorstate/mainwindow.h b/examples/statemachine/errorstate/mainwindow.h index 33122eb..622dabe 100644 --- a/examples/statemachine/errorstate/mainwindow.h +++ b/examples/statemachine/errorstate/mainwindow.h @@ -25,7 +25,6 @@ public slots: void runStep(); signals: - void gameOver(); void mapFull(); private: -- cgit v0.12 From 9371a8069c4c0e0a00172980004c967d5dd296fd Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 29 Apr 2009 16:40:31 +0200 Subject: Compile against new QState::ChildMode API. --- examples/statemachine/errorstate/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/statemachine/errorstate/mainwindow.cpp b/examples/statemachine/errorstate/mainwindow.cpp index 8a15e8b..e04ca94 100644 --- a/examples/statemachine/errorstate/mainwindow.cpp +++ b/examples/statemachine/errorstate/mainwindow.cpp @@ -137,7 +137,7 @@ void MainWindow::init() stoppedState->setInitialState(hs); - m_runningState = new QState(QState::ParallelGroup, m_machine->rootState()); + m_runningState = new QState(QState::ParallelStates, m_machine->rootState()); m_runningState->setObjectName("runningState"); m_runningState->assignProperty(addTankAction, "enabled", false); m_runningState->assignProperty(runGameAction, "enabled", false); -- cgit v0.12 From b599036b8cf4eda2a12a0127a484dff659e594b8 Mon Sep 17 00:00:00 2001 From: Kent Hansen Date: Wed, 29 Apr 2009 17:03:27 +0200 Subject: kill QStateMachine::animationsFinished(), introduce QState::polished() --- examples/animation/stickman/lifecycle.cpp | 4 +- src/corelib/statemachine/qstate.cpp | 16 ++ src/corelib/statemachine/qstate_p.h | 3 + src/corelib/statemachine/qstatemachine.cpp | 242 ++++++++++++++----------- src/corelib/statemachine/qstatemachine.h | 4 - src/corelib/statemachine/qstatemachine_p.h | 8 +- tests/auto/qstatemachine/tst_qstatemachine.cpp | 155 +++++++++++++++- 7 files changed, 317 insertions(+), 115 deletions(-) diff --git a/examples/animation/stickman/lifecycle.cpp b/examples/animation/stickman/lifecycle.cpp index df69d47..b22af55 100644 --- a/examples/animation/stickman/lifecycle.cpp +++ b/examples/animation/stickman/lifecycle.cpp @@ -203,14 +203,14 @@ QState *LifeCycle::makeState(QState *parentState, const QString &animationFileNa topLevel->setInitialState(frameState); } else { connectByAnimation(previousState, frameState, - new QSignalTransition(m_machine, SIGNAL(animationsFinished()))); + new QSignalTransition(previousState, SIGNAL(polished()))); } previousState = frameState; } // Loop connectByAnimation(previousState, topLevel->initialState(), - new QSignalTransition(m_machine, SIGNAL(animationsFinished()))); + new QSignalTransition(previousState, SIGNAL(polished()))); return topLevel; diff --git a/src/corelib/statemachine/qstate.cpp b/src/corelib/statemachine/qstate.cpp index 173cc8d..2d59ab8 100644 --- a/src/corelib/statemachine/qstate.cpp +++ b/src/corelib/statemachine/qstate.cpp @@ -139,6 +139,12 @@ void QStatePrivate::emitFinished() emit q->finished(); } +void QStatePrivate::emitPolished() +{ + Q_Q(QState); + emit q->polished(); +} + /*! Constructs a new state with the given \a parent state. */ @@ -222,6 +228,8 @@ QList QStatePrivate::transitions() const /*! Instructs this state to set the property with the given \a name of the given \a object to the given \a value when the state is entered. + + \sa polished() */ void QState::assignProperty(QObject *object, const char *name, const QVariant &value) @@ -444,4 +452,12 @@ bool QState::event(QEvent *e) This signal is emitted when a final child state of this state is entered. */ +/*! + \fn QState::polished() + + This signal is emitted when all properties have been assigned their final value. + + \sa QState::assignProperty(), QAbstractTransition::addAnimation() +*/ + QT_END_NAMESPACE diff --git a/src/corelib/statemachine/qstate_p.h b/src/corelib/statemachine/qstate_p.h index 0c8c858..1f913b4 100644 --- a/src/corelib/statemachine/qstate_p.h +++ b/src/corelib/statemachine/qstate_p.h @@ -63,6 +63,8 @@ QT_BEGIN_NAMESPACE struct QPropertyAssignment { + QPropertyAssignment() + : object(0) {} QPropertyAssignment(QObject *o, const QByteArray &n, const QVariant &v, bool es = true) : object(o), propertyName(n), value(v), explicitlySet(es) @@ -92,6 +94,7 @@ public: QList transitions() const; void emitFinished(); + void emitPolished(); QAbstractState *errorState; QAbstractState *initialState; diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 2f8a19e..9060f0e 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -375,13 +375,13 @@ void QStateMachinePrivate::microstep(const QList &enabledT qDebug() << q_func() << ": begin microstep( enabledTransitions:" << enabledTransitions << ")"; qDebug() << q_func() << ": configuration before exiting states:" << configuration; #endif - exitStates(enabledTransitions); + QList exitedStates = exitStates(enabledTransitions); #ifdef QSTATEMACHINE_DEBUG qDebug() << q_func() << ": configuration after exiting states:" << configuration; #endif executeTransitionContent(enabledTransitions); QList enteredStates = enterStates(enabledTransitions); - applyProperties(enabledTransitions, enteredStates); + applyProperties(enabledTransitions, exitedStates, enteredStates); #ifdef QSTATEMACHINE_DEBUG qDebug() << q_func() << ": configuration after entering states:" << configuration; qDebug() << q_func() << ": end microstep"; @@ -632,39 +632,15 @@ void QStateMachinePrivate::addStatesToEnter(QAbstractState *s, QState *root, } void QStateMachinePrivate::applyProperties(const QList &transitionList, + const QList &exitedStates, const QList &enteredStates) { -#ifndef QT_NO_ANIMATION Q_Q(QStateMachine); - // Gracefully terminate playing animations. - for (int i = 0; i < playingAnimations.size(); ++i) - playingAnimations.at(i)->stop(); - playingAnimations.clear(); - for (int i = 0; i < resetEndValues.size(); ++i) - qobject_cast(resetEndValues.at(i))->setEndValue(QVariant()); // ### generalize - resetEndValues.clear(); - - // Find the animations to use for the state change. - QList selectedAnimations; - if (animationsEnabled) { - for (int i = 0; i < transitionList.size(); ++i) { - QAbstractTransition *transition = transitionList.at(i); - - selectedAnimations << transition->animations(); - selectedAnimations << defaultAnimationsForSource.values(transition->sourceState()); - - QList targetStates = transition->targetStates(); - for (int j=0; j propertyAssignments; + // Process the property assignments of the entered states. + QHash > propertyAssignmentsForState; QHash pendingRestorables = registeredRestorables; for (int i = 0; i < enteredStates.size(); ++i) { QState *s = qobject_cast(enteredStates.at(i)); @@ -678,53 +654,114 @@ void QStateMachinePrivate::applyProperties(const QList &tr registerRestorable(assn.object, assn.propertyName); } pendingRestorables.remove(RestorableId(assn.object, assn.propertyName)); - propertyAssignments.append(assn); + propertyAssignmentsForState[s].append(assn); } } - propertyAssignments << restorablesToPropertyList(pendingRestorables); + if (!pendingRestorables.isEmpty()) { + QAbstractState *s; + if (!enteredStates.isEmpty()) + s = enteredStates.last(); // ### handle if parallel + else + s = 0; + propertyAssignmentsForState[s] << restorablesToPropertyList(pendingRestorables); + } #ifndef QT_NO_ANIMATION - // Set the animated properties that did not finish animating and that are not - // set in the new state. - for (int i = 0; i < propertiesForAnimations.size(); ++i) { - QPropertyAssignment assn = propertiesForAnimations.at(i).second; - bool found = false; - for (int j = 0; j < propertyAssignments.size(); ++j) { - if ((propertyAssignments.at(j).object == assn.object) - && (propertyAssignments.at(j).propertyName == assn.propertyName)) { - found = true; - break; + // Gracefully terminate playing animations for states that are exited. + for (int i = 0; i < exitedStates.size(); ++i) { + QAbstractState *s = exitedStates.at(i); + QList animations = animationsForState.take(s); + for (int j = 0; j < animations.size(); ++j) { + QAbstractAnimation *anim = animations.at(j); + QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished())); + stateForAnimation.remove(anim); + if (resetAnimationEndValues.contains(anim)) { + qobject_cast(anim)->setEndValue(QVariant()); // ### generalize + resetAnimationEndValues.remove(anim); + } + QPropertyAssignment assn = propertyForAnimation.take(anim); + Q_ASSERT(assn.object != 0); + // If there is no property assignment that sets this property, + // set the property to its target value. + bool found = false; + QHash >::const_iterator it; + for (it = propertyAssignmentsForState.constBegin(); it != propertyAssignmentsForState.constEnd(); ++it) { + const QList &assignments = it.value(); + for (int k = 0; k < assignments.size(); ++k) { + if ((assignments.at(k).object == assn.object) + && (assignments.at(k).propertyName == assn.propertyName)) { + found = true; + break; + } + } + } + if (!found) { + assn.object->setProperty(assn.propertyName, assn.value); } + // Stop the (top-level) animation. + // ### Stopping nested animation has weird behavior. + while (QAnimationGroup *group = anim->group()) + anim = group; + anim->stop(); } - if (!found) { - assn.object->setProperty(assn.propertyName, assn.value); + } + + // Find the animations to use for the state change. + QList selectedAnimations; + if (animationsEnabled) { + for (int i = 0; i < transitionList.size(); ++i) { + QAbstractTransition *transition = transitionList.at(i); + + selectedAnimations << transition->animations(); + selectedAnimations << defaultAnimationsForSource.values(transition->sourceState()); + + QList targetStates = transition->targetStates(); + for (int j=0; j::iterator it; - for (it = propertyAssignments.begin(); it != propertyAssignments.end(); ) { - QPair, QList > ret; - ret = initializeAnimation(anim, *it); - QList handlers = ret.first; - if (!handlers.isEmpty()) { - for (int j = 0; j < handlers.size(); ++j) - propertiesForAnimations.append(qMakePair(handlers.at(j), *it)); - it = propertyAssignments.erase(it); - } else { - ++it; + QHash >::iterator it; + for (it = propertyAssignmentsForState.begin(); it != propertyAssignmentsForState.end(); ) { + QList::iterator it2; + QAbstractState *s = it.key(); + QList &assignments = it.value(); + for (it2 = assignments.begin(); it2 != assignments.end(); ) { + QPair, QList > ret; + ret = initializeAnimation(anim, *it2); + QList handlers = ret.first; + if (!handlers.isEmpty()) { + for (int j = 0; j < handlers.size(); ++j) { + QAbstractAnimation *a = handlers.at(j); + propertyForAnimation.insert(a, *it2); + stateForAnimation.insert(a, s); + animationsForState[s].append(a); + // ### connect to just the top-level animation? + QObject::disconnect(a, SIGNAL(finished()), q, SLOT(_q_animationFinished())); + QObject::connect(a, SIGNAL(finished()), q, SLOT(_q_animationFinished())); + } + it2 = assignments.erase(it2); + } else { + ++it2; + } + for (int j = 0; j < ret.second.size(); ++j) + resetAnimationEndValues.insert(ret.second.at(j)); } - resetEndValues << ret.second; + if (assignments.isEmpty()) + it = propertyAssignmentsForState.erase(it); + else + ++it; } - // We require that at least one animation is valid. // ### generalize QList variantAnims = qFindChildren(anim); if (QVariantAnimation *va = qobject_cast(anim)) variantAnims.append(va); + bool hasValidEndValue = false; for (int j = 0; j < variantAnims.size(); ++j) { if (variantAnims.at(j)->endValue().isValid()) { @@ -734,18 +771,28 @@ void QStateMachinePrivate::applyProperties(const QList &tr } if (hasValidEndValue) { - QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished())); - QObject::connect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished())); anim->start(); - playingAnimations.append(anim); } } #endif // !QT_NO_ANIMATION // Immediately set the properties that are not animated. - for (int i = 0; i < propertyAssignments.size(); ++i) { - const QPropertyAssignment &assn = propertyAssignments.at(i); - assn.object->setProperty(assn.propertyName, assn.value); + { + QHash >::const_iterator it; + for (it = propertyAssignmentsForState.constBegin(); it != propertyAssignmentsForState.constEnd(); ++it) { + const QList &assignments = it.value(); + for (int i = 0; i < assignments.size(); ++i) { + const QPropertyAssignment &assn = assignments.at(i); + assn.object->setProperty(assn.propertyName, assn.value); + } + } + } + + // Emit polished signal for entered states that have no animated properties. + for (int i = 0; i < enteredStates.size(); ++i) { + QState *s = qobject_cast(enteredStates.at(i)); + if (s && !animationsForState.contains(s)) + QStatePrivate::get(s)->emitPolished(); } } @@ -965,37 +1012,35 @@ QStateMachinePrivate::initializeAnimation(QAbstractAnimation *abstractAnimation, return qMakePair(handledAnimations, localResetEndValues); } -static bool isAncestorOf(QObject *anc, QObject *o) -{ - for (o = o->parent() ; o != 0; o = o->parent()) { - if (o == anc) - return true; - } - return false; -} - void QStateMachinePrivate::_q_animationFinished() { Q_Q(QStateMachine); - QAbstractAnimation *animation = qobject_cast(q->sender()); - Q_ASSERT(animation != 0); - QList >::iterator it; - for (it = propertiesForAnimations.begin(); it != propertiesForAnimations.end(); ) { - QAbstractAnimation *a = (*it).first; - if (a == animation || isAncestorOf(animation, a)) { - QPropertyAssignment assn = (*it).second; - assn.object->setProperty(assn.propertyName, assn.value); - if (!assn.explicitlySet) - unregisterRestorable(assn.object, assn.propertyName); - it = propertiesForAnimations.erase(it); - } else { - ++it; - } + QAbstractAnimation *anim = qobject_cast(q->sender()); + Q_ASSERT(anim != 0); + QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished())); + if (resetAnimationEndValues.contains(anim)) { + qobject_cast(anim)->setEndValue(QVariant()); // ### generalize + resetAnimationEndValues.remove(anim); } - playingAnimations.removeOne(animation); - if (playingAnimations.isEmpty()) - emit q->animationsFinished(); + // Set the final property value. + QPropertyAssignment assn = propertyForAnimation.take(anim); + Q_ASSERT(assn.object != 0); + assn.object->setProperty(assn.propertyName, assn.value); + if (!assn.explicitlySet) + unregisterRestorable(assn.object, assn.propertyName); + + QAbstractState *state = stateForAnimation.take(anim); + Q_ASSERT(state != 0); + QHash >::iterator it; + it = animationsForState.find(state); + Q_ASSERT(it != animationsForState.end()); + QList &animations = it.value(); + animations.removeOne(anim); + if (animations.isEmpty()) { + animationsForState.erase(it); + QStatePrivate::get(qobject_cast(state))->emitPolished(); + } } #endif // !QT_NO_ANIMATION @@ -1056,7 +1101,8 @@ void QStateMachinePrivate::_q_start() transitions.append(initialTransition); executeTransitionContent(transitions); enterStates(transitions); - applyProperties(transitions, QList() << initial); + applyProperties(transitions, QList() << start, + QList() << initial); delete start; #ifdef QSTATEMACHINE_DEBUG @@ -1774,18 +1820,6 @@ QSet QStateMachine::configuration() const \sa QStateMachine::stop() */ -#ifndef QT_NO_ANIMATION - -/*! - \fn QStateMachine::animationsFinished() - - This signal is emitted when the state machine has finished playing all - animations associated with the latest transition (i.e., all properties have - reached their target values). -*/ - -#endif - /*! \reimp */ diff --git a/src/corelib/statemachine/qstatemachine.h b/src/corelib/statemachine/qstatemachine.h index 6e504d0..10bd443 100644 --- a/src/corelib/statemachine/qstatemachine.h +++ b/src/corelib/statemachine/qstatemachine.h @@ -136,10 +136,6 @@ Q_SIGNALS: void stopped(); void finished(); -#ifndef QT_NO_ANIMATION - void animationsFinished(); -#endif - protected: void postInternalEvent(QEvent *event); diff --git a/src/corelib/statemachine/qstatemachine_p.h b/src/corelib/statemachine/qstatemachine_p.h index 139fee8..b3707ea 100644 --- a/src/corelib/statemachine/qstatemachine_p.h +++ b/src/corelib/statemachine/qstatemachine_p.h @@ -131,6 +131,7 @@ public: QSet &statesForDefaultEntry); void applyProperties(const QList &transitionList, + const QList &exitedStates, const QList &enteredStates); bool isInFinalState(QAbstractState *s) const; @@ -186,9 +187,10 @@ public: initializeAnimation(QAbstractAnimation *abstractAnimation, const QPropertyAssignment &prop); - QList > propertiesForAnimations; - QList playingAnimations; - QList resetEndValues; + QHash > animationsForState; + QHash propertyForAnimation; + QHash stateForAnimation; + QSet resetAnimationEndValues; QList defaultAnimations; QMultiHash defaultAnimationsForSource; diff --git a/tests/auto/qstatemachine/tst_qstatemachine.cpp b/tests/auto/qstatemachine/tst_qstatemachine.cpp index 3acde49..7f0d39b 100644 --- a/tests/auto/qstatemachine/tst_qstatemachine.cpp +++ b/tests/auto/qstatemachine/tst_qstatemachine.cpp @@ -95,6 +95,8 @@ private slots: void rootState(); void addAndRemoveState(); void stateEntryAndExit(); + void assignProperty(); + void assignPropertyWithAnimation(); void postEvent(); void stateFinished(); void parallelStates(); @@ -1186,6 +1188,155 @@ void tst_QStateMachine::stateEntryAndExit() } } +void tst_QStateMachine::assignProperty() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(s1, "objectName", "s1"); + QFinalState *s2 = new QFinalState(machine.rootState()); + s1->addTransition(s2); + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(s1->objectName(), QString::fromLatin1("s1")); + + s1->assignProperty(s1, "objectName", "foo"); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(s1->objectName(), QString::fromLatin1("foo")); + + s1->assignProperty(s1, "noSuchProperty", 123); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(s1->objectName(), QString::fromLatin1("foo")); + QCOMPARE(s1->dynamicPropertyNames().size(), 1); + QCOMPARE(s1->dynamicPropertyNames().at(0), QByteArray("noSuchProperty")); + + QSignalSpy polishedSpy(s1, SIGNAL(polished())); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(polishedSpy.count(), 1); +} + +void tst_QStateMachine::assignPropertyWithAnimation() +{ + // Single animation + { + QStateMachine machine; + QObject obj; + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(250); + trans->addAnimation(&anim); + QFinalState *s3 = new QFinalState(machine.rootState()); + s2->addTransition(s2, SIGNAL(polished()), 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; + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(machine.rootState()); + 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.rootState()); + s2->addTransition(s2, SIGNAL(polished()), 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; + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(&obj, "foo", 123); + s1->assignProperty(&obj, "bar", 321); + QState *s2 = new QState(machine.rootState()); + 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.rootState()); + s2->addTransition(s2, SIGNAL(polished()), 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; + QState *s1 = new QState(machine.rootState()); + s1->setObjectName("s1"); + s1->assignProperty(&obj, "foo", 123); + s1->assignProperty(&obj, "bar", 456); + QState *s2 = new QState(machine.rootState()); + 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); + + 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(polished()), s22); + + QFinalState *s3 = new QFinalState(machine.rootState()); + s22->addTransition(s2, SIGNAL(polished()), 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); + } +} + struct StringEvent : public QEvent { public: @@ -2113,7 +2264,7 @@ void tst_QStateMachine::twoAnimations() QState *s3 = new QState(machine.rootState()); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); - s2->addTransition(&machine, SIGNAL(animationsFinished()), s3); + s2->addTransition(s2, SIGNAL(polished()), s3); machine.setInitialState(s1); machine.start(); @@ -2255,7 +2406,7 @@ void tst_QStateMachine::nestedTargetStateForAnimation() QState *s3 = new QState(machine.rootState()); QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); - s2->addTransition(&machine, SIGNAL(animationsFinished()), s3); + s2->addTransition(s2, SIGNAL(polished()), s3); machine.setInitialState(s1); machine.start(); -- cgit v0.12 From bfc92cacbeeb2d1a5ca39e31ccdb94f9674bd547 Mon Sep 17 00:00:00 2001 From: Thierry Bastian Date: Wed, 29 Apr 2009 18:47:46 +0200 Subject: Small refactor of QVariantAnimation::updateCurrentValue we only test the inequality of the new value compared to the previous one in case we have something conected to currentValueChanged signal. The comparison is quite heavy in QVariant. So avoiding it a good thing. --- src/corelib/animation/qvariantanimation.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/corelib/animation/qvariantanimation.cpp b/src/corelib/animation/qvariantanimation.cpp index 9f8cbf0..bb6cf1c 100644 --- a/src/corelib/animation/qvariantanimation.cpp +++ b/src/corelib/animation/qvariantanimation.cpp @@ -175,26 +175,16 @@ void QVariantAnimationPrivate::updateCurrentValue() endProgress = currentInterval.end.first; const qreal localProgress = (progress - startProgress) / (endProgress - startProgress); - bool changed = false; - { - //we do that here in a limited scope so that ret is dereferenced and frees memory - //and the call to updateCurrentValue can recreate QVariant of the same type (for ex. in - //QGraphicsItem::setPos - QVariant ret = q->interpolated(currentInterval.start.second, - currentInterval.end.second, - localProgress); - if (currentValue != ret) { - changed = true; - qSwap(currentValue, ret); - } - } - - if (changed) { - //the value has changed - q->updateCurrentValue(currentValue); + QVariant ret = q->interpolated(currentInterval.start.second, + currentInterval.end.second, + localProgress); + qSwap(currentValue, ret); + q->updateCurrentValue(currentValue); #ifndef QT_EXPERIMENTAL_SOLUTION if (connectedSignals & changedSignalMask) #endif + if (currentValue != ret) { + //the value has changed emit q->valueChanged(currentValue); } } -- cgit v0.12