diff options
Diffstat (limited to 'src/imports/scxmlstatemachine')
-rw-r--r-- | src/imports/scxmlstatemachine/plugin.cpp | 45 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/qmldir | 4 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/scxmlstatemachine.pro | 19 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/signalevent.cpp | 82 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/signalevent.h | 62 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/state.cpp | 94 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/state.h | 74 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/statemachine.cpp | 163 | ||||
-rw-r--r-- | src/imports/scxmlstatemachine/statemachine.h | 71 |
9 files changed, 614 insertions, 0 deletions
diff --git a/src/imports/scxmlstatemachine/plugin.cpp b/src/imports/scxmlstatemachine/plugin.cpp new file mode 100644 index 0000000..9b2f4d4 --- /dev/null +++ b/src/imports/scxmlstatemachine/plugin.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "statemachine.h" +#include "state.h" +#include "signalevent.h" + +#include <QQmlExtensionPlugin> +#include <qqml.h> + +QT_BEGIN_NAMESPACE + +class ScxmlStateMachinePlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Scxml/1.0") + +public: + void registerTypes(const char *uri) + { + qmlRegisterType<StateMachine>(uri, 1, 0, "StateMachine"); + qmlRegisterType<State>(uri, 1, 0, "State"); + qmlRegisterType<SignalEvent>(uri, 1, 0, "SignalEvent"); + qmlProtectModule(uri, 1); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/scxmlstatemachine/qmldir b/src/imports/scxmlstatemachine/qmldir new file mode 100644 index 0000000..a411f70 --- /dev/null +++ b/src/imports/scxmlstatemachine/qmldir @@ -0,0 +1,4 @@ +module Scxml +plugin scxmlstatemachine +classname ScxmlStateMachinePlugin + diff --git a/src/imports/scxmlstatemachine/scxmlstatemachine.pro b/src/imports/scxmlstatemachine/scxmlstatemachine.pro new file mode 100644 index 0000000..4cbf853 --- /dev/null +++ b/src/imports/scxmlstatemachine/scxmlstatemachine.pro @@ -0,0 +1,19 @@ +CXX_MODULE = qml +TARGET = scxmlstatemachine +TARGETPATH = Scxml +IMPORT_VERSION = 1.0 + +QT = qscxml qml-private core-private + +SOURCES = \ + $$PWD/plugin.cpp \ + $$PWD/signalevent.cpp \ + $$PWD/state.cpp \ + $$PWD/statemachine.cpp + +HEADERS = \ + $$PWD/signalevent.h \ + $$PWD/state.h \ + $$PWD/statemachine.h + +load(qml_plugin) diff --git a/src/imports/scxmlstatemachine/signalevent.cpp b/src/imports/scxmlstatemachine/signalevent.cpp new file mode 100644 index 0000000..a9c3739 --- /dev/null +++ b/src/imports/scxmlstatemachine/signalevent.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "signalevent.h" +#include "statemachine.h" + +#include <QQmlContext> +#include <QQmlInfo> +#include <QQmlEngine> + +#include <private/qjsvalue_p.h> +#include <private/qv8engine_p.h> + +SignalEvent::SignalEvent(StateMachine *parent) + : QObject(parent) +{} + +QString SignalEvent::eventName() const +{ + return QString::fromUtf8(m_eventName); +} + +void SignalEvent::setEventName(const QString &eventName) +{ + QByteArray name = eventName.toUtf8(); + if (name != m_eventName) { + m_eventName = name; + emit eventNameChanged(); + } +} + +const QJSValue &SignalEvent::signal() +{ + return m_signal; +} + +void SignalEvent::setSignal(const QJSValue &signal) +{ + if (m_signal.strictlyEquals(signal)) + return; + + disconnect(m_signalConnection); + + m_signal = signal; + + QV4::ExecutionEngine *jsEngine = QV8Engine::getV4(QQmlEngine::contextForObject(this)->engine()); + QV4::Scope scope(jsEngine); + + QV4::Scoped<QV4::QObjectMethod> qobjectSignal(scope, QJSValuePrivate::convertedToValue(jsEngine, m_signal)); + Q_ASSERT(qobjectSignal); + + QObject *sender = qobjectSignal->object(); + QByteArray signature = sender->metaObject()->method(qobjectSignal->methodIndex()).methodSignature(); + signature.prepend(SIGNAL()); + m_signalConnection = connect(sender, signature.constData(), this, SLOT(invoke())); + + emit signalChanged(); +} + +void SignalEvent::invoke() +{ + if (Scxml::StateTable *table = qobject_cast<StateMachine *>(parent())->stateMachine()) { + QMetaObject::invokeMethod(table, "submitEvent", Q_ARG(QByteArray, m_eventName)); + } else { + qmlInfo(this) << QStringLiteral("No state table found to submit event '%1'.").arg(QString::fromUtf8(m_eventName)); + } +} diff --git a/src/imports/scxmlstatemachine/signalevent.h b/src/imports/scxmlstatemachine/signalevent.h new file mode 100644 index 0000000..473fc35 --- /dev/null +++ b/src/imports/scxmlstatemachine/signalevent.h @@ -0,0 +1,62 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef SIGNALEVENT_H +#define SIGNALEVENT_H + +#include <QJSValue> +#include <QObject> + +namespace Scxml { +class ScxmlState; +} + +QT_BEGIN_NAMESPACE + +class StateMachine; +class SignalEvent: public QObject +{ + Q_OBJECT + Q_PROPERTY(QJSValue signal READ signal WRITE setSignal NOTIFY signalChanged) + Q_PROPERTY(QString eventName READ eventName WRITE setEventName NOTIFY eventNameChanged) + +public: + explicit SignalEvent(StateMachine *parent = 0); + + QString eventName() const; + void setEventName(const QString &eventName); + + const QJSValue &signal(); + void setSignal(const QJSValue &signal); + +Q_SIGNALS: + void signalChanged(); + void eventNameChanged(); + +public Q_SLOTS: + void invoke(); + +private: + QJSValue m_signal; + QByteArray m_eventName; + QMetaObject::Connection m_signalConnection; +}; + +QT_END_NAMESPACE + +#endif // SIGNALEVENT_H diff --git a/src/imports/scxmlstatemachine/state.cpp b/src/imports/scxmlstatemachine/state.cpp new file mode 100644 index 0000000..da240b4 --- /dev/null +++ b/src/imports/scxmlstatemachine/state.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "state.h" +#include "statemachine.h" + +#include <QScxml/scxmlstatetable.h> +#include <QQmlInfo> + +State::State(StateMachine *parent) + : QObject(parent) + , completed(false) + , active(false) +{} + +void State::componentComplete() +{ + completed = true; + establishConnections(); + + if (Scxml::StateTable *table = qobject_cast<StateMachine *>(parent())->stateMachine()) + active = table->currentStates().contains(m_scxmlName); + if (active) + emit activeChanged(active); +} + +bool State::isActive() const +{ + return active; +} + +QString State::scxmlName() const +{ + return m_scxmlName; +} + +void State::setScxmlName(const QString &scxmlName) +{ + if (m_scxmlName != scxmlName) { + m_scxmlName = scxmlName; + breakConnections(); + establishConnections(); + emit scxmlNameChanged(); + } +} + +void State::setActive(bool active) +{ + this->active = active; + emit activeChanged(active); +} + +void State::breakConnections() +{ + disconnect(activeConnection); + disconnect(didEnterConnection); + disconnect(willExitConnection); +} + +void State::establishConnections() +{ + if (!completed) + return; + + Scxml::StateTable *table = qobject_cast<StateMachine *>(parent())->stateMachine(); + if (table == nullptr) { + qmlInfo(this) << QStringLiteral("State is not part of a StateTable."); + return; + } + + if (!table->hasState(m_scxmlName)) { + qmlInfo(this) << QStringLiteral("No state '%1' found.").arg(m_scxmlName); + return; + } + + activeConnection = table->connect(m_scxmlName, SIGNAL(activeChanged(bool)), this, SLOT(setActive(bool))); + didEnterConnection = table->connect(m_scxmlName, SIGNAL(didEnter()), this, SIGNAL(didEnter())); + willExitConnection = table->connect(m_scxmlName, SIGNAL(willExit()), this, SIGNAL(willExit())); +} diff --git a/src/imports/scxmlstatemachine/state.h b/src/imports/scxmlstatemachine/state.h new file mode 100644 index 0000000..53c3e37 --- /dev/null +++ b/src/imports/scxmlstatemachine/state.h @@ -0,0 +1,74 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef STATE_H +#define STATE_H + +#include <QtQml/QQmlParserStatus> +#include <QtQml/QQmlListProperty> + +namespace Scxml { +class ScxmlState; +} + +QT_BEGIN_NAMESPACE + +class StateMachine; +class State: public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) + Q_PROPERTY(QString scxmlName READ scxmlName WRITE setScxmlName NOTIFY scxmlNameChanged) + +public: + explicit State(StateMachine *parent = 0); + + void classBegin() {} + void componentComplete(); + + bool isActive() const; + + QString scxmlName() const; + void setScxmlName(const QString &scxmlName); + +Q_SIGNALS: + void activeChanged(bool active); + void scxmlNameChanged(); + void didEnter(); + void willExit(); + +private Q_SLOTS: + void setActive(bool active); + +private: + void breakConnections(); + void establishConnections(); + +private: + QString m_scxmlName; + QMetaObject::Connection activeConnection; + QMetaObject::Connection didEnterConnection; + QMetaObject::Connection willExitConnection; + bool completed; + bool active; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/imports/scxmlstatemachine/statemachine.cpp b/src/imports/scxmlstatemachine/statemachine.cpp new file mode 100644 index 0000000..c953018 --- /dev/null +++ b/src/imports/scxmlstatemachine/statemachine.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#include "signalevent.h" +#include "state.h" +#include "statemachine.h" + +#include <QScxml/ecmascriptdatamodel.h> +#include <QScxml/scxmlstatetable.h> +#include <QScxml/scxmlparser.h> +#include <QQmlContext> +#include <QQmlEngine> +#include <QQmlInfo> +#include <QQmlFile> +#include <QBuffer> + +static void append(QQmlListProperty<QObject> *prop, QObject *o) +{ + if (!o) + return; + + if (State *state = qobject_cast<State *>(o)) { + state->setParent(prop->object); + static_cast<StateMachine::Kids *>(prop->data)->append(state); + emit static_cast<StateMachine *>(prop->object)->statesChanged(); + } else if (SignalEvent *event = qobject_cast<SignalEvent *>(o)) { + event->setParent(prop->object); + static_cast<StateMachine::Kids *>(prop->data)->append(event); + emit static_cast<StateMachine *>(prop->object)->statesChanged(); + } +} + +static int count(QQmlListProperty<QObject> *prop) +{ + return static_cast<StateMachine::Kids *>(prop->data)->count(); +} + +static QObject *at(QQmlListProperty<QObject> *prop, int index) +{ + return static_cast<StateMachine::Kids *>(prop->data)->at(index); +} + +static void clear(QQmlListProperty<QObject> *prop) +{ + static_cast<StateMachine::Kids *>(prop->data)->clear(); + emit static_cast<StateMachine *>(prop->object)->statesChanged(); +} + +StateMachine::StateMachine(QObject *parent) + : QObject(parent) +{ +} + +void StateMachine::componentComplete() +{ + if (m_table == nullptr) { + qmlInfo(this) << "No state machine loaded."; + return; + } + + bool ok = false; + bool moreOk = QMetaObject::invokeMethod(m_table, "init", Qt::DirectConnection, Q_RETURN_ARG(bool, ok)); + if (ok && moreOk) { + QMetaObject::invokeMethod(m_table, "start"); + } else { + qmlInfo(this) << "Failed to initialize the state machine."; + } +} + +QQmlListProperty<QObject> StateMachine::states() +{ + return QQmlListProperty<QObject>(this, &m_children, append, count, at, clear); +} + +Scxml::StateTable *StateMachine::stateMachine() const +{ + return m_table; +} + +void StateMachine::setStateMachine(Scxml::StateTable *table) +{ + qDebug()<<"setting state machine to"<<table; + if (m_table == nullptr && table != nullptr) { + m_table = table; + } else if (m_table) { + qmlInfo(this) << "Can set the table only once"; + } +} + +QUrl StateMachine::filename() +{ + return m_filename; +} + +void StateMachine::setFilename(const QUrl &filename) +{ + QUrl oldFilename = m_filename; + if (m_table) { + delete m_table; + m_table = nullptr; + } + + if (parse(filename)) { + m_filename = filename; + emit filenameChanged(); + } else { + m_filename.clear(); + if (!oldFilename.isEmpty()) { + emit filenameChanged(); + } + } +} + +bool StateMachine::parse(const QUrl &filename) +{ + if (!QQmlFile::isSynchronous(filename)) { + qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading: only synchronous file access is supported.").arg(filename.fileName()); + return false; + } + QQmlFile scxmlFile(QQmlEngine::contextForObject(this)->engine(), filename); + 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(filename.fileName()); + return false; + } + + QByteArray data(scxmlFile.dataByteArray()); + QBuffer buf(&data); + Q_ASSERT(buf.open(QIODevice::ReadOnly)); + QXmlStreamReader xmlReader(&buf); + Scxml::ScxmlParser parser(&xmlReader); + parser.parse(); + setStateMachine(parser.table()); + + if (parser.state() != Scxml::ScxmlParser::FinishedParsing || m_table == nullptr) { + qmlInfo(this) << QStringLiteral("Something went wrong while parsing '%1':").arg(filename.fileName()) << endl; + foreach (const Scxml::ErrorMessage &msg, parser.errors()) { + qmlInfo(this) << msg.fileName << QStringLiteral(":") << msg.line + << QStringLiteral(":") << msg.column + << QStringLiteral(": ") << msg.severityString() + << QStringLiteral(": ") << msg.msg; + } + + return false; + } + + return true; +} diff --git a/src/imports/scxmlstatemachine/statemachine.h b/src/imports/scxmlstatemachine/statemachine.h new file mode 100644 index 0000000..554e25d --- /dev/null +++ b/src/imports/scxmlstatemachine/statemachine.h @@ -0,0 +1,71 @@ +/**************************************************************************** + ** + ** Copyright (c) 2015 Digia Plc + ** For any questions to Digia, please use contact form at http://qt.digia.com/ + ** + ** All Rights Reserved. + ** + ** NOTICE: All information contained herein is, and remains + ** the property of Digia Plc and its suppliers, + ** if any. The intellectual and technical concepts contained + ** herein are proprietary to Digia Plc + ** and its suppliers and may be covered by Finnish and Foreign Patents, + ** patents in process, and are protected by trade secret or copyright law. + ** Dissemination of this information or reproduction of this material + ** is strictly forbidden unless prior written permission is obtained + ** from Digia Plc. + ****************************************************************************/ + +#ifndef STATEMACHINE_H +#define STATEMACHINE_H + +#include <QUrl> +#include <QVector> +#include <QQmlParserStatus> +#include <QQmlListProperty> +#include <QScxml/scxmlstatetable.h> + +QT_BEGIN_NAMESPACE + +class State; +class QQmlOpenMetaObject; +class StateMachine: public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QQmlListProperty<QObject> states READ states NOTIFY statesChanged DESIGNABLE false) + Q_PROPERTY(QUrl filename READ filename WRITE setFilename NOTIFY filenameChanged) + Q_PROPERTY(Scxml::StateTable* stateMachine READ stateMachine WRITE setStateMachine) + + Q_CLASSINFO("DefaultProperty", "states") + +public: + typedef QVector<QObject *> Kids; + explicit StateMachine(QObject *parent = 0); + + void classBegin() {} + void componentComplete(); + QQmlListProperty<QObject> states(); + + Scxml::StateTable *stateMachine() const; + void setStateMachine(Scxml::StateTable *stateMachine); + + QUrl filename(); + void setFilename(const QUrl &filename); + +Q_SIGNALS: + void statesChanged(); + void filenameChanged(); + +private: + bool parse(const QUrl &filename); + +private: + QUrl m_filename; + Kids m_children; + Scxml::StateTable *m_table = nullptr; +}; + +QT_END_NAMESPACE + +#endif |