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