From 314931009b6205e58391afcff6b25ee317dd7d91 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 6 Sep 2016 14:18:50 +0200 Subject: Add onEntry and onExit templates to QScxmlStateMachines These help to avoid repeating "if (isInState) ..." if you only want to execute a handler when a state is entered, or "if (!isInState) ..." if you want to execute it only when a state is left. Change-Id: I0a73585591163dd2741b2d9d4897ee50e9623853 Reviewed-by: Erik Verbruggen --- src/scxml/qscxmlstatemachine.cpp | 96 +++++++++++++++++++++++++++ src/scxml/qscxmlstatemachine.h | 78 ++++++++++++++++++++++ tests/auto/compiled/compiled.pro | 2 +- tests/auto/compiled/tst_compiled.cpp | 30 +++++++++ tests/auto/statemachine/statemachine.pro | 2 +- tests/auto/statemachine/tst_statemachine.cpp | 98 +++++++++++++++++++++++++++- 6 files changed, 303 insertions(+), 3 deletions(-) diff --git a/src/scxml/qscxmlstatemachine.cpp b/src/scxml/qscxmlstatemachine.cpp index 326cb80..915844c 100644 --- a/src/scxml/qscxmlstatemachine.cpp +++ b/src/scxml/qscxmlstatemachine.cpp @@ -177,6 +177,102 @@ Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine") Returns a handle to the connection, which can be used later to disconnect. */ +/*! + \fn auto QScxmlStateMachine::onEntry(const QObject *receiver, + const char *method) + + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver using QMetaObject::invokeMethod() if that argument + is \c true and \a receiver has not been deleted, yet. + + The given \a method must not accept any arguments. \a method is the plain + method name, not enclosed in \c SIGNAL() or \c SLOT(). + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is entered. + + onEntry() is only available if the compiler supports return type + deduction for functions. +*/ + +/*! + \fn auto QScxmlStateMachine::onExit(const QObject *receiver, + const char *method) + + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver using QMetaObject::invokeMethod() if that argument + is \c false and \a receiver has not been deleted, yet. + + The given \a method must not accept any arguments. \a method is the plain + method name, not enclosed in SIGNAL(...) or SLOT(...). + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is left. + + onExit() is only available if the compiler supports return type + deduction for functions. +*/ + +/*! + \fn QScxmlStateMachine::onEntry(Functor functor) + + Returns a functor that accepts a boolean argument and calls the given + \a functor if that argument is \c true. The given \a functor must not + accept any arguments. + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is entered. + + onEntry() is only available if the compiler supports return type + deduction for functions. + */ + +/*! + \fn QScxmlStateMachine::onExit(Functor functor) + + Returns a functor that accepts a boolean argument and calls the given + \a functor if that argument is \c false. The given \a functor must not + accept any arguments. + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is left. + + onExit() is only available if the compiler supports return type + deduction for functions. + */ + +/*! + \fn QScxmlStateMachine::onEntry(const QObject *receiver, + PointerToMemberFunction method) + + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver if that argument is \c true and the \a receiver + has not been deleted, yet. The given \a method must not accept any + arguments. + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is entered. + + onEntry() is only available if the compiler supports return type + deduction for functions. + */ + +/*! + \fn QScxmlStateMachine::onExit(const QObject *receiver, + PointerToMemberFunction method) + + Returns a functor that accepts a boolean argument and calls the given + \a method on \a receiver if that argument is \c false and the \a receiver + has not been deleted, yet. The given \a method must not accept any + arguments. + + This is useful to wrap handlers for connectToState() that should only + be executed when the state is left. + + onExit() is only available if the compiler supports return type + deduction for functions. + */ + namespace QScxmlInternal { static int signalIndex(const QMetaObject *meta, const QByteArray &signalName) diff --git a/src/scxml/qscxmlstatemachine.h b/src/scxml/qscxmlstatemachine.h index 787a7c6..e5a0b92 100644 --- a/src/scxml/qscxmlstatemachine.h +++ b/src/scxml/qscxmlstatemachine.h @@ -50,6 +50,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE class QIODevice; @@ -169,6 +170,83 @@ public: } #endif +#ifdef Q_QDOC + static auto onEntry(const QObject *receiver, const char *method); + static auto onExit(const QObject *receiver, const char *method); + + template + static auto onEntry(Functor functor); + + template + static auto onExit(Functor functor); + + template + static auto onEntry(const QObject *receiver, PointerToMemberFunction method); + + template + static auto onExit(const QObject *receiver, PointerToMemberFunction method); +#elif defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + static auto onEntry(const QObject *receiver, const char *method) + { + const QPointer receiverPointer(const_cast(receiver)); + return [receiverPointer, method](bool isEnteringState) { + if (isEnteringState && !receiverPointer.isNull()) + QMetaObject::invokeMethod(const_cast(receiverPointer.data()), method); + }; + } + + static auto onExit(const QObject *receiver, const char *method) + { + const QPointer receiverPointer(const_cast(receiver)); + return [receiverPointer, method](bool isEnteringState) { + if (!isEnteringState && !receiverPointer.isNull()) + QMetaObject::invokeMethod(receiverPointer.data(), method); + }; + } + + template + static auto onEntry(Functor functor) + { + return [functor](bool isEnteringState) { + if (isEnteringState) + functor(); + }; + } + + template + static auto onExit(Functor functor) + { + return [functor](bool isEnteringState) { + if (!isEnteringState) + functor(); + }; + } + + template + static auto onEntry(const typename QtPrivate::FunctionPointer::Object *receiver, + Func1 slot) + { + typedef typename QtPrivate::FunctionPointer::Object Object; + const QPointer receiverPointer(const_cast(receiver)); + return [receiverPointer, slot](bool isEnteringState) { + if (isEnteringState && !receiverPointer.isNull()) + (receiverPointer->*slot)(); + }; + } + + template + static auto onExit(const typename QtPrivate::FunctionPointer::Object *receiver, + Func1 slot) + { + typedef typename QtPrivate::FunctionPointer::Object Object; + const QPointer receiverPointer(const_cast(receiver)); + return [receiverPointer, slot](bool isEnteringState) { + if (!isEnteringState && !receiverPointer.isNull()) + (receiverPointer->*slot)(); + }; + } +#endif // defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + QMetaObject::Connection connectToEvent(const QString &scxmlEventSpec, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection); diff --git a/tests/auto/compiled/compiled.pro b/tests/auto/compiled/compiled.pro index 713f484..c13cbf4 100644 --- a/tests/auto/compiled/compiled.pro +++ b/tests/auto/compiled/compiled.pro @@ -1,5 +1,5 @@ QT = core gui qml testlib scxml -CONFIG += testcase +CONFIG += testcase c++14 TARGET = tst_compiled CONFIG += console diff --git a/tests/auto/compiled/tst_compiled.cpp b/tests/auto/compiled/tst_compiled.cpp index 0e4bb02..e09300e 100644 --- a/tests/auto/compiled/tst_compiled.cpp +++ b/tests/auto/compiled/tst_compiled.cpp @@ -135,8 +135,21 @@ public slots: { received = received || enabled; } + + void enter() + { + entered = true; + } + + void exit() + { + exited = true; + } + public: bool received = false; + bool entered = false; + bool exited = false; }; void tst_Compiled::connection() @@ -155,6 +168,16 @@ void tst_Compiled::connection() QMetaObject::Connection conB = stateMachine.connectToState("b", &receiverB, SLOT(receive(bool))); QMetaObject::Connection conFinal = stateMachine.connectToState("final", &receiverFinal, SLOT(receive(bool))); +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + // C++14 available: test for onEntry and onExit + typedef QScxmlStateMachine QXSM; + QMetaObject::Connection aEntry = stateMachine.connectToState("a", QXSM::onEntry(&receiverA, "enter")); + QMetaObject::Connection aExit = stateMachine.connectToState("a", QXSM::onExit(&receiverA, "exit")); + + QVERIFY(aEntry); + QVERIFY(aExit); +#endif + QVERIFY(conA); QVERIFY(conA1); QVERIFY(conA2); @@ -174,6 +197,13 @@ void tst_Compiled::connection() QVERIFY(disconnect(conA2)); QVERIFY(disconnect(conB)); QVERIFY(disconnect(conFinal)); + +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + QVERIFY(receiverA.entered); + QVERIFY(!receiverA.exited); + QVERIFY(disconnect(aEntry)); + QVERIFY(disconnect(aExit)); +#endif } class MyConnection : public Connection diff --git a/tests/auto/statemachine/statemachine.pro b/tests/auto/statemachine/statemachine.pro index 0e4de1a..95bf9a8 100644 --- a/tests/auto/statemachine/statemachine.pro +++ b/tests/auto/statemachine/statemachine.pro @@ -1,5 +1,5 @@ QT = core gui qml testlib scxml-private -CONFIG += testcase +CONFIG += testcase c++14 TARGET = tst_statemachine CONFIG += console diff --git a/tests/auto/statemachine/tst_statemachine.cpp b/tests/auto/statemachine/tst_statemachine.cpp index 227d427..1cf0518 100644 --- a/tests/auto/statemachine/tst_statemachine.cpp +++ b/tests/auto/statemachine/tst_statemachine.cpp @@ -47,6 +47,7 @@ private Q_SLOTS: void activeStateNames_data(); void activeStateNames(); void connections(); + void onExit(); void eventOccurred(); void doneDotStateEvent(); @@ -141,10 +142,21 @@ public slots: bReached = bReached || enabled; } + void aEnter() + { + aEntered = true; + } + + void aExit() + { + aExited = true; + } + public: bool aReached = false; bool bReached = false; - + bool aEntered = false; + bool aExited = false; }; void tst_StateMachine::connections() @@ -171,6 +183,44 @@ void tst_StateMachine::connections() }); QVERIFY(final); +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + // C++14 available, test onEntry and onExit + bool a1Entered = false; + bool a1Exited = false; + bool finalEntered = false; + bool finalExited = false; + typedef QScxmlStateMachine QXSM; + + QMetaObject::Connection aEntry = stateMachine->connectToState( + "a", QXSM::onEntry(&receiver, &Receiver::aEnter)); + QVERIFY(aEntry); + QMetaObject::Connection aExit = stateMachine->connectToState( + "a", QXSM::onExit(&receiver, &Receiver::aExit)); + QVERIFY(aExit); + QMetaObject::Connection a1Entry = stateMachine->connectToState("a1", &receiver, + QXSM::onEntry([&a1Entered]() { + a1Entered = true; + })); + QVERIFY(a1Entry); + QMetaObject::Connection a1Exit = stateMachine->connectToState("a1", &receiver, + QXSM::onExit([&a1Exited]() { + a1Exited = true; + })); + QVERIFY(a1Exit); + + QMetaObject::Connection finalEntry = stateMachine->connectToState( + "final", QXSM::onEntry([&finalEntered]() { + finalEntered = true; + })); + QVERIFY(finalEntry); + + QMetaObject::Connection finalExit = stateMachine->connectToState( + "final", QXSM::onExit([&finalExited]() { + finalExited = true; + })); + QVERIFY(finalExit); +#endif + stateMachine->start(); QTRY_VERIFY(a1Reached); @@ -182,6 +232,52 @@ void tst_StateMachine::connections() QVERIFY(disconnect(b)); QVERIFY(disconnect(a1)); QVERIFY(disconnect(final)); + +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + QVERIFY(receiver.aEntered); + QVERIFY(!receiver.aExited); + QVERIFY(a1Entered); + QVERIFY(!a1Exited); + QVERIFY(finalEntered); + QVERIFY(!finalExited); + + QVERIFY(disconnect(aEntry)); + QVERIFY(disconnect(aExit)); + QVERIFY(disconnect(a1Entry)); + QVERIFY(disconnect(a1Exit)); + QVERIFY(disconnect(finalEntry)); + QVERIFY(disconnect(finalExit)); +#endif +} + +void tst_StateMachine::onExit() +{ +#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304 + // Test onExit being actually called + + typedef QScxmlStateMachine QXSM; + QScopedPointer stateMachine(QXSM::fromFile(QString(":/tst_statemachine/eventoccurred.scxml"))); + + Receiver receiver; + bool aExited1 = false; + + stateMachine->connectToState("a", QXSM::onExit([&aExited1]() { aExited1 = true; })); + stateMachine->connectToState("a", QXSM::onExit(&receiver, &Receiver::aExit)); + stateMachine->connectToState("a", QXSM::onExit(&receiver, "aEnter")); + { + // Should not crash + Receiver receiver2; + stateMachine->connectToState("a", QXSM::onEntry(&receiver2, &Receiver::aEnter)); + stateMachine->connectToState("a", QXSM::onEntry(&receiver2, "aExit")); + stateMachine->connectToState("a", QXSM::onExit(&receiver2, &Receiver::aExit)); + stateMachine->connectToState("a", QXSM::onExit(&receiver2, "aEnter")); + } + + stateMachine->start(); + QTRY_VERIFY(receiver.aEntered); + QTRY_VERIFY(receiver.aExited); + QTRY_VERIFY(aExited1); +#endif } bool hasChildEventRouters(QScxmlStateMachine *stateMachine) -- cgit v1.2.3