From 2f2e31fea77aef558e5c6e3727f9b31cf018599a Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 7 Jul 2016 16:15:02 +0200 Subject: Better connection mechanism for events The connectToEvent() mechanism is analogous to connectToState() and allows arbitrary event specifications, with '.' and '*'. In addition a QML component is provided to make event connections available in QML. Change-Id: Ie45422481a794b1b350347c383318857e5dc3f6d Reviewed-by: Jarek Kobus --- examples/scxml/calculator-qml/calculator-qml.qml | 7 +- examples/scxml/calculator-widgets/mainwindow.cpp | 8 +- examples/scxml/pinball/mainwindow.cpp | 9 +- src/imports/scxmlstatemachine/eventconnection.cpp | 104 +++++++++++++++++ src/imports/scxmlstatemachine/eventconnection.h | 85 ++++++++++++++ src/imports/scxmlstatemachine/plugin.cpp | 2 + .../scxmlstatemachine/scxmlstatemachine.pro | 6 +- src/scxml/qscxmlparser.cpp | 1 - src/scxml/qscxmlstatemachine.cpp | 129 +++++++++++++++++---- src/scxml/qscxmlstatemachine.h | 65 ++++++++++- src/scxml/qscxmlstatemachine_p.h | 24 ++++ tests/auto/statemachine/statemachine.pro | 2 +- tests/auto/statemachine/tst_statemachine.cpp | 78 ++++++++++--- 13 files changed, 457 insertions(+), 63 deletions(-) create mode 100644 src/imports/scxmlstatemachine/eventconnection.cpp create mode 100644 src/imports/scxmlstatemachine/eventconnection.h diff --git a/examples/scxml/calculator-qml/calculator-qml.qml b/examples/scxml/calculator-qml/calculator-qml.qml index 846589f..9d679f6 100644 --- a/examples/scxml/calculator-qml/calculator-qml.qml +++ b/examples/scxml/calculator-qml/calculator-qml.qml @@ -51,6 +51,7 @@ import CalculatorStateMachine 1.0 import QtQuick 2.5 import QtQuick.Window 2.0 +import QtScxml 5.7 Window { id: window @@ -61,9 +62,9 @@ Window { CalculatorStateMachine { id: statemachine running: true - onEventOccurred: { - if (event.name === "updateDisplay") - resultText.text = event.data.display; + property var connection: EventConnection { + events: ["updateDisplay"] + onOccurred: resultText.text = event.data.display } } diff --git a/examples/scxml/calculator-widgets/mainwindow.cpp b/examples/scxml/calculator-widgets/mainwindow.cpp index eacafaa..d8c77f7 100644 --- a/examples/scxml/calculator-widgets/mainwindow.cpp +++ b/examples/scxml/calculator-widgets/mainwindow.cpp @@ -115,11 +115,9 @@ MainWindow::MainWindow(QScxmlStateMachine *machine, QWidget *parent) : m_machine->submitEvent("C"); }); - connect(m_machine, &QScxmlStateMachine::eventOccurred, [this](const QScxmlEvent &event) { - if (event.name() == QLatin1String("updateDisplay")) { - const QString display = event.data().toMap().value("display").toString(); - ui->display->setText(display); - } + m_machine->connectToEvent(QLatin1String("updateDisplay"), this, [this](const QScxmlEvent &event) { + const QString display = event.data().toMap().value("display").toString(); + ui->display->setText(display); }); } diff --git a/examples/scxml/pinball/mainwindow.cpp b/examples/scxml/pinball/mainwindow.cpp index 9e525c2..1e7b13c 100644 --- a/examples/scxml/pinball/mainwindow.cpp +++ b/examples/scxml/pinball/mainwindow.cpp @@ -89,8 +89,7 @@ MainWindow::MainWindow(Pinball *machine, QWidget *parent) : initAndConnect(QLatin1String("onState"), m_ui->ballOutButton); // datamodel update - connect(m_machine, SIGNAL(eventOccurred(const QScxmlEvent &)), - this, SLOT(eventOccurred(const QScxmlEvent &))); + m_machine->connectToEvent("updateScore", this, &MainWindow::eventOccurred); // gui interaction connect(m_ui->cButton, &QAbstractButton::clicked, @@ -129,12 +128,6 @@ void MainWindow::initAndConnect(const QString &state, QWidget *widget) void MainWindow::eventOccurred(const QScxmlEvent &event) { - if (event.originType() != QLatin1String("qt:signal")) - return; - - if (event.name() != QLatin1String("updateScore")) - return; - const QVariant data = event.data(); const QString highScore = data.toMap().value("highScore").toString(); m_ui->highScoreLabel->setText(highScore); diff --git a/src/imports/scxmlstatemachine/eventconnection.cpp b/src/imports/scxmlstatemachine/eventconnection.cpp new file mode 100644 index 0000000..3c0cc94 --- /dev/null +++ b/src/imports/scxmlstatemachine/eventconnection.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "eventconnection.h" + +QT_BEGIN_NAMESPACE + +QScxmlEventConnection::QScxmlEventConnection(QObject *parent) : + QObject(parent), m_stateMachine(nullptr) +{ +} + +QStringList QScxmlEventConnection::events() const +{ + return m_events; +} + +void QScxmlEventConnection::setEvents(const QStringList &events) +{ + if (events != m_events) { + m_events = events; + doConnect(); + emit eventsChanged(); + } +} + +QScxmlStateMachine *QScxmlEventConnection::stateMachine() const +{ + return m_stateMachine; +} + +void QScxmlEventConnection::setStateMachine(QScxmlStateMachine *stateMachine) +{ + if (stateMachine != m_stateMachine) { + m_stateMachine = stateMachine; + doConnect(); + emit stateMachineChanged(); + } +} + +void QScxmlEventConnection::doConnect() +{ + foreach (const QMetaObject::Connection &connection, m_connections) + disconnect(connection); + m_connections.clear(); + if (m_stateMachine) { + foreach (const QString &event, m_events) { + m_connections.append(m_stateMachine->connectToEvent(event, this, + &QScxmlEventConnection::occurred)); + } + + } + +} + +void QScxmlEventConnection::classBegin() +{ +} + +void QScxmlEventConnection::componentComplete() +{ + if (!m_stateMachine) { + if ((m_stateMachine = qobject_cast(parent()))) + doConnect(); + } +} + +QT_END_NAMESPACE diff --git a/src/imports/scxmlstatemachine/eventconnection.h b/src/imports/scxmlstatemachine/eventconnection.h new file mode 100644 index 0000000..3492634 --- /dev/null +++ b/src/imports/scxmlstatemachine/eventconnection.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 EVENTCONNECTION_H +#define EVENTCONNECTION_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QScxmlEventConnection : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QStringList events READ events WRITE setEvents NOTIFY eventsChanged) + Q_PROPERTY(QScxmlStateMachine *stateMachine READ stateMachine WRITE setStateMachine + NOTIFY stateMachineChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + QScxmlEventConnection(QObject *parent = nullptr); + + QStringList events() const; + void setEvents(const QStringList &events); + + QScxmlStateMachine *stateMachine() const; + void setStateMachine(QScxmlStateMachine *stateMachine); + +Q_SIGNALS: + void eventsChanged(); + void stateMachineChanged(); + + void occurred(const QScxmlEvent &event); + +private: + QScxmlStateMachine *m_stateMachine; + QStringList m_events; + + QList m_connections; + + void doConnect(); + void classBegin() override; + void componentComplete() override; +}; + +QT_END_NAMESPACE + +#endif // EVENTCONNECTION_H diff --git a/src/imports/scxmlstatemachine/plugin.cpp b/src/imports/scxmlstatemachine/plugin.cpp index 1837935..df833f0 100644 --- a/src/imports/scxmlstatemachine/plugin.cpp +++ b/src/imports/scxmlstatemachine/plugin.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "statemachineloader.h" +#include "eventconnection.h" #include "qscxmlevent.h" #include @@ -66,6 +67,7 @@ public: static const int qScxmlEventMetaTypeId = qMetaTypeId(); Q_UNUSED(qScxmlEventMetaTypeId) qmlRegisterType(uri, major, minor, "StateMachineLoader"); + qmlRegisterType(uri, major, minor, "EventConnection"); qmlProtectModule(uri, 1); } }; diff --git a/src/imports/scxmlstatemachine/scxmlstatemachine.pro b/src/imports/scxmlstatemachine/scxmlstatemachine.pro index 23497ac..40c3b49 100644 --- a/src/imports/scxmlstatemachine/scxmlstatemachine.pro +++ b/src/imports/scxmlstatemachine/scxmlstatemachine.pro @@ -5,10 +5,12 @@ QT = scxml qml-private core-private SOURCES = \ $$PWD/plugin.cpp \ - $$PWD/statemachineloader.cpp + $$PWD/statemachineloader.cpp \ + $$PWD/eventconnection.cpp HEADERS = \ - $$PWD/statemachineloader.h + $$PWD/statemachineloader.h \ + $$PWD/eventconnection.h load(qml_plugin) diff --git a/src/scxml/qscxmlparser.cpp b/src/scxml/qscxmlparser.cpp index a93ea79..e760621 100644 --- a/src/scxml/qscxmlparser.cpp +++ b/src/scxml/qscxmlparser.cpp @@ -722,7 +722,6 @@ private: << QStringLiteral("log") << QStringLiteral("reachedStableState") << QStringLiteral("finished") - << QStringLiteral("eventOccurred") << QStringLiteral("start") << QStringLiteral("setDataBinding") << QStringLiteral("setService") diff --git a/src/scxml/qscxmlstatemachine.cpp b/src/scxml/qscxmlstatemachine.cpp index d2e376b..c75c5cc 100644 --- a/src/scxml/qscxmlstatemachine.cpp +++ b/src/scxml/qscxmlstatemachine.cpp @@ -94,25 +94,6 @@ Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine") * \e event attribute of one \c tag in the SCXML file. */ -/*! - \fn QScxmlStateMachine::eventOccurred(const QScxmlEvent &event) - - This signal is emitted when the SCXML event \a event occurs. This signal is - emitted for all external events. - - \sa externalEventOccurred() -*/ - -/*! - \fn QScxmlStateMachine::externalEventOccurred(const QScxmlEvent &event) - - This signal is emitted for each \c element in the SCXML file that - contains the attribute \c {type="qt:signal"}. The event that occurred is - specified by \a event. - - \sa eventOccurred() -*/ - namespace QScxmlInternal { static int signalIndex(const QMetaObject *meta, const QByteArray &signalName) @@ -166,6 +147,91 @@ void EventLoopHook::timerEvent(QTimerEvent *timerEvent) } } +void ScxmlEventRouter::route(const QStringList &segments, QScxmlEvent *event) +{ + emit eventOccurred(*event); + if (!segments.isEmpty()) { + auto it = children.find(segments.first()); + if (it != children.end()) + it.value()->route(segments.mid(1), event); + } +} + +static QString nextSegment(const QStringList &segments) +{ + if (segments.isEmpty()) + return QString(); + + const QString &segment = segments.first(); + return segment == QLatin1String("*") ? QString() : segment; +} + +ScxmlEventRouter *ScxmlEventRouter::child(const QString &segment) +{ + ScxmlEventRouter *&child = children[segment]; + if (child == nullptr) + child = new ScxmlEventRouter(this); + return child; +} + +void ScxmlEventRouter::disconnectNotify(const QMetaMethod &signal) +{ + Q_UNUSED(signal); + + // Defer the actual work, as this may be called from a destructor, or the signal may not + // actually be disconnected, yet. + QTimer::singleShot(0, this, [this] { + if (!children.isEmpty() || receivers(SIGNAL(eventOccurred(QScxmlEvent))) > 0) + return; + + ScxmlEventRouter *parentRouter = qobject_cast(parent()); + if (!parentRouter) // root node + return; + + QHash::Iterator it = parentRouter->children.begin(), + end = parentRouter->children.end(); + for (; it != end; ++it) { + if (it.value() == this) { + parentRouter->children.erase(it); + parentRouter->disconnectNotify(QMetaMethod()); + break; + } + } + + deleteLater(); // The parent might delete itself, triggering QObject delete cascades. + }); +} + +QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments, + const QObject *receiver, + const char *method, + Qt::ConnectionType type) +{ + QString segment = nextSegment(segments); + return segment.isEmpty() ? + connect(this, SIGNAL(eventOccurred(QScxmlEvent)), receiver, method, type) : + child(segment)->connectToEvent(segments.mid(1), receiver, method, type); +} + +QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *method, + Qt::ConnectionType type) +{ + QString segment = nextSegment(segments); + if (segment.isEmpty()) { + const int *types = Q_NULLPTR; + if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) + types = QtPrivate::ConnectionTypes >::types(); + + const QMetaObject *meta = metaObject(); + static const int eventOccurredIndex = signalIndex(meta, "eventOccurred(QScxmlEvent)"); + return QObjectPrivate::connectImpl(this, eventOccurredIndex, receiver, slot, method, type, + types, meta); + } else { + return child(segment)->connectToEvent(segments.mid(1), receiver, slot, method, type); + } +} } // namespace QScxmlInternal @@ -313,10 +379,7 @@ void QScxmlStateMachinePrivate::postEvent(QScxmlEvent *event) } if (event->eventType() == QScxmlEvent::ExternalEvent) - emit q->eventOccurred(*event); - - if (event->originType() == QLatin1String("qt:signal")) - emit q->externalEventOccurred(*event); + m_router.route(event->name().split(QLatin1Char('.')), event); const int signalIndex = m_tableData->signalIndexForEvent(event->name()); if (signalIndex != -1) { @@ -1519,6 +1582,26 @@ QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlS return QObject::connect(this, signalName.constData(), receiver, method, type); } +QMetaObject::Connection QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, + const char *method, + Qt::ConnectionType type) +{ + Q_D(QScxmlStateMachine); + return d->m_router.connectToEvent(scxmlEventSpec.split(QLatin1Char('.')), receiver, method, + type); +} + +QMetaObject::Connection QScxmlStateMachine::connectToEventImpl(const QString &scxmlEventSpec, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type) +{ + Q_D(QScxmlStateMachine); + return d->m_router.connectToEvent(scxmlEventSpec.split(QLatin1Char('.')), receiver, slot, + slotObj, type); +} + /*! * Initializes the state machine. * diff --git a/src/scxml/qscxmlstatemachine.h b/src/scxml/qscxmlstatemachine.h index 7fb1184..f38416d 100644 --- a/src/scxml/qscxmlstatemachine.h +++ b/src/scxml/qscxmlstatemachine.h @@ -165,6 +165,65 @@ public: } #endif + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, const char *method, + Qt::ConnectionType type = Qt::AutoConnection); + +#ifdef Q_QDOC + template + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, + const QObject *receiver, PointerToMemberFunction method, + Qt::ConnectionType type = Qt::AutoConnection); + template + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, Functor functor, + Qt::ConnectionType type = Qt::AutoConnection); + template + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, + const QObject *context, Functor functor, + Qt::ConnectionType type = Qt::AutoConnection); +#else + + // connect state to a QObject slot + template + inline QMetaObject::Connection connectToEvent( + const QString &scxmlEventSpec, + const typename QtPrivate::FunctionPointer::Object *receiver, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + typedef QtPrivate::FunctionPointer SlotType; + return connectToEventImpl( + scxmlEventSpec, receiver, nullptr, + new QtPrivate::QSlotObject(slot), + type); + } + + // connect state to a functor or function pointer (without context) + template + inline typename QtPrivate::QEnableIf< + !QtPrivate::FunctionPointer::IsPointerToMemberFunction && + !QtPrivate::is_same::value, QMetaObject::Connection>::Type + connectToEvent(const QString &scxmlEventSpec, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + // Use this as context + return connectToEvent(scxmlEventSpec, this, slot, type); + } + + // connectToEvent to a functor or function pointer (with context) + template + inline typename QtPrivate::QEnableIf< + !QtPrivate::FunctionPointer::IsPointerToMemberFunction && + !QtPrivate::is_same::value, QMetaObject::Connection>::Type + connectToEvent(const QString &scxmlEventSpec, QObject *context, Func1 slot, + Qt::ConnectionType type = Qt::AutoConnection) + { + QtPrivate::QSlotObjectBase *slotObj = new QtPrivate::QFunctorSlotObject, void>(slot); + return connectToEventImpl(scxmlEventSpec, context, reinterpret_cast(&slot), + slotObj, type); + } +#endif + Q_INVOKABLE void submitEvent(QScxmlEvent *event); Q_INVOKABLE void submitEvent(const QString &eventName); Q_INVOKABLE void submitEvent(const QString &eventName, const QVariant &data); @@ -177,11 +236,9 @@ Q_SIGNALS: void log(const QString &label, const QString &msg); void reachedStableState(); void finished(); - void eventOccurred(const QScxmlEvent &event); void dataModelChanged(QScxmlDataModel *model); void initialValuesChanged(const QVariantMap &initialValues); void initializedChanged(bool initialized); - void externalEventOccurred(const QScxmlEvent &event); public Q_SLOTS: void start(); @@ -207,6 +264,10 @@ private: const QObject *receiver, void **slot, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type = Qt::AutoConnection); + QMetaObject::Connection connectToEventImpl(const QString &scxmlEventSpec, + const QObject *receiver, void **slot, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type = Qt::AutoConnection); }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmlstatemachine_p.h b/src/scxml/qscxmlstatemachine_p.h index 123d935..e1b2066 100644 --- a/src/scxml/qscxmlstatemachine_p.h +++ b/src/scxml/qscxmlstatemachine_p.h @@ -78,6 +78,29 @@ public: protected: void timerEvent(QTimerEvent *timerEvent) Q_DECL_OVERRIDE; }; + +class ScxmlEventRouter : public QObject +{ + Q_OBJECT +public: + ScxmlEventRouter(QObject *parent = nullptr) : QObject(parent) {} + QMetaObject::Connection connectToEvent(const QStringList &segments, const QObject *receiver, + const char *method, Qt::ConnectionType type); + QMetaObject::Connection connectToEvent(const QStringList &segments, const QObject *receiver, + void **slot, QtPrivate::QSlotObjectBase *method, + Qt::ConnectionType type); + + void route(const QStringList &segments, QScxmlEvent *event); + +signals: + void eventOccurred(const QScxmlEvent &event); + +private: + QHash children; + ScxmlEventRouter *child(const QString &segment); + + void disconnectNotify(const QMetaMethod &signal) override; +}; } // QScxmlInternal namespace class QScxmlInvokableService; @@ -297,6 +320,7 @@ public: // types & data fields: typedef std::vector> DelayedQueue; DelayedQueue m_delayedEvents; const QMetaObject *m_metaObject; + QScxmlInternal::ScxmlEventRouter m_router; private: QScopedPointer m_parserData; // used when created by StateMachine::fromFile. diff --git a/tests/auto/statemachine/statemachine.pro b/tests/auto/statemachine/statemachine.pro index c96ce21..eefbd7f 100644 --- a/tests/auto/statemachine/statemachine.pro +++ b/tests/auto/statemachine/statemachine.pro @@ -1,4 +1,4 @@ -QT = core gui qml testlib scxml +QT = core gui qml testlib scxml-private CONFIG += testcase TARGET = tst_statemachine diff --git a/tests/auto/statemachine/tst_statemachine.cpp b/tests/auto/statemachine/tst_statemachine.cpp index b3c7251..227d427 100644 --- a/tests/auto/statemachine/tst_statemachine.cpp +++ b/tests/auto/statemachine/tst_statemachine.cpp @@ -31,6 +31,7 @@ #include #include #include +#include Q_DECLARE_METATYPE(QScxmlError); @@ -183,6 +184,13 @@ void tst_StateMachine::connections() QVERIFY(disconnect(final)); } +bool hasChildEventRouters(QScxmlStateMachine *stateMachine) +{ + // Cast to QObject, to avoid ambigous "children" member. + const QObject &parentRouter = QScxmlStateMachinePrivate::get(stateMachine)->m_router; + return !parentRouter.children().isEmpty(); +} + void tst_StateMachine::eventOccurred() { QScopedPointer stateMachine(QScxmlStateMachine::fromFile(QString(":/tst_statemachine/eventoccurred.scxml"))); @@ -190,29 +198,63 @@ void tst_StateMachine::eventOccurred() qRegisterMetaType(); QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished())); - QSignalSpy eventOccurredSpy(stateMachine.data(), SIGNAL(eventOccurred(QScxmlEvent))); - QSignalSpy externalEventOccurredSpy(stateMachine.data(), SIGNAL(externalEventOccurred(QScxmlEvent))); + + int events = 0; + auto con1 = stateMachine->connectToEvent("internalEvent2", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 1); + QCOMPARE(event.name(), QString("internalEvent2")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con1); + + auto con2 = stateMachine->connectToEvent("externalEvent", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 2); + QCOMPARE(event.name(), QString("externalEvent")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con2); + + auto con3 = stateMachine->connectToEvent("timeout", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 3); + QCOMPARE(event.name(), QString("timeout")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con3); + + auto con4 = stateMachine->connectToEvent("done.*", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 4); + QCOMPARE(event.name(), QString("done.state.top")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con4); + + auto con5 = stateMachine->connectToEvent("done.state", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 5); + QCOMPARE(event.name(), QString("done.state.top")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con5); + + auto con6 = stateMachine->connectToEvent("done.state.top", [&events](const QScxmlEvent &event) { + QCOMPARE(++events, 6); + QCOMPARE(event.name(), QString("done.state.top")); + QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent); + }); + QVERIFY(con6); stateMachine->start(); finishedSpy.wait(5000); + QCOMPARE(events, 6); + + QVERIFY(disconnect(con1)); + QVERIFY(disconnect(con2)); + QVERIFY(disconnect(con3)); + QVERIFY(disconnect(con4)); + QVERIFY(disconnect(con5)); + QVERIFY(disconnect(con6)); - auto event = [&eventOccurredSpy](int eventIndex) -> QScxmlEvent { - return qvariant_cast(eventOccurredSpy.at(eventIndex).at(0)); - }; - - QCOMPARE(eventOccurredSpy.count(), 4); - QCOMPARE(event(0).name(), QLatin1String("internalEvent2")); - QCOMPARE(event(0).eventType(), QScxmlEvent::ExternalEvent); - QCOMPARE(event(1).name(), QLatin1String("externalEvent")); - QCOMPARE(event(1).eventType(), QScxmlEvent::ExternalEvent); - QCOMPARE(event(2).name(), QLatin1String("timeout")); - QCOMPARE(event(2).eventType(), QScxmlEvent::ExternalEvent); - QCOMPARE(event(3).name(), QLatin1String("done.state.top")); - QCOMPARE(event(3).eventType(), QScxmlEvent::ExternalEvent); - - QCOMPARE(externalEventOccurredSpy.count(), 1); - QCOMPARE(qvariant_cast(externalEventOccurredSpy.at(0).at(0)).name(), QLatin1String("externalEvent")); + QTRY_VERIFY(!hasChildEventRouters(stateMachine.data())); } void tst_StateMachine::doneDotStateEvent() -- cgit v1.2.3