More work on the script editor

This commit is contained in:
Michael Zanetti 2019-11-19 16:51:01 +01:00
parent d1d599a8ec
commit 0c6d75cdd0
15 changed files with 505 additions and 104 deletions

View File

@ -21,6 +21,7 @@
#include "engine.h"
#include "rulemanager.h"
#include "scriptmanager.h"
#include "logmanager.h"
#include "tagsmanager.h"
#include "configuration/nymeaconfiguration.h"
@ -39,6 +40,7 @@ Engine::Engine(QObject *parent) :
m_jsonRpcClient(new JsonRpcClient(m_connection, this)),
m_deviceManager(new DeviceManager(m_jsonRpcClient, this)),
m_ruleManager(new RuleManager(m_jsonRpcClient, this)),
m_scriptManager(new ScriptManager(m_jsonRpcClient, this)),
m_logManager(new LogManager(m_jsonRpcClient, this)),
m_tagsManager(new TagsManager(m_jsonRpcClient, this)),
m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)),
@ -80,6 +82,11 @@ RuleManager *Engine::ruleManager() const
return m_ruleManager;
}
ScriptManager *Engine::scriptManager() const
{
return m_scriptManager;
}
TagsManager *Engine::tagsManager() const
{
return m_tagsManager;
@ -144,6 +151,7 @@ void Engine::onDeviceManagerFetchingChanged()
{
if (!m_deviceManager->fetchingData()) {
m_ruleManager->init();
m_scriptManager->init();
m_nymeaConfiguration->init();
m_systemController->init();
if (m_jsonRpcClient->ensureServerVersion("1.7")) {

View File

@ -29,6 +29,7 @@
#include "wifisetup/bluetoothdiscovery.h"
class RuleManager;
class ScriptManager;
class LogManager;
class TagsManager;
class NymeaConfiguration;
@ -41,6 +42,7 @@ class Engine : public QObject
Q_PROPERTY(NymeaConnection* connection READ connection CONSTANT)
Q_PROPERTY(DeviceManager* deviceManager READ deviceManager CONSTANT)
Q_PROPERTY(RuleManager* ruleManager READ ruleManager CONSTANT)
Q_PROPERTY(ScriptManager* scriptManager READ scriptManager CONSTANT)
Q_PROPERTY(TagsManager* tagsManager READ tagsManager CONSTANT)
Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient CONSTANT)
Q_PROPERTY(NymeaConfiguration* nymeaConfiguration READ nymeaConfiguration CONSTANT)
@ -55,6 +57,7 @@ public:
NymeaConnection *connection() const;
DeviceManager *deviceManager() const;
RuleManager *ruleManager() const;
ScriptManager *scriptManager() const;
TagsManager *tagsManager() const;
JsonRpcClient *jsonRpcClient() const;
LogManager *logManager() const;
@ -68,6 +71,7 @@ private:
JsonRpcClient *m_jsonRpcClient;
DeviceManager *m_deviceManager;
RuleManager *m_ruleManager;
ScriptManager *m_scriptManager;
LogManager *m_logManager;
TagsManager *m_tagsManager;
NymeaConfiguration *m_nymeaConfiguration;

View File

@ -69,6 +69,9 @@
#include "types/networkdevices.h"
#include "types/networkdevice.h"
#include "scriptsyntaxhighlighter.h"
#include "scriptmanager.h"
#include "types/script.h"
#include "types/scripts.h"
#include <QtQml/qqml.h>
@ -232,6 +235,9 @@ void registerQmlTypes() {
qmlRegisterUncreatableType<WiredNetworkDevice>(uri, 1, 0, "WiredNetworkDevice", "Get it from NetworkDevices");
qmlRegisterUncreatableType<WirelessNetworkDevice>(uri, 1, 0, "WirelessNetworkDevice", "Get it from NetworkDevices");
qmlRegisterUncreatableType<ScriptManager>(uri, 1, 0, "ScriptManager", "Get it from Engine");
qmlRegisterUncreatableType<Scripts>(uri, 1, 0, "Scripts", "Getit from ScriptManager");
qmlRegisterUncreatableType<Script>(uri, 1, 0, "Script", "Getit from Scripts");
qmlRegisterType<ScriptSyntaxHighlighter>(uri, 1, 0, "ScriptSyntaxHighlighter");
qmlRegisterUncreatableType<CompletionProxyModel>(uri, 1, 0, "CompletionProxyModel", "Get it from ScriptSyntaxHighlighter");
}

View File

@ -48,6 +48,7 @@ SOURCES += \
devicediscovery.cpp \
models/packagesfiltermodel.cpp \
models/taglistmodel.cpp \
scriptmanager.cpp \
scriptsyntaxhighlighter.cpp \
vendorsproxy.cpp \
pluginsproxy.cpp \
@ -112,6 +113,7 @@ HEADERS += \
devicediscovery.h \
models/packagesfiltermodel.h \
models/taglistmodel.h \
scriptmanager.h \
scriptsyntaxhighlighter.h \
vendorsproxy.h \
pluginsproxy.h \

View File

@ -0,0 +1,83 @@
#include "scriptmanager.h"
#include "types/script.h"
#include "types/scripts.h"
ScriptManager::ScriptManager(JsonRpcClient *jsonClient, QObject *parent):
QObject(parent),
m_client(jsonClient)
{
m_scripts = new Scripts(this);
}
void ScriptManager::init()
{
m_scripts->clear();
m_client->sendCommand("Scripts.GetScripts", QVariantMap(), this, "onScriptsFetched");
}
Scripts *ScriptManager::scripts() const
{
return m_scripts;
}
int ScriptManager::addScript(const QString &content)
{
QVariantMap params;
params.insert("name", "Test");
params.insert("content", content);
return m_client->sendCommand("Scripts.AddScript", params, this, "onScriptAdded");
}
int ScriptManager::editScript(const QUuid &id, const QString &content)
{
QVariantMap params;
params.insert("id", id);
params.insert("content", content);
return m_client->sendCommand("Scripts.EditScript", params, this, "onScriptEdited");
}
int ScriptManager::removeScript(const QUuid &id)
{
QVariantMap params;
params.insert("id", id);
return m_client->sendCommand("Scripts.RemoveScript", params, this, "onScriptRemoved");
}
void ScriptManager::onScriptsFetched(const QVariantMap &params)
{
qDebug() << "scripts fetched" << params;
foreach (const QVariant &variant, params.value("params").toMap().value("scripts").toList()) {
qDebug() << "script" << variant.toMap().value("id").toUuid();
QUuid id = variant.toMap().value("id").toUuid();
Script *script = new Script(id);
script->setName(variant.toMap().value("name").toString());
m_scripts->addScript(script);
qDebug() << "Script added";
}
}
void ScriptManager::onScriptAdded(const QVariantMap &params)
{
qDebug() << "Script added" << params;
emit scriptAdded(params.value("id").toInt(),
params.value("params").toMap().value("scriptError").toString(),
params.value("params").toMap().value("script").toMap().value("id").toUuid(),
params.value("params").toMap().value("errors").toStringList());
}
void ScriptManager::onScriptEdited(const QVariantMap &params)
{
qDebug() << "Script edited" << params;
// emit scriptAdded(params.value("id").toInt(), params.value("script").toMap().value("id").toUuid());
emit scriptEdited(params.value("id").toInt(),
params.value("params").toMap().value("scriptError").toString(),
params.value("params").toMap().value("errors").toStringList());
}
void ScriptManager::onScriptRemoved(const QVariantMap &params)
{
emit scriptRemoved(params.value("id").toInt(), params.value("params").toMap().value("scriptError").toString());
}

View File

@ -0,0 +1,43 @@
#ifndef SCRIPTMANAGER_H
#define SCRIPTMANAGER_H
#include <QObject>
#include "jsonrpc/jsonrpcclient.h"
class Scripts;
class ScriptManager : public QObject
{
Q_OBJECT
Q_PROPERTY(Scripts* scripts READ scripts CONSTANT)
public:
explicit ScriptManager(JsonRpcClient* jsonClient, QObject *parent = nullptr);
void init();
Scripts *scripts() const;
public slots:
int addScript(const QString &content);
int editScript(const QUuid &id, const QString &content);
int removeScript(const QUuid &id);
signals:
void scriptAdded(int id, const QString &scriptError, const QUuid &scriptId, const QStringList &errors);
void scriptEdited(int id, const QString &scriptError, const QStringList &errors);
void scriptRemoved(int id, const QString &scriptError);
private slots:
void onScriptsFetched(const QVariantMap &params);
void onScriptAdded(const QVariantMap &params);
void onScriptEdited(const QVariantMap &params);
void onScriptRemoved(const QVariantMap &params);
private:
JsonRpcClient* m_client = nullptr;
Scripts *m_scripts = nullptr;
};
#endif // SCRIPTMANAGER_H

View File

@ -16,6 +16,8 @@ HEADERS += \
types/packages.h \
types/repositories.h \
types/repository.h \
types/script.h \
types/scripts.h \
types/types.h \
types/vendor.h \
types/vendors.h \
@ -72,6 +74,8 @@ SOURCES += \
types/packages.cpp \
types/repositories.cpp \
types/repository.cpp \
types/script.cpp \
types/scripts.cpp \
types/vendor.cpp \
types/vendors.cpp \
types/deviceclass.cpp \

View File

@ -0,0 +1,26 @@
#include "script.h"
Script::Script(const QUuid &id, QObject *parent):
QObject(parent),
m_id(id)
{
}
QUuid Script::id() const
{
return m_id;
}
QString Script::name() const
{
return m_name;
}
void Script::setName(const QString &name)
{
if (m_name != name) {
m_name = name;
emit nameChanged();
}
}

View File

@ -0,0 +1,28 @@
#ifndef SCRIPT_H
#define SCRIPT_H
#include <QObject>
#include <QUuid>
class Script : public QObject
{
Q_OBJECT
Q_PROPERTY(QUuid id READ id CONSTANT)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
explicit Script(const QUuid &id, QObject *parent = nullptr);
QUuid id() const;
QString name() const;
void setName(const QString &name);
signals:
void nameChanged();
private:
QUuid m_id;
QString m_name;
};
#endif // SCRIPT_H

View File

@ -0,0 +1,52 @@
#include "scripts.h"
#include "script.h"
Scripts::Scripts(QObject *parent) : QAbstractListModel(parent)
{
}
int Scripts::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant Scripts::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleId:
return m_list.at(index.row())->id();
case RoleName:
return m_list.at(index.row())->name();
}
return QVariant();
}
QHash<int, QByteArray> Scripts::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleId, "id");
roles.insert(RoleName, "name");
return roles;
}
void Scripts::clear()
{
beginResetModel();
qDeleteAll(m_list);
m_list.clear();
endResetModel();
emit countChanged();
}
void Scripts::addScript(Script *script)
{
script->setParent(this);
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
m_list.append(script);
endInsertRows();
emit countChanged();
}

