diff options
-rw-r--r-- | .qmake.conf | 2 | ||||
-rw-r--r-- | mkspecs/features/qscxmlc.prf | 2 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/eventconnection.cpp | 39 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/invokedservices.cpp | 33 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/statemachineloader.cpp | 29 | ||||
-rw-r--r-- | src/scxml/doc/qtscxml-instantiating-state-machines.qdoc | 2 | ||||
-rw-r--r-- | src/scxml/qscxmlinvokableservice.cpp | 29 | ||||
-rw-r--r-- | src/scxml/qscxmlinvokableservice_p.h | 5 | ||||
-rw-r--r-- | src/scxml/qscxmlstatemachine.cpp | 242 | ||||
-rw-r--r-- | tests/auto/statemachine/invoke.scxml | 73 | ||||
-rw-r--r-- | tests/auto/statemachine/tst_statemachine.cpp | 22 | ||||
-rw-r--r-- | tests/auto/statemachine/tst_statemachine.qrc | 1 | ||||
-rw-r--r-- | tools/qscxmlc/decl.t | 5 | ||||
-rw-r--r-- | tools/qscxmlc/doc/qscxmlc.qdoc | 8 | ||||
-rw-r--r-- | tools/qscxmlc/generator.cpp | 36 | ||||
-rw-r--r-- | tools/qscxmlc/generator.h | 3 | ||||
-rw-r--r-- | tools/qscxmlc/moc.h | 4 | ||||
-rw-r--r-- | tools/qscxmlc/qscxmlc.cpp | 4 | ||||
-rw-r--r-- | tools/qscxmlc/scxmlcppdumper.cpp | 64 | ||||
-rw-r--r-- | tools/qscxmlc/scxmlcppdumper.h | 6 | ||||
-rw-r--r-- | tools/tools.pro | 2 |
21 files changed, 560 insertions, 51 deletions
diff --git a/.qmake.conf b/.qmake.conf index 165ecba..ab3b383 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -2,4 +2,4 @@ load(qt_build_config) CONFIG += qt_example_installs CONFIG += warning_clean -MODULE_VERSION = 5.8.1 +MODULE_VERSION = 5.9.0 diff --git a/mkspecs/features/qscxmlc.prf b/mkspecs/features/qscxmlc.prf index 25f1f10..ae1b3ba 100644 --- a/mkspecs/features/qscxmlc.prf +++ b/mkspecs/features/qscxmlc.prf @@ -13,7 +13,7 @@ QSCXMLC_DIR = $$QSCXMLC_DIR$$SUFFIX qscxmlc.name = QSCXMLC ${QMAKE_FILE_IN}.h qscxmlc.input = STATECHARTS qscxmlc.variable_out = QSCXMLC_HEADERS -qscxmlc.commands = $$QMAKE_QSCXMLC ${QMAKE_FILE_IN} --header ${QMAKE_FILE_OUT} --impl $$QSCXMLC_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} +qscxmlc.commands = $$QMAKE_QSCXMLC ${QMAKE_FILE_IN} --header ${QMAKE_FILE_OUT} --impl $$QSCXMLC_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} $$QSCXMLC_ARGUMENTS !isEmpty(QSCXMLC_NAMESPACE): qscxmlc.commands = $${qscxmlc.commands} --namespace $$QSCXMLC_NAMESPACE qscxmlc.output = $$QSCXMLC_DIR/${QMAKE_FILE_BASE}$${first(QMAKE_EXT_H)} qscxmlc.CONFIG += target_predeps diff --git a/src/imports/scxmlstatemachine/eventconnection.cpp b/src/imports/scxmlstatemachine/eventconnection.cpp index 9803a9a..2a073bd 100644 --- a/src/imports/scxmlstatemachine/eventconnection.cpp +++ b/src/imports/scxmlstatemachine/eventconnection.cpp @@ -41,6 +41,45 @@ QT_BEGIN_NAMESPACE +/*! + \qmltype EventConnection + \instantiates QScxmlEventConnection + \inqmlmodule QtScxml + \since QtScxml 5.8 + + \brief Connects to events sent out by state machines. + + To receive a notification when a state machine sends out an event, a + connection can be created to the corresponding signal. +*/ + +/*! + \qmlproperty stringlist EventConnection::events + + The list of SCXML event specifiers that describe the events to listen for. + + Even though spaces are allowed in event specifications in SCXML documents, + they are not allowed in this list. However, the list can contain multiple + specifiers, to the same effect. +*/ + +/*! + \qmlproperty ScxmlStateMachine EventConnection::stateMachine + + The state machine that sends out the event. +*/ + +/*! + \qmlsignal EventConnection::occurred(event) + + This signal is emitted when the event \a event occurrs. + + The corresponding signal handler is \c onOccurred. + + \sa QScxmlEvent +*/ + + QScxmlEventConnection::QScxmlEventConnection(QObject *parent) : QObject(parent), m_stateMachine(nullptr) { diff --git a/src/imports/scxmlstatemachine/invokedservices.cpp b/src/imports/scxmlstatemachine/invokedservices.cpp index 93c97d6..6011557 100644 --- a/src/imports/scxmlstatemachine/invokedservices.cpp +++ b/src/imports/scxmlstatemachine/invokedservices.cpp @@ -42,10 +42,31 @@ QT_BEGIN_NAMESPACE +/*! + \qmltype InvokedServices + \instantiates QScxmlInvokedServices + \inqmlmodule QtScxml + \since QtScxml 5.8 + + \brief Provices access to the services invoked by state machines. + + Makes the invoked services easily accessible by their names, without + constantly iterating through QScxmlStateMachine::invokedServices. + + The services are called from state machines via the mechanism described in + \l{SCXML Specification - 6.4 <invoke>}. +*/ + QScxmlInvokedServices::QScxmlInvokedServices(QObject *parent) : QObject(parent) { } +/*! + \qmlproperty var InvokedServices::children + + The services invoked by the state machine. +*/ + QVariantMap QScxmlInvokedServices::children() { QVariantMap ret; @@ -61,6 +82,12 @@ void QScxmlInvokedServices::classBegin() { } +/*! + \qmlproperty ScxmlStateMachine InvokedServices::stateMachine + + The state machine that invoked the services. +*/ + QScxmlStateMachine *QScxmlInvokedServices::stateMachine() const { return m_stateMachine; @@ -81,6 +108,12 @@ void QScxmlInvokedServices::setStateMachine(QScxmlStateMachine *stateMachine) } } +/*! + \qmlproperty list<QtObject> InvokedServices::qmlChildren + + A list of additional QtObject types nested in this type. +*/ + QQmlListProperty<QObject> QScxmlInvokedServices::qmlChildren() { return QQmlListProperty<QObject>(this, m_qmlChildren); diff --git a/src/imports/scxmlstatemachine/statemachineloader.cpp b/src/imports/scxmlstatemachine/statemachineloader.cpp index 247d989..946988f 100644 --- a/src/imports/scxmlstatemachine/statemachineloader.cpp +++ b/src/imports/scxmlstatemachine/statemachineloader.cpp @@ -56,18 +56,6 @@ \since QtScxml 5.7 */ -/*! - \qmlsignal StateMachineLoader::sourceChanged() - This signal is emitted when the user changes the source URL for the SCXML document. -*/ - -/*! - \qmlsignal StateMachineLoader::stateMachineChanged() - - This signal is emitted when the stateMachine property changes. That is, when - a new state machine is loaded or when the old one becomes invalid. -*/ - QScxmlStateMachineLoader::QScxmlStateMachineLoader(QObject *parent) : QObject(parent) , m_dataModel(Q_NULLPTR) @@ -77,7 +65,7 @@ QScxmlStateMachineLoader::QScxmlStateMachineLoader(QObject *parent) } /*! - \qmlproperty QObject StateMachineLoader::stateMachine + \qmlproperty ScxmlStateMachine StateMachineLoader::stateMachine The state machine instance. */ @@ -87,9 +75,10 @@ QT_PREPEND_NAMESPACE(QScxmlStateMachine) *QScxmlStateMachineLoader::stateMachine } /*! - \qmlproperty string StateMachineLoader::source + \qmlproperty url StateMachineLoader::source - The url of the SCXML document to load. Only synchronously accessible URLs are supported. + The URL of the SCXML document to load. Only synchronously accessible URLs + are supported. */ QUrl QScxmlStateMachineLoader::source() { @@ -156,21 +145,21 @@ void QScxmlStateMachineLoader::setDataModel(QScxmlDataModel *dataModel) bool QScxmlStateMachineLoader::parse(const QUrl &source) { if (!QQmlFile::isSynchronous(source)) { - qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading: only synchronous access is supported.") + qmlWarning(this) << QStringLiteral("Cannot open '%1' for reading: only synchronous access is supported.") .arg(source.url()); return false; } QQmlFile scxmlFile(QQmlEngine::contextForObject(this)->engine(), source); if (scxmlFile.isError()) { // the synchronous case can only fail when the file is not found (or not readable). - qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading.").arg(source.url()); + qmlWarning(this) << QStringLiteral("Cannot open '%1' for reading.").arg(source.url()); return false; } QByteArray data(scxmlFile.dataByteArray()); QBuffer buf(&data); if (!buf.open(QIODevice::ReadOnly)) { - qmlInfo(this) << QStringLiteral("ERROR: cannot open input buffer for reading"); + qmlWarning(this) << QStringLiteral("Cannot open input buffer for reading"); return false; } @@ -189,12 +178,12 @@ bool QScxmlStateMachineLoader::parse(const QUrl &source) QMetaObject::invokeMethod(m_stateMachine, "start", Qt::QueuedConnection); return true; } else { - qmlInfo(this) << QStringLiteral("Something went wrong while parsing '%1':") + qmlWarning(this) << QStringLiteral("Something went wrong while parsing '%1':") .arg(source.url()) << endl; const auto errors = m_stateMachine->parseErrors(); for (const QScxmlError &error : errors) { - qmlInfo(this) << error.toString(); + qmlWarning(this) << error.toString(); } emit stateMachineChanged(); diff --git a/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc b/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc index c8e08a6..0737dff 100644 --- a/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc +++ b/src/scxml/doc/qtscxml-instantiating-state-machines.qdoc @@ -46,7 +46,7 @@ import QtScxml 5.8 Item { - property QtObject stateMachine: scxmlLoader.stateMachine + property StateMachine stateMachine: scxmlLoader.stateMachine StateMachineLoader { id: scxmlLoader diff --git a/src/scxml/qscxmlinvokableservice.cpp b/src/scxml/qscxmlinvokableservice.cpp index a897eaa..4cf9bd9 100644 --- a/src/scxml/qscxmlinvokableservice.cpp +++ b/src/scxml/qscxmlinvokableservice.cpp @@ -319,7 +319,7 @@ QVariantMap QScxmlInvokableServicePrivate::calculateData( QScxmlScxmlService::~QScxmlScxmlService() { - delete stateMachine; + delete m_stateMachine; } /*! @@ -329,7 +329,7 @@ QScxmlScxmlService::~QScxmlScxmlService() QScxmlScxmlService::QScxmlScxmlService(QScxmlStateMachine *stateMachine, QScxmlStateMachine *parentStateMachine, QScxmlInvokableServiceFactory *factory) - : QScxmlInvokableService(parentStateMachine, factory), stateMachine(stateMachine) + : QScxmlInvokableService(parentStateMachine, factory), m_stateMachine(stateMachine) { QScxmlStateMachinePrivate::get(stateMachine)->m_parentStateMachine = parentStateMachine; } @@ -340,7 +340,7 @@ QScxmlScxmlService::QScxmlScxmlService(QScxmlStateMachine *stateMachine, bool QScxmlScxmlService::start() { Q_D(QScxmlInvokableService); - qCDebug(qscxmlLog) << parentStateMachine() << "preparing to start" << stateMachine; + qCDebug(qscxmlLog) << parentStateMachine() << "preparing to start" << m_stateMachine; const QScxmlInvokableServiceFactory *factory = qobject_cast<QScxmlInvokableServiceFactory *>(parent()); @@ -355,15 +355,15 @@ bool QScxmlScxmlService::start() if (!ok) return false; - QScxmlStateMachinePrivate::get(stateMachine)->m_sessionId = id; - stateMachine->setInitialValues(data); - if (stateMachine->init()) { - qCDebug(qscxmlLog) << parentStateMachine() << "starting" << stateMachine; - stateMachine->start(); + QScxmlStateMachinePrivate::get(m_stateMachine)->m_sessionId = id; + m_stateMachine->setInitialValues(data); + if (m_stateMachine->init()) { + qCDebug(qscxmlLog) << parentStateMachine() << "starting" << m_stateMachine; + m_stateMachine->start(); return true; } - qCDebug(qscxmlLog) << parentStateMachine() << "failed to start" << stateMachine; + qCDebug(qscxmlLog) << parentStateMachine() << "failed to start" << m_stateMachine; return false; } @@ -372,7 +372,7 @@ bool QScxmlScxmlService::start() */ QString QScxmlScxmlService::id() const { - return stateMachine->sessionId(); + return m_stateMachine->sessionId(); } /*! @@ -380,7 +380,7 @@ QString QScxmlScxmlService::id() const */ QString QScxmlScxmlService::name() const { - return stateMachine->name(); + return m_stateMachine->name(); } /*! @@ -388,7 +388,12 @@ QString QScxmlScxmlService::name() const */ void QScxmlScxmlService::postEvent(QScxmlEvent *event) { - QScxmlStateMachinePrivate::get(stateMachine)->postEvent(event); + QScxmlStateMachinePrivate::get(m_stateMachine)->postEvent(event); +} + +QScxmlStateMachine *QScxmlScxmlService::stateMachine() const +{ + return m_stateMachine; } /*! diff --git a/src/scxml/qscxmlinvokableservice_p.h b/src/scxml/qscxmlinvokableservice_p.h index e2a9e7b..37ca870 100644 --- a/src/scxml/qscxmlinvokableservice_p.h +++ b/src/scxml/qscxmlinvokableservice_p.h @@ -88,6 +88,7 @@ class Q_SCXML_EXPORT QScxmlScxmlService: public QScxmlInvokableService { Q_OBJECT Q_DECLARE_PRIVATE(QScxmlInvokableService) + Q_PROPERTY(QScxmlStateMachine *stateMachine READ stateMachine CONSTANT) public: QScxmlScxmlService(QScxmlStateMachine *stateMachine, QScxmlStateMachine *parentStateMachine, @@ -98,8 +99,10 @@ public: QString id() const Q_DECL_OVERRIDE; QString name() const Q_DECL_OVERRIDE; void postEvent(QScxmlEvent *event) Q_DECL_OVERRIDE; + QScxmlStateMachine *stateMachine() const; - QScxmlStateMachine *stateMachine; +private: + QScxmlStateMachine *m_stateMachine; }; class QScxmlStaticScxmlServiceFactoryPrivate : public QScxmlInvokableServiceFactoryPrivate diff --git a/src/scxml/qscxmlstatemachine.cpp b/src/scxml/qscxmlstatemachine.cpp index 54bdb9b..e57630f 100644 --- a/src/scxml/qscxmlstatemachine.cpp +++ b/src/scxml/qscxmlstatemachine.cpp @@ -79,6 +79,22 @@ Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine") */ /*! + \qmltype ScxmlStateMachine + \instantiates QScxmlStateMachine + \inqmlmodule QtScxml + \since 5.7 + + \brief Provides an interface to the state machines created from SCXML files. + + The ScxmlStateMachine type is an implementation of the + \l{SCXML Specification}{State Chart XML (SCXML)}. + + All states that are defined in the SCXML file are accessible as properties + of this type. These properties are boolean values and indicate whether the + state is active or inactive. +*/ + +/*! \fn QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec, const QObject *receiver, PointerToMemberFunction method, @@ -415,7 +431,10 @@ QScxmlStateMachinePrivate::QScxmlStateMachinePrivate(const QMetaObject *metaObje , m_eventLoopHook(this) , m_metaObject(metaObject) , m_infoSignalProxy(nullptr) -{} +{ + static int metaType = qRegisterMetaType<QScxmlStateMachine *>(); + Q_UNUSED(metaType); +} QScxmlStateMachinePrivate::~QScxmlStateMachinePrivate() { @@ -1480,6 +1499,12 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! + \qmlproperty bool ScxmlStateMachine::running + + The running state of this state machine. +*/ + +/*! \property QScxmlStateMachine::dataModel \brief The data model to be used for this state machine. @@ -1496,6 +1521,22 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! + \qmlproperty ScxmlDataModel ScxmlStateMachine::dataModel + + The data model to be used for this state machine. + + SCXML data models are described in + \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more + information about supported data models, see \l {SCXML Compliance}. + + Changing the data model when the state machine has been \l initialized is + not specified in the SCXML standard and leads to undefined behavior. + + \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlEcmaScriptDataModel, + QScxmlCppDataModel +*/ + +/*! \property QScxmlStateMachine::initialized \brief Whether the state machine has been initialized. @@ -1506,6 +1547,13 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! + \qmlproperty bool ScxmlStateMachine::initialized + + This read-only property is set to \c true if the state machine has been + initialized, \c false otherwise. +*/ + +/*! \property QScxmlStateMachine::initialValues \brief The initial values to be used for setting up the data model. @@ -1514,6 +1562,12 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! + \qmlproperty var ScxmlStateMachine::initialValues + + The initial values to be used for setting up the data model. +*/ + +/*! \property QScxmlStateMachine::sessionId \brief The session ID of the current state machine. @@ -1528,12 +1582,32 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! + \qmlproperty string ScxmlStateMachine::sessionId + + The session ID of the current state machine. + + The session ID is used for message routing between parent and child state + machines. If a state machine is started by an \c <invoke> element, any event + it sends will have the \c invokeid field set to the session ID. The state + machine will use the origin of an event (which is set by the \e target or + \e targetexpr attribute in a \c <send> element) to dispatch messages to the + correct child state machine. +*/ + +/*! \property QScxmlStateMachine::name \brief The name of the state machine as set by the \e name attribute of the \c <scxml> tag. */ /*! + \qmlproperty string ScxmlStateMachine::name + + The name of the state machine as set by the \e name attribute of the + \c <scxml> tag. +*/ + +/*! \property QScxmlStateMachine::invoked \brief Whether the state machine was invoked from an outer state machine. @@ -1543,18 +1617,41 @@ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *p */ /*! + \qmlproperty bool ScxmlStateMachine::invoked + + Whether the state machine was invoked from an outer state machine. + + This read-only property is set to \c true when the state machine was started + as a service with the \c <invoke> element, \c false otherwise. + */ + +/*! \property QScxmlStateMachine::parseErrors \brief The list of parse errors that occurred while creating a state machine from an SCXML file. */ /*! + \qmlproperty var ScxmlStateMachine::parseErrors + + The list of parse errors that occurred while creating a state machine from + an SCXML file. + */ + +/*! \property QScxmlStateMachine::loader \brief The loader that is currently used to resolve and load URIs for the state machine. */ /*! + \qmlproperty Loader ScxmlStateMachine::loader + + The loader that is currently used to resolve and load URIs for the state + machine. + */ + +/*! \property QScxmlStateMachine::tableData \brief The table data that is used when generating C++ from an SCXML file. @@ -1674,6 +1771,22 @@ void QScxmlStateMachine::setTableData(QScxmlTableData *tableData) } /*! + \qmlmethod ScxmlStateMachine::stateNames(bool compress) + + Retrieves a list of state names of all states. + + When \a compress is \c true (the default), the states that contain child + states is filtered out and only the \e {leaf states} is returned. When it + is \c false, the full list of all states is returned. + + The returned list does not contain the states of possible nested state + machines. + + \note The order of the state names in the list is the order in which the + states occurred in the SCXML document. +*/ + +/*! * Retrieves a list of state names of all states. * * When \a compress is \c true (the default), the states that contain child states @@ -1699,6 +1812,17 @@ QStringList QScxmlStateMachine::stateNames(bool compress) const } /*! + \qmlmethod ScxmlStateMachine::activeStateNames(bool compress) + + Retrieves a list of state names of all active states. + + When a state is active, all its parent states are active by definition. When + \a compress is \c true (the default), the parent states are filtered out and + only the \e {leaf states} are returned. When it is \c false, the full list + of active states is returned. +*/ + +/*! * Retrieves a list of state names of all active states. * * When a state is active, all its parent states are active by definition. When \a compress @@ -1719,6 +1843,13 @@ QStringList QScxmlStateMachine::activeStateNames(bool compress) const } /*! + \qmlmethod ScxmlStateMachine::isActive(string scxmlStateName) + + Returns \c true if the state specified by \a scxmlStateName is active, + \c false otherwise. +*/ + +/*! * Returns \c true if the state specified by \a scxmlStateName is active, \c false otherwise. */ bool QScxmlStateMachine::isActive(const QString &scxmlStateName) const @@ -1810,6 +1941,17 @@ QMetaObject::Connection QScxmlStateMachine::connectToEventImpl(const QString &sc } /*! + \qmlmethod ScxmlStateMachine::init() + + Initializes the state machine by setting the initial values for \c <data> + elements and executing any \c <script> tags of the \c <scxml> tag. The + initial data values are taken from the \l initialValues property. + + Returns \c false if parse errors occur or if any of the initialization steps + fail. Returns \c true otherwise. +*/ + +/*! * Initializes the state machine. * * State machine initialization consists of calling QScxmlDataModel::setup(), setting the initial @@ -1886,10 +2028,24 @@ QString QScxmlStateMachine::name() const } /*! + \qmlmethod ScxmlStateMachine::submitEvent(event) + + Submits the SCXML event \a event to the internal or external event queue + depending on the priority of the event. + + When a delay is set, the event will be queued for delivery after the timeout + has passed. The state machine takes ownership of the event and deletes it + after processing. + + \sa QScxmlEvent + */ + +/*! * Submits the SCXML event \a event to the internal or external event queue depending on the * priority of the event. * * When a delay is set, the event will be queued for delivery after the timeout has passed. + * The state machine takes ownership of \a event and deletes it after processing. */ void QScxmlStateMachine::submitEvent(QScxmlEvent *event) { @@ -1924,6 +2080,12 @@ void QScxmlStateMachine::submitEvent(const QString &eventName) e->setEventType(QScxmlEvent::ExternalEvent); submitEvent(e); } +/*! + \qmlmethod ScxmlStateMachine::submitEvent(string eventName, var data) + + A utility method to create and submit an external event with the specified + \a eventName as the name and \a data as the payload data (optional). +*/ /*! * A utility method to create and submit an external event with the specified @@ -1943,6 +2105,12 @@ void QScxmlStateMachine::submitEvent(const QString &eventName, const QVariant &d } /*! + \qmlmethod ScxmlStateMachine::cancelDelayedEvent(string sendId) + + Cancels a delayed event with the specified \a sendId. +*/ + +/*! * Cancels a delayed event with the specified \a sendId. */ void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId) @@ -1963,6 +2131,24 @@ void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId) } /*! + \qmlmethod ScxmlStateMachine::isDispatchableTarget(string target) + + Returns \c true if a message to \a target can be dispatched by this state + machine. + + Valid targets are: + \list + \li \c #_parent for the parent state machine if the current state machine + is started by \c <invoke> + \li \c #_internal for the current state machine + \li \c #_scxml_sessionid, where \c sessionid is the session ID of the + current state machine + \li \c #_servicename, where \c servicename is the ID or name of a service + started with \c <invoke> by this state machine + \endlist + */ + +/*! * Returns \c true if a message to \a target can be dispatched by this state machine. * * Valid targets are: @@ -1997,6 +2183,13 @@ bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const } /*! + \qmlproperty list ScxmlStateMachine::invokedServices + + A list of SCXML services that were invoked from the main state machine + (possibly recursively). +*/ + +/*! \property QScxmlStateMachine::invokedServices \brief A list of SCXML services that were invoked from the main state machine (possibly recursively). @@ -2029,6 +2222,17 @@ QVector<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const */ /*! + \qmlsignal ScxmlStateMachine::log(string label, string msg) + + This signal is emitted if a \c <log> tag is used in the SCXML. \a label is + the value of the \e label attribute of the \c <log> tag. \a msg is the value + of the evaluated \e expr attribute of the \c <log> tag. If there is no + \e expr attribute, a null string will be returned. + + The corresponding signal handler is \c onLog(). +*/ + +/*! \fn QScxmlStateMachine::reachedStableState() This signal is emitted when the event queue is empty at the end of a macro step or when a final @@ -2036,6 +2240,15 @@ QVector<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const */ /*! + \qmlsignal ScxmlStateMachine::reachedStableState() + + This signal is emitted when the event queue is empty at the end of a macro + step or when a final state is reached. + + The corresponding signal handler is \c onreachedStableState(). +*/ + +/*! \fn QScxmlStateMachine::finished() This signal is emitted when the state machine reaches a top-level final state. @@ -2043,6 +2256,24 @@ QVector<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const \sa running */ +/*! + \qmlsignal ScxmlStateMachine::finished() + + This signal is emitted when the state machine reaches a top-level final + state. + + The corresponding signal handler is \c onFinished(). +*/ + +/*! + \qmlmethod ScxmlStateMachine::start() + + Starts this state machine. The machine resets its configuration and + transitions to the initial state. When a final top-level state + is entered, the machine emits the finished() signal. + + \sa stop(), finished() +*/ /*! Starts this state machine. The machine will reset its configuration and @@ -2071,6 +2302,15 @@ void QScxmlStateMachine::start() } /*! + \qmlmethod ScxmlStateMachine::stop() + + Stops this state machine. The machine will not execute any further state + transitions. Its \l running property is set to \c false. + + \sa start(), finished() +*/ + +/*! Stops this state machine. The machine will not execute any further state transitions. Its \c running property is set to \c false. diff --git a/tests/auto/statemachine/invoke.scxml b/tests/auto/statemachine/invoke.scxml new file mode 100644 index 0000000..1bac47a --- /dev/null +++ b/tests/auto/statemachine/invoke.scxml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/**************************************************************************** +** +** 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:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +--> +<scxml + xmlns="http://www.w3.org/2005/07/scxml" + version="1.0" + name="Directions" + initial="anyplace" +> + <state id="anyplace"> + <invoke type="http://www.w3.org/TR/scxml/"> + <content> + <scxml name="anywhere" version="1.0"> + <state id="here"> + <transition event="goThere" target="there"/> + </state> + <state id="there"> + <transition event="goHere" target="here"/> + </state> + </scxml> + </content> + </invoke> + </state> +</scxml> diff --git a/tests/auto/statemachine/tst_statemachine.cpp b/tests/auto/statemachine/tst_statemachine.cpp index 99d6661..ed1f424 100644 --- a/tests/auto/statemachine/tst_statemachine.cpp +++ b/tests/auto/statemachine/tst_statemachine.cpp @@ -31,6 +31,7 @@ #include <QXmlStreamReader> #include <QtScxml/qscxmlcompiler.h> #include <QtScxml/qscxmlstatemachine.h> +#include <QtScxml/qscxmlinvokableservice.h> #include <QtScxml/private/qscxmlstatemachine_p.h> Q_DECLARE_METATYPE(QScxmlError); @@ -52,6 +53,8 @@ private Q_SLOTS: void doneDotStateEvent(); void running(); + + void invokeStateMachine(); }; void tst_StateMachine::stateNames_data() @@ -387,6 +390,25 @@ void tst_StateMachine::running() QCOMPARE(stateMachine->isRunning(), false); } +void tst_StateMachine::invokeStateMachine() +{ + QScopedPointer<QScxmlStateMachine> stateMachine( + QScxmlStateMachine::fromFile(QString(":/tst_statemachine/invoke.scxml"))); + QVERIFY(!stateMachine.isNull()); + + stateMachine->start(); + QCOMPARE(stateMachine->isRunning(), true); + QTRY_VERIFY(stateMachine->activeStateNames().contains(QString("anyplace"))); + + QVector<QScxmlInvokableService *> services = stateMachine->invokedServices(); + QCOMPARE(services.length(), 1); + QVariant subMachineVariant = services[0]->property("stateMachine"); + QVERIFY(subMachineVariant.isValid()); + QScxmlStateMachine *subMachine = qvariant_cast<QScxmlStateMachine *>(subMachineVariant); + QVERIFY(subMachine); + QTRY_VERIFY(subMachine->activeStateNames().contains("here")); +} + QTEST_MAIN(tst_StateMachine) #include "tst_statemachine.moc" diff --git a/tests/auto/statemachine/tst_statemachine.qrc b/tests/auto/statemachine/tst_statemachine.qrc index 9ded2d0..c31fe4c 100644 --- a/tests/auto/statemachine/tst_statemachine.qrc +++ b/tests/auto/statemachine/tst_statemachine.qrc @@ -5,5 +5,6 @@ <file>statenamesnested.scxml</file> <file>ids1.scxml</file> <file>stateDotDoneEvent.scxml</file> + <file>invoke.scxml</file> </qresource> </RCC> diff --git a/tools/qscxmlc/decl.t b/tools/qscxmlc/decl.t index 648476d..baf1600 100644 --- a/tools/qscxmlc/decl.t +++ b/tools/qscxmlc/decl.t @@ -8,6 +8,11 @@ public: Q_INVOKABLE ${classname}(QObject *parent = 0); ~${classname}(); +${accessors} + +Q_SIGNALS: +${signals} + private: struct Data; friend struct Data; diff --git a/tools/qscxmlc/doc/qscxmlc.qdoc b/tools/qscxmlc/doc/qscxmlc.qdoc index 07b5c8d..bf38a63 100644 --- a/tools/qscxmlc/doc/qscxmlc.qdoc +++ b/tools/qscxmlc/doc/qscxmlc.qdoc @@ -58,7 +58,8 @@ \section1 Command-Line Options - The \c qscxmlc tool supports the following command-line options: + The \c qscxmlc tool supports the following command-line options, which can be specified using + the \c QSCXMLC_ARGUMENTS variable in the project file: \table \header @@ -83,5 +84,10 @@ \li The class name of the generated state machine. If none is specified, the value of the name attribute of the <scxml> tag is taken. If that attribute is not specified either, the basename (excluding path) is taken from the input file name. + \row + \li \c --statemethods + \li Generate extra accessor and signal methods for states. This way you can connect to + state changes with plain QObject::connect() and directly call a method to find out if + a state is currently active. \endtable */ diff --git a/tools/qscxmlc/generator.cpp b/tools/qscxmlc/generator.cpp index 9082efa..02e41c8 100644 --- a/tools/qscxmlc/generator.cpp +++ b/tools/qscxmlc/generator.cpp @@ -587,6 +587,7 @@ void Generator::generateCode() for (int signalindex = 0; signalindex < cdef->signalList.size(); ++signalindex) generateSignal(&cdef->signalList[signalindex], signalindex); + fprintf(out, "\n"); // // Generate plugin meta data // @@ -1149,7 +1150,7 @@ void Generator::generateStaticMetacall() //---- Changed from the original in moc if (f.implementation) { - fprintf(out, f.implementation, methodindex); + fprintf(out, f.implementation, "_o", methodindex); fprintf(out, " break;\n"); continue; } @@ -1223,7 +1224,7 @@ void Generator::generateStaticMetacall() bool anythingUsed = false; for (int methodindex = 0; methodindex < cdef->signalList.size(); ++methodindex) { const FunctionDef &f = cdef->signalList.at(methodindex); - if (f.wasCloned || !f.inPrivateClass.isEmpty() || f.isStatic || f.implementation) + if (f.wasCloned || !f.inPrivateClass.isEmpty() || f.isStatic || f.mangledName.isEmpty()) continue; anythingUsed = true; fprintf(out, " {\n"); @@ -1246,8 +1247,9 @@ void Generator::generateStaticMetacall() else fprintf(out, ");\n"); fprintf(out, " if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&%s::%s)) {\n", - cdef->classname.constData(), f.name.constData()); + cdef->classname.constData(), f.mangledName.constData()); fprintf(out, " *result = %d;\n", methodindex); + fprintf(out, " return;\n"); fprintf(out, " }\n }\n"); } if (!anythingUsed) @@ -1521,6 +1523,34 @@ void Generator::generateSignal(FunctionDef *def,int index) fprintf(out, "}\n"); } +void Generator::generateAccessorDefs() +{ + for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { + const PropertyDef &p = cdef->propertyList.at(propindex); + if (p.read.isEmpty() || p.mangledName.isEmpty()) + continue; + + fprintf(out, "bool %s::%s() const\n{\n return %s;\n}\n\n", cdef->classname.constData(), + p.mangledName.constData(), p.read.constData()); + } +} + +void Generator::generateSignalDefs() +{ + for (int methodindex = 0; methodindex < cdef->signalList.size(); ++methodindex) { + const FunctionDef &f = cdef->signalList.at(methodindex); + if (!f.implementation || f.mangledName.isEmpty()) + continue; + + fprintf(out, "void %s::%s(bool _t1)\n{\n", cdef->classname.constData(), + f.mangledName.constData()); + fprintf(out, " void *_a[] = { Q_NULLPTR, " + "const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };\n "); + fprintf(out, f.implementation, "this", methodindex); + fprintf(out, "\n}\n\n"); + } +} + #if 0 static void writePluginMetaData(FILE *out, const QJsonObject &data) { diff --git a/tools/qscxmlc/generator.h b/tools/qscxmlc/generator.h index 21d82d3..9109188 100644 --- a/tools/qscxmlc/generator.h +++ b/tools/qscxmlc/generator.h @@ -46,6 +46,9 @@ public: QByteArray> &knownQObjectClasses, const QHash<QByteArray, QByteArray> &knownGadgets, QIODevice &outfile); void generateCode(); + void generateAccessorDefs(); + void generateSignalDefs(); + private: bool registerableMetaType(const QByteArray &propertyType); void registerClassInfoStrings(); diff --git a/tools/qscxmlc/moc.h b/tools/qscxmlc/moc.h index 36cff5f..a2ecb88 100644 --- a/tools/qscxmlc/moc.h +++ b/tools/qscxmlc/moc.h @@ -83,6 +83,7 @@ struct FunctionDef QByteArray normalizedType; QByteArray tag; QByteArray name; + QByteArray mangledName; bool returnTypeIsVolatile; QList<ArgumentDef> arguments; @@ -114,7 +115,8 @@ struct FunctionDef struct PropertyDef { PropertyDef():notifyId(-1), constant(false), final(false), gspec(ValueSpec), revision(0){} - QByteArray name, type, member, read, write, reset, designable, scriptable, editable, stored, user, notify, inPrivateClass; + QByteArray name, mangledName, type, member, read, write, reset, designable, scriptable, + editable, stored, user, notify, inPrivateClass; int notifyId; bool constant; bool final; diff --git a/tools/qscxmlc/qscxmlc.cpp b/tools/qscxmlc/qscxmlc.cpp index 89abdaa..ce45ed0 100644 --- a/tools/qscxmlc/qscxmlc.cpp +++ b/tools/qscxmlc/qscxmlc.cpp @@ -122,6 +122,8 @@ int run(const QStringList &arguments) QCommandLineOption optionClassName(QLatin1String("classname"), QCoreApplication::translate("main", "Generate <name> for state machine class name."), QCoreApplication::translate("main", "name")); + QCommandLineOption optionStateMethods(QLatin1String("statemethods"), + QCoreApplication::translate("main", "Generate read and notify methods for states")); cmdParser.addPositionalArgument(QLatin1String("input"), QCoreApplication::translate("main", "Input SCXML file.")); @@ -130,6 +132,7 @@ int run(const QStringList &arguments) cmdParser.addOption(optionOutputHeaderName); cmdParser.addOption(optionOutputSourceName); cmdParser.addOption(optionClassName); + cmdParser.addOption(optionStateMethods); cmdParser.process(arguments); @@ -149,6 +152,7 @@ int run(const QStringList &arguments) const QString scxmlFileName = inputFiles.at(0); TranslationUnit options; + options.stateMethods = cmdParser.isSet(optionStateMethods); if (cmdParser.isSet(optionNamespace)) options.namespaceName = cmdParser.value(optionNamespace); QString outFileName = cmdParser.value(optionOutputBaseName); diff --git a/tools/qscxmlc/scxmlcppdumper.cpp b/tools/qscxmlc/scxmlcppdumper.cpp index db03d70..40ae1dd 100644 --- a/tools/qscxmlc/scxmlcppdumper.cpp +++ b/tools/qscxmlc/scxmlcppdumper.cpp @@ -506,6 +506,13 @@ void CppDumper::writeClass(const QString &className, const GeneratedTableData::M Replacements r; r[QStringLiteral("classname")] = className; r[QStringLiteral("properties")] = generatePropertyDecls(info); + if (m_translationUnit->stateMethods) { + r[QStringLiteral("accessors")] = generateAccessorDecls(info); + r[QStringLiteral("signals")] = generateSignalDecls(info); + } else { + r[QStringLiteral("accessors")] = QString(); + r[QStringLiteral("signals")] = QString(); + } genTemplate(h, QStringLiteral(":/decl.t"), r); } @@ -699,8 +706,42 @@ QString CppDumper::generatePropertyDecls(const GeneratedTableData::MetaDataInfo QString decls; for (const QString &stateName : info.stateNames) { - if (!stateName.isEmpty()) + if (stateName.isEmpty()) + continue; + + if (m_translationUnit->stateMethods) { + decls += QString::fromLatin1(" Q_PROPERTY(bool %1 READ %2 NOTIFY %3)\n") + .arg(stateName, mangleIdentifier(stateName), + mangleIdentifier(stateName + QStringLiteral("Changed"))); + } else { decls += QString::fromLatin1(" Q_PROPERTY(bool %1)\n").arg(stateName); + } + } + + return decls; +} + +QString CppDumper::generateAccessorDecls(const GeneratedTableData::MetaDataInfo &info) +{ + QString decls; + + for (const QString &stateName : info.stateNames) { + if (!stateName.isEmpty()) + decls += QString::fromLatin1(" bool %1() const;\n").arg(mangleIdentifier(stateName)); + } + + return decls; +} + +QString CppDumper::generateSignalDecls(const GeneratedTableData::MetaDataInfo &info) +{ + QString decls; + + for (const QString &stateName : info.stateNames) { + if (!stateName.isEmpty()) { + decls += QString::fromLatin1(" void %1(bool);\n") + .arg(mangleIdentifier(stateName + QStringLiteral("Changed"))); + } } return decls; @@ -735,16 +776,18 @@ QString CppDumper::generateMetaObject(const QString &className, if (stateName.isEmpty()) continue; - QByteArray mangledStateName = stateName.toUtf8(); + QByteArray utf8StateName = stateName.toUtf8(); FunctionDef signal; signal.type.name = "void"; signal.type.rawName = signal.type.name; signal.normalizedType = signal.type.name; - signal.name = mangledStateName + "Changed"; + signal.name = utf8StateName + "Changed"; + if (m_translationUnit->stateMethods) + signal.mangledName = mangleIdentifier(stateName + QStringLiteral("Changed")).toUtf8(); signal.access = FunctionDef::Public; signal.isSignal = true; - signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; + signal.implementation = "QMetaObject::activate(%s, &staticMetaObject, %d, _a);"; ArgumentDef arg; arg.type.name = "bool"; @@ -758,9 +801,11 @@ QString CppDumper::generateMetaObject(const QString &className, ++classDef.notifyableProperties; PropertyDef prop; prop.name = stateName.toUtf8(); + if (m_translationUnit->stateMethods) + prop.mangledName = mangleIdentifier(stateName).toUtf8(); prop.type = "bool"; prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")"; - prop.notify = mangledStateName + "Changed"; + prop.notify = utf8StateName + "Changed"; prop.notifyId = classDef.signalList.size() - 1; prop.gspec = PropertyDef::ValueSpec; prop.scriptable = "true"; @@ -773,8 +818,13 @@ QString CppDumper::generateMetaObject(const QString &className, QBuffer buf; buf.open(QIODevice::WriteOnly); - Generator(&classDef, QList<QByteArray>(), knownQObjectClasses, - QHash<QByteArray, QByteArray>(), buf).generateCode(); + Generator generator(&classDef, QList<QByteArray>(), knownQObjectClasses, + QHash<QByteArray, QByteArray>(), buf); + generator.generateCode(); + if (m_translationUnit->stateMethods) { + generator.generateAccessorDefs(); + generator.generateSignalDefs(); + } buf.close(); return QString::fromUtf8(buf.buffer()); } diff --git a/tools/qscxmlc/scxmlcppdumper.h b/tools/qscxmlc/scxmlcppdumper.h index bd49cea..a987f80 100644 --- a/tools/qscxmlc/scxmlcppdumper.h +++ b/tools/qscxmlc/scxmlcppdumper.h @@ -41,12 +41,14 @@ QT_BEGIN_NAMESPACE struct TranslationUnit { TranslationUnit() - : mainDocument(Q_NULLPTR) + : stateMethods(false) + , mainDocument(Q_NULLPTR) {} QString scxmlFileName; QString outHFileName, outCppFileName; QString namespaceName; + bool stateMethods; DocumentModel::ScxmlDocument *mainDocument; QList<DocumentModel::ScxmlDocument *> allDocuments; QHash<DocumentModel::ScxmlDocument *, QString> classnameForDocument; @@ -79,6 +81,8 @@ private: private: QString generatePropertyDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateAccessorDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateSignalDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); QString generateMetaObject(const QString &className, const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); diff --git a/tools/tools.pro b/tools/tools.pro index 1854cfc..6884612 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -1,2 +1,2 @@ TEMPLATE = subdirs -SUBDIRS = qscxmlc +qtConfig(commandlineparser): SUBDIRS = qscxmlc |