diff options
author | Erik Verbruggen <erik.verbruggen@theqtcompany.com> | 2015-06-02 15:30:50 +0200 |
---|---|---|
committer | Erik Verbruggen <erik.verbruggen@theqtcompany.com> | 2015-06-03 15:47:28 +0300 |
commit | 991ea63c6bae70404184b0fcb77aff87bebaa891 (patch) | |
tree | e8b9fa908ee10bc1f12e2c441d5c74a546b13e8e /src | |
parent | 944dad62a1f6125583d60b6406c26df7264d7004 (diff) |
Added QML module and an example using it.
This supports loading a state machine from a .scxml file, as well as
setting a state machine as a QObject on the QML context.
Change-Id: Iab2f12cb58f13b43912e83a08e67476d4c9296b5
Reviewed-by: Erik Verbruggen <erik.verbruggen@theqtcompany.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/qml-module/plugin.cpp | 50 | ||||
-rw-r--r-- | src/qml-module/qml-module.pro | 17 | ||||
-rw-r--r-- | src/qml-module/qmldir | 4 | ||||
-rw-r--r-- | src/qml-module/state.cpp | 62 | ||||
-rw-r--r-- | src/qml-module/state.h | 63 | ||||
-rw-r--r-- | src/qml-module/statemachine.cpp | 150 | ||||
-rw-r--r-- | src/qml-module/statemachine.h | 70 | ||||
-rw-r--r-- | src/qscxmllib/datamodel.cpp | 10 | ||||
-rw-r--r-- | src/qscxmllib/datamodel.h | 5 | ||||
-rw-r--r-- | src/qscxmllib/ecmascriptdatamodel.cpp | 20 | ||||
-rw-r--r-- | src/qscxmllib/ecmascriptdatamodel.h | 3 | ||||
-rw-r--r-- | src/qscxmllib/nulldatamodel.cpp | 5 | ||||
-rw-r--r-- | src/qscxmllib/nulldatamodel.h | 2 | ||||
-rw-r--r-- | src/qscxmllib/scxmlstatetable.cpp | 16 | ||||
-rw-r--r-- | src/qscxmllib/scxmlstatetable.h | 6 | ||||
-rw-r--r-- | src/qscxmllib/scxmlstatetable_p.h | 1 | ||||
-rw-r--r-- | src/src.pro | 4 |
17 files changed, 468 insertions, 20 deletions
diff --git a/src/qml-module/plugin.cpp b/src/qml-module/plugin.cpp new file mode 100644 index 0000000..041572b --- /dev/null +++ b/src/qml-module/plugin.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** + ** + ** 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 <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<QHistoryState>(uri, 1, 0, "HistoryState"); +// qmlRegisterType<FinalState>(uri, 1, 0, "FinalState"); +// qmlRegisterUncreatableType<QState>(uri, 1, 0, "QState", "Don't use this, use State instead"); +// qmlRegisterUncreatableType<QAbstractState>(uri, 1, 0, "QAbstractState", "Don't use this, use State instead"); +// qmlRegisterUncreatableType<QSignalTransition>(uri, 1, 0, "QSignalTransition", "Don't use this, use SignalTransition instead"); +// qmlRegisterType<SignalTransition>(uri, 1, 0, "SignalTransition"); +// qmlRegisterType<TimeoutTransition>(uri, 1, 0, "TimeoutTransition"); + qmlProtectModule(uri, 1); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/qml-module/qml-module.pro b/src/qml-module/qml-module.pro new file mode 100644 index 0000000..11823f2 --- /dev/null +++ b/src/qml-module/qml-module.pro @@ -0,0 +1,17 @@ +CXX_MODULE = qml +TARGET = scxmlstatemachine +TARGETPATH = Scxml +IMPORT_VERSION = 1.0 + +QT = qscxmllib #qml-private + +SOURCES = \ + $$PWD/plugin.cpp \ + $$PWD/state.cpp \ + $$PWD/statemachine.cpp + +HEADERS = \ + $$PWD/state.h \ + $$PWD/statemachine.h + +load(qml_plugin) diff --git a/src/qml-module/qmldir b/src/qml-module/qmldir new file mode 100644 index 0000000..a411f70 --- /dev/null +++ b/src/qml-module/qmldir @@ -0,0 +1,4 @@ +module Scxml +plugin scxmlstatemachine +classname ScxmlStateMachinePlugin + diff --git a/src/qml-module/state.cpp b/src/qml-module/state.cpp new file mode 100644 index 0000000..b42a7e0 --- /dev/null +++ b/src/qml-module/state.cpp @@ -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. + ****************************************************************************/ + +#include "state.h" +#include "statemachine.h" + +#include <QScxmlLib/scxmlstatetable.h> +#include <QQmlInfo> + +State::State(StateMachine *parent) + : QObject(parent) +{} + +void State::componentComplete() +{ + if (Scxml::StateTable *table = qobject_cast<StateMachine *>(parent())->stateMachine()) { + if (Scxml::ScxmlState *state = table->findState(m_scxmlName)) { + if (state != m_state) { + m_state = state; + connect(m_state, SIGNAL(activeChanged(bool)), this, SIGNAL(activeChanged(bool))); + connect(m_state, SIGNAL(didEnter()), this, SIGNAL(didEnter())); + connect(m_state, SIGNAL(willExit()), this, SIGNAL(willExit())); + } + } + } + + if (m_state == nullptr) + qmlInfo(this) << QStringLiteral("No state '%1' found.").arg(m_scxmlName); +} + +bool State::isActive() const +{ + return m_state && m_state->active(); +} + +QString State::scxmlName() const +{ + return m_scxmlName; +} + +void State::setScxmlName(const QString &scxmlName) +{ + if (m_scxmlName != scxmlName) { + m_scxmlName = scxmlName; + emit scxmlNameChanged(); + } +} diff --git a/src/qml-module/state.h b/src/qml-module/state.h new file mode 100644 index 0000000..b0581c7 --- /dev/null +++ b/src/qml-module/state.h @@ -0,0 +1,63 @@ +/**************************************************************************** + ** + ** 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: + QString m_scxmlName; + Scxml::ScxmlState *m_state = nullptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qml-module/statemachine.cpp b/src/qml-module/statemachine.cpp new file mode 100644 index 0000000..80d06d1 --- /dev/null +++ b/src/qml-module/statemachine.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** + ** + ** 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 <QScxmlLib/ecmascriptdatamodel.h> +#include <QScxmlLib/scxmlstatetable.h> +#include <QScxmlLib/scxmlparser.h> +#include <QQmlContext> +#include <QQmlEngine> +#include <QQmlInfo> +#include <QFile> + +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::States *>(prop->data)->append(state); + emit static_cast<StateMachine *>(prop->object)->statesChanged(); + } else { + qmlInfo(prop->object) << "StateMachine can only contain State items"; + } +} + +static int count(QQmlListProperty<QObject> *prop) +{ + return static_cast<StateMachine::States *>(prop->data)->count(); +} + +static QObject *at(QQmlListProperty<QObject> *prop, int index) +{ + return static_cast<StateMachine::States *>(prop->data)->at(index); +} + +static void clear(QQmlListProperty<QObject> *prop) +{ + static_cast<StateMachine::States *>(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; + } + + m_table->start(); +} + +QQmlListProperty<QObject> StateMachine::states() +{ + return QQmlListProperty<QObject>(this, &m_states, 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; + m_table->init(); + QQmlContext *context = QQmlEngine::contextForObject(parent()); + if (Scxml::EcmaScriptDataModel *dataModel = m_table->dataModel()->asEcmaScriptDataModel()) + dataModel->setEngine(context->engine()); + } else if (m_table) { + qmlInfo(this) << "Can set the table only once"; + } +} + +QString StateMachine::filename() +{ + return m_filename; +} + +void StateMachine::setFilename(const QString filename) +{ + QString 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 QString &filename) +{ + QFile scxmlFile(filename); + if (!scxmlFile.open(QIODevice::ReadOnly)) { + qmlInfo(this) << QStringLiteral("ERROR: cannot open '%1' for reading!").arg(filename); + return false; + } + + QXmlStreamReader xmlReader(&scxmlFile); + Scxml::ScxmlParser parser(&xmlReader); + parser.parse(); + scxmlFile.close(); + setStateMachine(parser.table()); + + if (parser.state() != Scxml::ScxmlParser::FinishedParsing || m_table == nullptr) { + qmlInfo(this) << QStringLiteral("Something went wrong while parsing '%1':").arg(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/qml-module/statemachine.h b/src/qml-module/statemachine.h new file mode 100644 index 0000000..c35fd35 --- /dev/null +++ b/src/qml-module/statemachine.h @@ -0,0 +1,70 @@ +/**************************************************************************** + ** + ** 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 <QVector> +#include <QQmlParserStatus> +#include <QQmlListProperty> +#include <QScxmlLib/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(QString filename READ filename WRITE setFilename NOTIFY filenameChanged) + Q_PROPERTY(Scxml::StateTable* stateMachine READ stateMachine WRITE setStateMachine) + + Q_CLASSINFO("DefaultProperty", "states") + +public: + typedef QVector<State *> States; + explicit StateMachine(QObject *parent = 0); + + void classBegin() {} + void componentComplete(); + QQmlListProperty<QObject> states(); + + Scxml::StateTable *stateMachine() const; + void setStateMachine(Scxml::StateTable *stateMachine); + + QString filename(); + void setFilename(const QString filename); + +Q_SIGNALS: + void statesChanged(); + void filenameChanged(); + +private: + bool parse(const QString &filename); + +private: + QString m_filename; + States m_states; + Scxml::StateTable *m_table = nullptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qscxmllib/datamodel.cpp b/src/qscxmllib/datamodel.cpp index 329f097..7cba99c 100644 --- a/src/qscxmllib/datamodel.cpp +++ b/src/qscxmllib/datamodel.cpp @@ -34,3 +34,13 @@ StateTable *DataModel::table() const { return m_table; } + +NullDataModel *DataModel::asNullDataModel() +{ + return nullptr; +} + +EcmaScriptDataModel *DataModel::asEcmaScriptDataModel() +{ + return nullptr; +} diff --git a/src/qscxmllib/datamodel.h b/src/qscxmllib/datamodel.h index 89e1138..05a9743 100644 --- a/src/qscxmllib/datamodel.h +++ b/src/qscxmllib/datamodel.h @@ -56,6 +56,8 @@ struct ForeachInfo { typedef qint32 EvaluatorId; enum { NoEvaluator = -1 }; +class NullDataModel; +class EcmaScriptDataModel; class SCXML_EXPORT DataModel { Q_DISABLE_COPY(DataModel) @@ -82,6 +84,9 @@ public: virtual void setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) = 0; + virtual NullDataModel *asNullDataModel(); + virtual EcmaScriptDataModel *asEcmaScriptDataModel(); + private: StateTable *m_table; }; diff --git a/src/qscxmllib/ecmascriptdatamodel.cpp b/src/qscxmllib/ecmascriptdatamodel.cpp index 452b551..0f0942f 100644 --- a/src/qscxmllib/ecmascriptdatamodel.cpp +++ b/src/qscxmllib/ecmascriptdatamodel.cpp @@ -176,7 +176,10 @@ public: { return q->table(); } QJSEngine *engine() const - { return q->engine(); } + { return jsEngine; } + + void setEngine(QJSEngine *engine) + { jsEngine = engine; } QString string(ExecutableContent::StringId id) const { return table()->tableData()->string(id); } @@ -292,6 +295,7 @@ private: // Uses private API private: EcmaScriptDataModel *q; + QJSEngine *jsEngine; QJSValue dataModel; }; @@ -381,7 +385,7 @@ bool EcmaScriptDataModel::evaluateForeach(EvaluatorId id, bool *ok, std::functio } QString item = d->string(info.item); - if (table()->engine()->evaluate(QStringLiteral("(function(){var %1 = 0})()").arg(item)).isError()) { + if (engine()->evaluate(QStringLiteral("(function(){var %1 = 0})()").arg(item)).isError()) { table()->submitError("error.execution", QStringLiteral("invalid item '%1' in %2") .arg(d->string(info.item), d->string(info.context)), sendid); *ok = false; @@ -431,7 +435,17 @@ void EcmaScriptDataModel::setStringProperty(const QString &name, const QString & d->setProperty(name, QJSValue(value), context, ok); } +EcmaScriptDataModel *EcmaScriptDataModel::asEcmaScriptDataModel() +{ + return this; +} + QJSEngine *EcmaScriptDataModel::engine() const { - return table()->engine(); + return d->engine(); +} + +void EcmaScriptDataModel::setEngine(QJSEngine *engine) +{ + d->setEngine(engine); } diff --git a/src/qscxmllib/ecmascriptdatamodel.h b/src/qscxmllib/ecmascriptdatamodel.h index 132ec89..2dd3412 100644 --- a/src/qscxmllib/ecmascriptdatamodel.h +++ b/src/qscxmllib/ecmascriptdatamodel.h @@ -49,7 +49,10 @@ public: bool hasProperty(const QString &name) const Q_DECL_OVERRIDE; void setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) Q_DECL_OVERRIDE; + virtual EcmaScriptDataModel *asEcmaScriptDataModel() Q_DECL_OVERRIDE; + QJSEngine *engine() const; + void setEngine(QJSEngine *engine); private: EcmaScriptDataModelPrivate *d; diff --git a/src/qscxmllib/nulldatamodel.cpp b/src/qscxmllib/nulldatamodel.cpp index 7ffd4c0..0eb0039 100644 --- a/src/qscxmllib/nulldatamodel.cpp +++ b/src/qscxmllib/nulldatamodel.cpp @@ -100,3 +100,8 @@ void NullDataModel::setStringProperty(const QString &name, const QString &value, Q_UNUSED(ok); Q_UNREACHABLE(); } + +NullDataModel *NullDataModel::asNullDataModel() +{ + return this; +} diff --git a/src/qscxmllib/nulldatamodel.h b/src/qscxmllib/nulldatamodel.h index 16909e0..ef40d77 100644 --- a/src/qscxmllib/nulldatamodel.h +++ b/src/qscxmllib/nulldatamodel.h @@ -42,6 +42,8 @@ public: QVariant property(const QString &name) const Q_DECL_OVERRIDE; bool hasProperty(const QString &name) const Q_DECL_OVERRIDE; void setStringProperty(const QString &name, const QString &value, const QString &context, bool *ok) Q_DECL_OVERRIDE; + + virtual NullDataModel *asNullDataModel() Q_DECL_OVERRIDE; }; } // Scxml namespace diff --git a/src/qscxmllib/scxmlstatetable.cpp b/src/qscxmllib/scxmlstatetable.cpp index 69ecde2..e1f8e1b 100644 --- a/src/qscxmllib/scxmlstatetable.cpp +++ b/src/qscxmllib/scxmlstatetable.cpp @@ -438,20 +438,6 @@ bool StateTable::init() return res; } -QJSEngine *StateTable::engine() const -{ - Q_D(const StateTable); - - return d->m_engine; -} - -void StateTable::setEngine(QJSEngine *engine) -{ - Q_D(StateTable); - - d->m_engine = engine; -} - QString StateTable::name() const { Q_D(const StateTable); @@ -938,10 +924,12 @@ void ScxmlState::onEntry(QEvent *event) } QState::onEntry(event); table()->executionEngine()->execute(d->onEntryInstructions); + emit didEnter(); } void ScxmlState::onExit(QEvent *event) { + emit willExit(); QState::onExit(event); table()->executionEngine()->execute(d->onExitInstructions); } diff --git a/src/qscxmllib/scxmlstatetable.h b/src/qscxmllib/scxmlstatetable.h index 72be037..59824e1 100644 --- a/src/qscxmllib/scxmlstatetable.h +++ b/src/qscxmllib/scxmlstatetable.h @@ -106,8 +106,6 @@ public: void doLog(const QString &label, const QString &msg); virtual bool init(); - QJSEngine *engine() const; - void setEngine(QJSEngine *engine); QString name() const; QStringList currentStates(bool compress = true); @@ -237,6 +235,10 @@ public: void setOnEntryInstructions(ExecutableContent::ContainerId instructions); void setOnExitInstructions(ExecutableContent::ContainerId instructions); +Q_SIGNALS: + void didEnter(); + void willExit(); + protected: ScxmlState(QStatePrivate &dd, QState *parent = 0); diff --git a/src/qscxmllib/scxmlstatetable_p.h b/src/qscxmllib/scxmlstatetable_p.h index e702d0e..b4b6eb1 100644 --- a/src/qscxmllib/scxmlstatetable_p.h +++ b/src/qscxmllib/scxmlstatetable_p.h @@ -53,7 +53,6 @@ public: // StateTable data fields: DataModel *m_dataModel = nullptr; const int m_sessionId; ExecutableContent::ContainerId m_initialSetup = ExecutableContent::NoInstruction; - QJSEngine *m_engine = nullptr; StateTable::BindingMethod m_dataBinding = StateTable::EarlyBinding; ExecutableContent::ExecutionEngine *m_executionEngine = nullptr; TableData *tableData = nullptr; diff --git a/src/src.pro b/src/src.pro index 66c8640..5a23ed5 100644 --- a/src/src.pro +++ b/src/src.pro @@ -7,3 +7,7 @@ SUBDIRS += \ qscxmlparse.depends = qscxmllib qscxmlcpp.depends = qscxmllib imports.depends = qscxmllib + +qtHaveModule(qml) { + SUBDIRS += qml-module +} |