diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2020-08-24 23:12:38 +0200 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2020-08-28 15:54:55 +0200 |
commit | c26817f0dc53af2253a683b48738c32cb1511e85 (patch) | |
tree | 75d14cd85da83579a7b2ffec67f6855a6e30ae13 | |
parent | d2372c0950bfa0485949307a68da2fb8a4e9808d (diff) |
Move state machine source into QtScxml
Task-number: QTBUG-80316
Change-Id: I9465d1b635733adca9dc8f6abad9b4f01f09d942
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
49 files changed, 8748 insertions, 15 deletions
diff --git a/configure.json b/configure.json index 9f9d32f..5d00755 100644 --- a/configure.json +++ b/configure.json @@ -1,5 +1,6 @@ { "subconfigs": [ - "src/scxml" + "src/scxml", + "src/statemachine" ] } diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 7c09ea9..298ef1a 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -1,3 +1,2 @@ TEMPLATE = subdirs -SUBDIRS = scxmlstatemachine -qtConfig(statemachine):SUBDIRS += statemachine +SUBDIRS = scxmlstatemachine statemachine diff --git a/src/imports/statemachine/finalstate.h b/src/imports/statemachine/finalstate.h index fef0e9c..3b7c451 100644 --- a/src/imports/statemachine/finalstate.h +++ b/src/imports/statemachine/finalstate.h @@ -43,7 +43,7 @@ #include "childrenprivate.h" #include "statemachine.h" -#include <QtCore/QFinalState> +#include <QtStateMachine/QFinalState> #include <QtQml/QQmlListProperty> #include <QtQml/qqml.h> diff --git a/src/imports/statemachine/signaltransition.h b/src/imports/statemachine/signaltransition.h index 7cef80b..d1286eb 100644 --- a/src/imports/statemachine/signaltransition.h +++ b/src/imports/statemachine/signaltransition.h @@ -40,7 +40,7 @@ #ifndef SIGNALTRANSITION_H #define SIGNALTRANSITION_H -#include <QtCore/QSignalTransition> +#include <QtStateMachine/QSignalTransition> #include <QtCore/QVariant> #include <QtQml/QJSValue> diff --git a/src/imports/statemachine/state.h b/src/imports/statemachine/state.h index 8cb454f..e2e75bb 100644 --- a/src/imports/statemachine/state.h +++ b/src/imports/statemachine/state.h @@ -42,7 +42,7 @@ #include "childrenprivate.h" -#include <QtCore/QState> +#include <QtStateMachine/QState> #include <QtQml/QQmlParserStatus> #include <QtQml/QQmlListProperty> #include <QtQml/qqml.h> diff --git a/src/imports/statemachine/statemachine.h b/src/imports/statemachine/statemachine.h index 85ac4cf..1d5fd5d 100644 --- a/src/imports/statemachine/statemachine.h +++ b/src/imports/statemachine/statemachine.h @@ -42,7 +42,7 @@ #include "childrenprivate.h" -#include <QtCore/QStateMachine> +#include <QtStateMachine/QStateMachine> #include <QtQml/QQmlParserStatus> #include <QtQml/QQmlListProperty> #include <QtQml/qqml.h> diff --git a/src/imports/statemachine/statemachine.pro b/src/imports/statemachine/statemachine.pro index cff81c2..ed94f46 100644 --- a/src/imports/statemachine/statemachine.pro +++ b/src/imports/statemachine/statemachine.pro @@ -3,7 +3,7 @@ TARGET = qtqmlstatemachine TARGETPATH = QtQml/StateMachine QML_IMPORT_VERSION = $$QT_VERSION -QT = core-private qml-private +QT = statemachine core-private qml-private SOURCES = \ $$PWD/finalstate.cpp \ diff --git a/src/imports/statemachine/statemachineforeign.h b/src/imports/statemachine/statemachineforeign.h index 7543d55..8456ded 100644 --- a/src/imports/statemachine/statemachineforeign.h +++ b/src/imports/statemachine/statemachineforeign.h @@ -41,10 +41,10 @@ #define STATEMACHINEFOREIGN_H #include <QtQml/qqml.h> -#include <QtCore/qhistorystate.h> -#include <QtCore/qstate.h> -#include <QtCore/qabstractstate.h> -#include <QtCore/qsignaltransition.h> +#include <QtStateMachine/qhistorystate.h> +#include <QtStateMachine/qstate.h> +#include <QtStateMachine/qabstractstate.h> +#include <QtStateMachine/qsignaltransition.h> struct QHistoryStateForeign { diff --git a/src/imports/statemachine/timeouttransition.h b/src/imports/statemachine/timeouttransition.h index 3d056b5..6fa2dd9 100644 --- a/src/imports/statemachine/timeouttransition.h +++ b/src/imports/statemachine/timeouttransition.h @@ -40,7 +40,7 @@ #ifndef TIMEOUTTRANSITION_H #define TIMEOUTTRANSITION_H -#include <QtCore/QSignalTransition> +#include <QtStateMachine/QSignalTransition> #include <QtQml/QQmlParserStatus> #include <QtQml/qqml.h> diff --git a/src/src.pro b/src/src.pro index e1073fb..ec86c5c 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1,8 +1,8 @@ TEMPLATE = subdirs -SUBDIRS += scxml +SUBDIRS += scxml statemachine qtHaveModule(qml) { SUBDIRS += imports - imports.depends = scxml + imports.depends = scxml statemachine } diff --git a/src/statemachine/configure.json b/src/statemachine/configure.json new file mode 100644 index 0000000..3449aca --- /dev/null +++ b/src/statemachine/configure.json @@ -0,0 +1,18 @@ +{ + "module": "statemachine", + + "features": { + "statemachine": { + "label": "State machine", + "purpose": "Provides hierarchical finite state machines.", + "section": "Utilities", + "output": [ "publicFeature", "feature" ] + }, + + "qeventtransition": { + "label": "QEventTransition class", + "condition": "features.statemachine", + "output": [ "publicFeature" ] + } + } +} diff --git a/src/statemachine/gui/qbasickeyeventtransition.cpp b/src/statemachine/gui/qbasickeyeventtransition.cpp new file mode 100644 index 0000000..1291f8b --- /dev/null +++ b/src/statemachine/gui/qbasickeyeventtransition.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbasickeyeventtransition_p.h" + +#include <QtGui/qevent.h> + +#include <private/qabstracttransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QBasicKeyEventTransition + \since 4.6 + \ingroup statemachine + + \brief The QBasicKeyEventTransition class provides a transition for Qt key events. +*/ + +class QBasicKeyEventTransitionPrivate : public QAbstractTransitionPrivate +{ + Q_DECLARE_PUBLIC(QBasicKeyEventTransition) +public: + QBasicKeyEventTransitionPrivate(); + + static QBasicKeyEventTransitionPrivate *get(QBasicKeyEventTransition *q); + + QEvent::Type eventType; + int key; + Qt::KeyboardModifiers modifierMask; +}; + +QBasicKeyEventTransitionPrivate::QBasicKeyEventTransitionPrivate() +{ + eventType = QEvent::None; + key = 0; + modifierMask = Qt::NoModifier; +} + +QBasicKeyEventTransitionPrivate *QBasicKeyEventTransitionPrivate::get(QBasicKeyEventTransition *q) +{ + return q->d_func(); +} + +/*! + Constructs a new key event transition with the given \a sourceState. +*/ +QBasicKeyEventTransition::QBasicKeyEventTransition(QState *sourceState) + : QAbstractTransition(*new QBasicKeyEventTransitionPrivate, sourceState) +{ +} + +/*! + Constructs a new event transition for events of the given \a type for the + given \a key, with the given \a sourceState. +*/ +QBasicKeyEventTransition::QBasicKeyEventTransition(QEvent::Type type, int key, + QState *sourceState) + : QAbstractTransition(*new QBasicKeyEventTransitionPrivate, sourceState) +{ + Q_D(QBasicKeyEventTransition); + d->eventType = type; + d->key = key; +} + +/*! + Constructs a new event transition for events of the given \a type for the + given \a key, with the given \a modifierMask and \a sourceState. +*/ +QBasicKeyEventTransition::QBasicKeyEventTransition(QEvent::Type type, int key, + Qt::KeyboardModifiers modifierMask, + QState *sourceState) + : QAbstractTransition(*new QBasicKeyEventTransitionPrivate, sourceState) +{ + Q_D(QBasicKeyEventTransition); + d->eventType = type; + d->key = key; + d->modifierMask = modifierMask; +} + +/*! + Destroys this event transition. +*/ +QBasicKeyEventTransition::~QBasicKeyEventTransition() +{ +} + +/*! + Returns the event type that this key event transition is associated with. +*/ +QEvent::Type QBasicKeyEventTransition::eventType() const +{ + Q_D(const QBasicKeyEventTransition); + return d->eventType; +} + +/*! + Sets the event \a type that this key event transition is associated with. +*/ +void QBasicKeyEventTransition::setEventType(QEvent::Type type) +{ + Q_D(QBasicKeyEventTransition); + d->eventType = type; +} + +/*! + Returns the key that this key event transition checks for. +*/ +int QBasicKeyEventTransition::key() const +{ + Q_D(const QBasicKeyEventTransition); + return d->key; +} + +/*! + Sets the key that this key event transition will check for. +*/ +void QBasicKeyEventTransition::setKey(int key) +{ + Q_D(QBasicKeyEventTransition); + d->key = key; +} + +/*! + Returns the keyboard modifier mask that this key event transition checks + for. +*/ +Qt::KeyboardModifiers QBasicKeyEventTransition::modifierMask() const +{ + Q_D(const QBasicKeyEventTransition); + return d->modifierMask; +} + +/*! + Sets the keyboard modifier mask that this key event transition will check + for. +*/ +void QBasicKeyEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QBasicKeyEventTransition); + d->modifierMask = modifierMask; +} + +/*! + \reimp +*/ +bool QBasicKeyEventTransition::eventTest(QEvent *event) +{ + Q_D(const QBasicKeyEventTransition); + if (event->type() == d->eventType) { + QKeyEvent *ke = static_cast<QKeyEvent*>(event); + return (ke->key() == d->key) + && ((ke->modifiers() & d->modifierMask) == d->modifierMask); + } + return false; +} + +/*! + \reimp +*/ +void QBasicKeyEventTransition::onTransition(QEvent *) +{ +} + +QT_END_NAMESPACE + +#include "moc_qbasickeyeventtransition_p.cpp" diff --git a/src/statemachine/gui/qbasickeyeventtransition_p.h b/src/statemachine/gui/qbasickeyeventtransition_p.h new file mode 100644 index 0000000..0947404 --- /dev/null +++ b/src/statemachine/gui/qbasickeyeventtransition_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBASICKEYEVENTTRANSITION_P_H +#define QBASICKEYEVENTTRANSITION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qevent.h> +#include <QtStateMachine/qabstracttransition.h> + +QT_REQUIRE_CONFIG(qeventtransition); + +QT_BEGIN_NAMESPACE + +class QBasicKeyEventTransitionPrivate; +class Q_AUTOTEST_EXPORT QBasicKeyEventTransition : public QAbstractTransition +{ + Q_OBJECT +public: + QBasicKeyEventTransition(QState *sourceState = nullptr); + QBasicKeyEventTransition(QEvent::Type type, int key, QState *sourceState = nullptr); + QBasicKeyEventTransition(QEvent::Type type, int key, + Qt::KeyboardModifiers modifierMask, + QState *sourceState = nullptr); + ~QBasicKeyEventTransition(); + + QEvent::Type eventType() const; + void setEventType(QEvent::Type type); + + int key() const; + void setKey(int key); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + +protected: + bool eventTest(QEvent *event) override; + void onTransition(QEvent *) override; + +private: + Q_DISABLE_COPY_MOVE(QBasicKeyEventTransition) + Q_DECLARE_PRIVATE(QBasicKeyEventTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/gui/qbasicmouseeventtransition.cpp b/src/statemachine/gui/qbasicmouseeventtransition.cpp new file mode 100644 index 0000000..2f6940c --- /dev/null +++ b/src/statemachine/gui/qbasicmouseeventtransition.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbasicmouseeventtransition_p.h" + +#include <QtGui/qevent.h> +#include <QtGui/qpainterpath.h> + +#include <private/qabstracttransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QBasicMouseEventTransition + \since 4.6 + \ingroup statemachine + + \brief The QBasicMouseEventTransition class provides a transition for Qt mouse events. +*/ + +class QBasicMouseEventTransitionPrivate : public QAbstractTransitionPrivate +{ + Q_DECLARE_PUBLIC(QBasicMouseEventTransition) +public: + QBasicMouseEventTransitionPrivate(); + + static QBasicMouseEventTransitionPrivate *get(QBasicMouseEventTransition *q); + + QEvent::Type eventType; + Qt::MouseButton button; + Qt::KeyboardModifiers modifierMask; + QPainterPath path; +}; + +QBasicMouseEventTransitionPrivate::QBasicMouseEventTransitionPrivate() +{ + eventType = QEvent::None; + button = Qt::NoButton; +} + +QBasicMouseEventTransitionPrivate *QBasicMouseEventTransitionPrivate::get(QBasicMouseEventTransition *q) +{ + return q->d_func(); +} + +/*! + Constructs a new mouse event transition with the given \a sourceState. +*/ +QBasicMouseEventTransition::QBasicMouseEventTransition(QState *sourceState) + : QAbstractTransition(*new QBasicMouseEventTransitionPrivate, sourceState) +{ +} + +/*! + Constructs a new mouse event transition for events of the given \a type. +*/ +QBasicMouseEventTransition::QBasicMouseEventTransition(QEvent::Type type, + Qt::MouseButton button, + QState *sourceState) + : QAbstractTransition(*new QBasicMouseEventTransitionPrivate, sourceState) +{ + Q_D(QBasicMouseEventTransition); + d->eventType = type; + d->button = button; +} + +/*! + Destroys this mouse event transition. +*/ +QBasicMouseEventTransition::~QBasicMouseEventTransition() +{ +} + +/*! + Returns the event type that this mouse event transition is associated with. +*/ +QEvent::Type QBasicMouseEventTransition::eventType() const +{ + Q_D(const QBasicMouseEventTransition); + return d->eventType; +} + +/*! + Sets the event \a type that this mouse event transition is associated with. +*/ +void QBasicMouseEventTransition::setEventType(QEvent::Type type) +{ + Q_D(QBasicMouseEventTransition); + d->eventType = type; +} + +/*! + Returns the button that this mouse event transition checks for. +*/ +Qt::MouseButton QBasicMouseEventTransition::button() const +{ + Q_D(const QBasicMouseEventTransition); + return d->button; +} + +/*! + Sets the button that this mouse event transition will check for. +*/ +void QBasicMouseEventTransition::setButton(Qt::MouseButton button) +{ + Q_D(QBasicMouseEventTransition); + d->button = button; +} + +/*! + Returns the keyboard modifier mask that this mouse event transition checks + for. +*/ +Qt::KeyboardModifiers QBasicMouseEventTransition::modifierMask() const +{ + Q_D(const QBasicMouseEventTransition); + return d->modifierMask; +} + +/*! + Sets the keyboard modifier mask that this mouse event transition will check + for. +*/ +void QBasicMouseEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QBasicMouseEventTransition); + d->modifierMask = modifierMask; +} + +/*! + Returns the hit test path for this mouse event transition. +*/ +QPainterPath QBasicMouseEventTransition::hitTestPath() const +{ + Q_D(const QBasicMouseEventTransition); + return d->path; +} + +/*! + Sets the hit test path for this mouse event transition. +*/ +void QBasicMouseEventTransition::setHitTestPath(const QPainterPath &path) +{ + Q_D(QBasicMouseEventTransition); + d->path = path; +} + +/*! + \reimp +*/ +bool QBasicMouseEventTransition::eventTest(QEvent *event) +{ + Q_D(const QBasicMouseEventTransition); + if (event->type() == d->eventType) { + QMouseEvent *me = static_cast<QMouseEvent*>(event); + return (me->button() == d->button) + && ((me->modifiers() & d->modifierMask) == d->modifierMask) + && (d->path.isEmpty() || d->path.contains(me->position().toPoint())); + } + return false; +} + +/*! + \reimp +*/ +void QBasicMouseEventTransition::onTransition(QEvent *) +{ +} + +QT_END_NAMESPACE + +#include "moc_qbasicmouseeventtransition_p.cpp" diff --git a/src/statemachine/gui/qbasicmouseeventtransition_p.h b/src/statemachine/gui/qbasicmouseeventtransition_p.h new file mode 100644 index 0000000..5966ef0 --- /dev/null +++ b/src/statemachine/gui/qbasicmouseeventtransition_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBASICMOUSEEVENTTRANSITION_P_H +#define QBASICMOUSEEVENTTRANSITION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qevent.h> +#include <QtStateMachine/qabstracttransition.h> + +QT_REQUIRE_CONFIG(qeventtransition); + +QT_BEGIN_NAMESPACE + +class QPainterPath; + +class QBasicMouseEventTransitionPrivate; +class Q_AUTOTEST_EXPORT QBasicMouseEventTransition : public QAbstractTransition +{ + Q_OBJECT +public: + QBasicMouseEventTransition(QState *sourceState = nullptr); + QBasicMouseEventTransition(QEvent::Type type, Qt::MouseButton button, + QState *sourceState = nullptr); + ~QBasicMouseEventTransition(); + + QEvent::Type eventType() const; + void setEventType(QEvent::Type type); + + Qt::MouseButton button() const; + void setButton(Qt::MouseButton button); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + + QPainterPath hitTestPath() const; + void setHitTestPath(const QPainterPath &path); + +protected: + bool eventTest(QEvent *event) override; + void onTransition(QEvent *) override; + +private: + Q_DISABLE_COPY_MOVE(QBasicMouseEventTransition) + Q_DECLARE_PRIVATE(QBasicMouseEventTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/gui/qkeyeventtransition.cpp b/src/statemachine/gui/qkeyeventtransition.cpp new file mode 100644 index 0000000..82c804c --- /dev/null +++ b/src/statemachine/gui/qkeyeventtransition.cpp @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkeyeventtransition.h" +#include "qbasickeyeventtransition_p.h" +#include "qstatemachine.h" + +#include <private/qeventtransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QKeyEventTransition + + \brief The QKeyEventTransition class provides a transition for key events. + + \since 4.6 + \ingroup statemachine + \inmodule QtStateMachine + + QKeyEventTransition is part of \l{The State Machine Framework}. + + \sa QState::addTransition() +*/ + +/*! + \property QKeyEventTransition::key + + \brief the key that this key event transition is associated with +*/ + +/*! + \property QKeyEventTransition::modifierMask + + \brief the keyboard modifier mask that this key event transition checks for +*/ + +class QKeyEventTransitionPrivate : public QEventTransitionPrivate +{ + Q_DECLARE_PUBLIC(QKeyEventTransition) +public: + QKeyEventTransitionPrivate() {} + + QBasicKeyEventTransition *transition; +}; + +/*! + Constructs a new key event transition with the given \a sourceState. +*/ +QKeyEventTransition::QKeyEventTransition(QState *sourceState) + : QEventTransition(*new QKeyEventTransitionPrivate, sourceState) +{ + Q_D(QKeyEventTransition); + d->transition = new QBasicKeyEventTransition(); +} + +/*! + Constructs a new key event transition for events of the given \a type for + the given \a object, with the given \a key and \a sourceState. +*/ +QKeyEventTransition::QKeyEventTransition(QObject *object, QEvent::Type type, + int key, QState *sourceState) + : QEventTransition(*new QKeyEventTransitionPrivate, object, type, sourceState) +{ + Q_D(QKeyEventTransition); + d->transition = new QBasicKeyEventTransition(type, key); +} + +/*! + Destroys this key event transition. +*/ +QKeyEventTransition::~QKeyEventTransition() +{ + Q_D(QKeyEventTransition); + delete d->transition; +} + +/*! + Returns the key that this key event transition checks for. +*/ +int QKeyEventTransition::key() const +{ + Q_D(const QKeyEventTransition); + return d->transition->key(); +} + +/*! + Sets the \a key that this key event transition will check for. +*/ +void QKeyEventTransition::setKey(int key) +{ + Q_D(QKeyEventTransition); + d->transition->setKey(key); +} + +/*! + Returns the keyboard modifier mask that this key event transition checks + for. +*/ +Qt::KeyboardModifiers QKeyEventTransition::modifierMask() const +{ + Q_D(const QKeyEventTransition); + return d->transition->modifierMask(); +} + +/*! + Sets the keyboard modifier mask that this key event transition will + check for to \a modifierMask. +*/ +void QKeyEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QKeyEventTransition); + d->transition->setModifierMask(modifierMask); +} + +/*! + \reimp +*/ +bool QKeyEventTransition::eventTest(QEvent *event) +{ + Q_D(const QKeyEventTransition); + if (!QEventTransition::eventTest(event)) + return false; + QStateMachine::WrappedEvent *we = static_cast<QStateMachine::WrappedEvent*>(event); + d->transition->setEventType(we->event()->type()); + return QAbstractTransitionPrivate::get(d->transition)->callEventTest(we->event()); +} + +/*! + \reimp +*/ +void QKeyEventTransition::onTransition(QEvent *event) +{ + QEventTransition::onTransition(event); +} + +QT_END_NAMESPACE + +#include "moc_qkeyeventtransition.cpp" diff --git a/src/statemachine/gui/qkeyeventtransition.h b/src/statemachine/gui/qkeyeventtransition.h new file mode 100644 index 0000000..e2b7326 --- /dev/null +++ b/src/statemachine/gui/qkeyeventtransition.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKEYEVENTTRANSITION_H +#define QKEYEVENTTRANSITION_H + +#include <QtStateMachine/qeventtransition.h> + +QT_REQUIRE_CONFIG(qeventtransition); + +QT_BEGIN_NAMESPACE + +class QKeyEventTransitionPrivate; +class Q_STATEMACHINE_EXPORT QKeyEventTransition : public QEventTransition +{ + Q_OBJECT + Q_PROPERTY(int key READ key WRITE setKey) + Q_PROPERTY(Qt::KeyboardModifiers modifierMask READ modifierMask WRITE setModifierMask) +public: + QKeyEventTransition(QState *sourceState = nullptr); + QKeyEventTransition(QObject *object, QEvent::Type type, int key, + QState *sourceState = nullptr); + ~QKeyEventTransition(); + + int key() const; + void setKey(int key); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + +protected: + void onTransition(QEvent *event) override; + bool eventTest(QEvent *event) override; + +private: + Q_DISABLE_COPY(QKeyEventTransition) + Q_DECLARE_PRIVATE(QKeyEventTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/gui/qmouseeventtransition.cpp b/src/statemachine/gui/qmouseeventtransition.cpp new file mode 100644 index 0000000..d1222c6 --- /dev/null +++ b/src/statemachine/gui/qmouseeventtransition.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmouseeventtransition.h" +#include "qbasicmouseeventtransition_p.h" +#include "qstatemachine.h" + +#include <QtGui/qpainterpath.h> +#include <private/qeventtransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QMouseEventTransition + + \brief The QMouseEventTransition class provides a transition for mouse events. + + \since 4.6 + \ingroup statemachine + \inmodule QtStateMachine + + QMouseEventTransition is part of \l{The State Machine Framework}. + + \sa QState::addTransition() +*/ + +/*! + \property QMouseEventTransition::button + + \brief the button that this mouse event transition is associated with +*/ + +/*! + \property QMouseEventTransition::modifierMask + + \brief the keyboard modifier mask that this mouse event transition checks for +*/ + +class QMouseEventTransitionPrivate : public QEventTransitionPrivate +{ + Q_DECLARE_PUBLIC(QMouseEventTransition) +public: + QMouseEventTransitionPrivate(); + + QBasicMouseEventTransition *transition; +}; + +QMouseEventTransitionPrivate::QMouseEventTransitionPrivate() +{ +} + +/*! + Constructs a new mouse event transition with the given \a sourceState. +*/ +QMouseEventTransition::QMouseEventTransition(QState *sourceState) + : QEventTransition(*new QMouseEventTransitionPrivate, sourceState) +{ + Q_D(QMouseEventTransition); + d->transition = new QBasicMouseEventTransition(); +} + +/*! + Constructs a new mouse event transition for events of the given \a type for + the given \a object, with the given \a button and \a sourceState. +*/ +QMouseEventTransition::QMouseEventTransition(QObject *object, QEvent::Type type, + Qt::MouseButton button, + QState *sourceState) + : QEventTransition(*new QMouseEventTransitionPrivate, object, type, sourceState) +{ + Q_D(QMouseEventTransition); + d->transition = new QBasicMouseEventTransition(type, button); +} + +/*! + Destroys this mouse event transition. +*/ +QMouseEventTransition::~QMouseEventTransition() +{ + Q_D(QMouseEventTransition); + delete d->transition; +} + +/*! + Returns the button that this mouse event transition checks for. +*/ +Qt::MouseButton QMouseEventTransition::button() const +{ + Q_D(const QMouseEventTransition); + return d->transition->button(); +} + +/*! + Sets the \a button that this mouse event transition will check for. +*/ +void QMouseEventTransition::setButton(Qt::MouseButton button) +{ + Q_D(QMouseEventTransition); + d->transition->setButton(button); +} + +/*! + Returns the keyboard modifier mask that this mouse event transition checks + for. +*/ +Qt::KeyboardModifiers QMouseEventTransition::modifierMask() const +{ + Q_D(const QMouseEventTransition); + return d->transition->modifierMask(); +} + +/*! + Sets the keyboard modifier mask that this mouse event transition will + check for to \a modifierMask. +*/ +void QMouseEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QMouseEventTransition); + d->transition->setModifierMask(modifierMask); +} + +/*! + Returns the hit test path for this mouse event transition. +*/ +QPainterPath QMouseEventTransition::hitTestPath() const +{ + Q_D(const QMouseEventTransition); + return d->transition->hitTestPath(); +} + +/*! + Sets the hit test path for this mouse event transition to \a path. + If a valid path has been set, the transition will only trigger if the mouse + event position (QMouseEvent::pos()) is inside the path. + + \sa QPainterPath::contains() +*/ +void QMouseEventTransition::setHitTestPath(const QPainterPath &path) +{ + Q_D(QMouseEventTransition); + d->transition->setHitTestPath(path); +} + +/*! + \reimp +*/ +bool QMouseEventTransition::eventTest(QEvent *event) +{ + Q_D(const QMouseEventTransition); + if (!QEventTransition::eventTest(event)) + return false; + QStateMachine::WrappedEvent *we = static_cast<QStateMachine::WrappedEvent*>(event); + d->transition->setEventType(we->event()->type()); + return QAbstractTransitionPrivate::get(d->transition)->callEventTest(we->event()); +} + +/*! + \reimp +*/ +void QMouseEventTransition::onTransition(QEvent *event) +{ + QEventTransition::onTransition(event); +} + +QT_END_NAMESPACE + +#include "moc_qmouseeventtransition.cpp" diff --git a/src/statemachine/gui/qmouseeventtransition.h b/src/statemachine/gui/qmouseeventtransition.h new file mode 100644 index 0000000..d1cf32b --- /dev/null +++ b/src/statemachine/gui/qmouseeventtransition.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSEEVENTTRANSITION_H +#define QMOUSEEVENTTRANSITION_H + +#include <QtStateMachine/qeventtransition.h> + +QT_REQUIRE_CONFIG(qeventtransition); + +QT_BEGIN_NAMESPACE + +class QMouseEventTransitionPrivate; +class QPainterPath; +class Q_STATEMACHINE_EXPORT QMouseEventTransition : public QEventTransition +{ + Q_OBJECT + Q_PROPERTY(Qt::MouseButton button READ button WRITE setButton) + Q_PROPERTY(Qt::KeyboardModifiers modifierMask READ modifierMask WRITE setModifierMask) +public: + QMouseEventTransition(QState *sourceState = nullptr); + QMouseEventTransition(QObject *object, QEvent::Type type, + Qt::MouseButton button, QState *sourceState = nullptr); + ~QMouseEventTransition(); + + Qt::MouseButton button() const; + void setButton(Qt::MouseButton button); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + + QPainterPath hitTestPath() const; + void setHitTestPath(const QPainterPath &path); + +protected: + void onTransition(QEvent *event) override; + bool eventTest(QEvent *event) override; + +private: + Q_DISABLE_COPY(QMouseEventTransition) + Q_DECLARE_PRIVATE(QMouseEventTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/gui/statemachine.pri b/src/statemachine/gui/statemachine.pri new file mode 100644 index 0000000..e51088a --- /dev/null +++ b/src/statemachine/gui/statemachine.pri @@ -0,0 +1,16 @@ +!qtHaveModule(gui)): return() + +qtConfig(qeventtransition) { + QT += gui + QT_FOR_PRIVATE += gui-private + HEADERS += \ + $$PWD/qkeyeventtransition.h \ + $$PWD/qmouseeventtransition.h \ + $$PWD/qbasickeyeventtransition_p.h \ + $$PWD/qbasicmouseeventtransition_p.h + SOURCES += \ + $$PWD/qkeyeventtransition.cpp \ + $$PWD/qmouseeventtransition.cpp \ + $$PWD/qbasickeyeventtransition.cpp \ + $$PWD/qbasicmouseeventtransition.cpp +} diff --git a/src/statemachine/qabstractstate.cpp b/src/statemachine/qabstractstate.cpp new file mode 100644 index 0000000..6106e70 --- /dev/null +++ b/src/statemachine/qabstractstate.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractstate.h" +#include "qabstractstate_p.h" +#include "qstate.h" +#include "qstate_p.h" +#include "qstatemachine.h" +#include "qstatemachine_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractState + \inmodule QtStateMachine + + \brief The QAbstractState class is the base class of states of a QStateMachine. + + \since 4.6 + \ingroup statemachine + + The QAbstractState class is the abstract base class of states that are part + of a QStateMachine. It defines the interface that all state objects have in + common. QAbstractState is part of \l{The State Machine Framework}. + + The entered() signal is emitted when the state has been entered. The + exited() signal is emitted when the state has been exited. + + The parentState() function returns the state's parent state. The machine() + function returns the state machine that the state is part of. + + \section1 Subclassing + + The onEntry() function is called when the state is entered; reimplement this + function to perform custom processing when the state is entered. + + The onExit() function is called when the state is exited; reimplement this + function to perform custom processing when the state is exited. +*/ + +/*! + \property QAbstractState::active + \since 5.4 + + \brief the active property of this state. A state is active between + entered() and exited() signals. +*/ + + +QAbstractStatePrivate::QAbstractStatePrivate(StateType type) + : stateType(type), isMachine(false), active(false), parentState(nullptr) +{ +} + +QStateMachine *QAbstractStatePrivate::machine() const +{ + QObject *par = parent; + while (par != nullptr) { + if (QStateMachine *mach = qobject_cast<QStateMachine*>(par)) + return mach; + par = par->parent(); + } + return nullptr; +} + +void QAbstractStatePrivate::callOnEntry(QEvent *e) +{ + Q_Q(QAbstractState); + q->onEntry(e); +} + +void QAbstractStatePrivate::callOnExit(QEvent *e) +{ + Q_Q(QAbstractState); + q->onExit(e); +} + +void QAbstractStatePrivate::emitEntered() +{ + Q_Q(QAbstractState); + emit q->entered(QAbstractState::QPrivateSignal()); + if (!active) { + active = true; + emit q->activeChanged(true); + } +} + +void QAbstractStatePrivate::emitExited() +{ + Q_Q(QAbstractState); + if (active) { + active = false; + emit q->activeChanged(false); + } + emit q->exited(QAbstractState::QPrivateSignal()); +} + +/*! + Constructs a new state with the given \a parent state. +*/ +QAbstractState::QAbstractState(QState *parent) + : QObject(*new QAbstractStatePrivate(QAbstractStatePrivate::AbstractState), parent) +{ +} + +/*! + \internal +*/ +QAbstractState::QAbstractState(QAbstractStatePrivate &dd, QState *parent) + : QObject(dd, parent) +{ +} + +/*! + Destroys this state. +*/ +QAbstractState::~QAbstractState() +{ +} + +/*! + Returns this state's parent state, or \nullptr if the state has no + parent state. +*/ +QState *QAbstractState::parentState() const +{ + Q_D(const QAbstractState); + if (d->parentState != parent()) + d->parentState = qobject_cast<QState*>(parent()); + return d->parentState; +} + +/*! + Returns the state machine that this state is part of, or \nullptr if + the state is not part of a state machine. +*/ +QStateMachine *QAbstractState::machine() const +{ + Q_D(const QAbstractState); + return d->machine(); +} + +/*! + Returns whether this state is active. + + \sa activeChanged(bool), entered(), exited() +*/ +bool QAbstractState::active() const +{ + Q_D(const QAbstractState); + return d->active; +} + +/*! + \fn QAbstractState::onExit(QEvent *event) + + This function is called when the state is exited. The given \a event is what + caused the state to be exited. Reimplement this function to perform custom + processing when the state is exited. +*/ + +/*! + \fn QAbstractState::onEntry(QEvent *event) + + This function is called when the state is entered. The given \a event is + what caused the state to be entered. Reimplement this function to perform + custom processing when the state is entered. +*/ + +/*! + \fn QAbstractState::entered() + + This signal is emitted when the state has been entered (after onEntry() has + been called). +*/ + +/*! + \fn QAbstractState::exited() + + This signal is emitted when the state has been exited (after onExit() has + been called). +*/ + +/*! + \fn QAbstractState::activeChanged(bool active) + \since 5.4 + + This signal is emitted when the active property is changed with \a active as argument. + + \sa QAbstractState::active, entered(), exited() +*/ + +/*! + \reimp +*/ +bool QAbstractState::event(QEvent *e) +{ + return QObject::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qabstractstate.cpp" diff --git a/src/statemachine/qabstractstate.h b/src/statemachine/qabstractstate.h new file mode 100644 index 0000000..d83d0d0 --- /dev/null +++ b/src/statemachine/qabstractstate.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSTATE_H +#define QABSTRACTSTATE_H + +#include <QtCore/qobject.h> +#include <QtStateMachine/qstatemachineglobal.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QState; +class QStateMachine; + +class QAbstractStatePrivate; +class Q_STATEMACHINE_EXPORT QAbstractState : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool active READ active NOTIFY activeChanged) +public: + ~QAbstractState(); + + QState *parentState() const; + QStateMachine *machine() const; + + bool active() const; + +Q_SIGNALS: + void entered(QPrivateSignal); + void exited(QPrivateSignal); + void activeChanged(bool active); + +protected: + QAbstractState(QState *parent = nullptr); + + virtual void onEntry(QEvent *event) = 0; + virtual void onExit(QEvent *event) = 0; + + bool event(QEvent *e) override; + +protected: + QAbstractState(QAbstractStatePrivate &dd, QState *parent); + +private: + Q_DISABLE_COPY(QAbstractState) + Q_DECLARE_PRIVATE(QAbstractState) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qabstractstate_p.h b/src/statemachine/qabstractstate_p.h new file mode 100644 index 0000000..e2098ee --- /dev/null +++ b/src/statemachine/qabstractstate_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSTATE_P_H +#define QABSTRACTSTATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qobject_p.h> +#include <QtStateMachine/qabstractstate.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QStateMachine; + +class QState; +class QAbstractStatePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractState) + +public: + enum StateType { + AbstractState, + StandardState, + FinalState, + HistoryState + }; + + QAbstractStatePrivate(StateType type); + + static QAbstractStatePrivate *get(QAbstractState *q) + { return q->d_func(); } + static const QAbstractStatePrivate *get(const QAbstractState *q) + { return q->d_func(); } + + QStateMachine *machine() const; + + void callOnEntry(QEvent *e); + void callOnExit(QEvent *e); + + void emitEntered(); + void emitExited(); + + uint stateType:30; + uint isMachine:1; + bool active:1; + mutable QState *parentState; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSTATE_P_H diff --git a/src/statemachine/qabstracttransition.cpp b/src/statemachine/qabstracttransition.cpp new file mode 100644 index 0000000..cc6f617 --- /dev/null +++ b/src/statemachine/qabstracttransition.cpp @@ -0,0 +1,435 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstracttransition.h" +#include "qabstracttransition_p.h" +#include "qabstractstate.h" +#include "qhistorystate.h" +#include "qstate.h" +#include "qstatemachine.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractTransition + \inmodule QtStateMachine + + \brief The QAbstractTransition class is the base class of transitions between QAbstractState objects. + + \since 4.6 + \ingroup statemachine + + The QAbstractTransition class is the abstract base class of transitions + between states (QAbstractState objects) of a + QStateMachine. QAbstractTransition is part of \l{The State Machine + Framework}. + + The sourceState() function returns the source of the transition. The + targetStates() function returns the targets of the transition. The machine() + function returns the state machine that the transition is part of. + + The triggered() signal is emitted when the transition has been triggered. + + Transitions can cause animations to be played. Use the addAnimation() + function to add an animation to the transition. + + \section1 Subclassing + + The eventTest() function is called by the state machine to determine whether + an event should trigger the transition. In your reimplementation you + typically check the event type and cast the event object to the proper type, + and check that one or more properties of the event meet your criteria. + + The onTransition() function is called when the transition is triggered; + reimplement this function to perform custom processing for the transition. +*/ + +/*! + \property QAbstractTransition::sourceState + + \brief the source state (parent) of this transition +*/ + +/*! + \property QAbstractTransition::targetState + + \brief the target state of this transition + + If a transition has no target state, the transition may still be + triggered, but this will not cause the state machine's configuration to + change (i.e. the current state will not be exited and re-entered). +*/ + +/*! + \property QAbstractTransition::targetStates + + \brief the target states of this transition + + If multiple states are specified, all must be descendants of the same + parallel group state. +*/ + +/*! + \property QAbstractTransition::transitionType + + \brief indicates whether this transition is an internal transition, or an external transition. + + Internal and external transitions behave the same, except for the case of a transition whose + source state is a compound state and whose target(s) is a descendant of the source. In such a + case, an internal transition will not exit and re-enter its source state, while an external one + will. + + By default, the type is an external transition. +*/ + +/*! + \enum QAbstractTransition::TransitionType + + This enum specifies the kind of transition. By default, the type is an external transition. + + \value ExternalTransition Any state that is the source state of a transition (which is not a + target-less transition) is left, and re-entered when necessary. + \value InternalTransition If the target state of a transition is a sub-state of a compound state, + and that compound state is the source state, an internal transition will + not leave the source state. + + \sa QAbstractTransition::transitionType +*/ + +QAbstractTransitionPrivate::QAbstractTransitionPrivate() + : transitionType(QAbstractTransition::ExternalTransition) +{ +} + +QStateMachine *QAbstractTransitionPrivate::machine() const +{ + if (QState *source = sourceState()) + return source->machine(); + Q_Q(const QAbstractTransition); + if (QHistoryState *parent = qobject_cast<QHistoryState *>(q->parent())) + return parent->machine(); + return nullptr; +} + +bool QAbstractTransitionPrivate::callEventTest(QEvent *e) +{ + Q_Q(QAbstractTransition); + return q->eventTest(e); +} + +void QAbstractTransitionPrivate::callOnTransition(QEvent *e) +{ + Q_Q(QAbstractTransition); + q->onTransition(e); +} + +QState *QAbstractTransitionPrivate::sourceState() const +{ + return qobject_cast<QState*>(parent); +} + +void QAbstractTransitionPrivate::emitTriggered() +{ + Q_Q(QAbstractTransition); + emit q->triggered(QAbstractTransition::QPrivateSignal()); +} + +/*! + Constructs a new QAbstractTransition object with the given \a sourceState. +*/ +QAbstractTransition::QAbstractTransition(QState *sourceState) + : QObject(*new QAbstractTransitionPrivate, sourceState) +{ +} + +/*! + \internal +*/ +QAbstractTransition::QAbstractTransition(QAbstractTransitionPrivate &dd, + QState *parent) + : QObject(dd, parent) +{ +} + +/*! + Destroys this transition. +*/ +QAbstractTransition::~QAbstractTransition() +{ +} + +/*! + Returns the source state of this transition, or \nullptr if this + transition has no source state. +*/ +QState *QAbstractTransition::sourceState() const +{ + Q_D(const QAbstractTransition); + return d->sourceState(); +} + +/*! + Returns the target state of this transition, or \nullptr if the + transition has no target. +*/ +QAbstractState *QAbstractTransition::targetState() const +{ + Q_D(const QAbstractTransition); + if (d->targetStates.isEmpty()) + return nullptr; + return d->targetStates.first().data(); +} + +/*! + Sets the \a target state of this transition. +*/ +void QAbstractTransition::setTargetState(QAbstractState* target) +{ + Q_D(QAbstractTransition); + if ((d->targetStates.size() == 1 && target == d->targetStates.at(0).data()) || + (d->targetStates.isEmpty() && target == nullptr)) { + return; + } + if (!target) + d->targetStates.clear(); + else + setTargetStates(QList<QAbstractState*>() << target); + emit targetStateChanged(QPrivateSignal()); +} + +/*! + Returns the target states of this transition, or an empty list if this + transition has no target states. +*/ +QList<QAbstractState*> QAbstractTransition::targetStates() const +{ + Q_D(const QAbstractTransition); + QList<QAbstractState*> result; + for (int i = 0; i < d->targetStates.size(); ++i) { + QAbstractState *target = d->targetStates.at(i).data(); + if (target) + result.append(target); + } + return result; +} + +/*! + Sets the target states of this transition to be the given \a targets. +*/ +void QAbstractTransition::setTargetStates(const QList<QAbstractState*> &targets) +{ + Q_D(QAbstractTransition); + + // Verify if any of the new target states is a null-pointer: + for (int i = 0; i < targets.size(); ++i) { + if (targets.at(i) == nullptr) { + qWarning("QAbstractTransition::setTargetStates: target state(s) cannot be null"); + return; + } + } + + // First clean out any target states that got destroyed, but for which we still have a QPointer + // around. + for (int i = 0; i < d->targetStates.size(); ) { + if (d->targetStates.at(i).isNull()) { + d->targetStates.remove(i); + } else { + ++i; + } + } + + // Easy check: if both lists are empty, we're done. + if (targets.isEmpty() && d->targetStates.isEmpty()) + return; + + bool sameList = true; + + if (targets.size() != d->targetStates.size()) { + // If the sizes of the lists are different, we don't need to be smart: they're different. So + // we can just set the new list as the targetStates. + sameList = false; + } else { + QList<QPointer<QAbstractState>> copy(d->targetStates); + for (int i = 0; i < targets.size(); ++i) { + sameList &= copy.removeOne(targets.at(i)); + if (!sameList) + break; // ok, we now know the lists are not the same, so stop the loop. + } + + sameList &= copy.isEmpty(); + } + + if (sameList) + return; + + d->targetStates.resize(targets.size()); + for (int i = 0; i < targets.size(); ++i) { + d->targetStates[i] = targets.at(i); + } + + emit targetStatesChanged(QPrivateSignal()); +} + +/*! + Returns the type of the transition. +*/ +QAbstractTransition::TransitionType QAbstractTransition::transitionType() const +{ + Q_D(const QAbstractTransition); + return d->transitionType; +} + +/*! + Sets the type of the transition to \a type. +*/ +void QAbstractTransition::setTransitionType(TransitionType type) +{ + Q_D(QAbstractTransition); + d->transitionType = type; +} + +/*! + Returns the state machine that this transition is part of, or + \nullptr if the transition is not part of a state machine. +*/ +QStateMachine *QAbstractTransition::machine() const +{ + Q_D(const QAbstractTransition); + return d->machine(); +} + +#if QT_CONFIG(animation) + +/*! + Adds the given \a animation to this transition. + The transition does not take ownership of the animation. + + \sa removeAnimation(), animations() +*/ +void QAbstractTransition::addAnimation(QAbstractAnimation *animation) +{ + Q_D(QAbstractTransition); + if (!animation) { + qWarning("QAbstractTransition::addAnimation: cannot add null animation"); + return; + } + d->animations.append(animation); +} + +/*! + Removes the given \a animation from this transition. + + \sa addAnimation() +*/ +void QAbstractTransition::removeAnimation(QAbstractAnimation *animation) +{ + Q_D(QAbstractTransition); + if (!animation) { + qWarning("QAbstractTransition::removeAnimation: cannot remove null animation"); + return; + } + d->animations.removeOne(animation); +} + +/*! + Returns the list of animations associated with this transition, or an empty + list if it has no animations. + + \sa addAnimation() +*/ +QList<QAbstractAnimation*> QAbstractTransition::animations() const +{ + Q_D(const QAbstractTransition); + return d->animations; +} + +#endif + +/*! + \fn QAbstractTransition::eventTest(QEvent *event) + + This function is called to determine whether the given \a event should cause + this transition to trigger. Reimplement this function and return true if the + event should trigger the transition, otherwise return false. +*/ + +/*! + \fn QAbstractTransition::onTransition(QEvent *event) + + This function is called when the transition is triggered. The given \a event + is what caused the transition to trigger. Reimplement this function to + perform custom processing when the transition is triggered. +*/ + +/*! + \fn QAbstractTransition::triggered() + + This signal is emitted when the transition has been triggered (after + onTransition() has been called). +*/ + +/*! + \fn QAbstractTransition::targetStateChanged() + \since 5.4 + + This signal is emitted when the targetState property is changed. + + \sa QAbstractTransition::targetState +*/ + +/*! + \fn QAbstractTransition::targetStatesChanged() + \since 5.4 + + This signal is emitted when the targetStates property is changed. + + \sa QAbstractTransition::targetStates +*/ + +/*! + \reimp +*/ +bool QAbstractTransition::event(QEvent *e) +{ + return QObject::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qabstracttransition.cpp" diff --git a/src/statemachine/qabstracttransition.h b/src/statemachine/qabstracttransition.h new file mode 100644 index 0000000..739d4b7 --- /dev/null +++ b/src/statemachine/qabstracttransition.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTTRANSITION_H +#define QABSTRACTTRANSITION_H + +#include <QtCore/qlist.h> +#include <QtCore/qobject.h> + +#include <QtStateMachine/qstatemachineglobal.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QEvent; +class QAbstractState; +class QState; +class QStateMachine; + +#if QT_CONFIG(animation) +class QAbstractAnimation; +#endif + +class QAbstractTransitionPrivate; +class Q_STATEMACHINE_EXPORT QAbstractTransition : public QObject +{ + Q_OBJECT + Q_PROPERTY(QState* sourceState READ sourceState) + Q_PROPERTY(QAbstractState* targetState READ targetState WRITE setTargetState NOTIFY targetStateChanged) + Q_PROPERTY(QList<QAbstractState*> targetStates READ targetStates WRITE setTargetStates NOTIFY targetStatesChanged) + Q_PROPERTY(TransitionType transitionType READ transitionType WRITE setTransitionType REVISION(1, 1)) +public: + enum TransitionType { + ExternalTransition, + InternalTransition + }; + Q_ENUM(TransitionType) + + QAbstractTransition(QState *sourceState = nullptr); + virtual ~QAbstractTransition(); + + QState *sourceState() const; + QAbstractState *targetState() const; + void setTargetState(QAbstractState* target); + QList<QAbstractState*> targetStates() const; + void setTargetStates(const QList<QAbstractState*> &targets); + + TransitionType transitionType() const; + void setTransitionType(TransitionType type); + + QStateMachine *machine() const; + +#if QT_CONFIG(animation) + void addAnimation(QAbstractAnimation *animation); + void removeAnimation(QAbstractAnimation *animation); + QList<QAbstractAnimation*> animations() const; +#endif + +Q_SIGNALS: + void triggered(QPrivateSignal); + void targetStateChanged(QPrivateSignal); + void targetStatesChanged(QPrivateSignal); + +protected: + virtual bool eventTest(QEvent *event) = 0; + + virtual void onTransition(QEvent *event) = 0; + + bool event(QEvent *e) override; + +protected: + QAbstractTransition(QAbstractTransitionPrivate &dd, QState *parent); + +private: + Q_DISABLE_COPY(QAbstractTransition) + Q_DECLARE_PRIVATE(QAbstractTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qabstracttransition_p.h b/src/statemachine/qabstracttransition_p.h new file mode 100644 index 0000000..d9b6ec0 --- /dev/null +++ b/src/statemachine/qabstracttransition_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTTRANSITION_P_H +#define QABSTRACTTRANSITION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qobject_p.h> + +#include <QtCore/qlist.h> +#include <QtCore/qsharedpointer.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QAbstractState; +class QState; +class QStateMachine; + +class QAbstractTransition; +class Q_STATEMACHINE_EXPORT QAbstractTransitionPrivate + : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractTransition) +public: + QAbstractTransitionPrivate(); + + static QAbstractTransitionPrivate *get(QAbstractTransition *q) + { return q->d_func(); } + + bool callEventTest(QEvent *e); + virtual void callOnTransition(QEvent *e); + QState *sourceState() const; + QStateMachine *machine() const; + void emitTriggered(); + + QList<QPointer<QAbstractState>> targetStates; + QAbstractTransition::TransitionType transitionType; + +#if QT_CONFIG(animation) + QList<QAbstractAnimation*> animations; +#endif +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qeventtransition.cpp b/src/statemachine/qeventtransition.cpp new file mode 100644 index 0000000..2b6fa67 --- /dev/null +++ b/src/statemachine/qeventtransition.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qeventtransition.h" +#include "qeventtransition_p.h" +#include "qstate.h" +#include "qstate_p.h" +#include "qstatemachine.h" +#include "qstatemachine_p.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QEventTransition + \inmodule QtStateMachine + + \brief The QEventTransition class provides a QObject-specific transition for Qt events. + + \since 4.6 + \ingroup statemachine + + A QEventTransition object binds an event to a particular QObject. + QEventTransition is part of \l{The State Machine Framework}. + + Example: + + \code + QPushButton *button = ...; + QState *s1 = ...; + QState *s2 = ...; + // If in s1 and the button receives an Enter event, transition to s2 + QEventTransition *enterTransition = new QEventTransition(button, QEvent::Enter); + enterTransition->setTargetState(s2); + s1->addTransition(enterTransition); + // If in s2 and the button receives an Exit event, transition back to s1 + QEventTransition *leaveTransition = new QEventTransition(button, QEvent::Leave); + leaveTransition->setTargetState(s1); + s2->addTransition(leaveTransition); + \endcode + + \section1 Subclassing + + When reimplementing the eventTest() function, you should first call the base + implementation to verify that the event is a QStateMachine::WrappedEvent for + the proper object and event type. You may then cast the event to a + QStateMachine::WrappedEvent and get the original event by calling + QStateMachine::WrappedEvent::event(), and perform additional checks on that + object. + + \sa QState::addTransition() +*/ + +/*! + \property QEventTransition::eventSource + + \brief the event source that this event transition is associated with +*/ + +/*! + \property QEventTransition::eventType + + \brief the type of event that this event transition is associated with +*/ +QEventTransitionPrivate::QEventTransitionPrivate() +{ + object = nullptr; + eventType = QEvent::None; + registered = false; +} + +QEventTransitionPrivate::~QEventTransitionPrivate() +{ +} + +void QEventTransitionPrivate::unregister() +{ + Q_Q(QEventTransition); + if (!registered || !machine()) + return; + QStateMachinePrivate::get(machine())->unregisterEventTransition(q); +} + +void QEventTransitionPrivate::maybeRegister() +{ + Q_Q(QEventTransition); + if (QStateMachine *mach = machine()) + QStateMachinePrivate::get(mach)->maybeRegisterEventTransition(q); +} + +/*! + Constructs a new QEventTransition object with the given \a sourceState. +*/ +QEventTransition::QEventTransition(QState *sourceState) + : QAbstractTransition(*new QEventTransitionPrivate, sourceState) +{ +} + +/*! + Constructs a new QEventTransition object associated with events of the given + \a type for the given \a object, and with the given \a sourceState. +*/ +QEventTransition::QEventTransition(QObject *object, QEvent::Type type, + QState *sourceState) + : QAbstractTransition(*new QEventTransitionPrivate, sourceState) +{ + Q_D(QEventTransition); + d->registered = false; + d->object = object; + d->eventType = type; + d->maybeRegister(); +} + +/*! + \internal +*/ +QEventTransition::QEventTransition(QEventTransitionPrivate &dd, QState *parent) + : QAbstractTransition(dd, parent) +{ +} + +/*! + \internal +*/ +QEventTransition::QEventTransition(QEventTransitionPrivate &dd, QObject *object, + QEvent::Type type, QState *parent) + : QAbstractTransition(dd, parent) +{ + Q_D(QEventTransition); + d->registered = false; + d->object = object; + d->eventType = type; + d->maybeRegister(); +} + +/*! + Destroys this QObject event transition. +*/ +QEventTransition::~QEventTransition() +{ +} + +/*! + Returns the event type that this event transition is associated with. +*/ +QEvent::Type QEventTransition::eventType() const +{ + Q_D(const QEventTransition); + return d->eventType; +} + +/*! + Sets the event \a type that this event transition is associated with. +*/ +void QEventTransition::setEventType(QEvent::Type type) +{ + Q_D(QEventTransition); + if (d->eventType == type) + return; + d->unregister(); + d->eventType = type; + d->maybeRegister(); +} + +/*! + Returns the event source associated with this event transition. +*/ +QObject *QEventTransition::eventSource() const +{ + Q_D(const QEventTransition); + return d->object; +} + +/*! + Sets the event source associated with this event transition to be the given + \a object. +*/ +void QEventTransition::setEventSource(QObject *object) +{ + Q_D(QEventTransition); + if (d->object == object) + return; + d->unregister(); + d->object = object; + d->maybeRegister(); +} + +/*! + \reimp +*/ +bool QEventTransition::eventTest(QEvent *event) +{ + Q_D(const QEventTransition); + if (event->type() == QEvent::StateMachineWrapped) { + QStateMachine::WrappedEvent *we = static_cast<QStateMachine::WrappedEvent*>(event); + return (we->object() == d->object) + && (we->event()->type() == d->eventType); + } + return false; +} + +/*! + \reimp +*/ +void QEventTransition::onTransition(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +bool QEventTransition::event(QEvent *e) +{ + return QAbstractTransition::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qeventtransition.cpp" diff --git a/src/statemachine/qeventtransition.h b/src/statemachine/qeventtransition.h new file mode 100644 index 0000000..a8dae5b --- /dev/null +++ b/src/statemachine/qeventtransition.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTTRANSITION_H +#define QEVENTTRANSITION_H + +#include <QtCore/qcoreevent.h> +#include <QtStateMachine/qabstracttransition.h> + +QT_REQUIRE_CONFIG(qeventtransition); + +QT_BEGIN_NAMESPACE + +class QEventTransitionPrivate; +class Q_STATEMACHINE_EXPORT QEventTransition : public QAbstractTransition +{ + Q_OBJECT + Q_PROPERTY(QObject* eventSource READ eventSource WRITE setEventSource) + Q_PROPERTY(QEvent::Type eventType READ eventType WRITE setEventType) +public: + QEventTransition(QState *sourceState = nullptr); + QEventTransition(QObject *object, QEvent::Type type, QState *sourceState = nullptr); + ~QEventTransition(); + + QObject *eventSource() const; + void setEventSource(QObject *object); + + QEvent::Type eventType() const; + void setEventType(QEvent::Type type); + +protected: + bool eventTest(QEvent *event) override; + void onTransition(QEvent *event) override; + + bool event(QEvent *e) override; + +protected: + QEventTransition(QEventTransitionPrivate &dd, QState *parent); + QEventTransition(QEventTransitionPrivate &dd, QObject *object, + QEvent::Type type, QState *parent); + +private: + Q_DISABLE_COPY(QEventTransition) + Q_DECLARE_PRIVATE(QEventTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qeventtransition_p.h b/src/statemachine/qeventtransition_p.h new file mode 100644 index 0000000..642b19a --- /dev/null +++ b/src/statemachine/qeventtransition_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTTRANSITION_P_H +#define QEVENTTRANSITION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstracttransition_p.h" + +QT_REQUIRE_CONFIG(qeventtransition); + +QT_BEGIN_NAMESPACE + +class QEventTransition; +class Q_STATEMACHINE_EXPORT QEventTransitionPrivate : public QAbstractTransitionPrivate +{ + Q_DECLARE_PUBLIC(QEventTransition) +public: + QEventTransitionPrivate(); + ~QEventTransitionPrivate(); + + static QEventTransitionPrivate *get(QEventTransition *q) + { return q->d_func(); } + + void unregister(); + void maybeRegister(); + + QObject *object; + bool registered; + QEvent::Type eventType; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qfinalstate.cpp b/src/statemachine/qfinalstate.cpp new file mode 100644 index 0000000..9028aa6 --- /dev/null +++ b/src/statemachine/qfinalstate.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfinalstate_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QFinalState + \inmodule QtStateMachine + + \brief The QFinalState class provides a final state. + + \since 4.6 + \ingroup statemachine + + A final state is used to communicate that (part of) a QStateMachine has + finished its work. When a final top-level state is entered, the state + machine's \l{QStateMachine::finished()}{finished}() signal is emitted. In + general, when a final substate (a child of a QState) is entered, the parent + state's \l{QState::finished()}{finished}() signal is emitted. QFinalState + is part of \l{The State Machine Framework}. + + To use a final state, you create a QFinalState object and add a transition + to it from another state. Example: + + \code + QPushButton button; + + QStateMachine machine; + QState *s1 = new QState(); + QFinalState *s2 = new QFinalState(); + s1->addTransition(&button, SIGNAL(clicked()), s2); + machine.addState(s1); + machine.addState(s2); + + QObject::connect(&machine, SIGNAL(finished()), QApplication::instance(), SLOT(quit())); + machine.setInitialState(s1); + machine.start(); + \endcode + + \sa QState::finished() +*/ + +QFinalStatePrivate::QFinalStatePrivate() + : QAbstractStatePrivate(FinalState) +{ +} + +QFinalStatePrivate::~QFinalStatePrivate() +{ + // to prevent vtables being generated in every file that includes the private header +} + +/*! + Constructs a new QFinalState object with the given \a parent state. +*/ +QFinalState::QFinalState(QState *parent) + : QAbstractState(*new QFinalStatePrivate, parent) +{ +} + +/*! + \internal + */ +QFinalState::QFinalState(QFinalStatePrivate &dd, QState *parent) + : QAbstractState(dd, parent) +{ +} + + +/*! + Destroys this final state. +*/ +QFinalState::~QFinalState() +{ +} + +/*! + \reimp +*/ +void QFinalState::onEntry(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QFinalState::onExit(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +bool QFinalState::event(QEvent *e) +{ + return QAbstractState::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qfinalstate.cpp" diff --git a/src/statemachine/qfinalstate.h b/src/statemachine/qfinalstate.h new file mode 100644 index 0000000..8e1cbaf --- /dev/null +++ b/src/statemachine/qfinalstate.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFINALSTATE_H +#define QFINALSTATE_H + +#include <QtStateMachine/qabstractstate.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QFinalStatePrivate; +class Q_STATEMACHINE_EXPORT QFinalState : public QAbstractState +{ + Q_OBJECT +public: + QFinalState(QState *parent = nullptr); + ~QFinalState(); + +protected: + void onEntry(QEvent *event) override; + void onExit(QEvent *event) override; + + bool event(QEvent *e) override; + +protected: + explicit QFinalState(QFinalStatePrivate &dd, QState *parent); + +private: + Q_DISABLE_COPY(QFinalState) + Q_DECLARE_PRIVATE(QFinalState) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qfinalstate_p.h b/src/statemachine/qfinalstate_p.h new file mode 100644 index 0000000..8551ff7 --- /dev/null +++ b/src/statemachine/qfinalstate_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFINALSTATE_P_H +#define QFINALSTATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfinalstate.h" +#include "private/qabstractstate_p.h" + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class Q_STATEMACHINE_EXPORT QFinalStatePrivate : public QAbstractStatePrivate +{ + Q_DECLARE_PUBLIC(QFinalState) + +public: + QFinalStatePrivate(); + ~QFinalStatePrivate(); +}; + +QT_END_NAMESPACE + +#endif // QFINALSTATE_P_H diff --git a/src/statemachine/qhistorystate.cpp b/src/statemachine/qhistorystate.cpp new file mode 100644 index 0000000..c02b75c --- /dev/null +++ b/src/statemachine/qhistorystate.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhistorystate.h" +#include "qhistorystate_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QHistoryState + \inmodule QtStateMachine + + \brief The QHistoryState class provides a means of returning to a previously active substate. + + \since 4.6 + \ingroup statemachine + + A history state is a pseudo-state that represents the child state that the + parent state was in the last time the parent state was exited. A transition + with a history state as its target is in fact a transition to one or more + other child states of the parent state. QHistoryState is part of \l{The + State Machine Framework}. + + Use the setDefaultState() function to set the state that should be entered + if the parent state has never been entered. Example: + + \code + QStateMachine machine; + + QState *s1 = new QState(); + QState *s11 = new QState(s1); + QState *s12 = new QState(s1); + + QHistoryState *s1h = new QHistoryState(s1); + s1h->setDefaultState(s11); + + machine.addState(s1); + + QState *s2 = new QState(); + machine.addState(s2); + + QPushButton *button = new QPushButton(); + // Clicking the button will cause the state machine to enter the child state + // that s1 was in the last time s1 was exited, or the history state's default + // state if s1 has never been entered. + s1->addTransition(button, SIGNAL(clicked()), s1h); + \endcode + + If more than one default state has to be entered, or if the transition to the default state(s) + has to be acted upon, the defaultTransition should be set instead. Note that the eventTest() + method of that transition will never be called: the selection and execution of the transition is + done automatically when entering the history state. + + By default a history state is shallow, meaning that it won't remember nested + states. This can be configured through the historyType property. +*/ + +/*! + \property QHistoryState::defaultTransition + + \brief the default transition of this history state +*/ + +/*! + \property QHistoryState::defaultState + + \brief the default state of this history state +*/ + +/*! + \property QHistoryState::historyType + + \brief the type of history that this history state records + + The default value of this property is QHistoryState::ShallowHistory. +*/ + +/*! + \enum QHistoryState::HistoryType + + This enum specifies the type of history that a QHistoryState records. + + \value ShallowHistory Only the immediate child states of the parent state + are recorded. In this case a transition with the history state as its + target will end up in the immediate child state that the parent was in the + last time it was exited. This is the default. + + \value DeepHistory Nested states are recorded. In this case a transition + with the history state as its target will end up in the most deeply nested + descendant state the parent was in the last time it was exited. +*/ + +namespace { +class DefaultStateTransition: public QAbstractTransition +{ + Q_OBJECT + +public: + DefaultStateTransition(QHistoryState *source, QAbstractState *target); + +protected: + // It doesn't matter whether this transition matches any event or not. It is always associated + // with a QHistoryState, and as soon as the state-machine detects that it enters a history + // state, it will handle this transition as a special case. The history state itself is never + // entered either: either the stored configuration will be used, or the target(s) of this + // transition are used. + bool eventTest(QEvent *event) override { Q_UNUSED(event); return false; } + void onTransition(QEvent *event) override { Q_UNUSED(event); } +}; +} + +QHistoryStatePrivate::QHistoryStatePrivate() + : QAbstractStatePrivate(HistoryState) + , defaultTransition(nullptr) + , historyType(QHistoryState::ShallowHistory) +{ +} + +DefaultStateTransition::DefaultStateTransition(QHistoryState *source, QAbstractState *target) + : QAbstractTransition() +{ + setParent(source); + setTargetState(target); +} + +/*! + Constructs a new shallow history state with the given \a parent state. +*/ +QHistoryState::QHistoryState(QState *parent) + : QAbstractState(*new QHistoryStatePrivate, parent) +{ +} +/*! + Constructs a new history state of the given \a type, with the given \a + parent state. +*/ +QHistoryState::QHistoryState(HistoryType type, QState *parent) + : QAbstractState(*new QHistoryStatePrivate, parent) +{ + Q_D(QHistoryState); + d->historyType = type; +} + +/*! + Destroys this history state. +*/ +QHistoryState::~QHistoryState() +{ +} + +/*! + Returns this history state's default transition. The default transition is + taken when the history state has never been entered before. The target states + of the default transition therefore make up the default state. + + \since 5.6 +*/ +QAbstractTransition *QHistoryState::defaultTransition() const +{ + Q_D(const QHistoryState); + return d->defaultTransition; +} + +/*! + Sets this history state's default transition to be the given \a transition. + This will set the source state of the \a transition to the history state. + + Note that the eventTest method of the \a transition will never be called. + + \since 5.6 +*/ +void QHistoryState::setDefaultTransition(QAbstractTransition *transition) +{ + Q_D(QHistoryState); + if (d->defaultTransition != transition) { + d->defaultTransition = transition; + transition->setParent(this); + emit defaultTransitionChanged(QHistoryState::QPrivateSignal()); + } +} + +/*! + Returns this history state's default state. The default state indicates the + state to transition to if the parent state has never been entered before. +*/ +QAbstractState *QHistoryState::defaultState() const +{ + Q_D(const QHistoryState); + return d->defaultTransition ? d->defaultTransition->targetState() : nullptr; +} + +static inline bool isSoleEntry(const QList<QAbstractState*> &states, const QAbstractState * state) +{ + return states.size() == 1 && states.first() == state; +} + +/*! + Sets this history state's default state to be the given \a state. + \a state must be a sibling of this history state. + + Note that this function does not set \a state as the initial state + of its parent. +*/ +void QHistoryState::setDefaultState(QAbstractState *state) +{ + Q_D(QHistoryState); + if (state && state->parentState() != parentState()) { + qWarning("QHistoryState::setDefaultState: state %p does not belong " + "to this history state's group (%p)", state, parentState()); + return; + } + if (!d->defaultTransition || !isSoleEntry(d->defaultTransition->targetStates(), state)) { + if (!d->defaultTransition || !qobject_cast<DefaultStateTransition*>(d->defaultTransition)) { + d->defaultTransition = new DefaultStateTransition(this, state); + emit defaultTransitionChanged(QHistoryState::QPrivateSignal()); + } else { + d->defaultTransition->setTargetState(state); + } + emit defaultStateChanged(QHistoryState::QPrivateSignal()); + } +} + +/*! + Returns the type of history that this history state records. +*/ +QHistoryState::HistoryType QHistoryState::historyType() const +{ + Q_D(const QHistoryState); + return d->historyType; +} + +/*! + Sets the \a type of history that this history state records. +*/ +void QHistoryState::setHistoryType(HistoryType type) +{ + Q_D(QHistoryState); + if (d->historyType != type) { + d->historyType = type; + emit historyTypeChanged(QHistoryState::QPrivateSignal()); + } +} + +/*! + \reimp +*/ +void QHistoryState::onEntry(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QHistoryState::onExit(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +bool QHistoryState::event(QEvent *e) +{ + return QAbstractState::event(e); +} + +/*! + \fn QHistoryState::defaultStateChanged() + \since 5.4 + + This signal is emitted when the defaultState property is changed. + + \sa QHistoryState::defaultState +*/ + +/*! + \fn QHistoryState::historyTypeChanged() + \since 5.4 + + This signal is emitted when the historyType property is changed. + + \sa QHistoryState::historyType +*/ + +/*! + \fn QHistoryState::defaultTransitionChanged() + \since 5.6 + + This signal is emitted when the defaultTransition property is changed. + + \sa QHistoryState::defaultTransition +*/ + +QT_END_NAMESPACE + +#include "moc_qhistorystate.cpp" +#include "qhistorystate.moc" diff --git a/src/statemachine/qhistorystate.h b/src/statemachine/qhistorystate.h new file mode 100644 index 0000000..a4f76ba --- /dev/null +++ b/src/statemachine/qhistorystate.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHISTORYSTATE_H +#define QHISTORYSTATE_H + +#include <QtStateMachine/qabstractstate.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QAbstractTransition; +class QHistoryStatePrivate; +class Q_STATEMACHINE_EXPORT QHistoryState : public QAbstractState +{ + Q_OBJECT + Q_PROPERTY(QAbstractState* defaultState READ defaultState WRITE setDefaultState NOTIFY defaultStateChanged) + Q_PROPERTY(QAbstractTransition* defaultTransition READ defaultTransition WRITE setDefaultTransition NOTIFY defaultTransitionChanged) + Q_PROPERTY(HistoryType historyType READ historyType WRITE setHistoryType NOTIFY historyTypeChanged) +public: + enum HistoryType { + ShallowHistory, + DeepHistory + }; + Q_ENUM(HistoryType) + + QHistoryState(QState *parent = nullptr); + QHistoryState(HistoryType type, QState *parent = nullptr); + ~QHistoryState(); + + QAbstractTransition *defaultTransition() const; + void setDefaultTransition(QAbstractTransition *transition); + + QAbstractState *defaultState() const; + void setDefaultState(QAbstractState *state); + + HistoryType historyType() const; + void setHistoryType(HistoryType type); + +Q_SIGNALS: + void defaultTransitionChanged(QPrivateSignal); + void defaultStateChanged(QPrivateSignal); + void historyTypeChanged(QPrivateSignal); + +protected: + void onEntry(QEvent *event) override; + void onExit(QEvent *event) override; + + bool event(QEvent *e) override; + +private: + Q_DISABLE_COPY(QHistoryState) + Q_DECLARE_PRIVATE(QHistoryState) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qhistorystate_p.h b/src/statemachine/qhistorystate_p.h new file mode 100644 index 0000000..bb9fe12 --- /dev/null +++ b/src/statemachine/qhistorystate_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHISTORYSTATE_P_H +#define QHISTORYSTATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractstate_p.h" + +#include <QtCore/qlist.h> + +#include <QtStateMachine/qabstracttransition.h> +#include <QtStateMachine/qhistorystate.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QHistoryStatePrivate : public QAbstractStatePrivate +{ + Q_DECLARE_PUBLIC(QHistoryState) + +public: + QHistoryStatePrivate(); + + static QHistoryStatePrivate *get(QHistoryState *q) + { return q->d_func(); } + + QAbstractTransition *defaultTransition; + QHistoryState::HistoryType historyType; + QList<QAbstractState*> configuration; +}; + +QT_END_NAMESPACE + +#endif // QHISTORYSTATE_P_H diff --git a/src/statemachine/qsignaleventgenerator_p.h b/src/statemachine/qsignaleventgenerator_p.h new file mode 100644 index 0000000..178da9b --- /dev/null +++ b/src/statemachine/qsignaleventgenerator_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIGNALEVENTGENERATOR_P_H +#define QSIGNALEVENTGENERATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include <QtCore/qobject.h> + +#include <QtStateMachine/qstatemachineglobal.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QStateMachine; + +class QSignalEventGenerator : public QObject +{ + Q_OBJECT +public: + QSignalEventGenerator(QStateMachine *parent); + +private Q_SLOTS: + void execute(QMethodRawArguments a); + +private: + Q_DISABLE_COPY_MOVE(QSignalEventGenerator) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qsignaltransition.cpp b/src/statemachine/qsignaltransition.cpp new file mode 100644 index 0000000..bc88741 --- /dev/null +++ b/src/statemachine/qsignaltransition.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsignaltransition.h" +#include "qsignaltransition_p.h" +#include "qstate.h" +#include "qstate_p.h" +#include "qstatemachine.h" +#include "qstatemachine_p.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QSignalTransition + \inmodule QtStateMachine + + \brief The QSignalTransition class provides a transition based on a Qt signal. + + \since 4.6 + \ingroup statemachine + + Typically you would use the overload of QState::addTransition() that takes a + sender and signal as arguments, rather than creating QSignalTransition + objects directly. QSignalTransition is part of \l{The State Machine + Framework}. + + You can subclass QSignalTransition and reimplement eventTest() to make a + signal transition conditional; the event object passed to eventTest() will + be a QStateMachine::SignalEvent object. Example: + + \code + class CheckedTransition : public QSignalTransition + { + public: + CheckedTransition(QCheckBox *check) + : QSignalTransition(check, SIGNAL(stateChanged(int))) {} + protected: + bool eventTest(QEvent *e) { + if (!QSignalTransition::eventTest(e)) + return false; + QStateMachine::SignalEvent *se = static_cast<QStateMachine::SignalEvent*>(e); + return (se->arguments().at(0).toInt() == Qt::Checked); + } + }; + + ... + + QCheckBox *check = new QCheckBox(); + check->setTristate(true); + + QState *s1 = new QState(); + QState *s2 = new QState(); + CheckedTransition *t1 = new CheckedTransition(check); + t1->setTargetState(s2); + s1->addTransition(t1); + \endcode +*/ + +/*! + \property QSignalTransition::senderObject + + \brief the sender object that this signal transition is associated with +*/ + +/*! + \property QSignalTransition::signal + + \brief the signal that this signal transition is associated with +*/ + +QSignalTransitionPrivate::QSignalTransitionPrivate() +{ + sender = nullptr; + signalIndex = -1; +} + +void QSignalTransitionPrivate::unregister() +{ + Q_Q(QSignalTransition); + if ((signalIndex == -1) || !machine()) + return; + QStateMachinePrivate::get(machine())->unregisterSignalTransition(q); +} + +void QSignalTransitionPrivate::maybeRegister() +{ + Q_Q(QSignalTransition); + if (QStateMachine *mach = machine()) + QStateMachinePrivate::get(mach)->maybeRegisterSignalTransition(q); +} + +/*! + Constructs a new signal transition with the given \a sourceState. +*/ +QSignalTransition::QSignalTransition(QState *sourceState) + : QAbstractTransition(*new QSignalTransitionPrivate, sourceState) +{ +} + +/*! + Constructs a new signal transition associated with the given \a signal of + the given \a sender, and with the given \a sourceState. +*/ +QSignalTransition::QSignalTransition(const QObject *sender, const char *signal, + QState *sourceState) + : QAbstractTransition(*new QSignalTransitionPrivate, sourceState) +{ + Q_D(QSignalTransition); + d->sender = sender; + d->signal = signal; + d->maybeRegister(); +} + +/*! + \fn template <typename PointerToMemberFunction> QSignalTransition::QSignalTransition(const QObject *sender, PointerToMemberFunction signal, QState *sourceState) + \since 5.7 + \overload + + Constructs a new signal transition associated with the given \a signal of + the given \a sender object and with the given \a sourceState. + This constructor is enabled if the compiler supports delegating constructors, + as indicated by the presence of the macro Q_COMPILER_DELEGATING_CONSTRUCTORS. +*/ + +/*! + Destroys this signal transition. +*/ +QSignalTransition::~QSignalTransition() +{ +} + +/*! + Returns the sender object associated with this signal transition. +*/ +QObject *QSignalTransition::senderObject() const +{ + Q_D(const QSignalTransition); + return const_cast<QObject *>(d->sender); +} + +/*! + Sets the \a sender object associated with this signal transition. +*/ +void QSignalTransition::setSenderObject(const QObject *sender) +{ + Q_D(QSignalTransition); + if (sender == d->sender) + return; + d->unregister(); + d->sender = sender; + d->maybeRegister(); + emit senderObjectChanged(QPrivateSignal()); +} + +/*! + Returns the signal associated with this signal transition. +*/ +QByteArray QSignalTransition::signal() const +{ + Q_D(const QSignalTransition); + return d->signal; +} + +/*! + Sets the \a signal associated with this signal transition. +*/ +void QSignalTransition::setSignal(const QByteArray &signal) +{ + Q_D(QSignalTransition); + if (signal == d->signal) + return; + d->unregister(); + d->signal = signal; + d->maybeRegister(); + emit signalChanged(QPrivateSignal()); +} + +/*! + \reimp + + The default implementation returns \c true if the \a event is a + QStateMachine::SignalEvent object and the event's sender and signal index + match this transition, and returns \c false otherwise. +*/ +bool QSignalTransition::eventTest(QEvent *event) +{ + Q_D(const QSignalTransition); + if (event->type() == QEvent::StateMachineSignal) { + if (d->signalIndex == -1) + return false; + QStateMachine::SignalEvent *se = static_cast<QStateMachine::SignalEvent*>(event); + return (se->sender() == d->sender) + && (se->signalIndex() == d->signalIndex); + } + return false; +} + +/*! + \reimp +*/ +void QSignalTransition::onTransition(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +bool QSignalTransition::event(QEvent *e) +{ + return QAbstractTransition::event(e); +} + +/*! + \fn QSignalTransition::senderObjectChanged() + \since 5.4 + + This signal is emitted when the senderObject property is changed. + + \sa QSignalTransition::senderObject +*/ + +/*! + \fn QSignalTransition::signalChanged() + \since 5.4 + + This signal is emitted when the signal property is changed. + + \sa QSignalTransition::signal +*/ + +void QSignalTransitionPrivate::callOnTransition(QEvent *e) +{ + Q_Q(QSignalTransition); + + if (e->type() == QEvent::StateMachineSignal) { + QStateMachine::SignalEvent *se = static_cast<QStateMachine::SignalEvent *>(e); + int savedSignalIndex = se->m_signalIndex; + se->m_signalIndex = originalSignalIndex; + q->onTransition(e); + se->m_signalIndex = savedSignalIndex; + } else { + q->onTransition(e); + } +} + + +QT_END_NAMESPACE + +#include "moc_qsignaltransition.cpp" diff --git a/src/statemachine/qsignaltransition.h b/src/statemachine/qsignaltransition.h new file mode 100644 index 0000000..7e64b4c --- /dev/null +++ b/src/statemachine/qsignaltransition.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIGNALTRANSITION_H +#define QSIGNALTRANSITION_H + +#include <QtCore/qmetaobject.h> +#include <QtStateMachine/qabstracttransition.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QSignalTransitionPrivate; +class Q_STATEMACHINE_EXPORT QSignalTransition : public QAbstractTransition +{ + Q_OBJECT + Q_PROPERTY(QObject* senderObject READ senderObject WRITE setSenderObject NOTIFY senderObjectChanged) + Q_PROPERTY(QByteArray signal READ signal WRITE setSignal NOTIFY signalChanged) + +public: + QSignalTransition(QState *sourceState = nullptr); + QSignalTransition(const QObject *sender, const char *signal, + QState *sourceState = nullptr); +#ifdef Q_QDOC + template<typename PointerToMemberFunction> + QSignalTransition(const QObject *object, PointerToMemberFunction signal, + QState *sourceState = nullptr); +#elif defined(Q_COMPILER_DELEGATING_CONSTRUCTORS) + template <typename Func> + QSignalTransition(const typename QtPrivate::FunctionPointer<Func>::Object *obj, + Func sig, QState *srcState = nullptr) + : QSignalTransition(obj, QMetaMethod::fromSignal(sig).methodSignature().constData(), srcState) + { + } +#endif + + ~QSignalTransition(); + + QObject *senderObject() const; + void setSenderObject(const QObject *sender); + + QByteArray signal() const; + void setSignal(const QByteArray &signal); + +protected: + bool eventTest(QEvent *event) override; + void onTransition(QEvent *event) override; + + bool event(QEvent *e) override; + +Q_SIGNALS: + void senderObjectChanged(QPrivateSignal); + void signalChanged(QPrivateSignal); + +private: + Q_DISABLE_COPY(QSignalTransition) + Q_DECLARE_PRIVATE(QSignalTransition) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qsignaltransition_p.h b/src/statemachine/qsignaltransition_p.h new file mode 100644 index 0000000..b3de334 --- /dev/null +++ b/src/statemachine/qsignaltransition_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIGNALTRANSITION_P_H +#define QSIGNALTRANSITION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstracttransition_p.h" + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QSignalTransition; +class QSignalTransitionPrivate : public QAbstractTransitionPrivate +{ + Q_DECLARE_PUBLIC(QSignalTransition) +public: + QSignalTransitionPrivate(); + + static QSignalTransitionPrivate *get(QSignalTransition *q) + { return q->d_func(); } + + void unregister(); + void maybeRegister(); + + void callOnTransition(QEvent *e) override; + + const QObject *sender; + QByteArray signal; + int signalIndex; + int originalSignalIndex; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qstate.cpp b/src/statemachine/qstate.cpp new file mode 100644 index 0000000..66f1841 --- /dev/null +++ b/src/statemachine/qstate.cpp @@ -0,0 +1,603 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstate_p.h" +#include "qhistorystate.h" +#include "qhistorystate_p.h" +#include "qabstracttransition.h" +#include "qabstracttransition_p.h" +#include "qsignaltransition.h" +#include "qstatemachine.h" +#include "qstatemachine_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QState + \inmodule QtStateMachine + + \brief The QState class provides a general-purpose state for QStateMachine. + + \since 4.6 + \ingroup statemachine + + QState objects can have child states, and can have transitions to other + states. QState is part of \l{The State Machine Framework}. + + The addTransition() function adds a transition. The removeTransition() + function removes a transition. The transitions() function returns the + state's outgoing transitions. + + The assignProperty() function is used for defining property assignments that + should be performed when a state is entered. + + Top-level states must be passed a QStateMachine object as their parent + state, or added to a state machine using QStateMachine::addState(). + + \section1 States with Child States + + The childMode property determines how child states are treated. For + non-parallel state groups, the setInitialState() function must be called to + set the initial state. The child states are mutually exclusive states, and + the state machine needs to know which child state to enter when the parent + state is the target of a transition. + + The state emits the QState::finished() signal when a final child state + (QFinalState) is entered. + + The setErrorState() sets the state's error state. The error state is the + state that the state machine will transition to if an error is detected when + attempting to enter the state (e.g. because no initial state has been set). + +*/ + +/*! + \property QState::initialState + + \brief the initial state of this state (one of its child states) +*/ + +/*! + \property QState::errorState + + \brief the error state of this state +*/ + +/*! + \property QState::childMode + + \brief the child mode of this state + + The default value of this property is QState::ExclusiveStates. +*/ + +/*! + \enum QState::ChildMode + + This enum specifies how a state's child states are treated. + + \value ExclusiveStates The child states are mutually exclusive and an + initial state must be set by calling QState::setInitialState(). + + \value ParallelStates The child states are parallel. When the parent state + is entered, all its child states are entered in parallel. +*/ + +/*! + \enum QState::RestorePolicy + + This enum specifies the restore policy type. The restore policy + takes effect when the machine enters a state which sets one or more + properties. If the restore policy is set to RestoreProperties, + the state machine will save the original value of the property before the + new value is set. + + Later, when the machine either enters a state which does not set + a value for the given property, the property will automatically be restored + to its initial value. + + Only one initial value will be saved for any given property. If a value for a property has + already been saved by the state machine, it will not be overwritten until the property has been + successfully restored. + + \value DontRestoreProperties The state machine should not save the initial values of properties + and restore them later. + \value RestoreProperties The state machine should save the initial values of properties + and restore them later. + + \sa QStateMachine::globalRestorePolicy, QState::assignProperty() +*/ + +QStatePrivate::QStatePrivate() + : QAbstractStatePrivate(StandardState), + errorState(nullptr), initialState(nullptr), childMode(QState::ExclusiveStates), + childStatesListNeedsRefresh(true), transitionsListNeedsRefresh(true) +{ +} + +QStatePrivate::~QStatePrivate() +{ +} + +void QStatePrivate::emitFinished() +{ + Q_Q(QState); + emit q->finished(QState::QPrivateSignal()); +} + +void QStatePrivate::emitPropertiesAssigned() +{ + Q_Q(QState); + emit q->propertiesAssigned(QState::QPrivateSignal()); +} + +/*! + Constructs a new state with the given \a parent state. +*/ +QState::QState(QState *parent) + : QAbstractState(*new QStatePrivate, parent) +{ +} + +/*! + Constructs a new state with the given \a childMode and the given \a parent + state. +*/ +QState::QState(ChildMode childMode, QState *parent) + : QAbstractState(*new QStatePrivate, parent) +{ + Q_D(QState); + d->childMode = childMode; +} + +/*! + \internal +*/ +QState::QState(QStatePrivate &dd, QState *parent) + : QAbstractState(dd, parent) +{ +} + +/*! + Destroys this state. +*/ +QState::~QState() +{ +} + +QList<QAbstractState*> QStatePrivate::childStates() const +{ + if (childStatesListNeedsRefresh) { + childStatesList.clear(); + QList<QObject*>::const_iterator it; + for (it = children.constBegin(); it != children.constEnd(); ++it) { + QAbstractState *s = qobject_cast<QAbstractState*>(*it); + if (!s || qobject_cast<QHistoryState*>(s)) + continue; + childStatesList.append(s); + } + childStatesListNeedsRefresh = false; + } + return childStatesList; +} + +QList<QHistoryState*> QStatePrivate::historyStates() const +{ + QList<QHistoryState*> result; + QList<QObject*>::const_iterator it; + for (it = children.constBegin(); it != children.constEnd(); ++it) { + QHistoryState *h = qobject_cast<QHistoryState*>(*it); + if (h) + result.append(h); + } + return result; +} + +QList<QAbstractTransition*> QStatePrivate::transitions() const +{ + if (transitionsListNeedsRefresh) { + transitionsList.clear(); + QList<QObject*>::const_iterator it; + for (it = children.constBegin(); it != children.constEnd(); ++it) { + QAbstractTransition *t = qobject_cast<QAbstractTransition*>(*it); + if (t) + transitionsList.append(t); + } + transitionsListNeedsRefresh = false; + } + return transitionsList; +} + +#ifndef QT_NO_PROPERTIES + +/*! + 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 propertiesAssigned() +*/ +void QState::assignProperty(QObject *object, const char *name, + const QVariant &value) +{ + Q_D(QState); + if (!object) { + qWarning("QState::assignProperty: cannot assign property '%s' of null object", name); + return; + } + for (int i = 0; i < d->propertyAssignments.size(); ++i) { + QPropertyAssignment &assn = d->propertyAssignments[i]; + if (assn.hasTarget(object, name)) { + assn.value = value; + return; + } + } + d->propertyAssignments.append(QPropertyAssignment(object, name, value)); +} + +#endif // QT_NO_PROPERTIES + +/*! + Returns this state's error state. + + \sa QStateMachine::error() +*/ +QAbstractState *QState::errorState() const +{ + Q_D(const QState); + return d->errorState; +} + +/*! + Sets this state's error state to be the given \a state. If the error state + is not set, or if it is set to \nullptr, the state will inherit its parent's error + state recursively. If no error state is set for the state itself or any of + its ancestors, an error will cause the machine to stop executing and an error + will be printed to the console. +*/ +void QState::setErrorState(QAbstractState *state) +{ + Q_D(QState); + if (state != nullptr && qobject_cast<QStateMachine*>(state)) { + qWarning("QStateMachine::setErrorState: root state cannot be error state"); + return; + } + if (state != nullptr && (!state->machine() || ((state->machine() != machine()) && !qobject_cast<QStateMachine*>(this)))) { + qWarning("QState::setErrorState: error state cannot belong " + "to a different state machine"); + return; + } + + if (d->errorState != state) { + d->errorState = state; + emit errorStateChanged(QState::QPrivateSignal()); + } +} + +/*! + Adds the given \a transition. The transition has this state as the source. + This state takes ownership of the transition. +*/ +void QState::addTransition(QAbstractTransition *transition) +{ + Q_D(QState); + if (!transition) { + qWarning("QState::addTransition: cannot add null transition"); + return ; + } + + transition->setParent(this); + const QList<QPointer<QAbstractState>> &targets = QAbstractTransitionPrivate::get(transition)->targetStates; + for (int i = 0; i < targets.size(); ++i) { + QAbstractState *t = targets.at(i).data(); + if (!t) { + qWarning("QState::addTransition: cannot add transition to null state"); + return ; + } + if ((QAbstractStatePrivate::get(t)->machine() != d->machine()) + && QAbstractStatePrivate::get(t)->machine() && d->machine()) { + qWarning("QState::addTransition: cannot add transition " + "to a state in a different state machine"); + return ; + } + } + if (QStateMachine *mach = machine()) + QStateMachinePrivate::get(mach)->maybeRegisterTransition(transition); +} + +/*! + \fn template <typename PointerToMemberFunction> QState::addTransition(const QObject *sender, PointerToMemberFunction signal, QAbstractState *target); + \since 5.5 + \overload + + Adds a transition associated with the given \a signal of the given \a sender + object, and returns the new QSignalTransition object. The transition has + this state as the source, and the given \a target as the target state. +*/ + +/*! + Adds a transition associated with the given \a signal of the given \a sender + object, and returns the new QSignalTransition object. The transition has + this state as the source, and the given \a target as the target state. +*/ +QSignalTransition *QState::addTransition(const QObject *sender, const char *signal, + QAbstractState *target) +{ + if (!sender) { + qWarning("QState::addTransition: sender cannot be null"); + return nullptr; + } + if (!signal) { + qWarning("QState::addTransition: signal cannot be null"); + return nullptr; + } + if (!target) { + qWarning("QState::addTransition: cannot add transition to null state"); + return nullptr; + } + int offset = (*signal == '0'+QSIGNAL_CODE) ? 1 : 0; + const QMetaObject *meta = sender->metaObject(); + if (meta->indexOfSignal(signal+offset) == -1) { + if (meta->indexOfSignal(QMetaObject::normalizedSignature(signal+offset)) == -1) { + qWarning("QState::addTransition: no such signal %s::%s", + meta->className(), signal+offset); + return nullptr; + } + } + QSignalTransition *trans = new QSignalTransition(sender, signal); + trans->setTargetState(target); + addTransition(trans); + return trans; +} + +namespace { + +// ### Make public? +class UnconditionalTransition : public QAbstractTransition +{ +public: + UnconditionalTransition(QAbstractState *target) + : QAbstractTransition() + { setTargetState(target); } +protected: + void onTransition(QEvent *) override {} + bool eventTest(QEvent *) override { return true; } +}; + +} // namespace + +/*! + Adds an unconditional transition from this state to the given \a target + state, and returns then new transition object. +*/ +QAbstractTransition *QState::addTransition(QAbstractState *target) +{ + if (!target) { + qWarning("QState::addTransition: cannot add transition to null state"); + return nullptr; + } + UnconditionalTransition *trans = new UnconditionalTransition(target); + addTransition(trans); + return trans; +} + +/*! + Removes the given \a transition from this state. The state releases + ownership of the transition. + + \sa addTransition() +*/ +void QState::removeTransition(QAbstractTransition *transition) +{ + Q_D(QState); + if (!transition) { + qWarning("QState::removeTransition: cannot remove null transition"); + return; + } + if (transition->sourceState() != this) { + qWarning("QState::removeTransition: transition %p's source state (%p)" + " is different from this state (%p)", + transition, transition->sourceState(), this); + return; + } + QStateMachinePrivate *mach = QStateMachinePrivate::get(d->machine()); + if (mach) + mach->unregisterTransition(transition); + transition->setParent(nullptr); +} + +/*! + \since 4.7 + + Returns this state's outgoing transitions (i.e. transitions where + this state is the \l{QAbstractTransition::sourceState()}{source + state}), or an empty list if this state has no outgoing transitions. + + \sa addTransition() +*/ +QList<QAbstractTransition*> QState::transitions() const +{ + Q_D(const QState); + return d->transitions(); +} + +/*! + \reimp +*/ +void QState::onEntry(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QState::onExit(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + Returns this state's initial state, or \nullptr if the state has no + initial state. +*/ +QAbstractState *QState::initialState() const +{ + Q_D(const QState); + return d->initialState; +} + +/*! + Sets this state's initial state to be the given \a state. + \a state has to be a child of this state. +*/ +void QState::setInitialState(QAbstractState *state) +{ + Q_D(QState); + if (d->childMode == QState::ParallelStates) { + qWarning("QState::setInitialState: ignoring attempt to set initial state " + "of parallel state group %p", this); + return; + } + if (state && (state->parentState() != this)) { + qWarning("QState::setInitialState: state %p is not a child of this state (%p)", + state, this); + return; + } + if (d->initialState != state) { + d->initialState = state; + emit initialStateChanged(QState::QPrivateSignal()); + } +} + +/*! + Returns the child mode of this state. +*/ +QState::ChildMode QState::childMode() const +{ + Q_D(const QState); + return d->childMode; +} + +/*! + Sets the child \a mode of this state. +*/ +void QState::setChildMode(ChildMode mode) +{ + Q_D(QState); + + if (mode == QState::ParallelStates && d->initialState) { + qWarning("QState::setChildMode: setting the child-mode of state %p to " + "parallel removes the initial state", this); + d->initialState = nullptr; + emit initialStateChanged(QState::QPrivateSignal()); + } + + if (d->childMode != mode) { + d->childMode = mode; + emit childModeChanged(QState::QPrivateSignal()); + } +} + +/*! + \reimp +*/ +bool QState::event(QEvent *e) +{ + Q_D(QState); + if ((e->type() == QEvent::ChildAdded) || (e->type() == QEvent::ChildRemoved)) { + d->childStatesListNeedsRefresh = true; + d->transitionsListNeedsRefresh = true; + if ((e->type() == QEvent::ChildRemoved) && (static_cast<QChildEvent *>(e)->child() == d->initialState)) + d->initialState = nullptr; + } + return QAbstractState::event(e); +} + +/*! + \fn QState::finished() + + This signal is emitted when a final child state of this state is entered. + + \sa QFinalState +*/ + +/*! + \fn QState::propertiesAssigned() + + This signal is emitted when all properties have been assigned their final value. If the state + assigns a value to one or more properties for which an animation exists (either set on the + transition or as a default animation on the state machine), then the signal will not be emitted + until all such animations have finished playing. + + If there are no relevant animations, or no property assignments defined for the state, then + the signal will be emitted immediately before the state is entered. + + \sa QState::assignProperty(), QAbstractTransition::addAnimation() +*/ + +/*! + \fn QState::childModeChanged() + \since 5.4 + + This signal is emitted when the childMode property is changed. + + \sa QState::childMode +*/ + +/*! + \fn QState::initialStateChanged() + \since 5.4 + + This signal is emitted when the initialState property is changed. + + \sa QState::initialState +*/ + +/*! + \fn QState::errorStateChanged() + \since 5.4 + + This signal is emitted when the errorState property is changed. + + \sa QState::errorState +*/ + +QT_END_NAMESPACE + +#include "moc_qstate.cpp" diff --git a/src/statemachine/qstate.h b/src/statemachine/qstate.h new file mode 100644 index 0000000..586e888 --- /dev/null +++ b/src/statemachine/qstate.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATE_H +#define QSTATE_H + +#include <QtCore/qlist.h> +#include <QtCore/qmetaobject.h> + +#include <QtStateMachine/qabstractstate.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QAbstractTransition; +class QSignalTransition; + +class QStatePrivate; +class Q_STATEMACHINE_EXPORT QState : public QAbstractState +{ + Q_OBJECT + Q_PROPERTY(QAbstractState* initialState READ initialState WRITE setInitialState NOTIFY initialStateChanged) + Q_PROPERTY(QAbstractState* errorState READ errorState WRITE setErrorState NOTIFY errorStateChanged) + Q_PROPERTY(ChildMode childMode READ childMode WRITE setChildMode NOTIFY childModeChanged) +public: + enum ChildMode { + ExclusiveStates, + ParallelStates + }; + Q_ENUM(ChildMode) + + enum RestorePolicy { + DontRestoreProperties, + RestoreProperties + }; + Q_ENUM(RestorePolicy) + + QState(QState *parent = nullptr); + QState(ChildMode childMode, QState *parent = nullptr); + ~QState(); + + QAbstractState *errorState() const; + void setErrorState(QAbstractState *state); + + void addTransition(QAbstractTransition *transition); + QSignalTransition *addTransition(const QObject *sender, const char *signal, QAbstractState *target); +#ifdef Q_QDOC + template<typename PointerToMemberFunction> + QSignalTransition *addTransition(const QObject *sender, PointerToMemberFunction signal, + QAbstractState *target); +#else + template <typename Func> + QSignalTransition *addTransition(const typename QtPrivate::FunctionPointer<Func>::Object *obj, + Func signal, QAbstractState *target) + { + const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal); + return addTransition(obj, signalMetaMethod.methodSignature().constData(), target); + } +#endif // Q_QDOC + QAbstractTransition *addTransition(QAbstractState *target); + void removeTransition(QAbstractTransition *transition); + QList<QAbstractTransition*> transitions() const; + + QAbstractState *initialState() const; + void setInitialState(QAbstractState *state); + + ChildMode childMode() const; + void setChildMode(ChildMode mode); + +#ifndef QT_NO_PROPERTIES + void assignProperty(QObject *object, const char *name, + const QVariant &value); +#endif + +Q_SIGNALS: + void finished(QPrivateSignal); + void propertiesAssigned(QPrivateSignal); + void childModeChanged(QPrivateSignal); + void initialStateChanged(QPrivateSignal); + void errorStateChanged(QPrivateSignal); + +protected: + void onEntry(QEvent *event) override; + void onExit(QEvent *event) override; + + bool event(QEvent *e) override; + +protected: + QState(QStatePrivate &dd, QState *parent); + +private: + Q_DISABLE_COPY(QState) + Q_DECLARE_PRIVATE(QState) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qstate_p.h b/src/statemachine/qstate_p.h new file mode 100644 index 0000000..113a5fa --- /dev/null +++ b/src/statemachine/qstate_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATE_P_H +#define QSTATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qstate.h" +#include "private/qabstractstate_p.h" + +#include <QtCore/qlist.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qpointer.h> +#include <QtCore/qvariant.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PROPERTIES + +struct QPropertyAssignment +{ + QPropertyAssignment() + : object(nullptr), explicitlySet(true) {} + QPropertyAssignment(QObject *o, const QByteArray &n, + const QVariant &v, bool es = true) + : object(o), propertyName(n), value(v), explicitlySet(es) + {} + + bool objectDeleted() const { return !object; } + void write() const { Q_ASSERT(object != nullptr); object->setProperty(propertyName, value); } + bool hasTarget(QObject *o, const QByteArray &pn) const + { return object == o && propertyName == pn; } + + QPointer<QObject> object; + QByteArray propertyName; + QVariant value; + bool explicitlySet; // false means the property is being restored to its old value +}; +Q_DECLARE_TYPEINFO(QPropertyAssignment, Q_MOVABLE_TYPE); + +#endif // QT_NO_PROPERTIES + +class QAbstractTransition; +class QHistoryState; + +class QState; +class Q_STATEMACHINE_EXPORT QStatePrivate : public QAbstractStatePrivate +{ + Q_DECLARE_PUBLIC(QState) +public: + QStatePrivate(); + ~QStatePrivate(); + + static QStatePrivate *get(QState *q) { return q ? q->d_func() : nullptr; } + static const QStatePrivate *get(const QState *q) { return q? q->d_func() : nullptr; } + + QList<QAbstractState*> childStates() const; + QList<QHistoryState*> historyStates() const; + QList<QAbstractTransition*> transitions() const; + + void emitFinished(); + void emitPropertiesAssigned(); + + QAbstractState *errorState; + QAbstractState *initialState; + QState::ChildMode childMode; + mutable bool childStatesListNeedsRefresh; + mutable bool transitionsListNeedsRefresh; + mutable QList<QAbstractState*> childStatesList; + mutable QList<QAbstractTransition*> transitionsList; + +#ifndef QT_NO_PROPERTIES + QList<QPropertyAssignment> propertyAssignments; +#endif +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qstatemachine.cpp b/src/statemachine/qstatemachine.cpp new file mode 100644 index 0000000..2733b87 --- /dev/null +++ b/src/statemachine/qstatemachine.cpp @@ -0,0 +1,3207 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstatemachine.h" +#include "qstate.h" +#include "qstate_p.h" +#include "qstatemachine_p.h" +#include "qabstracttransition.h" +#include "qabstracttransition_p.h" +#include "qsignaltransition.h" +#include "qsignaltransition_p.h" +#include "qsignaleventgenerator_p.h" +#include "qabstractstate.h" +#include "qabstractstate_p.h" +#include "qfinalstate.h" +#include "qhistorystate.h" +#include "qhistorystate_p.h" + +#include "private/qcoreapplication_p.h" +#include "private/qobject_p.h" +#include "private/qthread_p.h" + +#if QT_CONFIG(qeventtransition) +#include "qeventtransition.h" +#include "qeventtransition_p.h" +#endif + +#if QT_CONFIG(animation) +#include "qpropertyanimation.h" +#include "qanimationgroup.h" +#include <private/qvariantanimation_p.h> +#endif + +#include <QtCore/qmetaobject.h> +#include <qdebug.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +enum { + Offset0 = 0x00000000, + Offset1 = 0x00008000, + Offset2 = 0x00080000, + Offset3 = 0x00800000, + + Size0 = Offset1 - Offset0, + Size1 = Offset2 - Offset1, + Size2 = Offset3 - Offset2, + Size3 = QStateMachinePrivate::FreeListDefaultConstants::MaxIndex - Offset3 +}; + +const int QStateMachinePrivate::FreeListDefaultConstants::Sizes[FreeListDefaultConstants::BlockCount] = { + Size0, + Size1, + Size2, + Size3 +}; + +/*! + \class QStateMachine + \inmodule QtCore + \reentrant + + \brief The QStateMachine class provides a hierarchical finite state machine. + + \since 4.6 + \ingroup statemachine + + QStateMachine is based on the concepts and notation of + \l{http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf}{Statecharts}. + QStateMachine is part of \l{The State Machine Framework}. + + A state machine manages a set of states (classes that inherit from + QAbstractState) and transitions (descendants of + QAbstractTransition) between those states; these states and + transitions define a state graph. Once a state graph has been + built, the state machine can execute it. QStateMachine's + execution algorithm is based on the \l{http://www.w3.org/TR/scxml/}{State Chart XML (SCXML)} + algorithm. The framework's \l{The State Machine + Framework}{overview} gives several state graphs and the code to + build them. + + Use the addState() function to add a top-level state to the state machine. + States are removed with the removeState() function. Removing states while + the machine is running is discouraged. + + Before the machine can be started, the \l{initialState}{initial + state} must be set. The initial state is the state that the + machine enters when started. You can then start() the state + machine. The started() signal is emitted when the initial state is + entered. + + The machine is event driven and keeps its own event loop. Events + are posted to the machine through postEvent(). Note that this + means that it executes asynchronously, and that it will not + progress without a running event loop. You will normally not have + to post events to the machine directly as Qt's transitions, e.g., + QEventTransition and its subclasses, handle this. But for custom + transitions triggered by events, postEvent() is useful. + + The state machine processes events and takes transitions until a + top-level final state is entered; the state machine then emits the + finished() signal. You can also stop() the state machine + explicitly. The stopped() signal is emitted in this case. + + The following snippet shows a state machine that will finish when a button + is clicked: + + \snippet code/src_corelib_statemachine_qstatemachine.cpp simple state machine + + This code example uses QState, which inherits QAbstractState. The + QState class provides a state that you can use to set properties + and invoke methods on \l{QObject}s when the state is entered or + exited. It also contains convenience functions for adding + transitions, e.g., \l{QSignalTransition}s as in this example. See + the QState class description for further details. + + If an error is encountered, the machine will look for an + \l{errorState}{error state}, and if one is available, it will + enter this state. The types of errors possible are described by the + \l{QStateMachine::}{Error} enum. After the error state is entered, + the type of the error can be retrieved with error(). The execution + of the state graph will not stop when the error state is entered. If + no error state applies to the erroneous state, the machine will stop + executing and an error message will be printed to the console. + + \note Important: setting the \l{ChildMode} of a state machine to parallel (\l{ParallelStates}) + results in an invalid state machine. It can only be set to (or kept as) + \l{ExclusiveStates}. + + \sa QAbstractState, QAbstractTransition, QState, {The State Machine Framework} +*/ + +/*! + \property QStateMachine::errorString + + \brief the error string of this state machine +*/ + +/*! + \property QStateMachine::globalRestorePolicy + + \brief the restore policy for states of this state machine. + + The default value of this property is + QState::DontRestoreProperties. +*/ + +/*! + \property QStateMachine::running + \since 5.4 + + \brief the running state of this state machine + + \sa start(), stop(), started(), stopped(), runningChanged() +*/ + +#if QT_CONFIG(animation) +/*! + \property QStateMachine::animated + + \brief whether animations are enabled + + The default value of this property is \c true. + + \sa QAbstractTransition::addAnimation() +*/ +#endif + +// #define QSTATEMACHINE_DEBUG +// #define QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + +struct CalculationCache { + struct TransitionInfo { + QList<QAbstractState*> effectiveTargetStates; + QSet<QAbstractState*> exitSet; + QAbstractState *transitionDomain; + + bool effectiveTargetStatesIsKnown: 1; + bool exitSetIsKnown : 1; + bool transitionDomainIsKnown : 1; + + TransitionInfo() + : transitionDomain(nullptr) + , effectiveTargetStatesIsKnown(false) + , exitSetIsKnown(false) + , transitionDomainIsKnown(false) + {} + }; + + typedef QHash<QAbstractTransition *, TransitionInfo> TransitionInfoCache; + TransitionInfoCache cache; + + bool effectiveTargetStates(QAbstractTransition *t, QList<QAbstractState *> *targets) const + { + Q_ASSERT(targets); + + TransitionInfoCache::const_iterator cacheIt = cache.find(t); + if (cacheIt == cache.end() || !cacheIt->effectiveTargetStatesIsKnown) + return false; + + *targets = cacheIt->effectiveTargetStates; + return true; + } + + void insert(QAbstractTransition *t, const QList<QAbstractState *> &targets) + { + TransitionInfoCache::iterator cacheIt = cache.find(t); + TransitionInfo &ti = cacheIt == cache.end() + ? *cache.insert(t, TransitionInfo()) + : *cacheIt; + + Q_ASSERT(!ti.effectiveTargetStatesIsKnown); + ti.effectiveTargetStates = targets; + ti.effectiveTargetStatesIsKnown = true; + } + + bool exitSet(QAbstractTransition *t, QSet<QAbstractState *> *exits) const + { + Q_ASSERT(exits); + + TransitionInfoCache::const_iterator cacheIt = cache.find(t); + if (cacheIt == cache.end() || !cacheIt->exitSetIsKnown) + return false; + + *exits = cacheIt->exitSet; + return true; + } + + void insert(QAbstractTransition *t, const QSet<QAbstractState *> &exits) + { + TransitionInfoCache::iterator cacheIt = cache.find(t); + TransitionInfo &ti = cacheIt == cache.end() + ? *cache.insert(t, TransitionInfo()) + : *cacheIt; + + Q_ASSERT(!ti.exitSetIsKnown); + ti.exitSet = exits; + ti.exitSetIsKnown = true; + } + + bool transitionDomain(QAbstractTransition *t, QAbstractState **domain) const + { + Q_ASSERT(domain); + + TransitionInfoCache::const_iterator cacheIt = cache.find(t); + if (cacheIt == cache.end() || !cacheIt->transitionDomainIsKnown) + return false; + + *domain = cacheIt->transitionDomain; + return true; + } + + void insert(QAbstractTransition *t, QAbstractState *domain) + { + TransitionInfoCache::iterator cacheIt = cache.find(t); + TransitionInfo &ti = cacheIt == cache.end() + ? *cache.insert(t, TransitionInfo()) + : *cacheIt; + + Q_ASSERT(!ti.transitionDomainIsKnown); + ti.transitionDomain = domain; + ti.transitionDomainIsKnown = true; + } +}; + +/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +function isDescendant(state1, state2) + +Returns 'true' if state1 is a descendant of state2 (a child, or a child of a child, or a child of a +child of a child, etc.) Otherwise returns 'false'. +*/ +static inline bool isDescendant(const QAbstractState *state1, const QAbstractState *state2) +{ + Q_ASSERT(state1 != nullptr); + + for (QAbstractState *it = state1->parentState(); it != nullptr; it = it->parentState()) { + if (it == state2) + return true; + } + + return false; +} + +static bool containsDecendantOf(const QSet<QAbstractState *> &states, const QAbstractState *node) +{ + for (QAbstractState *s : states) + if (isDescendant(s, node)) + return true; + + return false; +} + +static int descendantDepth(const QAbstractState *state, const QAbstractState *ancestor) +{ + int depth = 0; + for (const QAbstractState *it = state; it != nullptr; it = it->parentState()) { + if (it == ancestor) + break; + ++depth; + } + return depth; +} + +/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +function getProperAncestors(state1, state2) + +If state2 is null, returns the set of all ancestors of state1 in ancestry order (state1's parent +followed by the parent's parent, etc. up to an including the <scxml> element). If state2 is +non-null, returns in ancestry order the set of all ancestors of state1, up to but not including +state2. (A "proper ancestor" of a state is its parent, or the parent's parent, or the parent's +parent's parent, etc.))If state2 is state1's parent, or equal to state1, or a descendant of state1, +this returns the empty set. +*/ +static QList<QState *> getProperAncestors(const QAbstractState *state, const QAbstractState *upperBound) +{ + Q_ASSERT(state != nullptr); + QList<QState *> result; + result.reserve(16); + for (QState *it = state->parentState(); it && it != upperBound; it = it->parentState()) { + result.append(it); + } + return result; +} + +/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +function getEffectiveTargetStates(transition) + +Returns the states that will be the target when 'transition' is taken, dereferencing any history states. + +function getEffectiveTargetStates(transition) + targets = new OrderedSet() + for s in transition.target + if isHistoryState(s): + if historyValue[s.id]: + targets.union(historyValue[s.id]) + else: + targets.union(getEffectiveTargetStates(s.transition)) + else: + targets.add(s) + return targets +*/ +static QList<QAbstractState *> getEffectiveTargetStates(QAbstractTransition *transition, CalculationCache *cache) +{ + Q_ASSERT(cache); + + QList<QAbstractState *> targetsList; + if (cache->effectiveTargetStates(transition, &targetsList)) + return targetsList; + + QSet<QAbstractState *> targets; + const auto targetStates = transition->targetStates(); + for (QAbstractState *s : targetStates) { + if (QHistoryState *historyState = QStateMachinePrivate::toHistoryState(s)) { + QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(historyState)->configuration; + if (!historyConfiguration.isEmpty()) { + // There is a saved history, so apply that. + targets.unite(QSet<QAbstractState *>(historyConfiguration.constBegin(), historyConfiguration.constEnd())); + } else if (QAbstractTransition *defaultTransition = historyState->defaultTransition()) { + // No saved history, take all default transition targets. + const auto &targetStates = defaultTransition->targetStates(); + targets.unite(QSet<QAbstractState *>(targetStates.constBegin(), targetStates.constEnd())); + } else { + // Woops, we found a history state without a default state. That's not valid! + QStateMachinePrivate *m = QStateMachinePrivate::get(historyState->machine()); + m->setError(QStateMachine::NoDefaultStateInHistoryStateError, historyState); + } + } else { + targets.insert(s); + } + } + + targetsList = targets.values(); + cache->insert(transition, targetsList); + return targetsList; +} + +QStateMachinePrivate::QStateMachinePrivate() +{ + isMachine = true; + + state = NotRunning; + processing = false; + processingScheduled = false; + stop = false; + stopProcessingReason = EventQueueEmpty; + error = QStateMachine::NoError; + globalRestorePolicy = QState::DontRestoreProperties; + signalEventGenerator = nullptr; +#if QT_CONFIG(animation) + animated = true; +#endif +} + +QStateMachinePrivate::~QStateMachinePrivate() +{ + qDeleteAll(internalEventQueue); + qDeleteAll(externalEventQueue); + + for (QHash<int, DelayedEvent>::const_iterator it = delayedEvents.cbegin(), eit = delayedEvents.cend(); it != eit; ++it) { + delete it.value().event; + } +} + +QState *QStateMachinePrivate::rootState() const +{ + return const_cast<QStateMachine*>(q_func()); +} + +static int indexOfDescendant(QState *s, QAbstractState *desc) +{ + QList<QAbstractState*> childStates = QStatePrivate::get(s)->childStates(); + for (int i = 0; i < childStates.size(); ++i) { + QAbstractState *c = childStates.at(i); + if ((c == desc) || isDescendant(desc, c)) { + return i; + } + } + return -1; +} + +bool QStateMachinePrivate::transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2) +{ + QState *s1 = t1->sourceState(), *s2 = t2->sourceState(); + if (s1 == s2) { + QList<QAbstractTransition*> transitions = QStatePrivate::get(s1)->transitions(); + return transitions.indexOf(t1) < transitions.indexOf(t2); + } else if (isDescendant(s1, s2)) { + return true; + } else if (isDescendant(s2, s1)) { + return false; + } else { + Q_ASSERT(s1->machine() != nullptr); + QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine()); + QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2); + Q_ASSERT(lca != nullptr); + int s1Depth = descendantDepth(s1, lca); + int s2Depth = descendantDepth(s2, lca); + if (s1Depth == s2Depth) + return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2)); + else + return s1Depth > s2Depth; + } +} + +bool QStateMachinePrivate::stateEntryLessThan(QAbstractState *s1, QAbstractState *s2) +{ + if (s1->parent() == s2->parent()) { + return s1->parent()->children().indexOf(s1) + < s2->parent()->children().indexOf(s2); + } else if (isDescendant(s1, s2)) { + return false; + } else if (isDescendant(s2, s1)) { + return true; + } else { + Q_ASSERT(s1->machine() != nullptr); + QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine()); + QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2); + Q_ASSERT(lca != nullptr); + return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2)); + } +} + +bool QStateMachinePrivate::stateExitLessThan(QAbstractState *s1, QAbstractState *s2) +{ + if (s1->parent() == s2->parent()) { + return s2->parent()->children().indexOf(s2) + < s1->parent()->children().indexOf(s1); + } else if (isDescendant(s1, s2)) { + return true; + } else if (isDescendant(s2, s1)) { + return false; + } else { + Q_ASSERT(s1->machine() != nullptr); + QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine()); + QState *lca = mach->findLCA(QList<QAbstractState*>() << s1 << s2); + Q_ASSERT(lca != nullptr); + return (indexOfDescendant(lca, s2) < indexOfDescendant(lca, s1)); + } +} + +QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states, bool onlyCompound) +{ + if (states.isEmpty()) + return nullptr; + QList<QState *> ancestors = getProperAncestors(states.at(0), rootState()->parentState()); + for (int i = 0; i < ancestors.size(); ++i) { + QState *anc = ancestors.at(i); + if (onlyCompound && !isCompound(anc)) + continue; + + bool ok = true; + for (int j = states.size() - 1; (j > 0) && ok; --j) { + const QAbstractState *s = states.at(j); + if (!isDescendant(s, anc)) + ok = false; + } + if (ok) + return anc; + } + + // Oops, this should never happen! The state machine itself is a common ancestor of all states, + // no matter what. But, for the onlyCompound case: we probably have a state machine whose + // childMode is set to parallel, which is illegal. However, we're stuck with it (and with + // exposing this invalid/dangerous API to users), so recover in the least horrible way. + setError(QStateMachine::StateMachineChildModeSetToParallelError, q_func()); + return q_func(); // make the statemachine the LCA/LCCA (which it should have been anyway) +} + +QState *QStateMachinePrivate::findLCCA(const QList<QAbstractState*> &states) +{ + return findLCA(states, true); +} + +QList<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event, CalculationCache *cache) +{ + Q_ASSERT(cache); + Q_Q(const QStateMachine); + + QVarLengthArray<QAbstractState *> configuration_sorted; + for (QAbstractState *s : qAsConst(configuration)) { + if (isAtomic(s)) + configuration_sorted.append(s); + } + std::sort(configuration_sorted.begin(), configuration_sorted.end(), stateEntryLessThan); + + QList<QAbstractTransition*> enabledTransitions; + const_cast<QStateMachine *>(q)->beginSelectTransitions(event); + for (QAbstractState *state : qAsConst(configuration_sorted)) { + QList<QState *> lst = getProperAncestors(state, nullptr); + if (QState *grp = toStandardState(state)) + lst.prepend(grp); + bool found = false; + for (int j = 0; (j < lst.size()) && !found; ++j) { + QState *s = lst.at(j); + QList<QAbstractTransition*> transitions = QStatePrivate::get(s)->transitions(); + for (int k = 0; k < transitions.size(); ++k) { + QAbstractTransition *t = transitions.at(k); + if (QAbstractTransitionPrivate::get(t)->callEventTest(event)) { +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": selecting transition" << t; +#endif + enabledTransitions.append(t); + found = true; + break; + } + } + } + } + + if (!enabledTransitions.isEmpty()) { + removeConflictingTransitions(enabledTransitions, cache); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": enabled transitions after removing conflicts:" << enabledTransitions; +#endif + } + const_cast<QStateMachine*>(q)->endSelectTransitions(event); + return enabledTransitions; +} + +/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +function removeConflictingTransitions(enabledTransitions): + filteredTransitions = new OrderedSet() + // toList sorts the transitions in the order of the states that selected them + for t1 in enabledTransitions.toList(): + t1Preempted = false; + transitionsToRemove = new OrderedSet() + for t2 in filteredTransitions.toList(): + if computeExitSet([t1]).hasIntersection(computeExitSet([t2])): + if isDescendant(t1.source, t2.source): + transitionsToRemove.add(t2) + else: + t1Preempted = true + break + if not t1Preempted: + for t3 in transitionsToRemove.toList(): + filteredTransitions.delete(t3) + filteredTransitions.add(t1) + + return filteredTransitions + +Note: the implementation below does not build the transitionsToRemove, but removes them in-place. +*/ +void QStateMachinePrivate::removeConflictingTransitions(QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache) +{ + Q_ASSERT(cache); + + if (enabledTransitions.size() < 2) + return; // There is no transition to conflict with. + + QList<QAbstractTransition*> filteredTransitions; + filteredTransitions.reserve(enabledTransitions.size()); + std::sort(enabledTransitions.begin(), enabledTransitions.end(), transitionStateEntryLessThan); + + for (QAbstractTransition *t1 : qAsConst(enabledTransitions)) { + bool t1Preempted = false; + const QSet<QAbstractState*> exitSetT1 = computeExitSet_Unordered(t1, cache); + QList<QAbstractTransition*>::iterator t2It = filteredTransitions.begin(); + while (t2It != filteredTransitions.end()) { + QAbstractTransition *t2 = *t2It; + if (t1 == t2) { + // Special case: someone added the same transition object to a state twice. In this + // case, t2 (which is already in the list) "preempts" t1. + t1Preempted = true; + break; + } + + QSet<QAbstractState*> exitSetT2 = computeExitSet_Unordered(t2, cache); + if (!exitSetT1.intersects(exitSetT2)) { + // No conflict, no cry. Next patient please. + ++t2It; + } else { + // Houston, we have a conflict. Check which transition can be removed. + if (isDescendant(t1->sourceState(), t2->sourceState())) { + // t1 preempts t2, so we can remove t2 + t2It = filteredTransitions.erase(t2It); + } else { + // t2 preempts t1, so there's no use in looking further and we don't need to add + // t1 to the list. + t1Preempted = true; + break; + } + } + } + if (!t1Preempted) + filteredTransitions.append(t1); + } + + enabledTransitions = filteredTransitions; +} + +void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions, + CalculationCache *cache) +{ + Q_ASSERT(cache); + +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": begin microstep( enabledTransitions:" << enabledTransitions << ')'; + qDebug() << q_func() << ": configuration before exiting states:" << configuration; +#endif + QList<QAbstractState*> exitedStates = computeExitSet(enabledTransitions, cache); + QHash<RestorableId, QVariant> pendingRestorables = computePendingRestorables(exitedStates); + + QSet<QAbstractState*> statesForDefaultEntry; + QList<QAbstractState*> enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry, cache); + +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": computed exit set:" << exitedStates; + qDebug() << q_func() << ": computed entry set:" << enteredStates; +#endif + + QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForEnteredStates = + computePropertyAssignments(enteredStates, pendingRestorables); + if (!pendingRestorables.isEmpty()) { + // Add "implicit" assignments for restored properties to the first + // (outermost) entered state + Q_ASSERT(!enteredStates.isEmpty()); + QAbstractState *s = enteredStates.constFirst(); + assignmentsForEnteredStates[s] << restorablesToPropertyList(pendingRestorables); + } + + exitStates(event, exitedStates, assignmentsForEnteredStates); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": configuration after exiting states:" << configuration; +#endif + + executeTransitionContent(event, enabledTransitions); + +#if QT_CONFIG(animation) + QList<QAbstractAnimation *> selectedAnimations = selectAnimations(enabledTransitions); +#endif + + enterStates(event, exitedStates, enteredStates, statesForDefaultEntry, assignmentsForEnteredStates +#if QT_CONFIG(animation) + , selectedAnimations +#endif + ); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": configuration after entering states:" << configuration; + qDebug() << q_func() << ": end microstep"; +#endif +} + +/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +procedure computeExitSet(enabledTransitions) + +For each transition t in enabledTransitions, if t is targetless then do nothing, else compute the +transition's domain. (This will be the source state in the case of internal transitions) or the +least common compound ancestor state of the source state and target states of t (in the case of +external transitions. Add to the statesToExit set all states in the configuration that are +descendants of the domain. + +function computeExitSet(transitions) + statesToExit = new OrderedSet + for t in transitions: + if (t.target): + domain = getTransitionDomain(t) + for s in configuration: + if isDescendant(s,domain): + statesToExit.add(s) + return statesToExit +*/ +QList<QAbstractState*> QStateMachinePrivate::computeExitSet(const QList<QAbstractTransition*> &enabledTransitions, + CalculationCache *cache) +{ + Q_ASSERT(cache); + + QList<QAbstractState*> statesToExit_sorted = computeExitSet_Unordered(enabledTransitions, cache).values(); + std::sort(statesToExit_sorted.begin(), statesToExit_sorted.end(), stateExitLessThan); + return statesToExit_sorted; +} + +QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(const QList<QAbstractTransition*> &enabledTransitions, + CalculationCache *cache) +{ + Q_ASSERT(cache); + + QSet<QAbstractState*> statesToExit; + for (QAbstractTransition *t : enabledTransitions) + statesToExit.unite(computeExitSet_Unordered(t, cache)); + return statesToExit; +} + +QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(QAbstractTransition *t, + CalculationCache *cache) +{ + Q_ASSERT(cache); + + QSet<QAbstractState*> statesToExit; + if (cache->exitSet(t, &statesToExit)) + return statesToExit; + + QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache); + QAbstractState *domain = getTransitionDomain(t, effectiveTargetStates, cache); + if (domain == nullptr && !t->targetStates().isEmpty()) { + // So we didn't find the least common ancestor for the source and target states of the + // transition. If there were not target states, that would be fine: then the transition + // will fire any events or signals, but not exit the state. + // + // However, there are target states, so it's either a node without a parent (or parent's + // parent, etc), or the state belongs to a different state machine. Either way, this + // makes the state machine invalid. + if (error == QStateMachine::NoError) + setError(QStateMachine::NoCommonAncestorForTransitionError, t->sourceState()); + QList<QAbstractState *> lst = pendingErrorStates.values(); + lst.prepend(t->sourceState()); + + domain = findLCCA(lst); + Q_ASSERT(domain != nullptr); + } + + for (QAbstractState* s : qAsConst(configuration)) { + if (isDescendant(s, domain)) + statesToExit.insert(s); + } + + cache->insert(t, statesToExit); + return statesToExit; +} + +void QStateMachinePrivate::exitStates(QEvent *event, const QList<QAbstractState *> &statesToExit_sorted, + const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates) +{ + for (int i = 0; i < statesToExit_sorted.size(); ++i) { + QAbstractState *s = statesToExit_sorted.at(i); + if (QState *grp = toStandardState(s)) { + QList<QHistoryState*> hlst = QStatePrivate::get(grp)->historyStates(); + for (int j = 0; j < hlst.size(); ++j) { + QHistoryState *h = hlst.at(j); + QHistoryStatePrivate::get(h)->configuration.clear(); + QSet<QAbstractState*>::const_iterator it; + for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { + QAbstractState *s0 = *it; + if (QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) { + if (isAtomic(s0) && isDescendant(s0, s)) + QHistoryStatePrivate::get(h)->configuration.append(s0); + } else if (s0->parentState() == s) { + QHistoryStatePrivate::get(h)->configuration.append(s0); + } + } +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": recorded" << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow") + << "history for" << s << "in" << h << ':' << QHistoryStatePrivate::get(h)->configuration; +#endif + } + } + } + for (int i = 0; i < statesToExit_sorted.size(); ++i) { + QAbstractState *s = statesToExit_sorted.at(i); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": exiting" << s; +#endif + QAbstractStatePrivate::get(s)->callOnExit(event); + +#if QT_CONFIG(animation) + terminateActiveAnimations(s, assignmentsForEnteredStates); +#else + Q_UNUSED(assignmentsForEnteredStates); +#endif + + configuration.remove(s); + QAbstractStatePrivate::get(s)->emitExited(); + } +} + +void QStateMachinePrivate::executeTransitionContent(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions) +{ + for (int i = 0; i < enabledTransitions.size(); ++i) { + QAbstractTransition *t = enabledTransitions.at(i); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": triggering" << t; +#endif + QAbstractTransitionPrivate::get(t)->callOnTransition(event); + QAbstractTransitionPrivate::get(t)->emitTriggered(); + } +} + +QList<QAbstractState*> QStateMachinePrivate::computeEntrySet(const QList<QAbstractTransition *> &enabledTransitions, + QSet<QAbstractState *> &statesForDefaultEntry, + CalculationCache *cache) +{ + Q_ASSERT(cache); + + QSet<QAbstractState*> statesToEnter; + if (pendingErrorStates.isEmpty()) { + for (QAbstractTransition *t : enabledTransitions) { + const auto targetStates = t->targetStates(); + for (QAbstractState *s : targetStates) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry); + + const QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache); + QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates, cache); + for (QAbstractState *s : effectiveTargetStates) + addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry); + } + } + + // Did an error occur while selecting transitions? Then we enter the error state. + if (!pendingErrorStates.isEmpty()) { + statesToEnter.clear(); + statesToEnter = pendingErrorStates; + statesForDefaultEntry = pendingErrorStatesForDefaultEntry; + pendingErrorStates.clear(); + pendingErrorStatesForDefaultEntry.clear(); + } + + QList<QAbstractState*> statesToEnter_sorted = statesToEnter.values(); + std::sort(statesToEnter_sorted.begin(), statesToEnter_sorted.end(), stateEntryLessThan); + return statesToEnter_sorted; +} + +/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +function getTransitionDomain(transition) + +Return the compound state such that 1) all states that are exited or entered as a result of taking +'transition' are descendants of it 2) no descendant of it has this property. + +function getTransitionDomain(t) + tstates = getEffectiveTargetStates(t) + if not tstates: + return null + elif t.type == "internal" and isCompoundState(t.source) and tstates.every(lambda s: isDescendant(s,t.source)): + return t.source + else: + return findLCCA([t.source].append(tstates)) +*/ +QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t, + const QList<QAbstractState *> &effectiveTargetStates, + CalculationCache *cache) +{ + Q_ASSERT(cache); + + if (effectiveTargetStates.isEmpty()) + return nullptr; + + QAbstractState *domain = nullptr; + if (cache->transitionDomain(t, &domain)) + return domain; + + if (t->transitionType() == QAbstractTransition::InternalTransition) { + if (QState *tSource = t->sourceState()) { + if (isCompound(tSource)) { + bool allDescendants = true; + for (QAbstractState *s : effectiveTargetStates) { + if (!isDescendant(s, tSource)) { + allDescendants = false; + break; + } + } + + if (allDescendants) + return tSource; + } + } + } + + QList<QAbstractState *> states(effectiveTargetStates); + if (QAbstractState *src = t->sourceState()) + states.prepend(src); + domain = findLCCA(states); + cache->insert(t, domain); + return domain; +} + +void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState *> &exitedStates_sorted, + const QList<QAbstractState *> &statesToEnter_sorted, + const QSet<QAbstractState *> &statesForDefaultEntry, + QHash<QAbstractState *, QList<QPropertyAssignment>> &propertyAssignmentsForState +#if QT_CONFIG(animation) + , const QList<QAbstractAnimation *> &selectedAnimations +#endif + ) +{ +#ifdef QSTATEMACHINE_DEBUG + Q_Q(QStateMachine); +#endif + for (int i = 0; i < statesToEnter_sorted.size(); ++i) { + QAbstractState *s = statesToEnter_sorted.at(i); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": entering" << s; +#endif + configuration.insert(s); + registerTransitions(s); + +#if QT_CONFIG(animation) + initializeAnimations(s, selectedAnimations, exitedStates_sorted, propertyAssignmentsForState); +#endif + + // Immediately set the properties that are not animated. + { + const auto assignments = propertyAssignmentsForState.value(s); + for (const auto &assn : assignments) { + if (globalRestorePolicy == QState::RestoreProperties) { + if (assn.explicitlySet) { + if (!hasRestorable(s, assn.object, assn.propertyName)) { + QVariant value = savedValueForRestorable(exitedStates_sorted, assn.object, assn.propertyName); + unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName); + registerRestorable(s, assn.object, assn.propertyName, value); + } + } else { + // The property is being restored, hence no need to + // save the current value. Discard any saved values in + // exited states, since those are now stale. + unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName); + } + } + assn.write(); + } + } + + QAbstractStatePrivate::get(s)->callOnEntry(event); + QAbstractStatePrivate::get(s)->emitEntered(); + + // FIXME: + // See the "initial transitions" comment in addDescendantStatesToEnter first, then implement: +// if (statesForDefaultEntry.contains(s)) { +// // ### executeContent(s.initial.transition.children()) +// } + Q_UNUSED(statesForDefaultEntry); + + if (QHistoryState *h = toHistoryState(s)) + QAbstractTransitionPrivate::get(h->defaultTransition())->callOnTransition(event); + + // Emit propertiesAssigned signal if the state has no animated properties. + { + QState *ss = toStandardState(s); + if (ss + #if QT_CONFIG(animation) + && !animationsForState.contains(s) + #endif + ) + QStatePrivate::get(ss)->emitPropertiesAssigned(); + } + + if (isFinal(s)) { + QState *parent = s->parentState(); + if (parent) { + if (parent != rootState()) { + QFinalState *finalState = qobject_cast<QFinalState *>(s); + Q_ASSERT(finalState); + emitStateFinished(parent, finalState); + } + QState *grandparent = parent->parentState(); + if (grandparent && isParallel(grandparent)) { + bool allChildStatesFinal = true; + QList<QAbstractState*> childStates = QStatePrivate::get(grandparent)->childStates(); + for (int j = 0; j < childStates.size(); ++j) { + QAbstractState *cs = childStates.at(j); + if (!isInFinalState(cs)) { + allChildStatesFinal = false; + break; + } + } + if (allChildStatesFinal && (grandparent != rootState())) { + QFinalState *finalState = qobject_cast<QFinalState *>(s); + Q_ASSERT(finalState); + emitStateFinished(grandparent, finalState); + } + } + } + } + } + { + QSet<QAbstractState*>::const_iterator it; + for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { + if (isFinal(*it)) { + QState *parent = (*it)->parentState(); + if (((parent == rootState()) + && (rootState()->childMode() == QState::ExclusiveStates)) + || ((parent->parentState() == rootState()) + && (rootState()->childMode() == QState::ParallelStates) + && isInFinalState(rootState()))) { + processing = false; + stopProcessingReason = Finished; + break; + } + } + } + } +// qDebug() << "configuration:" << configuration.toList(); +} + +/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ has a bug. See + * QTBUG-44963 for details. The algorithm here is as described in + * http://www.w3.org/Voice/2013/scxml-irp/SCXML.htm as of Friday March 13, 2015. + +procedure addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry, defaultHistoryContent): + if isHistoryState(state): + if historyValue[state.id]: + for s in historyValue[state.id]: + addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) + for s in historyValue[state.id]: + addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) + else: + defaultHistoryContent[state.parent.id] = state.transition.content + for s in state.transition.target: + addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) + for s in state.transition.target: + addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent) + else: + statesToEnter.add(state) + if isCompoundState(state): + statesForDefaultEntry.add(state) + for s in state.initial.transition.target: + addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent) + for s in state.initial.transition.target: + addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent) + else: + if isParallelState(state): + for child in getChildStates(state): + if not statesToEnter.some(lambda s: isDescendant(s,child)): + addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) +*/ +void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *state, + QSet<QAbstractState*> &statesToEnter, + QSet<QAbstractState*> &statesForDefaultEntry) +{ + if (QHistoryState *h = toHistoryState(state)) { + const QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(h)->configuration; + if (!historyConfiguration.isEmpty()) { + for (QAbstractState *s : historyConfiguration) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry); + for (QAbstractState *s : historyConfiguration) + addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry); + +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": restoring" + << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow") + << "history from" << state << ':' << historyConfiguration; +#endif + } else { + QList<QAbstractState*> defaultHistoryContent; + if (QAbstractTransition *t = QHistoryStatePrivate::get(h)->defaultTransition) + defaultHistoryContent = t->targetStates(); + + if (defaultHistoryContent.isEmpty()) { + setError(QStateMachine::NoDefaultStateInHistoryStateError, h); + } else { + for (QAbstractState *s : qAsConst(defaultHistoryContent)) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry); + for (QAbstractState *s : qAsConst(defaultHistoryContent)) + addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": initial history targets for" << state << ':' << defaultHistoryContent; +#endif + } + } + } else { + if (state == rootState()) { + // Error has already been set by exitStates(). + Q_ASSERT(error != QStateMachine::NoError); + return; + } + statesToEnter.insert(state); + if (isCompound(state)) { + statesForDefaultEntry.insert(state); + if (QAbstractState *initial = toStandardState(state)->initialState()) { + Q_ASSERT(initial->machine() == q_func()); + + // FIXME: + // Qt does not support initial transitions (which is a problem for parallel states). + // The way it simulates this for other states, is by having a single initial state. + // See also the FIXME in enterStates. + statesForDefaultEntry.insert(initial); + + addDescendantStatesToEnter(initial, statesToEnter, statesForDefaultEntry); + addAncestorStatesToEnter(initial, state, statesToEnter, statesForDefaultEntry); + } else { + setError(QStateMachine::NoInitialStateError, state); + return; + } + } else if (isParallel(state)) { + QState *grp = toStandardState(state); + const auto childStates = QStatePrivate::get(grp)->childStates(); + for (QAbstractState *child : childStates) { + if (!containsDecendantOf(statesToEnter, child)) + addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry); + } + } + } +} + + +/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : + +procedure addAncestorStatesToEnter(state, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent) + for anc in getProperAncestors(state,ancestor): + statesToEnter.add(anc) + if isParallelState(anc): + for child in getChildStates(anc): + if not statesToEnter.some(lambda s: isDescendant(s,child)): + addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent) +*/ +void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QAbstractState *ancestor, + QSet<QAbstractState*> &statesToEnter, + QSet<QAbstractState*> &statesForDefaultEntry) +{ + const auto properAncestors = getProperAncestors(s, ancestor); + for (QState *anc : properAncestors) { + if (!anc->parentState()) + continue; + statesToEnter.insert(anc); + if (isParallel(anc)) { + const auto childStates = QStatePrivate::get(anc)->childStates(); + for (QAbstractState *child : childStates) { + if (!containsDecendantOf(statesToEnter, child)) + addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry); + } + } + } +} + +bool QStateMachinePrivate::isFinal(const QAbstractState *s) +{ + return s && (QAbstractStatePrivate::get(s)->stateType == QAbstractStatePrivate::FinalState); +} + +bool QStateMachinePrivate::isParallel(const QAbstractState *s) +{ + const QState *ss = toStandardState(s); + return ss && (QStatePrivate::get(ss)->childMode == QState::ParallelStates); +} + +bool QStateMachinePrivate::isCompound(const QAbstractState *s) const +{ + const QState *group = toStandardState(s); + if (!group) + return false; + bool isMachine = QStatePrivate::get(group)->isMachine; + // Don't treat the machine as compound if it's a sub-state of this machine + if (isMachine && (group != rootState())) + return false; + return (!isParallel(group) && !QStatePrivate::get(group)->childStates().isEmpty()); +} + +bool QStateMachinePrivate::isAtomic(const QAbstractState *s) const +{ + const QState *ss = toStandardState(s); + return (ss && QStatePrivate::get(ss)->childStates().isEmpty()) + || isFinal(s) + // Treat the machine as atomic if it's a sub-state of this machine + || (ss && QStatePrivate::get(ss)->isMachine && (ss != rootState())); +} + +QState *QStateMachinePrivate::toStandardState(QAbstractState *state) +{ + if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState)) + return static_cast<QState*>(state); + return nullptr; +} + +const QState *QStateMachinePrivate::toStandardState(const QAbstractState *state) +{ + if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState)) + return static_cast<const QState*>(state); + return nullptr; +} + +QFinalState *QStateMachinePrivate::toFinalState(QAbstractState *state) +{ + if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::FinalState)) + return static_cast<QFinalState*>(state); + return nullptr; +} + +QHistoryState *QStateMachinePrivate::toHistoryState(QAbstractState *state) +{ + if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::HistoryState)) + return static_cast<QHistoryState*>(state); + return nullptr; +} + +bool QStateMachinePrivate::isInFinalState(QAbstractState* s) const +{ + if (isCompound(s)) { + QState *grp = toStandardState(s); + QList<QAbstractState*> lst = QStatePrivate::get(grp)->childStates(); + for (int i = 0; i < lst.size(); ++i) { + QAbstractState *cs = lst.at(i); + if (isFinal(cs) && configuration.contains(cs)) + return true; + } + return false; + } else if (isParallel(s)) { + QState *grp = toStandardState(s); + QList<QAbstractState*> lst = QStatePrivate::get(grp)->childStates(); + for (int i = 0; i < lst.size(); ++i) { + QAbstractState *cs = lst.at(i); + if (!isInFinalState(cs)) + return false; + } + return true; + } + else + return false; +} + +#ifndef QT_NO_PROPERTIES + +/*! + \internal + Returns \c true if the given state has saved the value of the given property, + otherwise returns \c false. +*/ +bool QStateMachinePrivate::hasRestorable(QAbstractState *state, QObject *object, + const QByteArray &propertyName) const +{ + RestorableId id(object, propertyName); + return registeredRestorablesForState.value(state).contains(id); +} + +/*! + \internal + Returns the value to save for the property identified by \a id. + If an exited state (member of \a exitedStates_sorted) has saved a value for + the property, the saved value from the last (outermost) state that will be + exited is returned (in practice carrying the saved value on to the next + state). Otherwise, the current value of the property is returned. +*/ +QVariant QStateMachinePrivate::savedValueForRestorable(const QList<QAbstractState*> &exitedStates_sorted, + QObject *object, const QByteArray &propertyName) const +{ +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": savedValueForRestorable(" << exitedStates_sorted << object << propertyName << ')'; +#endif + for (int i = exitedStates_sorted.size() - 1; i >= 0; --i) { + QAbstractState *s = exitedStates_sorted.at(i); + QHash<RestorableId, QVariant> restorables = registeredRestorablesForState.value(s); + QHash<RestorableId, QVariant>::const_iterator it = restorables.constFind(RestorableId(object, propertyName)); + if (it != restorables.constEnd()) { +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": using" << it.value() << "from" << s; +#endif + return it.value(); + } + } +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": falling back to current value"; +#endif + return object->property(propertyName); +} + +void QStateMachinePrivate::registerRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName, + const QVariant &value) +{ +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": registerRestorable(" << state << object << propertyName << value << ')'; +#endif + RestorableId id(object, propertyName); + QHash<RestorableId, QVariant> &restorables = registeredRestorablesForState[state]; + if (!restorables.contains(id)) + restorables.insert(id, value); +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + else + qDebug() << q_func() << ": (already registered)"; +#endif +} + +void QStateMachinePrivate::unregisterRestorables(const QList<QAbstractState *> &states, QObject *object, + const QByteArray &propertyName) +{ +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": unregisterRestorables(" << states << object << propertyName << ')'; +#endif + RestorableId id(object, propertyName); + for (int i = 0; i < states.size(); ++i) { + QAbstractState *s = states.at(i); + QHash<QAbstractState*, QHash<RestorableId, QVariant> >::iterator it; + it = registeredRestorablesForState.find(s); + if (it == registeredRestorablesForState.end()) + continue; + QHash<RestorableId, QVariant> &restorables = it.value(); + const auto it2 = restorables.constFind(id); + if (it2 == restorables.cend()) + continue; +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": unregistered for" << s; +#endif + restorables.erase(it2); + if (restorables.isEmpty()) + registeredRestorablesForState.erase(it); + } +} + +QList<QPropertyAssignment> QStateMachinePrivate::restorablesToPropertyList(const QHash<RestorableId, QVariant> &restorables) const +{ + QList<QPropertyAssignment> result; + QHash<RestorableId, QVariant>::const_iterator it; + for (it = restorables.constBegin(); it != restorables.constEnd(); ++it) { + const RestorableId &id = it.key(); + if (!id.object()) { + // Property object was deleted + continue; + } +#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG + qDebug() << q_func() << ": restoring" << id.object() << id.proertyName() << "to" << it.value(); +#endif + result.append(QPropertyAssignment(id.object(), id.propertyName(), it.value(), /*explicitlySet=*/false)); + } + return result; +} + +/*! + \internal + Computes the set of properties whose values should be restored given that + the states \a statesToExit_sorted will be exited. + + If a particular (object, propertyName) pair occurs more than once (i.e., + because nested states are being exited), the value from the last (outermost) + exited state takes precedence. + + The result of this function must be filtered according to the explicit + property assignments (QState::assignProperty()) of the entered states + before the property restoration is actually performed; i.e., if an entered + state assigns to a property that would otherwise be restored, that property + should not be restored after all, but the saved value from the exited state + should be remembered by the entered state (see registerRestorable()). +*/ +QHash<QStateMachinePrivate::RestorableId, QVariant> QStateMachinePrivate::computePendingRestorables( + const QList<QAbstractState*> &statesToExit_sorted) const +{ + QHash<QStateMachinePrivate::RestorableId, QVariant> restorables; + for (int i = statesToExit_sorted.size() - 1; i >= 0; --i) { + QAbstractState *s = statesToExit_sorted.at(i); + QHash<QStateMachinePrivate::RestorableId, QVariant> rs = registeredRestorablesForState.value(s); + QHash<QStateMachinePrivate::RestorableId, QVariant>::const_iterator it; + for (it = rs.constBegin(); it != rs.constEnd(); ++it) { + if (!restorables.contains(it.key())) + restorables.insert(it.key(), it.value()); + } + } + return restorables; +} + +/*! + \internal + Computes the ordered sets of property assignments for the states to be + entered, \a statesToEnter_sorted. Also filters \a pendingRestorables (removes + properties that should not be restored because they are assigned by an + entered state). +*/ +QHash<QAbstractState *, QList<QPropertyAssignment>> QStateMachinePrivate::computePropertyAssignments( + const QList<QAbstractState*> &statesToEnter_sorted, QHash<RestorableId, QVariant> &pendingRestorables) const +{ + QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForState; + for (int i = 0; i < statesToEnter_sorted.size(); ++i) { + QState *s = toStandardState(statesToEnter_sorted.at(i)); + if (!s) + continue; + + QList<QPropertyAssignment> &assignments = QStatePrivate::get(s)->propertyAssignments; + for (int j = 0; j < assignments.size(); ++j) { + const QPropertyAssignment &assn = assignments.at(j); + if (assn.objectDeleted()) { + assignments.removeAt(j--); + } else { + pendingRestorables.remove(RestorableId(assn.object, assn.propertyName)); + assignmentsForState[s].append(assn); + } + } + } + return assignmentsForState; +} + +#endif // QT_NO_PROPERTIES + +QAbstractState *QStateMachinePrivate::findErrorState(QAbstractState *context) +{ + // Find error state recursively in parent hierarchy if not set explicitly for context state + QAbstractState *errorState = nullptr; + if (context != nullptr) { + QState *s = toStandardState(context); + if (s != nullptr) + errorState = s->errorState(); + + if (errorState == nullptr) + errorState = findErrorState(context->parentState()); + } + + return errorState; +} + +void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractState *currentContext) +{ + Q_Q(QStateMachine); + + error = errorCode; + switch (errorCode) { + case QStateMachine::NoInitialStateError: + Q_ASSERT(currentContext != nullptr); + + errorString = QStateMachine::tr("Missing initial state in compound state '%1'") + .arg(currentContext->objectName()); + + break; + case QStateMachine::NoDefaultStateInHistoryStateError: + Q_ASSERT(currentContext != nullptr); + + errorString = QStateMachine::tr("Missing default state in history state '%1'") + .arg(currentContext->objectName()); + break; + + case QStateMachine::NoCommonAncestorForTransitionError: + Q_ASSERT(currentContext != nullptr); + + errorString = QStateMachine::tr("No common ancestor for targets and source of transition from state '%1'") + .arg(currentContext->objectName()); + break; + + case QStateMachine::StateMachineChildModeSetToParallelError: + Q_ASSERT(currentContext != nullptr); + + errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'.") + .arg(currentContext->objectName()); + break; + + default: + errorString = QStateMachine::tr("Unknown error"); + }; + + pendingErrorStates.clear(); + pendingErrorStatesForDefaultEntry.clear(); + + QAbstractState *currentErrorState = findErrorState(currentContext); + + // Avoid infinite loop if the error state itself has an error + if (currentContext == currentErrorState) + currentErrorState = nullptr; + + Q_ASSERT(currentErrorState != rootState()); + + if (currentErrorState != nullptr) { +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": entering error state" << currentErrorState << "from" << currentContext; +#endif + pendingErrorStates.insert(currentErrorState); + addDescendantStatesToEnter(currentErrorState, pendingErrorStates, pendingErrorStatesForDefaultEntry); + addAncestorStatesToEnter(currentErrorState, rootState(), pendingErrorStates, pendingErrorStatesForDefaultEntry); + pendingErrorStates -= configuration; + } else { + qWarning("Unrecoverable error detected in running state machine: %ls", + qUtf16Printable(errorString)); + q->stop(); + } +} + +#if QT_CONFIG(animation) + +QStateMachinePrivate::InitializeAnimationResult +QStateMachinePrivate::initializeAnimation(QAbstractAnimation *abstractAnimation, + const QPropertyAssignment &prop) +{ + InitializeAnimationResult result; + QAnimationGroup *group = qobject_cast<QAnimationGroup*>(abstractAnimation); + if (group) { + for (int i = 0; i < group->animationCount(); ++i) { + QAbstractAnimation *animationChild = group->animationAt(i); + const auto ret = initializeAnimation(animationChild, prop); + result.handledAnimations << ret.handledAnimations; + result.localResetEndValues << ret.localResetEndValues; + } + } else { + QPropertyAnimation *animation = qobject_cast<QPropertyAnimation *>(abstractAnimation); + if (animation != nullptr + && prop.object == animation->targetObject() + && prop.propertyName == animation->propertyName()) { + + // Only change end value if it is undefined + if (!animation->endValue().isValid()) { + animation->setEndValue(prop.value); + result.localResetEndValues.append(animation); + } + result.handledAnimations.append(animation); + } + } + return result; +} + +void QStateMachinePrivate::_q_animationFinished() +{ + Q_Q(QStateMachine); + QAbstractAnimation *anim = qobject_cast<QAbstractAnimation*>(q->sender()); + Q_ASSERT(anim != nullptr); + QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished())); + if (resetAnimationEndValues.contains(anim)) { + qobject_cast<QVariantAnimation*>(anim)->setEndValue(QVariant()); // ### generalize + resetAnimationEndValues.remove(anim); + } + + QAbstractState *state = stateForAnimation.take(anim); + Q_ASSERT(state != nullptr); + +#ifndef QT_NO_PROPERTIES + // Set the final property value. + QPropertyAssignment assn = propertyForAnimation.take(anim); + assn.write(); + if (!assn.explicitlySet) + unregisterRestorables(QList<QAbstractState*>() << state, assn.object, assn.propertyName); +#endif + + QHash<QAbstractState*, QList<QAbstractAnimation*> >::iterator it; + it = animationsForState.find(state); + Q_ASSERT(it != animationsForState.end()); + QList<QAbstractAnimation*> &animations = it.value(); + animations.removeOne(anim); + if (animations.isEmpty()) { + animationsForState.erase(it); + QStatePrivate::get(toStandardState(state))->emitPropertiesAssigned(); + } +} + +QList<QAbstractAnimation *> QStateMachinePrivate::selectAnimations(const QList<QAbstractTransition *> &transitionList) const +{ + QList<QAbstractAnimation *> selectedAnimations; + if (animated) { + for (int i = 0; i < transitionList.size(); ++i) { + QAbstractTransition *transition = transitionList.at(i); + + selectedAnimations << transition->animations(); + selectedAnimations << defaultAnimationsForSource.values(transition->sourceState()); + + QList<QAbstractState *> targetStates = transition->targetStates(); + for (int j=0; j<targetStates.size(); ++j) + selectedAnimations << defaultAnimationsForTarget.values(targetStates.at(j)); + } + selectedAnimations << defaultAnimations; + } + return selectedAnimations; +} + +void QStateMachinePrivate::terminateActiveAnimations(QAbstractState *state, + const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates) +{ + Q_Q(QStateMachine); + QList<QAbstractAnimation*> animations = animationsForState.take(state); + for (int i = 0; i < animations.size(); ++i) { + QAbstractAnimation *anim = animations.at(i); + QObject::disconnect(anim, SIGNAL(finished()), q, SLOT(_q_animationFinished())); + stateForAnimation.remove(anim); + + // Stop the (top-level) animation. + // ### Stopping nested animation has weird behavior. + QAbstractAnimation *topLevelAnim = anim; + while (QAnimationGroup *group = topLevelAnim->group()) + topLevelAnim = group; + topLevelAnim->stop(); + + if (resetAnimationEndValues.contains(anim)) { + qobject_cast<QVariantAnimation*>(anim)->setEndValue(QVariant()); // ### generalize + resetAnimationEndValues.remove(anim); + } + QPropertyAssignment assn = propertyForAnimation.take(anim); + Q_ASSERT(assn.object != nullptr); + // If there is no property assignment that sets this property, + // set the property to its target value. + bool found = false; + for (auto it = assignmentsForEnteredStates.constBegin(); it != assignmentsForEnteredStates.constEnd(); ++it) { + const QList<QPropertyAssignment> &assignments = it.value(); + for (int j = 0; j < assignments.size(); ++j) { + if (assignments.at(j).hasTarget(assn.object, assn.propertyName)) { + found = true; + break; + } + } + } + if (!found) { + assn.write(); + if (!assn.explicitlySet) + unregisterRestorables(QList<QAbstractState*>() << state, assn.object, assn.propertyName); + } + } +} + +void QStateMachinePrivate::initializeAnimations(QAbstractState *state, const QList<QAbstractAnimation *> &selectedAnimations, + const QList<QAbstractState*> &exitedStates_sorted, + QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates) +{ + Q_Q(QStateMachine); + if (!assignmentsForEnteredStates.contains(state)) + return; + QList<QPropertyAssignment> &assignments = assignmentsForEnteredStates[state]; + for (int i = 0; i < selectedAnimations.size(); ++i) { + QAbstractAnimation *anim = selectedAnimations.at(i); + for (auto it = assignments.begin(); it != assignments.end(); ) { + const QPropertyAssignment &assn = *it; + const auto ret = initializeAnimation(anim, assn); + if (!ret.handledAnimations.isEmpty()) { + for (int j = 0; j < ret.handledAnimations.size(); ++j) { + QAbstractAnimation *a = ret.handledAnimations.at(j); + propertyForAnimation.insert(a, assn); + stateForAnimation.insert(a, state); + animationsForState[state].append(a); + // ### connect to just the top-level animation? + QObject::connect(a, SIGNAL(finished()), q, SLOT(_q_animationFinished()), Qt::UniqueConnection); + } + if ((globalRestorePolicy == QState::RestoreProperties) + && !hasRestorable(state, assn.object, assn.propertyName)) { + QVariant value = savedValueForRestorable(exitedStates_sorted, assn.object, assn.propertyName); + unregisterRestorables(exitedStates_sorted, assn.object, assn.propertyName); + registerRestorable(state, assn.object, assn.propertyName, value); + } + it = assignments.erase(it); + } else { + ++it; + } + for (int j = 0; j < ret.localResetEndValues.size(); ++j) + resetAnimationEndValues.insert(ret.localResetEndValues.at(j)); + } + // We require that at least one animation is valid. + // ### generalize + QList<QVariantAnimation*> variantAnims = anim->findChildren<QVariantAnimation*>(); + if (QVariantAnimation *va = qobject_cast<QVariantAnimation*>(anim)) + variantAnims.append(va); + + bool hasValidEndValue = false; + for (int j = 0; j < variantAnims.size(); ++j) { + if (variantAnims.at(j)->endValue().isValid()) { + hasValidEndValue = true; + break; + } + } + + if (hasValidEndValue) { + if (anim->state() == QAbstractAnimation::Running) { + // The animation is still running. This can happen if the + // animation is a group, and one of its children just finished, + // and that caused a state to emit its propertiesAssigned() signal, and + // that triggered a transition in the machine. + // Just stop the animation so it is correctly restarted again. + anim->stop(); + } + anim->start(); + } + + if (assignments.isEmpty()) { + assignmentsForEnteredStates.remove(state); + break; + } + } +} + +#endif // animation + +QAbstractTransition *QStateMachinePrivate::createInitialTransition() const +{ + class InitialTransition : public QAbstractTransition + { + public: + InitialTransition(const QList<QAbstractState *> &targets) + : QAbstractTransition() + { setTargetStates(targets); } + protected: + bool eventTest(QEvent *) override { return true; } + void onTransition(QEvent *) override {} + }; + + QState *root = rootState(); + Q_ASSERT(root != nullptr); + QList<QAbstractState *> targets; + switch (root->childMode()) { + case QState::ExclusiveStates: + targets.append(root->initialState()); + break; + case QState::ParallelStates: + targets = QStatePrivate::get(root)->childStates(); + break; + } + return new InitialTransition(targets); +} + +void QStateMachinePrivate::clearHistory() +{ + Q_Q(QStateMachine); + QList<QHistoryState*> historyStates = q->findChildren<QHistoryState*>(); + for (int i = 0; i < historyStates.size(); ++i) { + QHistoryState *h = historyStates.at(i); + QHistoryStatePrivate::get(h)->configuration.clear(); + } +} + +/*! + \internal + + Registers all signal transitions whose sender object lives in another thread. + + Normally, signal transitions are lazily registered (when a state becomes + active). But if the sender is in a different thread, the transition must be + registered early to keep the state machine from "dropping" signals; e.g., + a second (transition-bound) signal could be emitted on the sender thread + before the state machine gets to process the first signal. +*/ +void QStateMachinePrivate::registerMultiThreadedSignalTransitions() +{ + Q_Q(QStateMachine); + QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>(); + for (int i = 0; i < transitions.size(); ++i) { + QSignalTransition *t = transitions.at(i); + if ((t->machine() == q) && t->senderObject() && (t->senderObject()->thread() != q->thread())) + registerSignalTransition(t); + } +} + +void QStateMachinePrivate::_q_start() +{ + Q_Q(QStateMachine); + Q_ASSERT(state == Starting); + // iterate over a copy, since we emit signals which may cause + // 'configuration' to change, resulting in undefined behavior when + // iterating at the same time: + const auto config = configuration; + for (QAbstractState *state : config) { + QAbstractStatePrivate *abstractStatePrivate = QAbstractStatePrivate::get(state); + abstractStatePrivate->active = false; + emit state->activeChanged(false); + } + configuration.clear(); + qDeleteAll(internalEventQueue); + internalEventQueue.clear(); + qDeleteAll(externalEventQueue); + externalEventQueue.clear(); + clearHistory(); + + registerMultiThreadedSignalTransitions(); + + startupHook(); + +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": starting"; +#endif + state = Running; + processingScheduled = true; // we call _q_process() below + + QList<QAbstractTransition*> transitions; + CalculationCache calculationCache; + QAbstractTransition *initialTransition = createInitialTransition(); + transitions.append(initialTransition); + + QEvent nullEvent(QEvent::None); + executeTransitionContent(&nullEvent, transitions); + QList<QAbstractState*> exitedStates = QList<QAbstractState*>(); + QSet<QAbstractState*> statesForDefaultEntry; + QList<QAbstractState*> enteredStates = computeEntrySet(transitions, statesForDefaultEntry, &calculationCache); + QHash<RestorableId, QVariant> pendingRestorables; + QHash<QAbstractState *, QList<QPropertyAssignment>> assignmentsForEnteredStates = + computePropertyAssignments(enteredStates, pendingRestorables); +#if QT_CONFIG(animation) + QList<QAbstractAnimation *> selectedAnimations = selectAnimations(transitions); +#endif + // enterStates() will set stopProcessingReason to Finished if a final + // state is entered. + stopProcessingReason = EventQueueEmpty; + enterStates(&nullEvent, exitedStates, enteredStates, statesForDefaultEntry, + assignmentsForEnteredStates +#if QT_CONFIG(animation) + , selectedAnimations +#endif + ); + delete initialTransition; + +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": initial configuration:" << configuration; +#endif + + emit q->started(QStateMachine::QPrivateSignal()); + emit q->runningChanged(true); + + if (stopProcessingReason == Finished) { + // The state machine immediately reached a final state. + processingScheduled = false; + state = NotRunning; + unregisterAllTransitions(); + emitFinished(); + emit q->runningChanged(false); + exitInterpreter(); + } else { + _q_process(); + } +} + +void QStateMachinePrivate::_q_process() +{ + Q_Q(QStateMachine); + Q_ASSERT(state == Running); + Q_ASSERT(!processing); + processing = true; + processingScheduled = false; + beginMacrostep(); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": starting the event processing loop"; +#endif + bool didChange = false; + while (processing) { + if (stop) { + processing = false; + break; + } + QList<QAbstractTransition*> enabledTransitions; + CalculationCache calculationCache; + + QEvent *e = new QEvent(QEvent::None); + enabledTransitions = selectTransitions(e, &calculationCache); + if (enabledTransitions.isEmpty()) { + delete e; + e = nullptr; + } + while (enabledTransitions.isEmpty() && ((e = dequeueInternalEvent()) != nullptr)) { +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": dequeued internal event" << e << "of type" << e->type(); +#endif + enabledTransitions = selectTransitions(e, &calculationCache); + if (enabledTransitions.isEmpty()) { + delete e; + e = nullptr; + } + } + while (enabledTransitions.isEmpty() && ((e = dequeueExternalEvent()) != nullptr)) { +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": dequeued external event" << e << "of type" << e->type(); +#endif + enabledTransitions = selectTransitions(e, &calculationCache); + if (enabledTransitions.isEmpty()) { + delete e; + e = nullptr; + } + } + if (enabledTransitions.isEmpty()) { + if (isInternalEventQueueEmpty()) { + processing = false; + stopProcessingReason = EventQueueEmpty; + noMicrostep(); +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": no transitions enabled"; +#endif + } + } else { + didChange = true; + q->beginMicrostep(e); + microstep(e, enabledTransitions, &calculationCache); + q->endMicrostep(e); + } + delete e; + } +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": finished the event processing loop"; +#endif + if (stop) { + stop = false; + stopProcessingReason = Stopped; + } + + switch (stopProcessingReason) { + case EventQueueEmpty: + processedPendingEvents(didChange); + break; + case Finished: + state = NotRunning; + cancelAllDelayedEvents(); + unregisterAllTransitions(); + emitFinished(); + emit q->runningChanged(false); + break; + case Stopped: + state = NotRunning; + cancelAllDelayedEvents(); + unregisterAllTransitions(); + emit q->stopped(QStateMachine::QPrivateSignal()); + emit q->runningChanged(false); + break; + } + endMacrostep(didChange); + if (stopProcessingReason == Finished) + exitInterpreter(); +} + +void QStateMachinePrivate::_q_startDelayedEventTimer(int id, int delay) +{ + Q_Q(QStateMachine); + QMutexLocker locker(&delayedEventsMutex); + QHash<int, DelayedEvent>::iterator it = delayedEvents.find(id); + if (it != delayedEvents.end()) { + DelayedEvent &e = it.value(); + Q_ASSERT(!e.timerId); + e.timerId = q->startTimer(delay); + if (!e.timerId) { + qWarning("QStateMachine::postDelayedEvent: failed to start timer (id=%d, delay=%d)", id, delay); + delete e.event; + delayedEvents.erase(it); + delayedEventIdFreeList.release(id); + } else { + timerIdToDelayedEventId.insert(e.timerId, id); + } + } else { + // It's been cancelled already + delayedEventIdFreeList.release(id); + } +} + +void QStateMachinePrivate::_q_killDelayedEventTimer(int id, int timerId) +{ + Q_Q(QStateMachine); + q->killTimer(timerId); + QMutexLocker locker(&delayedEventsMutex); + delayedEventIdFreeList.release(id); +} + +void QStateMachinePrivate::postInternalEvent(QEvent *e) +{ + QMutexLocker locker(&internalEventMutex); + internalEventQueue.append(e); +} + +void QStateMachinePrivate::postExternalEvent(QEvent *e) +{ + QMutexLocker locker(&externalEventMutex); + externalEventQueue.append(e); +} + +QEvent *QStateMachinePrivate::dequeueInternalEvent() +{ + QMutexLocker locker(&internalEventMutex); + if (internalEventQueue.isEmpty()) + return nullptr; + return internalEventQueue.takeFirst(); +} + +QEvent *QStateMachinePrivate::dequeueExternalEvent() +{ + QMutexLocker locker(&externalEventMutex); + if (externalEventQueue.isEmpty()) + return nullptr; + return externalEventQueue.takeFirst(); +} + +bool QStateMachinePrivate::isInternalEventQueueEmpty() +{ + QMutexLocker locker(&internalEventMutex); + return internalEventQueue.isEmpty(); +} + +bool QStateMachinePrivate::isExternalEventQueueEmpty() +{ + QMutexLocker locker(&externalEventMutex); + return externalEventQueue.isEmpty(); +} + +void QStateMachinePrivate::processEvents(EventProcessingMode processingMode) +{ + Q_Q(QStateMachine); + if ((state != Running) || processing || processingScheduled) + return; + switch (processingMode) { + case DirectProcessing: + if (QThread::currentThread() == q->thread()) { + _q_process(); + break; + } + // processing must be done in the machine thread, so: + Q_FALLTHROUGH(); + case QueuedProcessing: + processingScheduled = true; + QMetaObject::invokeMethod(q, "_q_process", Qt::QueuedConnection); + break; + } +} + +void QStateMachinePrivate::cancelAllDelayedEvents() +{ + Q_Q(QStateMachine); + QMutexLocker locker(&delayedEventsMutex); + QHash<int, DelayedEvent>::const_iterator it; + for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) { + const DelayedEvent &e = it.value(); + if (e.timerId) { + timerIdToDelayedEventId.remove(e.timerId); + q->killTimer(e.timerId); + delayedEventIdFreeList.release(it.key()); + } else { + // Cancellation will be detected in pending _q_startDelayedEventTimer() call + } + delete e.event; + } + delayedEvents.clear(); +} + +/* + This function is called when the state machine is performing no + microstep because no transition is enabled (i.e. an event is ignored). + + The default implementation does nothing. +*/ +void QStateMachinePrivate::noMicrostep() +{ } + +/* + This function is called when the state machine has reached a stable + state (no pending events), and has not finished yet. + For each event the state machine receives it is guaranteed that + 1) beginMacrostep is called + 2) selectTransition is called at least once + 3) begin/endMicrostep is called at least once or noMicrostep is called + at least once (possibly both, but at least one) + 4) the state machine either enters an infinite loop, or stops (runningChanged(false), + and either finished or stopped are emitted), or processedPendingEvents() is called. + 5) if the machine is not in an infinite loop endMacrostep is called + 6) when the machine is finished and all processing (like signal emission) is done, + exitInterpreter() is called. (This is the same name as the SCXML specification uses.) + + didChange is set to true if at least one microstep was performed, it is possible + that the machine returned to exactly the same state as before, but some transitions + were triggered. + + The default implementation does nothing. +*/ +void QStateMachinePrivate::processedPendingEvents(bool didChange) +{ + Q_UNUSED(didChange); +} + +void QStateMachinePrivate::beginMacrostep() +{ } + +void QStateMachinePrivate::endMacrostep(bool didChange) +{ + Q_UNUSED(didChange); +} + +void QStateMachinePrivate::exitInterpreter() +{ +} + +void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState) +{ + Q_UNUSED(guiltyState); + Q_ASSERT(guiltyState); + +#ifdef QSTATEMACHINE_DEBUG + Q_Q(QStateMachine); + qDebug() << q << ": emitting finished signal for" << forState; +#endif + + QStatePrivate::get(forState)->emitFinished(); +} + +void QStateMachinePrivate::startupHook() +{ +} + +namespace _QStateMachine_Internal{ + +class GoToStateTransition : public QAbstractTransition +{ + Q_OBJECT +public: + GoToStateTransition(QAbstractState *target) + : QAbstractTransition() + { setTargetState(target); } +protected: + void onTransition(QEvent *) override { deleteLater(); } + bool eventTest(QEvent *) override { return true; } +}; + +} // namespace +// mingw compiler tries to export QObject::findChild<GoToStateTransition>(), +// which doesn't work if its in an anonymous namespace. +using namespace _QStateMachine_Internal; +/*! + \internal + + Causes this state machine to unconditionally transition to the given + \a targetState. + + Provides a backdoor for using the state machine "imperatively"; i.e. rather + than defining explicit transitions, you drive the machine's execution by + calling this function. It breaks the whole integrity of the + transition-driven model, but is provided for pragmatic reasons. +*/ +void QStateMachinePrivate::goToState(QAbstractState *targetState) +{ + if (!targetState) { + qWarning("QStateMachine::goToState(): cannot go to null state"); + return; + } + + if (configuration.contains(targetState)) + return; + + Q_ASSERT(state == Running); + QState *sourceState = nullptr; + QSet<QAbstractState*>::const_iterator it; + for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) { + sourceState = toStandardState(*it); + if (sourceState != nullptr) + break; + } + + Q_ASSERT(sourceState != nullptr); + // Reuse previous GoToStateTransition in case of several calls to + // goToState() in a row. + GoToStateTransition *trans = sourceState->findChild<GoToStateTransition*>(); + if (!trans) { + trans = new GoToStateTransition(targetState); + sourceState->addTransition(trans); + } else { + trans->setTargetState(targetState); + } + + processEvents(QueuedProcessing); +} + +void QStateMachinePrivate::registerTransitions(QAbstractState *state) +{ + QState *group = toStandardState(state); + if (!group) + return; + QList<QAbstractTransition*> transitions = QStatePrivate::get(group)->transitions(); + for (int i = 0; i < transitions.size(); ++i) { + QAbstractTransition *t = transitions.at(i); + registerTransition(t); + } +} + +void QStateMachinePrivate::maybeRegisterTransition(QAbstractTransition *transition) +{ + if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) { + maybeRegisterSignalTransition(st); + } +#if QT_CONFIG(qeventtransition) + else if (QEventTransition *et = qobject_cast<QEventTransition*>(transition)) { + maybeRegisterEventTransition(et); + } +#endif +} + +void QStateMachinePrivate::registerTransition(QAbstractTransition *transition) +{ + if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) { + registerSignalTransition(st); + } +#if QT_CONFIG(qeventtransition) + else if (QEventTransition *oet = qobject_cast<QEventTransition*>(transition)) { + registerEventTransition(oet); + } +#endif +} + +void QStateMachinePrivate::unregisterTransition(QAbstractTransition *transition) +{ + if (QSignalTransition *st = qobject_cast<QSignalTransition*>(transition)) { + unregisterSignalTransition(st); + } +#if QT_CONFIG(qeventtransition) + else if (QEventTransition *oet = qobject_cast<QEventTransition*>(transition)) { + unregisterEventTransition(oet); + } +#endif +} + +void QStateMachinePrivate::maybeRegisterSignalTransition(QSignalTransition *transition) +{ + Q_Q(QStateMachine); + if ((state == Running) && (configuration.contains(transition->sourceState()) + || (transition->senderObject() && (transition->senderObject()->thread() != q->thread())))) { + registerSignalTransition(transition); + } +} + +void QStateMachinePrivate::registerSignalTransition(QSignalTransition *transition) +{ + Q_Q(QStateMachine); + if (QSignalTransitionPrivate::get(transition)->signalIndex != -1) + return; // already registered + const QObject *sender = QSignalTransitionPrivate::get(transition)->sender; + if (!sender) + return; + QByteArray signal = QSignalTransitionPrivate::get(transition)->signal; + if (signal.isEmpty()) + return; + if (signal.startsWith('0'+QSIGNAL_CODE)) + signal.remove(0, 1); + const QMetaObject *meta = sender->metaObject(); + int signalIndex = meta->indexOfSignal(signal); + int originalSignalIndex = signalIndex; + if (signalIndex == -1) { + signalIndex = meta->indexOfSignal(QMetaObject::normalizedSignature(signal)); + if (signalIndex == -1) { + qWarning("QSignalTransition: no such signal: %s::%s", + meta->className(), signal.constData()); + return; + } + originalSignalIndex = signalIndex; + } + // The signal index we actually want to connect to is the one + // that is going to be sent, i.e. the non-cloned original index. + while (meta->method(signalIndex).attributes() & QMetaMethod::Cloned) + --signalIndex; + + connectionsMutex.lock(); + QList<int> &connectedSignalIndexes = connections[sender]; + if (connectedSignalIndexes.size() <= signalIndex) + connectedSignalIndexes.resize(signalIndex+1); + if (connectedSignalIndexes.at(signalIndex) == 0) { + if (!signalEventGenerator) + signalEventGenerator = new QSignalEventGenerator(q); + static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset(); + bool ok = QMetaObject::connect(sender, signalIndex, signalEventGenerator, generatorMethodOffset); + if (!ok) { +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": FAILED to add signal transition from" << transition->sourceState() + << ": ( sender =" << sender << ", signal =" << signal + << ", targets =" << transition->targetStates() << ')'; +#endif + return; + } + } + ++connectedSignalIndexes[signalIndex]; + connectionsMutex.unlock(); + + QSignalTransitionPrivate::get(transition)->signalIndex = signalIndex; + QSignalTransitionPrivate::get(transition)->originalSignalIndex = originalSignalIndex; +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": added signal transition from" << transition->sourceState() + << ": ( sender =" << sender << ", signal =" << signal + << ", targets =" << transition->targetStates() << ')'; +#endif +} + +void QStateMachinePrivate::unregisterSignalTransition(QSignalTransition *transition) +{ + int signalIndex = QSignalTransitionPrivate::get(transition)->signalIndex; + if (signalIndex == -1) + return; // not registered + const QObject *sender = QSignalTransitionPrivate::get(transition)->sender; + QSignalTransitionPrivate::get(transition)->signalIndex = -1; + + connectionsMutex.lock(); + QList<int> &connectedSignalIndexes = connections[sender]; + Q_ASSERT(connectedSignalIndexes.size() > signalIndex); + Q_ASSERT(connectedSignalIndexes.at(signalIndex) != 0); + if (--connectedSignalIndexes[signalIndex] == 0) { + Q_ASSERT(signalEventGenerator != nullptr); + static const int generatorMethodOffset = QSignalEventGenerator::staticMetaObject.methodOffset(); + QMetaObject::disconnect(sender, signalIndex, signalEventGenerator, generatorMethodOffset); + int sum = 0; + for (int i = 0; i < connectedSignalIndexes.size(); ++i) + sum += connectedSignalIndexes.at(i); + if (sum == 0) + connections.remove(sender); + } + connectionsMutex.unlock(); +} + +void QStateMachinePrivate::unregisterAllTransitions() +{ + Q_Q(QStateMachine); + { + QList<QSignalTransition*> transitions = rootState()->findChildren<QSignalTransition*>(); + for (int i = 0; i < transitions.size(); ++i) { + QSignalTransition *t = transitions.at(i); + if (t->machine() == q) + unregisterSignalTransition(t); + } + } +#if QT_CONFIG(qeventtransition) + { + QList<QEventTransition*> transitions = rootState()->findChildren<QEventTransition*>(); + for (int i = 0; i < transitions.size(); ++i) { + QEventTransition *t = transitions.at(i); + if (t->machine() == q) + unregisterEventTransition(t); + } + } +#endif +} + +#if QT_CONFIG(qeventtransition) +void QStateMachinePrivate::maybeRegisterEventTransition(QEventTransition *transition) +{ + if ((state == Running) && configuration.contains(transition->sourceState())) + registerEventTransition(transition); +} + +void QStateMachinePrivate::registerEventTransition(QEventTransition *transition) +{ + Q_Q(QStateMachine); + if (QEventTransitionPrivate::get(transition)->registered) + return; + if (transition->eventType() >= QEvent::User) { + qWarning("QObject event transitions are not supported for custom types"); + return; + } + QObject *object = QEventTransitionPrivate::get(transition)->object; + if (!object) + return; + QObjectPrivate *od = QObjectPrivate::get(object); + if (!od->extraData || !od->extraData->eventFilters.contains(q)) + object->installEventFilter(q); + ++qobjectEvents[object][transition->eventType()]; + QEventTransitionPrivate::get(transition)->registered = true; +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q << ": added event transition from" << transition->sourceState() + << ": ( object =" << object << ", event =" << transition->eventType() + << ", targets =" << transition->targetStates() << ')'; +#endif +} + +void QStateMachinePrivate::unregisterEventTransition(QEventTransition *transition) +{ + Q_Q(QStateMachine); + if (!QEventTransitionPrivate::get(transition)->registered) + return; + QObject *object = QEventTransitionPrivate::get(transition)->object; + QHash<QEvent::Type, int> &events = qobjectEvents[object]; + Q_ASSERT(events.value(transition->eventType()) > 0); + if (--events[transition->eventType()] == 0) { + events.remove(transition->eventType()); + int sum = 0; + QHash<QEvent::Type, int>::const_iterator it; + for (it = events.constBegin(); it != events.constEnd(); ++it) + sum += it.value(); + if (sum == 0) { + qobjectEvents.remove(object); + object->removeEventFilter(q); + } + } + QEventTransitionPrivate::get(transition)->registered = false; +} + +void QStateMachinePrivate::handleFilteredEvent(QObject *watched, QEvent *event) +{ + auto *tmp = QCoreApplication::instance(); + auto *app = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(tmp)); + + if (app && qobjectEvents.value(watched).contains(event->type())) { + postInternalEvent(new QStateMachine::WrappedEvent(watched, app->cloneEvent(event))); + processEvents(DirectProcessing); + } +} +#endif + +void QStateMachinePrivate::handleTransitionSignal(QObject *sender, int signalIndex, + void **argv) +{ +#ifndef QT_NO_DEBUG + connectionsMutex.lock(); + Q_ASSERT(connections[sender].at(signalIndex) != 0); + connectionsMutex.unlock(); +#endif + const QMetaObject *meta = sender->metaObject(); + QMetaMethod method = meta->method(signalIndex); + int argc = method.parameterCount(); + QList<QVariant> vargs; + vargs.reserve(argc); + for (int i = 0; i < argc; ++i) { + auto type = method.parameterMetaType(i); + vargs.append(QVariant(type, argv[i+1])); + } + +#ifdef QSTATEMACHINE_DEBUG + qDebug() << q_func() << ": sending signal event ( sender =" << sender + << ", signal =" << method.methodSignature().constData() << ')'; +#endif + postInternalEvent(new QStateMachine::SignalEvent(sender, signalIndex, vargs)); + processEvents(DirectProcessing); +} + +/*! + Constructs a new state machine with the given \a parent. +*/ +QStateMachine::QStateMachine(QObject *parent) + : QState(*new QStateMachinePrivate, /*parentState=*/nullptr) +{ + // Can't pass the parent to the QState constructor, as it expects a QState + // But this works as expected regardless of whether parent is a QState or not + setParent(parent); +} + +/*! + \since 5.0 + \deprecated + + Constructs a new state machine with the given \a childMode + and \a parent. + + \warning Do not set the \a childMode to anything else than \l{ExclusiveStates}, otherwise the + state machine is invalid, and might work incorrectly. +*/ +QStateMachine::QStateMachine(QState::ChildMode childMode, QObject *parent) + : QState(*new QStateMachinePrivate, /*parentState=*/nullptr) +{ + Q_D(QStateMachine); + d->childMode = childMode; + setParent(parent); // See comment in constructor above + + if (childMode != ExclusiveStates) { + //### FIXME for Qt6: remove this constructor completely, and hide the childMode property. + // Yes, the StateMachine itself is conceptually a state, but it should only expose a limited + // number of properties. The execution algorithm (in the URL below) treats a state machine + // as a state, but from an API point of view, it's questionable if the QStateMachine should + // inherit from QState. + // + // See function findLCCA in https://www.w3.org/TR/2014/WD-scxml-20140529/#AlgorithmforSCXMLInterpretation + // to see where setting childMode to parallel will break down. + qWarning() << "Invalid childMode for QStateMachine" << this; + } +} + +/*! + \internal +*/ +QStateMachine::QStateMachine(QStateMachinePrivate &dd, QObject *parent) + : QState(dd, /*parentState=*/nullptr) +{ + setParent(parent); +} + +/*! + Destroys this state machine. +*/ +QStateMachine::~QStateMachine() +{ +} + +/*! + \enum QStateMachine::EventPriority + + This enum type specifies the priority of an event posted to the state + machine using postEvent(). + + Events of high priority are processed before events of normal priority. + + \value NormalPriority The event has normal priority. + \value HighPriority The event has high priority. +*/ + +/*! \enum QStateMachine::Error + + This enum type defines errors that can occur in the state machine at run time. When the state + machine encounters an unrecoverable error at run time, it will set the error code returned + by error(), the error message returned by errorString(), and enter an error state based on + the context of the error. + + \value NoError No error has occurred. + \value NoInitialStateError The machine has entered a QState with children which does not have an + initial state set. The context of this error is the state which is missing an initial + state. + \value NoDefaultStateInHistoryStateError The machine has entered a QHistoryState which does not have + a default state set. The context of this error is the QHistoryState which is missing a + default state. + \value NoCommonAncestorForTransitionError The machine has selected a transition whose source + and targets are not part of the same tree of states, and thus are not part of the same + state machine. Commonly, this could mean that one of the states has not been given + any parent or added to any machine. The context of this error is the source state of + the transition. + \value StateMachineChildModeSetToParallelError The machine's \l childMode + property was set to \l{QState::ParallelStates}. This is illegal. + Only states may be declared as parallel, not the state machine + itself. This enum value was added in Qt 5.14. + + \sa setErrorState() +*/ + +/*! + Returns the error code of the last error that occurred in the state machine. +*/ +QStateMachine::Error QStateMachine::error() const +{ + Q_D(const QStateMachine); + return d->error; +} + +/*! + Returns the error string of the last error that occurred in the state machine. +*/ +QString QStateMachine::errorString() const +{ + Q_D(const QStateMachine); + return d->errorString; +} + +/*! + Clears the error string and error code of the state machine. +*/ +void QStateMachine::clearError() +{ + Q_D(QStateMachine); + d->errorString.clear(); + d->error = NoError; +} + +/*! + Returns the restore policy of the state machine. + + \sa setGlobalRestorePolicy() +*/ +QState::RestorePolicy QStateMachine::globalRestorePolicy() const +{ + Q_D(const QStateMachine); + return d->globalRestorePolicy; +} + +/*! + Sets the restore policy of the state machine to \a restorePolicy. The default + restore policy is QState::DontRestoreProperties. + + \sa globalRestorePolicy() +*/ +void QStateMachine::setGlobalRestorePolicy(QState::RestorePolicy restorePolicy) +{ + Q_D(QStateMachine); + d->globalRestorePolicy = restorePolicy; +} + +/*! + Adds the given \a state to this state machine. The state becomes a top-level + state and the state machine takes ownership of the state. + + If the state is already in a different machine, it will first be removed + from its old machine, and then added to this machine. + + \sa removeState(), setInitialState() +*/ +void QStateMachine::addState(QAbstractState *state) +{ + if (!state) { + qWarning("QStateMachine::addState: cannot add null state"); + return; + } + if (QAbstractStatePrivate::get(state)->machine() == this) { + qWarning("QStateMachine::addState: state has already been added to this machine"); + return; + } + state->setParent(this); +} + +/*! + Removes the given \a state from this state machine. The state machine + releases ownership of the state. + + \sa addState() +*/ +void QStateMachine::removeState(QAbstractState *state) +{ + if (!state) { + qWarning("QStateMachine::removeState: cannot remove null state"); + return; + } + if (QAbstractStatePrivate::get(state)->machine() != this) { + qWarning("QStateMachine::removeState: state %p's machine (%p)" + " is different from this machine (%p)", + state, QAbstractStatePrivate::get(state)->machine(), this); + return; + } + state->setParent(nullptr); +} + +bool QStateMachine::isRunning() const +{ + Q_D(const QStateMachine); + return (d->state == QStateMachinePrivate::Running); +} + +/*! + Starts this state machine. The machine will reset its configuration and + transition to the initial state. When a final top-level state (QFinalState) + is entered, the machine will emit the finished() signal. + + \note A state machine will not run without a running event loop, such as + the main application event loop started with QCoreApplication::exec() or + QApplication::exec(). + + \sa started(), finished(), stop(), initialState(), setRunning() +*/ +void QStateMachine::start() +{ + Q_D(QStateMachine); + + if ((childMode() == QState::ExclusiveStates) && (initialState() == nullptr)) { + qWarning("QStateMachine::start: No initial state set for machine. Refusing to start."); + return; + } + + switch (d->state) { + case QStateMachinePrivate::NotRunning: + d->state = QStateMachinePrivate::Starting; + QMetaObject::invokeMethod(this, "_q_start", Qt::QueuedConnection); + break; + case QStateMachinePrivate::Starting: + break; + case QStateMachinePrivate::Running: + qWarning("QStateMachine::start(): already running"); + break; + } +} + +/*! + Stops this state machine. The state machine will stop processing events and + then emit the stopped() signal. + + \sa stopped(), start(), setRunning() +*/ +void QStateMachine::stop() +{ + Q_D(QStateMachine); + switch (d->state) { + case QStateMachinePrivate::NotRunning: + break; + case QStateMachinePrivate::Starting: + // the machine will exit as soon as it enters the event processing loop + d->stop = true; + break; + case QStateMachinePrivate::Running: + d->stop = true; + d->processEvents(QStateMachinePrivate::QueuedProcessing); + break; + } +} + +void QStateMachine::setRunning(bool running) +{ + if (running) + start(); + else + stop(); +} + +/*! + \threadsafe + + Posts the given \a event of the given \a priority for processing by this + state machine. + + This function returns immediately. The event is added to the state machine's + event queue. Events are processed in the order posted. The state machine + takes ownership of the event and deletes it once it has been processed. + + You can only post events when the state machine is running or when it is starting up. + + \sa postDelayedEvent() +*/ +void QStateMachine::postEvent(QEvent *event, EventPriority priority) +{ + Q_D(QStateMachine); + switch (d->state) { + case QStateMachinePrivate::Running: + case QStateMachinePrivate::Starting: + break; + default: + qWarning("QStateMachine::postEvent: cannot post event when the state machine is not running"); + return; + } + if (!event) { + qWarning("QStateMachine::postEvent: cannot post null event"); + return; + } +#ifdef QSTATEMACHINE_DEBUG + qDebug() << this << ": posting event" << event; +#endif + switch (priority) { + case NormalPriority: + d->postExternalEvent(event); + break; + case HighPriority: + d->postInternalEvent(event); + break; + } + d->processEvents(QStateMachinePrivate::QueuedProcessing); +} + +/*! + \threadsafe + + Posts the given \a event for processing by this state machine, with the + given \a delay in milliseconds. Returns an identifier associated with the + delayed event, or -1 if the event could not be posted. + + This function returns immediately. When the delay has expired, the event + will be added to the state machine's event queue for processing. The state + machine takes ownership of the event and deletes it once it has been + processed. + + You can only post events when the state machine is running. + + \sa cancelDelayedEvent(), postEvent() +*/ +int QStateMachine::postDelayedEvent(QEvent *event, int delay) +{ + Q_D(QStateMachine); + if (d->state != QStateMachinePrivate::Running) { + qWarning("QStateMachine::postDelayedEvent: cannot post event when the state machine is not running"); + return -1; + } + if (!event) { + qWarning("QStateMachine::postDelayedEvent: cannot post null event"); + return -1; + } + if (delay < 0) { + qWarning("QStateMachine::postDelayedEvent: delay cannot be negative"); + return -1; + } +#ifdef QSTATEMACHINE_DEBUG + qDebug() << this << ": posting event" << event << "with delay" << delay; +#endif + QMutexLocker locker(&d->delayedEventsMutex); + int id = d->delayedEventIdFreeList.next(); + bool inMachineThread = (QThread::currentThread() == thread()); + int timerId = inMachineThread ? startTimer(delay) : 0; + if (inMachineThread && !timerId) { + qWarning("QStateMachine::postDelayedEvent: failed to start timer with interval %d", delay); + d->delayedEventIdFreeList.release(id); + return -1; + } + QStateMachinePrivate::DelayedEvent delayedEvent(event, timerId); + d->delayedEvents.insert(id, delayedEvent); + if (timerId) { + d->timerIdToDelayedEventId.insert(timerId, id); + } else { + Q_ASSERT(!inMachineThread); + QMetaObject::invokeMethod(this, "_q_startDelayedEventTimer", + Qt::QueuedConnection, + Q_ARG(int, id), + Q_ARG(int, delay)); + } + return id; +} + +/*! + \threadsafe + + Cancels the delayed event identified by the given \a id. The id should be a + value returned by a call to postDelayedEvent(). Returns \c true if the event + was successfully cancelled, otherwise returns \c false. + + \sa postDelayedEvent() +*/ +bool QStateMachine::cancelDelayedEvent(int id) +{ + Q_D(QStateMachine); + if (d->state != QStateMachinePrivate::Running) { + qWarning("QStateMachine::cancelDelayedEvent: the machine is not running"); + return false; + } + QMutexLocker locker(&d->delayedEventsMutex); + QStateMachinePrivate::DelayedEvent e = d->delayedEvents.take(id); + if (!e.event) + return false; + if (e.timerId) { + d->timerIdToDelayedEventId.remove(e.timerId); + bool inMachineThread = (QThread::currentThread() == thread()); + if (inMachineThread) { + killTimer(e.timerId); + d->delayedEventIdFreeList.release(id); + } else { + QMetaObject::invokeMethod(this, "_q_killDelayedEventTimer", + Qt::QueuedConnection, + Q_ARG(int, id), + Q_ARG(int, e.timerId)); + } + } else { + // Cancellation will be detected in pending _q_startDelayedEventTimer() call + } + delete e.event; + return true; +} + +/*! + Returns the maximal consistent set of states (including parallel and final + states) that this state machine is currently in. If a state \c s is in the + configuration, it is always the case that the parent of \c s is also in + c. Note, however, that the machine itself is not an explicit member of the + configuration. +*/ +QSet<QAbstractState*> QStateMachine::configuration() const +{ + Q_D(const QStateMachine); + return d->configuration; +} + +/*! + \fn QStateMachine::started() + + This signal is emitted when the state machine has entered its initial state + (QStateMachine::initialState). + + \sa QStateMachine::finished(), QStateMachine::start() +*/ + +/*! + \fn QStateMachine::stopped() + + This signal is emitted when the state machine has stopped. + + \sa QStateMachine::stop(), QStateMachine::finished() +*/ + +/*! + \reimp +*/ +bool QStateMachine::event(QEvent *e) +{ + Q_D(QStateMachine); + if (e->type() == QEvent::Timer) { + QTimerEvent *te = static_cast<QTimerEvent*>(e); + int tid = te->timerId(); + if (d->state != QStateMachinePrivate::Running) { + // This event has been cancelled already + QMutexLocker locker(&d->delayedEventsMutex); + Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid)); + return true; + } + d->delayedEventsMutex.lock(); + int id = d->timerIdToDelayedEventId.take(tid); + QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(id); + if (ee.event != nullptr) { + Q_ASSERT(ee.timerId == tid); + killTimer(tid); + d->delayedEventIdFreeList.release(id); + d->delayedEventsMutex.unlock(); + d->postExternalEvent(ee.event); + d->processEvents(QStateMachinePrivate::DirectProcessing); + return true; + } else { + d->delayedEventsMutex.unlock(); + } + } + return QState::event(e); +} + +#if QT_CONFIG(qeventtransition) +/*! + \reimp +*/ +bool QStateMachine::eventFilter(QObject *watched, QEvent *event) +{ + Q_D(QStateMachine); + d->handleFilteredEvent(watched, event); + return false; +} +#endif + +/*! + \internal + + This function is called when the state machine is about to select + transitions based on the given \a event. + + The default implementation does nothing. +*/ +void QStateMachine::beginSelectTransitions(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \internal + + This function is called when the state machine has finished selecting + transitions based on the given \a event. + + The default implementation does nothing. +*/ +void QStateMachine::endSelectTransitions(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \internal + + This function is called when the state machine is about to do a microstep. + + The default implementation does nothing. +*/ +void QStateMachine::beginMicrostep(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \internal + + This function is called when the state machine has finished doing a + microstep. + + The default implementation does nothing. +*/ +void QStateMachine::endMicrostep(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp + This function will call start() to start the state machine. +*/ +void QStateMachine::onEntry(QEvent *event) +{ + start(); + QState::onEntry(event); +} + +/*! + \reimp + This function will call stop() to stop the state machine and + subsequently emit the stopped() signal. +*/ +void QStateMachine::onExit(QEvent *event) +{ + stop(); + QState::onExit(event); +} + +#if QT_CONFIG(animation) + +/*! + Returns whether animations are enabled for this state machine. +*/ +bool QStateMachine::isAnimated() const +{ + Q_D(const QStateMachine); + return d->animated; +} + +/*! + Sets whether animations are \a enabled for this state machine. +*/ +void QStateMachine::setAnimated(bool enabled) +{ + Q_D(QStateMachine); + d->animated = enabled; +} + +/*! + Adds a default \a animation to be considered for any transition. +*/ +void QStateMachine::addDefaultAnimation(QAbstractAnimation *animation) +{ + Q_D(QStateMachine); + d->defaultAnimations.append(animation); +} + +/*! + Returns the list of default animations that will be considered for any transition. +*/ +QList<QAbstractAnimation*> QStateMachine::defaultAnimations() const +{ + Q_D(const QStateMachine); + return d->defaultAnimations; +} + +/*! + Removes \a animation from the list of default animations. +*/ +void QStateMachine::removeDefaultAnimation(QAbstractAnimation *animation) +{ + Q_D(QStateMachine); + d->defaultAnimations.removeAll(animation); +} + +#endif // animation + +void QSignalEventGenerator::execute(QMethodRawArguments a) +{ + auto machinePrivate = QStateMachinePrivate::get(qobject_cast<QStateMachine*>(parent())); + if (machinePrivate->state != QStateMachinePrivate::Running) + return; + int signalIndex = senderSignalIndex(); + Q_ASSERT(signalIndex != -1); + machinePrivate->handleTransitionSignal(sender(), signalIndex, a.arguments); +} + +QSignalEventGenerator::QSignalEventGenerator(QStateMachine *parent) + : QObject(parent) +{ +} + +/*! + \class QStateMachine::SignalEvent + \inmodule QtCore + + \brief The SignalEvent class represents a Qt signal event. + + \since 4.6 + \ingroup statemachine + + A signal event is generated by a QStateMachine in response to a Qt + signal. The QSignalTransition class provides a transition associated with a + signal event. QStateMachine::SignalEvent is part of \l{The State Machine Framework}. + + The sender() function returns the object that generated the signal. The + signalIndex() function returns the index of the signal. The arguments() + function returns the arguments of the signal. + + \sa QSignalTransition +*/ + +/*! + \internal + + Constructs a new SignalEvent object with the given \a sender, \a + signalIndex and \a arguments. +*/ +QStateMachine::SignalEvent::SignalEvent(QObject *sender, int signalIndex, + const QList<QVariant> &arguments) + : QEvent(QEvent::StateMachineSignal), m_sender(sender), + m_signalIndex(signalIndex), m_arguments(arguments) +{ +} + +/*! + Destroys this SignalEvent. +*/ +QStateMachine::SignalEvent::~SignalEvent() +{ +} + +/*! + \fn QStateMachine::SignalEvent::sender() const + + Returns the object that emitted the signal. + + \sa QObject::sender() +*/ + +/*! + \fn QStateMachine::SignalEvent::signalIndex() const + + Returns the index of the signal. + + \sa QMetaObject::indexOfSignal(), QMetaObject::method() +*/ + +/*! + \fn QStateMachine::SignalEvent::arguments() const + + Returns the arguments of the signal. +*/ + + +/*! + \class QStateMachine::WrappedEvent + \inmodule QtCore + + \brief The WrappedEvent class inherits QEvent and holds a clone of an event associated with a QObject. + + \since 4.6 + \ingroup statemachine + + A wrapped event is generated by a QStateMachine in response to a Qt + event. The QEventTransition class provides a transition associated with a + such an event. QStateMachine::WrappedEvent is part of \l{The State Machine + Framework}. + + The object() function returns the object that generated the event. The + event() function returns a clone of the original event. + + \sa QEventTransition +*/ + +/*! + \internal + + Constructs a new WrappedEvent object with the given \a object + and \a event. + + The WrappedEvent object takes ownership of \a event. +*/ +QStateMachine::WrappedEvent::WrappedEvent(QObject *object, QEvent *event) + : QEvent(QEvent::StateMachineWrapped), m_object(object), m_event(event) +{ +} + +/*! + Destroys this WrappedEvent. +*/ +QStateMachine::WrappedEvent::~WrappedEvent() +{ + delete m_event; +} + +/*! + \fn QStateMachine::WrappedEvent::object() const + + Returns the object that the event is associated with. +*/ + +/*! + \fn QStateMachine::WrappedEvent::event() const + + Returns a clone of the original event. +*/ + +/*! + \fn QStateMachine::runningChanged(bool running) + \since 5.4 + + This signal is emitted when the running property is changed with \a running as argument. + + \sa QStateMachine::running +*/ + +/*! + \fn QStateMachine::postDelayedEvent(QEvent *event, std::chrono::milliseconds delay) + \since 5.15 + \overload + \threadsafe + + Posts the given \a event for processing by this state machine, with the + given \a delay in milliseconds. Returns an identifier associated with the + delayed event, or -1 if the event could not be posted. + + This function returns immediately. When the delay has expired, the event + will be added to the state machine's event queue for processing. The state + machine takes ownership of the event and deletes it once it has been + processed. + + You can only post events when the state machine is running. + + \sa cancelDelayedEvent(), postEvent() +*/ + +QT_END_NAMESPACE + +#include "qstatemachine.moc" +#include "moc_qstatemachine.cpp" diff --git a/src/statemachine/qstatemachine.h b/src/statemachine/qstatemachine.h new file mode 100644 index 0000000..11c1e49 --- /dev/null +++ b/src/statemachine/qstatemachine.h @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATEMACHINE_H +#define QSTATEMACHINE_H + +#include <QtCore/qcoreevent.h> +#include <QtCore/qlist.h> +#include <QtCore/qobject.h> +#include <QtCore/qset.h> +#include <QtCore/qvariant.h> + +#include <QtStateMachine/qstate.h> + +#if __has_include(<chrono>) +# include <chrono> +#endif + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QStateMachinePrivate; +class QAbstractAnimation; +class Q_STATEMACHINE_EXPORT QStateMachine : public QState +{ + Q_OBJECT + Q_PROPERTY(QString errorString READ errorString) + Q_PROPERTY(QState::RestorePolicy globalRestorePolicy READ globalRestorePolicy WRITE setGlobalRestorePolicy) + Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) +#if QT_CONFIG(animation) + Q_PROPERTY(bool animated READ isAnimated WRITE setAnimated) +#endif +public: + class Q_STATEMACHINE_EXPORT SignalEvent : public QEvent + { + public: + SignalEvent(QObject *sender, int signalIndex, + const QList<QVariant> &arguments); + ~SignalEvent(); + + inline QObject *sender() const { return m_sender; } + inline int signalIndex() const { return m_signalIndex; } + inline QList<QVariant> arguments() const { return m_arguments; } + + private: + QObject *m_sender; + int m_signalIndex; + QList<QVariant> m_arguments; + + friend class QSignalTransitionPrivate; + }; + + class Q_STATEMACHINE_EXPORT WrappedEvent : public QEvent + { + public: + WrappedEvent(QObject *object, QEvent *event); + ~WrappedEvent(); + + inline QObject *object() const { return m_object; } + inline QEvent *event() const { return m_event; } + + private: + QObject *m_object; + QEvent *m_event; + }; + + enum EventPriority { + NormalPriority, + HighPriority + }; + + enum Error { + NoError, + NoInitialStateError, + NoDefaultStateInHistoryStateError, + NoCommonAncestorForTransitionError, + StateMachineChildModeSetToParallelError + }; + + explicit QStateMachine(QObject *parent = nullptr); + explicit QStateMachine(QState::ChildMode childMode, QObject *parent = nullptr); + ~QStateMachine(); + + void addState(QAbstractState *state); + void removeState(QAbstractState *state); + + Error error() const; + QString errorString() const; + void clearError(); + + bool isRunning() const; + +#if QT_CONFIG(animation) + bool isAnimated() const; + void setAnimated(bool enabled); + + void addDefaultAnimation(QAbstractAnimation *animation); + QList<QAbstractAnimation *> defaultAnimations() const; + void removeDefaultAnimation(QAbstractAnimation *animation); +#endif // animation + + QState::RestorePolicy globalRestorePolicy() const; + void setGlobalRestorePolicy(QState::RestorePolicy restorePolicy); + + void postEvent(QEvent *event, EventPriority priority = NormalPriority); + int postDelayedEvent(QEvent *event, int delay); + bool cancelDelayedEvent(int id); + + QSet<QAbstractState*> configuration() const; + +#if QT_CONFIG(qeventtransition) + bool eventFilter(QObject *watched, QEvent *event) override; +#endif + +#if __has_include(<chrono>) || defined(Q_QDOC) + int postDelayedEvent(QEvent *event, std::chrono::milliseconds delay) + { + return postDelayedEvent(event, int(delay.count())); + } +#endif + +public Q_SLOTS: + void start(); + void stop(); + void setRunning(bool running); + +Q_SIGNALS: + void started(QPrivateSignal); + void stopped(QPrivateSignal); + void runningChanged(bool running); + + +protected: + void onEntry(QEvent *event) override; + void onExit(QEvent *event) override; + + virtual void beginSelectTransitions(QEvent *event); + virtual void endSelectTransitions(QEvent *event); + + virtual void beginMicrostep(QEvent *event); + virtual void endMicrostep(QEvent *event); + + bool event(QEvent *e) override; + +protected: + QStateMachine(QStateMachinePrivate &dd, QObject *parent); + +private: + Q_DISABLE_COPY(QStateMachine) + Q_DECLARE_PRIVATE(QStateMachine) + Q_PRIVATE_SLOT(d_func(), void _q_start()) + Q_PRIVATE_SLOT(d_func(), void _q_process()) +#if QT_CONFIG(animation) + Q_PRIVATE_SLOT(d_func(), void _q_animationFinished()) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_startDelayedEventTimer(int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_killDelayedEventTimer(int, int)) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qstatemachine_p.h b/src/statemachine/qstatemachine_p.h new file mode 100644 index 0000000..9a2f085 --- /dev/null +++ b/src/statemachine/qstatemachine_p.h @@ -0,0 +1,340 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATEMACHINE_P_H +#define QSTATEMACHINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qstate_p.h" + +#include <QtCore/qcoreevent.h> +#include <QtCore/qhash.h> +#include <QtCore/qlist.h> +#include <QtCore/qmutex.h> +#include <QtCore/qpair.h> +#include <QtCore/qpointer.h> +#include <QtCore/qset.h> + +#include <QtCore/private/qfreelist_p.h> + +QT_REQUIRE_CONFIG(statemachine); + +QT_BEGIN_NAMESPACE + +class QEvent; +#if QT_CONFIG(qeventtransition) +class QEventTransition; +#endif +class QSignalEventGenerator; +class QSignalTransition; +class QAbstractState; +class QAbstractTransition; +class QFinalState; +class QHistoryState; +class QState; + +#if QT_CONFIG(animation) +class QAbstractAnimation; +#endif + +struct CalculationCache; +class QStateMachine; +class Q_STATEMACHINE_EXPORT QStateMachinePrivate : public QStatePrivate +{ + Q_DECLARE_PUBLIC(QStateMachine) +public: + enum State { + NotRunning, + Starting, + Running + }; + enum EventProcessingMode { + DirectProcessing, + QueuedProcessing + }; + enum StopProcessingReason { + EventQueueEmpty, + Finished, + Stopped + }; + + QStateMachinePrivate(); + ~QStateMachinePrivate(); + + static QStateMachinePrivate *get(QStateMachine *q) + { return q ? q->d_func() : nullptr; } + + QState *findLCA(const QList<QAbstractState*> &states, bool onlyCompound = false); + QState *findLCCA(const QList<QAbstractState*> &states); + + static bool transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2); + static bool stateEntryLessThan(QAbstractState *s1, QAbstractState *s2); + static bool stateExitLessThan(QAbstractState *s1, QAbstractState *s2); + + QAbstractState *findErrorState(QAbstractState *context); + void setError(QStateMachine::Error error, QAbstractState *currentContext); + + // private slots + void _q_start(); + void _q_process(); +#if QT_CONFIG(animation) + void _q_animationFinished(); +#endif + void _q_startDelayedEventTimer(int id, int delay); + void _q_killDelayedEventTimer(int id, int timerId); + + QState *rootState() const; + + void clearHistory(); + QAbstractTransition *createInitialTransition() const; + + void removeConflictingTransitions(QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache); + void microstep(QEvent *event, const QList<QAbstractTransition*> &transitionList, CalculationCache *cache); + QList<QAbstractTransition *> selectTransitions(QEvent *event, CalculationCache *cache); + virtual void noMicrostep(); + virtual void processedPendingEvents(bool didChange); + virtual void beginMacrostep(); + virtual void endMacrostep(bool didChange); + virtual void exitInterpreter(); + virtual void exitStates(QEvent *event, const QList<QAbstractState *> &statesToExit_sorted, + const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates); + QList<QAbstractState*> computeExitSet(const QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache); + QSet<QAbstractState*> computeExitSet_Unordered(const QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache); + QSet<QAbstractState*> computeExitSet_Unordered(QAbstractTransition *t, CalculationCache *cache); + void executeTransitionContent(QEvent *event, const QList<QAbstractTransition*> &transitionList); + virtual void enterStates(QEvent *event, const QList<QAbstractState*> &exitedStates_sorted, + const QList<QAbstractState*> &statesToEnter_sorted, + const QSet<QAbstractState*> &statesForDefaultEntry, + QHash<QAbstractState *, QList<QPropertyAssignment>> &propertyAssignmentsForState +#if QT_CONFIG(animation) + , const QList<QAbstractAnimation*> &selectedAnimations +#endif + ); + QList<QAbstractState*> computeEntrySet(const QList<QAbstractTransition*> &enabledTransitions, + QSet<QAbstractState*> &statesForDefaultEntry, CalculationCache *cache); + QAbstractState *getTransitionDomain(QAbstractTransition *t, + const QList<QAbstractState *> &effectiveTargetStates, + CalculationCache *cache); + void addDescendantStatesToEnter(QAbstractState *state, + QSet<QAbstractState*> &statesToEnter, + QSet<QAbstractState*> &statesForDefaultEntry); + void addAncestorStatesToEnter(QAbstractState *s, QAbstractState *ancestor, + QSet<QAbstractState*> &statesToEnter, + QSet<QAbstractState*> &statesForDefaultEntry); + + static QState *toStandardState(QAbstractState *state); + static const QState *toStandardState(const QAbstractState *state); + static QFinalState *toFinalState(QAbstractState *state); + static QHistoryState *toHistoryState(QAbstractState *state); + + bool isInFinalState(QAbstractState *s) const; + static bool isFinal(const QAbstractState *s); + static bool isParallel(const QAbstractState *s); + bool isCompound(const QAbstractState *s) const; + bool isAtomic(const QAbstractState *s) const; + + void goToState(QAbstractState *targetState); + + void registerTransitions(QAbstractState *state); + void maybeRegisterTransition(QAbstractTransition *transition); + void registerTransition(QAbstractTransition *transition); + void maybeRegisterSignalTransition(QSignalTransition *transition); + void registerSignalTransition(QSignalTransition *transition); + void unregisterSignalTransition(QSignalTransition *transition); + void registerMultiThreadedSignalTransitions(); +#if QT_CONFIG(qeventtransition) + void maybeRegisterEventTransition(QEventTransition *transition); + void registerEventTransition(QEventTransition *transition); + void unregisterEventTransition(QEventTransition *transition); + void handleFilteredEvent(QObject *watched, QEvent *event); +#endif + void unregisterTransition(QAbstractTransition *transition); + void unregisterAllTransitions(); + void handleTransitionSignal(QObject *sender, int signalIndex, + void **args); + + void postInternalEvent(QEvent *e); + void postExternalEvent(QEvent *e); + QEvent *dequeueInternalEvent(); + QEvent *dequeueExternalEvent(); + bool isInternalEventQueueEmpty(); + bool isExternalEventQueueEmpty(); + void processEvents(EventProcessingMode processingMode); + void cancelAllDelayedEvents(); + + virtual void emitStateFinished(QState *forState, QFinalState *guiltyState); + virtual void startupHook(); + +#ifndef QT_NO_PROPERTIES + class RestorableId { + QPointer<QObject> guard; + QObject *obj; + QByteArray prop; + friend size_t qHash(const RestorableId &key, size_t seed) + noexcept(noexcept(qHash(std::declval<QByteArray>()))) + { return qHash(qMakePair(key.obj, key.prop), seed); } + friend bool operator==(const RestorableId &lhs, const RestorableId &rhs) noexcept + { return lhs.obj == rhs.obj && lhs.prop == rhs.prop; } + friend bool operator!=(const RestorableId &lhs, const RestorableId &rhs) noexcept + { return !operator==(lhs, rhs); } + public: + explicit RestorableId(QObject *o, QByteArray p) noexcept : guard(o), obj(o), prop(std::move(p)) {} + QObject *object() const noexcept { return guard; } + QByteArray propertyName() const noexcept { return prop; } + }; + QHash<QAbstractState*, QHash<RestorableId, QVariant> > registeredRestorablesForState; + bool hasRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName) const; + QVariant savedValueForRestorable(const QList<QAbstractState*> &exitedStates_sorted, + QObject *object, const QByteArray &propertyName) const; + void registerRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName, + const QVariant &value); + void unregisterRestorables(const QList<QAbstractState*> &states, QObject *object, + const QByteArray &propertyName); + QList<QPropertyAssignment> restorablesToPropertyList(const QHash<RestorableId, QVariant> &restorables) const; + QHash<RestorableId, QVariant> computePendingRestorables(const QList<QAbstractState*> &statesToExit_sorted) const; + QHash<QAbstractState *, QList<QPropertyAssignment>> computePropertyAssignments( + const QList<QAbstractState*> &statesToEnter_sorted, + QHash<RestorableId, QVariant> &pendingRestorables) const; +#endif + + State state; + bool processing; + bool processingScheduled; + bool stop; + StopProcessingReason stopProcessingReason; + QSet<QAbstractState*> configuration; + QList<QEvent*> internalEventQueue; + QList<QEvent*> externalEventQueue; + QMutex internalEventMutex; + QMutex externalEventMutex; + + QStateMachine::Error error; + QState::RestorePolicy globalRestorePolicy; + + QString errorString; + QSet<QAbstractState *> pendingErrorStates; + QSet<QAbstractState *> pendingErrorStatesForDefaultEntry; + +#if QT_CONFIG(animation) + bool animated; + + struct InitializeAnimationResult { + QList<QAbstractAnimation*> handledAnimations; + QList<QAbstractAnimation*> localResetEndValues; + + void swap(InitializeAnimationResult &other) noexcept + { + qSwap(handledAnimations, other.handledAnimations); + qSwap(localResetEndValues, other.localResetEndValues); + } + }; + + InitializeAnimationResult + initializeAnimation(QAbstractAnimation *abstractAnimation, + const QPropertyAssignment &prop); + + QHash<QAbstractState*, QList<QAbstractAnimation*> > animationsForState; + QHash<QAbstractAnimation*, QPropertyAssignment> propertyForAnimation; + QHash<QAbstractAnimation*, QAbstractState*> stateForAnimation; + QSet<QAbstractAnimation*> resetAnimationEndValues; + + QList<QAbstractAnimation *> defaultAnimations; + QMultiHash<QAbstractState *, QAbstractAnimation *> defaultAnimationsForSource; + QMultiHash<QAbstractState *, QAbstractAnimation *> defaultAnimationsForTarget; + + QList<QAbstractAnimation *> selectAnimations(const QList<QAbstractTransition *> &transitionList) const; + void terminateActiveAnimations(QAbstractState *state, + const QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates); + void initializeAnimations(QAbstractState *state, const QList<QAbstractAnimation*> &selectedAnimations, + const QList<QAbstractState *> &exitedStates_sorted, + QHash<QAbstractState *, QList<QPropertyAssignment>> &assignmentsForEnteredStates); +#endif // animation + + QSignalEventGenerator *signalEventGenerator; + + QHash<const QObject *, QList<int>> connections; + QMutex connectionsMutex; +#if QT_CONFIG(qeventtransition) + QHash<QObject*, QHash<QEvent::Type, int> > qobjectEvents; +#endif + struct FreeListDefaultConstants + { + // used by QFreeList, make sure to define all of when customizing + enum { + InitialNextValue = 0, + IndexMask = 0x00ffffff, + SerialMask = ~IndexMask & ~0x80000000, + SerialCounter = IndexMask + 1, + MaxIndex = IndexMask, + BlockCount = 4 + }; + + static const int Sizes[BlockCount]; + }; + QFreeList<void, FreeListDefaultConstants> delayedEventIdFreeList; + + struct DelayedEvent { + QEvent *event; + int timerId; + DelayedEvent(QEvent *e, int tid) + : event(e), timerId(tid) {} + DelayedEvent() + : event(nullptr), timerId(0) {} + }; + QHash<int, DelayedEvent> delayedEvents; + QHash<int, int> timerIdToDelayedEventId; + QMutex delayedEventsMutex; +}; +#if QT_CONFIG(animation) +Q_DECLARE_SHARED(QStateMachinePrivate::InitializeAnimationResult) +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/statemachine/qstatemachineglobal.h b/src/statemachine/qstatemachineglobal.h new file mode 100644 index 0000000..46ea21e --- /dev/null +++ b/src/statemachine/qstatemachineglobal.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtScxml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATEMACHINEGLOBAL_H +#define QSTATEMACHINEGLOBAL_H + +#include <QtCore/qglobal.h> +#include <QtStateMachine/qtstatemachine-config.h> + +QT_BEGIN_NAMESPACE + +#if defined(QT_STATIC) || defined(BUILD_QSTATEMACHINE) +# define Q_STATEMACHINE_EXPORT +#else +# ifdef QT_BUILD_STATEMACHINE_LIB +# define Q_STATEMACHINE_EXPORT Q_DECL_EXPORT +# else +# define Q_STATEMACHINE_EXPORT Q_DECL_IMPORT +# endif +#endif + +QT_END_NAMESPACE + +#endif // QSTATEMACHINEGLOBAL_H diff --git a/src/statemachine/statemachine.pri b/src/statemachine/statemachine.pri new file mode 100644 index 0000000..f436fe1 --- /dev/null +++ b/src/statemachine/statemachine.pri @@ -0,0 +1,31 @@ +HEADERS += $$PWD/qstatemachine.h \ + $$PWD/qstatemachine_p.h \ + $$PWD/qsignaleventgenerator_p.h \ + $$PWD/qabstractstate.h \ + $$PWD/qabstractstate_p.h \ + $$PWD/qstate.h \ + $$PWD/qstate_p.h \ + $$PWD/qfinalstate.h \ + $$PWD/qfinalstate_p.h \ + $$PWD/qhistorystate.h \ + $$PWD/qhistorystate_p.h \ + $$PWD/qabstracttransition.h \ + $$PWD/qabstracttransition_p.h \ + $$PWD/qsignaltransition.h \ + $$PWD/qsignaltransition_p.h + +SOURCES += $$PWD/qstatemachine.cpp \ + $$PWD/qabstractstate.cpp \ + $$PWD/qstate.cpp \ + $$PWD/qfinalstate.cpp \ + $$PWD/qhistorystate.cpp \ + $$PWD/qabstracttransition.cpp \ + $$PWD/qsignaltransition.cpp + +qtConfig(qeventtransition) { + HEADERS += \ + $$PWD/qeventtransition.h \ + $$PWD/qeventtransition_p.h + SOURCES += \ + $$PWD/qeventtransition.cpp +} diff --git a/src/statemachine/statemachine.pro b/src/statemachine/statemachine.pro new file mode 100644 index 0000000..f5b0012 --- /dev/null +++ b/src/statemachine/statemachine.pro @@ -0,0 +1,14 @@ +!qtConfig(statemachine): return() + +TARGET = QtStateMachine +QT = core +CONFIG += c++11 + +QT_FOR_PRIVATE = core-private +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + +include(statemachine.pri) +include(gui/statemachine.pri) +HEADERS += qstatemachineglobal.h + +load(qt_module) diff --git a/sync.profile b/sync.profile index 1a8e936..8cb4265 100644 --- a/sync.profile +++ b/sync.profile @@ -1,5 +1,6 @@ %modules = ( # path to module name map "QtScxml" => "$basedir/src/scxml", + "QtStateMachine" => "$basedir/src/statemachine", ); %moduleheaders = ( # restrict the module headers to those found in relative path ); |