diff options
Diffstat (limited to 'doc/src/statemachine.qdoc')
-rw-r--r-- | doc/src/statemachine.qdoc | 412 |
1 files changed, 375 insertions, 37 deletions
diff --git a/doc/src/statemachine.qdoc b/doc/src/statemachine.qdoc index c79839f..27bd4f8 100644 --- a/doc/src/statemachine.qdoc +++ b/doc/src/statemachine.qdoc @@ -1,11 +1,41 @@ /**************************************************************************** ** -** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** -** This file is part of the $MODULE$ of the Qt Toolkit. +** This file is part of the documentation of the Qt Toolkit. ** -** $TROLLTECH_DUAL_LICENSE$ +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ ** ****************************************************************************/ @@ -13,6 +43,7 @@ \page statemachine-api.html \title The State Machine Framework \brief An overview of the State Machine framework for constructing and executing state graphs. + \ingroup architecture \tableofcontents @@ -33,10 +64,10 @@ The State Machine framework provides an API and execution model that can be used to effectively embed the elements and semantics of statecharts in Qt - applications. The framework integrates tightly with Qt's existing event - system and meta-object system; for example, transitions between states can - be triggered by signals, and states can be configured to set properties and - invoke methods on QObjects. + applications. The framework integrates tightly with Qt's meta-object system; + for example, transitions between states can be triggered by signals, and + states can be configured to set properties and invoke methods on QObjects. + Qt's event system is used to drive the state machines. \section1 A Simple State Machine @@ -52,60 +83,79 @@ \endomit The following snippet shows the code needed to create such a state machine. + First, we create the state machine and states: \code QStateMachine machine; QState *s1 = new QState(); QState *s2 = new QState(); QState *s3 = new QState(); + \endcode + + Then, we create the transitions by using the QState::addTransition() + function: + \code s1->addTransition(button, SIGNAL(clicked()), s2); s2->addTransition(button, SIGNAL(clicked()), s3); s3->addTransition(button, SIGNAL(clicked()), s1); + \endcode + + Next, we add the states to the machine and set the machine's initial state: + \code machine.addState(s1); machine.addState(s2); machine.addState(s3); machine.setInitialState(s1); + \endcode + Finally, we start the state machine: + + \code machine.start(); \endcode - Once the state machine has been set up, you need to start it by calling - QStateMachine::start(). The state machine executes asynchronously, i.e. it - becomes part of your application's event loop. + The state machine executes asynchronously, i.e. it becomes part of your + application's event loop. + + \section1 Doing Useful Work on State Entry and Exit - The above state machine is perfectly fine, but it doesn't \e do anything; it - merely transitions from one state to another. The - QState::setPropertyOnEntry() function can be used to have a state set a - property of a QObject when the state is entered. In the following snippet, - the value that should be assigned to a QLabel's text property is specified - for each state: + The above state machine merely transitions from one state to another, it + doesn't perform any operations. The QState::assignProperty() function can be + used to have a state set a property of a QObject when the state is + entered. In the following snippet, the value that should be assigned to a + QLabel's text property is specified for each state: \code - s1->setPropertyOnEntry(label, "text", "In state s1"); - s2->setPropertyOnEntry(label, "text", "In state s2"); - s3->setPropertyOnEntry(label, "text", "In state s3"); + s1->assignProperty(label, "text", "In state s1"); + s2->assignProperty(label, "text", "In state s2"); + s3->assignProperty(label, "text", "In state s3"); \endcode When any of the states is entered, the label's text will be changed accordingly. - The QState::invokeMethodOnEntry() function can be used to have a state - invoke a method (a slot) of a QObject when the state is entered. In the + The QState::entered() signal is emitted when the state is entered, and the + QState::exited() signal is emitted when the state is exited. In the following snippet, the button's showMaximized() slot will be called when - state \c s3 is entered: + state \c s3 is entered, and the button's showMinimized() slot will be called + when \c s3 is exited: \code - s2->invokeMethodOnEntry(button, "showMaximized"); + QObject::connect(s3, SIGNAL(entered()), button, SLOT(showMaximized())); + QObject::connect(s3, SIGNAL(exited()), button, SLOT(showMinimized())); \endcode - \section1 Sharing Transitions By Grouping States + \section1 State Machines That Finish The state machine defined in the previous section never finishes. In order for a state machine to be able to finish, it needs to have a top-level \e - final state. When the state machine enters a top-level final state, the - machine will emit the finished() signal and halt. + final state (QFinalState object). When the state machine enters a top-level + final state, the machine will emit the QStateMachine::finished() signal and + halt. + + \section1 Sharing Transitions By Grouping States Assume we wanted the user to be able to quit the application at any time by clicking a Quit button. In order to achieve this, we need to create a final @@ -167,6 +217,9 @@ s12>addTransition(quitButton, SIGNAL(clicked()), s12); \endcode + A transition can have any state as its target, i.e. the target state does + not have to be on the same level in the state hierarchy as the source state. + \section1 Using History States to Save and Restore the Current State Imagine that we wanted to add an "interrupt" mechanism to the example @@ -204,12 +257,12 @@ QHistoryState *s1h = s1->addHistoryState(); QState *s3 = new QState(); - s3->setPropertyOnEntry(label, "text", "In s3"); + s3->assignProperty(label, "text", "In s3"); QMessageBox mbox; mbox.addButton(QMessageBox::Ok); mbox.setText("Interrupted!"); mbox.setIcon(QMessageBox::Information); - s3->invokeMethodOnEntry(&mbox, "exec"); + QObject::connect(s3, SIGNAL(entered()), &mbox, SLOT(exec())); s3->addTransition(s1h); machine.addState(s3); @@ -243,21 +296,26 @@ \caption This is a caption \endomit - To create a parallel state group, pass QState::ParallelStateGroup to the - QState constructor. + To create a parallel state group, pass QState::ParallelStates to the QState + constructor. \code - QState *s1 = new QState(QState::ParallelStateGroup); + QState *s1 = new QState(QState::ParallelStates); // s11 and s12 will be entered in parallel QState *s11 = new QState(s1); QState *s12 = new QState(s1); \endcode + When a parallel state group is entered, all its child states will be + simultaneously entered. Transitions within the individual child states + operate normally. However, any of the child states may take a transition + outside the parent state. When this happens, the parent state and all of its + child states are exited. + \section1 Detecting that a Composite State has Finished - A child state can be final; when a final child state is entered, a - QStateFinishedEvent is generated for the parent state. You can use the - QStateFinishedTransition class to trigger a transition based on this event. + A child state can be final (a QFinalState object); when a final child state + is entered, the parent state emits the QState::finished() signal. \img statemachine-finished.png \omit @@ -266,7 +324,287 @@ This is useful when you want to hide the internal details of a state; i.e. the only thing the outside world should be able to do is enter the - state, and get a notification when the state has finished (i.e. when a final - child state has been entered). + state, and get a notification when the state has completed its work. + + For parallel state groups, the QState::finished() signal is emitted when \e + all the child states have entered final states. + + \section1 Events, Transitions and Guards + + A QStateMachine runs its own event loop. For signal transitions + (QSignalTransition objects), QStateMachine automatically posts a + QSignalEvent to itself when it intercepts the corresponding signal; + similarly, for QObject event transitions (QEventTransition objects) a + QWrappedEvent is posted. + + You can post your own events to the state machine using + QStateMachine::postEvent(). + + When posting a custom event to the state machine, you typically also have + one or more custom transitions that can be triggered from events of that + type. To create such a transition, you subclass QAbstractTransition and + reimplement QAbstractTransition::eventTest(), where you check if an event + matches your event type (and optionally other criteria, e.g. attributes of + the event object). + + Here we define our own custom event type, \c StringEvent, for posting + strings to the state machine: + + \code + struct StringEvent : public QEvent + { + StringEvent(const QString &val) + : QEvent(QEvent::Type(QEvent::User+1)), + value(val) {} + + QString value; + }; + \endcode + + Next, we define a transition that only triggers when the event's string + matches a particular string (a \e guarded transition): + + \code + class StringTransition : public QAbstractTransition + { + public: + StringTransition(const QString &value) + : m_value(value) {} + + protected: + virtual bool eventTest(QEvent *e) const + { + if (e->type() != QEvent::Type(QEvent::User+1)) // StringEvent + return false; + StringEvent *se = static_cast<StringEvent*>(e); + return (m_value == se->value); + } + + virtual void onTransition(QEvent *) {} + + private: + QString m_value; + }; + \endcode + + In the eventTest() reimplementation, we first check if the event type is the + desired one; if so, we cast the event to a StringEvent and perform the + string comparison. + + The following is a statechart that uses the custom event and transition: + + \img statemachine-customevents.png + \omit + \caption This is a caption + \endomit + + Here's what the implementation of the statechart looks like: + + \code + QStateMachine machine; + QState *s1 = new QState(); + QState *s2 = new QState(); + QFinalState *done = new QFinalState(); - */ + StringTransition *t1 = new StringTransition("Hello"); + t1->setTargetState(s2); + s1->addTransition(t1); + StringTransition *t2 = new StringTransition("world"); + t2->setTargetState(done); + s2->addTransition(t2); + + machine.addState(s1); + machine.addState(s2); + machine.addState(done); + machine.setInitialState(s1); + \endcode + + Once the machine is started, we can post events to it. + + \code + machine.postEvent(new StringEvent("Hello")); + machine.postEvent(new StringEvent("world")); + \endcode + + \section1 Using Restore Policy To Automatically Restore Properties + + In some state machines it can be useful to focus the attention on assigning properties in states, + not on restoring them when the state is no longer active. If you know that a property should + always be restored to its initial value when the machine enters a state that does not explicitly + give the property a value, you can set the global restore policy to + QStateMachine::RestoreProperties. + + \code + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + \endcode + + When this restore policy is set, the machine will automatically restore all properties. If it + enters a state where a given property is not set, it will first search the hierarchy of ancestors + to see if the property is defined there. If it is, the property will be restored to the value + defined by the closest ancestor. If not, it will be restored to its initial value (i.e. the + value of the property before any property assignments in states were executed.) + + Take the following code: + \code + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QState *s1 = new QState(); + s1->assignProperty(object, "fooBar", 1.0); + machine.addState(s1); + machine.setInitialState(s1); + + QState *s2 = new QState(); + machine.addState(s2); + \endcode + + Lets say the property \c fooBar is 0.0 when the machine starts. When the machine is in state + \c s1, the property will be 1.0, since the state explicitly assigns this value to it. When the + machine is in state \c s2, no value is explicitly defined for the property, so it will implicitly + be restored to 0.0. + + If we are using nested states, the parent defines a value for the property which is inherited by + all descendants that do not explicitly assign a value to the property. + \code + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QState *s1 = new QState(); + s1->assignProperty(object, "fooBar", 1.0); + machine.addState(s1); + machine.setInitialState(s1); + + QState *s2 = new QState(s1); + s2->assignProperty(object, "fooBar", 2.0); + s1->setInitialState(s2); + + QState *s3 = new QState(s1); + \endcode + + Here \c s1 has two children: \c s2 and \c s3. When \c s2 is entered, the property \c fooBar + will have the value 2.0, since this is explicitly defined for the state. When the machine is in + state \c s3, no value is defined for the state, but \c s1 defines the property to be 1.0, so this + is the value that will be assigned to \c fooBar. + + \section1 Animating Property Assignments + + The State Machine API connects with the Animation API in Qt to allow automatically animating + properties as they are assigned in states. + + Say we have the following code: + \code + QState *s1 = new QState(); + QState *s2 = new QState(); + + s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50)); + s2->assignProperty(button, "geometry", QRectF(0, 0, 100, 100)); + + s1->addTransition(button, SIGNAL(clicked()), s2); + \endcode + + Here we define two states of a user interface. In \c s1 the \c button is small, and in \c s2 + it is bigger. If we click the button to transition from \c s1 to \c s2, the geometry of the button + will be set immediately when a given state has been entered. If we want the transition to be + smooth, however, all we need to do is make a QPropertyAnimation and add this to the transition + object. + + \code + QState *s1 = new QState(); + QState *s2 = new QState(); + + s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50)); + s2->assignProperty(button, "geometry", QRectF(0, 0, 100, 100)); + + QSignalTransition *transition = s1->addTransition(button, SIGNAL(clicked()), s2); + transition->addAnimation(new QPropertyAnimation(button, "geometry")); + \endcode + + Adding an animation for the property in question means that the property assignment will no + longer take immediate effect when the state has been entered. Instead, the animation will start + playing when the state has been entered and smoothly animate the property assignment. Since we + do not set the start value or end value of the animation, these will be set implicitly. The + start value of the animation will be the property's current value when the animation starts, and + the end value will be set based on the property assignments defined for the state. + + If the global restore policy of the state machine is set to QStateMachine::RestoreProperties, + it is possible to also add animations for the property restorations. + + \section1 Detecting That All Properties Have Been Set In A State + + When animations are used to assign properties, a state no longer defines the exact values that a + property will have when the machine is in the given state. While the animation is running, the + property can potentially have any value, depending on the animation. + + In some cases, it can be useful to be able to detect when the property has actually been assigned + the value defined by a state. For this, we can use the state's polished() signal. + \code + QState *s1 = new QState(); + s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50)); + + QState *s2 = new QState(); + + s1->addTransition(s1, SIGNAL(polished()), s2); + \endcode + + The machine will be in state \c s1 until the \c geometry property has been set. Then it will + immediately transition into \c s2. If the transition into \c s1 has an animation for the \c + geometry property, then the machine will stay in \c s1 until the animation has finished. If there + is no animation, it will simply set the property and immediately enter state \c s2. + + Either way, when the machine is in state \c s2, the property \c geometry has been assigned the + defined value. + + If the global restore policy is set to QStateMachine::RestoreProperties, the state will not emit + the polished() signal until these have been executed as well. + + \section1 What happens if a state is exited before the animation has finished + + If a state has property assignments, and the transition into the state has animations for the + properties, the state can potentially be exited before the properties have been assigned to the + values defines by the state. This is true in particular when there are transitions out from the + state that do not depend on the state being polished, as described in the previous section. + + The State Machine API guarantees that a property assigned by the state machine either: + \list + \o Has a value explicitly assigned to the property. + \o Is currently being animated into a value explicitly assigned to the property. + \endlist + + When a state is exited prior to the animation finishing, the behavior of the state machine depends + on the target state of the transition. If the target state explicitly assigns a value to the + property, no additional action will be taken. The property will be assigned the value defined by + the target state. + + If the target state does not assign any value to the property, there are two + options: By default, the property will be assigned the value defined by the state it is leaving + (the value it would have been assigned if the animation had been permitted to finish playing.) If + a global restore policy is set, however, this will take precedence, and the property will be + restored as usual. + + \section1 Default Animations + + As described earlier, you can add animations to transitions to make sure property assignments + in the target state are animated. If you want a specific animation to be used for a given property + regardless of which transition is taken, you can add it as a default animation to the state + machine. This is in particular useful when the properties assigned (or restored) by specific + states is not known when the machine is constructed. + + \code + QState *s1 = new QState(); + QState *s2 = new QState(); + + s2->assignProperty(object, "fooBar", 2.0); + s1->addTransition(s2); + + QStateMachine machine; + machine.setInitialState(s1); + machine.addDefaultAnimation(new QPropertyAnimation(object, "fooBar")); + \endcode + + When the machine is in state \c s2, the machine will play the default animation for the + property \c fooBar since this property is assigned by \c s2. + + Note that animations explicitly set on transitions will take precedence over any default + animation for the given property. +*/ |