View File

@ -0,0 +1,35 @@
#ifndef SCRIPTS_H
#define SCRIPTS_H
#include <QObject>
#include <QAbstractListModel>
class Script;
class Scripts : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RoleId,
RoleName
};
explicit Scripts(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
void clear();
void addScript(Script *script);
signals:
void countChanged();
private:
QList<Script*> m_list;
};
#endif // SCRIPTS_H

View File

@ -204,5 +204,6 @@
<file>ui/components/TimePicker.qml</file>
<file>ui/components/DatePicker.qml</file>
<file>ui/magic/ScriptEditor.qml</file>
<file>ui/magic/ScriptsPage.qml</file>
</qresource>
</RCC>

View File

@ -13,7 +13,7 @@ Page {
HeaderButton {
imageSource: Qt.resolvedUrl("images/magic.svg")
onClicked: {
pageStack.push("magic/ScriptEditor.qml")
pageStack.push("magic/ScriptsPage.qml")
}
}

View File

@ -2,6 +2,7 @@ import QtQuick 2.0
import QtQuick.Controls 2.2
import "../components"
import Nymea 1.0
import QtQuick.Layouts 1.2
Page {
id: root
@ -9,124 +10,179 @@ Page {
header: NymeaHeader {
text: qsTr("Script editor")
onBackPressed: pageStack.pop()
}
Rectangle {
color: "white"
anchors.fill: parent
TextEdit {
id: scriptEdit
anchors.fill: parent
font.family: "Monospace"
Keys.onPressed: {
print("key", event.key)
// Things only to happen when we're not autocompleting
if (!completionBox.visible) {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter:
syntax.newLine();
event.accepted = true;
return;
case Qt.Key_Tab:
syntax.indent(selectionStart, selectionEnd);
event.accepted = true;
return;
case Qt.Key_Backtab:
syntax.unindent(selectionStart, selectionEnd);
event.accepted = true;
return;
}
}
// things to happen in any case
switch (event.key) {
case Qt.Key_BraceRight:
syntax.closeBlock();
event.accepted = true;
return;
}
// Things to do only when we're autocompleting
if (completionBox.visible) {
switch (event.key) {
case Qt.Key_Escape:
completionBox.hide();
event.accepted = true;
break;
case Qt.Key_Down:
completionBox.next();
event.accepted = true;
break;
case Qt.Key_Up:
completionBox.previous();
event.accepted = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
syntax.complete(completionBox.currentIndex)
event.accepted = true;
break;
}
HeaderButton {
imageSource: "../images/tick.svg"
onClicked: {
if (!d.scriptId) {
d.callId = engine.scriptManager.addScript(scriptEdit.text)
} else {
print("editing script", d.scriptId)
d.callId = engine.scriptManager.editScript(d.scriptId, scriptEdit.text)
}
}
}
}
Rectangle {
id: completionBox
border.width: 1
border.color: "black"
height: syntax.completionModel.count * 30
width: 200
x: scriptEdit.cursorRectangle.x
y: scriptEdit.cursorRectangle.y + scriptEdit.cursorRectangle.height
visible: syntax.completionModel.count > 0 && !hidden
property bool hidden: false
Connections {
target: syntax.completionModel
onCountChanged: {
completionBox.hidden = false;
completionBox.currentIndex = 0;
}
}
QtObject {
id: d
property int callId
property var scriptId
}
property int currentIndex: 0
function next() { currentIndex = (currentIndex + 1) % syntax.completionModel.count}
function previous() {
currentIndex--;
if (currentIndex < 0) {
currentIndex = syntax.completionModel.count - 1
}
}
function hide() {
hidden = true;
}
Component.onCompleted: {
d.callId = engine.scriptManager.addScript(scriptEdit.text);
}
ListView {
anchors.fill: parent
model: syntax.completionModel
delegate: Rectangle {
height: 30
width: parent.width
color: index == completionBox.currentIndex ? "blue" : "white"
Label {
text: model.displayText
color: "black"
width: parent.width
elide: Text.ElideRight
Connections {
target: engine.scriptManager
onScriptAdded: {
if (id == d.callId) {
if (scriptError == "ScriptErrorNoError") {
d.scriptId = scriptId;
}
errorListView.model = errors
}
}
onScriptEdited: {
if (id == d.callId) {
errorListView.model = errors
}
}
}
ColumnLayout {
anchors.fill: parent
Rectangle {
color: "white"
Layout.fillWidth: true
Layout.fillHeight: true
TextEdit {
id: scriptEdit
anchors.fill: parent
font.family: "Monospace"
Keys.onPressed: {
print("key", event.key)
// Things only to happen when we're not autocompleting
if (!completionBox.visible) {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter:
syntax.newLine();
event.accepted = true;
return;
case Qt.Key_Tab:
syntax.indent(selectionStart, selectionEnd);
event.accepted = true;
return;
case Qt.Key_Backtab:
syntax.unindent(selectionStart, selectionEnd);
event.accepted = true;
return;
}
MouseArea {
anchors.fill: parent
onClicked: {
syntax.complete(index)
}
// things to happen in any case
switch (event.key) {
case Qt.Key_BraceRight:
syntax.closeBlock();
event.accepted = true;
return;
}
// Things to do only when we're autocompleting
if (completionBox.visible) {
switch (event.key) {
case Qt.Key_Escape:
completionBox.hide();
event.accepted = true;
break;
case Qt.Key_Down:
completionBox.next();
event.accepted = true;
break;
case Qt.Key_Up:
completionBox.previous();
event.accepted = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
syntax.complete(completionBox.currentIndex)
event.accepted = true;
break;
}
}
}
Rectangle {
id: completionBox
border.width: 1
border.color: "black"
height: syntax.completionModel.count * 30
width: 200
x: scriptEdit.cursorRectangle.x
y: scriptEdit.cursorRectangle.y + scriptEdit.cursorRectangle.height
visible: syntax.completionModel.count > 0 && !hidden
property bool hidden: false
Connections {
target: syntax.completionModel
onCountChanged: {
completionBox.hidden = false;
completionBox.currentIndex = 0;
}
}
property int currentIndex: 0
function next() { currentIndex = (currentIndex + 1) % syntax.completionModel.count}
function previous() {
currentIndex--;
if (currentIndex < 0) {
currentIndex = syntax.completionModel.count - 1
}
}
function hide() {
hidden = true;
}
ListView {
anchors.fill: parent
model: syntax.completionModel
delegate: Rectangle {
height: 30
width: parent.width
color: index == completionBox.currentIndex ? "blue" : "white"
Label {
text: model.displayText
color: "black"
width: parent.width
elide: Text.ElideRight
}
MouseArea {
anchors.fill: parent
onClicked: {
syntax.complete(index)
}
}
}
}
}
}
}
ListView {
id: errorListView
Layout.fillWidth: true
Layout.preferredHeight: 100
delegate: Label {
width: parent.width
text: modelData
}
}
}
ScriptSyntaxHighlighter {
id: syntax
engine: _engine

View File

@ -0,0 +1,53 @@
import QtQuick 2.0
import Nymea 1.0
import QtQuick.Controls 2.2
import "../components"
Page {
header: NymeaHeader {
text: qsTr("Scripts")
onBackPressed: pageStack.pop();
HeaderButton {
text: qsTr("Add new script")
imageSource: "../images/add.svg"
onClicked: {
pageStack.push("ScriptEditor.qml");
}
}
}
QtObject {
id: d
property int pendingAction: -1
}
Connections {
target: engine.scriptManager
onScriptRemoved: {
if (id == d.pendingAction) {
d.pendingAction = -1;
}
}
}
ListView {
anchors.fill: parent
model: engine.scriptManager.scripts
delegate: NymeaListItemDelegate {
width: parent.width
text: model.name
subText: model.id
canDelete: true
onDeleteClicked: {
print("removing script", model.id)
d.pendingAction = engine.scriptManager.removeScript(model.id);
}
}
}
BusyOverlay {
id: busyOverlay
visible: d.pendingAction != -1
}
}