diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index 8fa8854e..54bd1744 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -43,6 +43,12 @@ #include "tagsmanager.h" #include "models/tagsproxymodel.h" #include "types/tag.h" +#include "ruletemplates/ruletemplates.h" +#include "ruletemplates/ruletemplate.h" +#include "ruletemplates/eventdescriptortemplate.h" +#include "ruletemplates/stateevaluatortemplate.h" +#include "ruletemplates/statedescriptortemplate.h" +#include "ruletemplates/ruleactiontemplate.h" static QObject* interfacesModel_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { @@ -162,6 +168,15 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "WirelessAccessPoints", "Can't create this in QML. Get it from the Engine instance."); qmlRegisterUncreatableType(uri, 1, 0, "WirelessAccessPoints", "Can't create this in QML. Get it from the Engine instance."); + qmlRegisterType(uri, 1, 0, "RuleTemplates"); + qmlRegisterType(uri, 1, 0, "RuleTemplatesFilterModel"); + qmlRegisterUncreatableType(uri, 1, 0, "RuleTemplate", "Get them from RuleTemplates"); + qmlRegisterUncreatableType(uri, 1, 0, "EventDescriptorTemplates", "Get it from RuleTemplate"); + qmlRegisterUncreatableType(uri, 1, 0, "EventDescriptorTemplate", "Get it from EventDescriptorTemplates"); + qmlRegisterUncreatableType(uri, 1, 0, "StateEvaluatorTemplate", "Get it from RuleTemplate"); + qmlRegisterUncreatableType(uri, 1, 0, "StateDescriptorTemplate", "Get it from StateEvaluatorTemplate"); + qmlRegisterUncreatableType(uri, 1, 0, "RuleActionTemplates", "Get it from RuleTemplate"); + qmlRegisterUncreatableType(uri, 1, 0, "RuleActionTemplate", "Get it from RuleActionTemplates"); } #endif // LIBNYMEAAPPCORE_H diff --git a/libnymea-app-core/libnymea-app-core.pro b/libnymea-app-core/libnymea-app-core.pro index c0745888..bfc94288 100644 --- a/libnymea-app-core/libnymea-app-core.pro +++ b/libnymea-app-core/libnymea-app-core.pro @@ -61,6 +61,13 @@ SOURCES += \ models/tagsproxymodel.cpp \ tagsmanager.cpp \ wifisetup/wirelessaccesspointsproxy.cpp \ + models/tagsproxymodel.cpp \ + ruletemplates/ruletemplate.cpp \ + ruletemplates/ruletemplates.cpp \ + ruletemplates/eventdescriptortemplate.cpp \ + ruletemplates/ruleactiontemplate.cpp \ + ruletemplates/stateevaluatortemplate.cpp \ + ruletemplates/statedescriptortemplate.cpp HEADERS += \ engine.h \ @@ -107,7 +114,13 @@ HEADERS += \ models/interfacesproxy.h \ tagsmanager.h \ models/tagsproxymodel.h \ - wifisetup/wirelessaccesspointsproxy.h + wifisetup/wirelessaccesspointsproxy.h \ + ruletemplates/ruletemplate.h \ + ruletemplates/ruletemplates.h \ + ruletemplates/eventdescriptortemplate.h \ + ruletemplates/ruleactiontemplate.h \ + ruletemplates/stateevaluatortemplate.h \ + ruletemplates/statedescriptortemplate.h unix { target.path = /usr/lib diff --git a/libnymea-app-core/rulemanager.cpp b/libnymea-app-core/rulemanager.cpp index feb16a5a..3f22ac99 100644 --- a/libnymea-app-core/rulemanager.cpp +++ b/libnymea-app-core/rulemanager.cpp @@ -51,7 +51,7 @@ Rules *RuleManager::rules() const Rule *RuleManager::createNewRule() { - return new Rule(); + return new Rule(QUuid(), this); } void RuleManager::addRule(const QVariantMap params) @@ -154,7 +154,7 @@ void RuleManager::getRuleDetailsReply(const QVariantMap ¶ms) qDebug() << "Got rule details for a rule we don't know"; return; } -// qDebug() << "got rule details for rule" << ruleMap; + qDebug() << "got rule details for rule" << qUtf8Printable(QJsonDocument::fromVariant(ruleMap).toJson()); parseEventDescriptors(ruleMap.value("eventDescriptors").toList(), rule); parseRuleActions(ruleMap.value("actions").toList(), rule); parseRuleExitActions(ruleMap.value("exitActions").toList(), rule); @@ -222,6 +222,7 @@ void RuleManager::parseEventDescriptors(const QVariantList &eventDescriptorList, paramDescriptor->setOperatorType((ParamDescriptor::ValueOperator)operatorEnum.keyToValue(paramDescriptorVariant.toMap().value("operator").toString().toLocal8Bit())); eventDescriptor->paramDescriptors()->addParamDescriptor(paramDescriptor); } + qDebug() << "Adding eventdescriptor" << eventDescriptor->deviceId() << eventDescriptor->eventTypeId(); rule->eventDescriptors()->addEventDescriptor(eventDescriptor); } } @@ -242,7 +243,6 @@ StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluat } else { sd = new StateDescriptor(sdMap.value("interface").toString(), sdMap.value("interfaceState").toString(), op, sdMap.value("value"), stateEvaluator); } - qDebug() << "Created StateDescriptor:" << sd->interfaceName() << sd->interfaceState() << sd->deviceId() << sd->stateTypeId(); stateEvaluator->setStateDescriptor(sd); foreach (const QVariant &childEvaluatorVariant, stateEvaluatorMap.value("childEvaluators").toList()) { diff --git a/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp b/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp new file mode 100644 index 00000000..0c3796e6 --- /dev/null +++ b/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp @@ -0,0 +1,31 @@ +#include "eventdescriptortemplate.h" + +EventDescriptorTemplate::EventDescriptorTemplate(const QString &interfaceName, const QString &interfaceEvent, int selectionId, SelectionMode selectionMode, QObject *parent): + QObject(parent), + m_interfaceName(interfaceName), + m_interfaceEvent(interfaceEvent), + m_selectionId(selectionId), + m_selectionMode(selectionMode) +{ + +} + +QString EventDescriptorTemplate::interfaceName() const +{ + return m_interfaceName; +} + +QString EventDescriptorTemplate::interfaceEvent() const +{ + return m_interfaceEvent; +} + +int EventDescriptorTemplate::selectionId() const +{ + return m_selectionId; +} + +EventDescriptorTemplate::SelectionMode EventDescriptorTemplate::selectionMode() const +{ + return m_selectionMode; +} diff --git a/libnymea-app-core/ruletemplates/eventdescriptortemplate.h b/libnymea-app-core/ruletemplates/eventdescriptortemplate.h new file mode 100644 index 00000000..e59734ce --- /dev/null +++ b/libnymea-app-core/ruletemplates/eventdescriptortemplate.h @@ -0,0 +1,68 @@ +#ifndef EVENTDESCRIPTORTEMPLATE_H +#define EVENTDESCRIPTORTEMPLATE_H + +#include + +class EventDescriptorTemplate : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString interfaceName READ interfaceName CONSTANT) + Q_PROPERTY(QString interfaceEvent READ interfaceEvent CONSTANT) + Q_PROPERTY(int selectionId READ selectionId CONSTANT) + Q_PROPERTY(SelectionMode selectionMode READ selectionMode CONSTANT) +public: + enum SelectionMode { + SelectionModeAny, + SelectionModeDevice, + SelectionModeInterface, + }; + Q_ENUM(SelectionMode) + + explicit EventDescriptorTemplate(const QString &interfaceName, const QString &interfaceEvent, int selectionId, SelectionMode selectionMode = SelectionModeAny, QObject *parent = nullptr); + + QString interfaceName() const; + QString interfaceEvent() const; + int selectionId() const; + SelectionMode selectionMode() const; + +private: + QString m_interfaceName; + QString m_interfaceEvent; + int m_selectionId = 0; + SelectionMode m_selectionMode = SelectionModeAny; +}; + +#include + +class EventDescriptorTemplates: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + EventDescriptorTemplates(QObject *parent = nullptr): QAbstractListModel(parent) {} + + int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_list.count(); } + QVariant data(const QModelIndex &index, int role) const override { Q_UNUSED(index); Q_UNUSED(role); return QVariant(); } + + void addEventDescriptorTemplate(EventDescriptorTemplate *eventDescriptorTemplate) { + eventDescriptorTemplate->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(eventDescriptorTemplate); + endInsertRows(); + emit countChanged(); + } + + Q_INVOKABLE EventDescriptorTemplate* get(int index) const { + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); + } + +signals: + void countChanged(); + +private: + QList m_list; +}; +#endif // EVENTDESCRIPTORTEMPLATE_H diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp b/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp new file mode 100644 index 00000000..add78c99 --- /dev/null +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp @@ -0,0 +1,38 @@ +#include "ruleactiontemplate.h" + + +RuleActionTemplate::RuleActionTemplate(const QString &interfaceName, const QString &interfaceAction, int selectionId, SelectionMode selectionMode, QObject *parent): + QObject(parent), + m_interfaceName(interfaceName), + m_interfaceAction(interfaceAction), + m_selectionId(selectionId), + m_selectionMode(selectionMode), + m_ruleActionParams(new RuleActionParams(this)) +{ + +} + +QString RuleActionTemplate::interfaceName() const +{ + return m_interfaceName; +} + +QString RuleActionTemplate::interfaceAction() const +{ + return m_interfaceAction; +} + +int RuleActionTemplate::selectionId() const +{ + return m_selectionId; +} + +RuleActionTemplate::SelectionMode RuleActionTemplate::selectionMode() const +{ + return m_selectionMode; +} + +RuleActionParams *RuleActionTemplate::ruleActionParams() const +{ + return m_ruleActionParams; +} diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.h b/libnymea-app-core/ruletemplates/ruleactiontemplate.h new file mode 100644 index 00000000..6935a1e8 --- /dev/null +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.h @@ -0,0 +1,74 @@ +#ifndef RULEACTIONTEMPLATE_H +#define RULEACTIONTEMPLATE_H + +#include "types/ruleactionparams.h" + +#include + +class RuleActionTemplate : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString interfaceName READ interfaceName CONSTANT) + Q_PROPERTY(QString interfaceAction READ interfaceAction CONSTANT) + Q_PROPERTY(int selectionId READ selectionId CONSTANT) + Q_PROPERTY(SelectionMode selectionMode READ selectionMode CONSTANT) + Q_PROPERTY(RuleActionParams* ruleActionParams READ ruleActionParams CONSTANT) + +public: + enum SelectionMode { + SelectionModeAny, + SelectionModeDevice, + SelectionModeInterface + }; + Q_ENUM(SelectionMode) + + explicit RuleActionTemplate(const QString &interfaceName, const QString &interfaceAction, int selectionId, SelectionMode selectionMode = SelectionModeAny, QObject *parent = nullptr); + + QString interfaceName() const; + QString interfaceAction() const; + int selectionId() const; + SelectionMode selectionMode() const; + RuleActionParams* ruleActionParams() const; + +private: + QString m_interfaceName; + QString m_interfaceAction; + int m_selectionId = 0; + SelectionMode m_selectionMode = SelectionModeAny; + RuleActionParams* m_ruleActionParams = nullptr; +}; + +#include + +class RuleActionTemplates: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + RuleActionTemplates(QObject *parent = nullptr): QAbstractListModel(parent) {} + int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_list.count(); } + QVariant data(const QModelIndex &index, int role) const override { Q_UNUSED(index); Q_UNUSED(role); return QVariant(); } + + void addRuleActionTemplate(RuleActionTemplate* ruleActionTemplate) { + ruleActionTemplate->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(ruleActionTemplate); + endInsertRows(); + emit countChanged(); + } + + Q_INVOKABLE RuleActionTemplate* get(int index) const { + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); + } + +signals: + void countChanged(); + +private: + QList m_list; +}; + +#endif // RULEACTIONTEMPLATE_H diff --git a/libnymea-app-core/ruletemplates/ruletemplate.cpp b/libnymea-app-core/ruletemplates/ruletemplate.cpp new file mode 100644 index 00000000..5df8a6b1 --- /dev/null +++ b/libnymea-app-core/ruletemplates/ruletemplate.cpp @@ -0,0 +1,53 @@ +#include "ruletemplate.h" +#include "eventdescriptortemplate.h" +#include "stateevaluatortemplate.h" +#include "ruleactiontemplate.h" + +RuleTemplate::RuleTemplate(const QString &description, const QString &ruleNameTemplate, QObject *parent): + QObject(parent), + m_description(description), + m_ruleNameTemplate(ruleNameTemplate), + m_eventDescriptorTemplates(new EventDescriptorTemplates(this)), + m_ruleActionTemplates(new RuleActionTemplates(this)), + m_ruleExitActionTemplates(new RuleActionTemplates(this)) +{ +} + +QString RuleTemplate::description() const +{ + return m_description; +} + +QString RuleTemplate::ruleNameTemplate() const +{ + return m_ruleNameTemplate; +} + +EventDescriptorTemplates *RuleTemplate::eventDescriptorTemplates() const +{ + return m_eventDescriptorTemplates; +} + +StateEvaluatorTemplate *RuleTemplate::stateEvaluatorTemplate() const +{ + return m_stateEvaluatorTemplate; +} + +void RuleTemplate::setStateEvaluatorTemplate(StateEvaluatorTemplate *stateEvaluatorTemplate) +{ + if (m_stateEvaluatorTemplate) { + m_stateEvaluatorTemplate->deleteLater(); + } + stateEvaluatorTemplate->setParent(this); + m_stateEvaluatorTemplate = stateEvaluatorTemplate; +} + +RuleActionTemplates *RuleTemplate::ruleActionTemplates() const +{ + return m_ruleActionTemplates; +} + +RuleActionTemplates *RuleTemplate::ruleExitActionTemplates() const +{ + return m_ruleExitActionTemplates; +} diff --git a/libnymea-app-core/ruletemplates/ruletemplate.h b/libnymea-app-core/ruletemplates/ruletemplate.h new file mode 100644 index 00000000..2e46386b --- /dev/null +++ b/libnymea-app-core/ruletemplates/ruletemplate.h @@ -0,0 +1,41 @@ +#ifndef RULETEMPLATE_H +#define RULETEMPLATE_H + +#include + +class EventDescriptorTemplates; +class RuleActionTemplates; +class StateEvaluatorTemplate; + +class RuleTemplate : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString ruleNameTemplate READ ruleNameTemplate CONSTANT) + Q_PROPERTY(EventDescriptorTemplates* eventDescriptorTemplates READ eventDescriptorTemplates CONSTANT) + Q_PROPERTY(StateEvaluatorTemplate* stateEvaluatorTemplate READ stateEvaluatorTemplate CONSTANT) + Q_PROPERTY(RuleActionTemplates* ruleActionTemplates READ ruleActionTemplates CONSTANT) + Q_PROPERTY(RuleActionTemplates* ruleExitActionTemplates READ ruleExitActionTemplates CONSTANT) + +public: + explicit RuleTemplate(const QString &description, const QString &ruleNameTemplate, QObject *parent = nullptr); + + QString description() const; + QString ruleNameTemplate() const; + + EventDescriptorTemplates* eventDescriptorTemplates() const; + StateEvaluatorTemplate* stateEvaluatorTemplate() const; + void setStateEvaluatorTemplate(StateEvaluatorTemplate *stateEvaluatorTemplate); + RuleActionTemplates* ruleActionTemplates() const; + RuleActionTemplates* ruleExitActionTemplates() const; + +private: + QString m_description; + QString m_ruleNameTemplate; + EventDescriptorTemplates* m_eventDescriptorTemplates = nullptr; + StateEvaluatorTemplate* m_stateEvaluatorTemplate = nullptr; + RuleActionTemplates *m_ruleActionTemplates = nullptr; + RuleActionTemplates *m_ruleExitActionTemplates = nullptr; +}; + +#endif // RULETEMPLATE_H diff --git a/libnymea-app-core/ruletemplates/ruletemplates.cpp b/libnymea-app-core/ruletemplates/ruletemplates.cpp new file mode 100644 index 00000000..92626348 --- /dev/null +++ b/libnymea-app-core/ruletemplates/ruletemplates.cpp @@ -0,0 +1,141 @@ +#include "ruletemplates.h" + +#include "ruletemplate.h" +#include "eventdescriptortemplate.h" +#include "ruleactiontemplate.h" +#include "stateevaluatortemplate.h" + +#include "types/ruleactionparam.h" +#include "types/ruleactionparams.h" + +#include + +RuleTemplates::RuleTemplates(QObject *parent) : QAbstractListModel(parent) +{ + RuleTemplate* t; + EventDescriptorTemplate* evt; + StateEvaluatorTemplate* set; + RuleActionTemplate* rat; + RuleActionTemplate* reat; // exit + + t = new RuleTemplate("Switch a light", "%0 switches %1", this); + evt = new EventDescriptorTemplate("button", "pressed", 0, EventDescriptorTemplate::SelectionModeDevice); + t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); + set = new StateEvaluatorTemplate(new StateDescriptorTemplate("light", "power", 1, StateDescriptorTemplate::ValueOperatorEquals, false)); + t->setStateEvaluatorTemplate(set); + rat = new RuleActionTemplate("light", "power", 1, RuleActionTemplate::SelectionModeDevice); + rat->ruleActionParams()->setRuleActionParamByName("power", true); + t->ruleActionTemplates()->addRuleActionTemplate(rat); + reat = new RuleActionTemplate("light", "power", 1, RuleActionTemplate::SelectionModeDevice); + reat->ruleActionParams()->setRuleActionParamByName("power", false); + t->ruleExitActionTemplates()->addRuleActionTemplate(reat); + m_list.append(t); + + t = new RuleTemplate("Intelligent blinds", "Intelligent blinds %1", this); + set = new StateEvaluatorTemplate(new StateDescriptorTemplate("temperaturesensor", "temperature", 0, StateDescriptorTemplate::ValueOperatorGreater, 20)); + t->setStateEvaluatorTemplate(set); + rat = new RuleActionTemplate("simpleclosable", "close", 1, RuleActionTemplate::SelectionModeDevice); + t->ruleActionTemplates()->addRuleActionTemplate(rat); + reat = new RuleActionTemplate("simpleclosable", "open", 1, RuleActionTemplate::SelectionModeDevice); + t->ruleExitActionTemplates()->addRuleActionTemplate(reat); + m_list.append(t); + + t = new RuleTemplate("Leave home - This will turn of everything when you press a button.", "Leave home", this); + evt = new EventDescriptorTemplate("button", "pressed", 0, EventDescriptorTemplate::SelectionModeDevice); + t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); + rat = new RuleActionTemplate("power", "power", 1, RuleActionTemplate::SelectionModeInterface); + t->ruleActionTemplates()->addRuleActionTemplate(rat); + m_list.append(t); + + + t = new RuleTemplate("Remind me to water my plant", "Remind me to water my %0 plant", this); + evt = new EventDescriptorTemplate("humiditysensor", "humidity", 0, EventDescriptorTemplate::SelectionModeDevice); + t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); + m_list.append(t); + +} + +int RuleTemplates::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant RuleTemplates::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleDescription: + return m_list.at(index.row())->description(); + } + return QVariant(); +} + +QHash RuleTemplates::roleNames() const +{ + QHash roles; + roles.insert(RoleDescription, "description"); + return roles; +} + +RuleTemplate *RuleTemplates::get(int index) const +{ + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); +} + +bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + if (!m_ruleTemplates) { + return false; + } + RuleTemplate *t = m_ruleTemplates->get(source_row); + qDebug() << "---------------" << t->description() << m_filterInterfaceNames; + if (!m_filterInterfaceNames.isEmpty()) { + bool found = false; + for (int i = 0; i < t->eventDescriptorTemplates()->rowCount(); i++) { + if (m_filterInterfaceNames.contains(t->eventDescriptorTemplates()->get(i)->interfaceName())) { + found = true; + break; + } + } + if (!found && t->stateEvaluatorTemplate() && stateEvaluatorTemplateContainsInterface(t->stateEvaluatorTemplate(), m_filterInterfaceNames)) { + found = true; + } + if (!found) { + for (int i = 0; i < t->ruleActionTemplates()->rowCount(); i++) { + if (m_filterInterfaceNames.contains(t->ruleActionTemplates()->get(i)->interfaceName())) { + found = true; + break; + } + } + } + if (!found) { + for (int i = 0; i < t->ruleExitActionTemplates()->rowCount(); i++) { + if (m_filterInterfaceNames.contains(t->ruleExitActionTemplates()->get(i)->interfaceName())) { + found = true; + break; + } + } + } + if (!found) { + return false; + } + } + return true; +} + +bool RuleTemplatesFilterModel::stateEvaluatorTemplateContainsInterface(StateEvaluatorTemplate *stateEvaluatorTemplate, const QStringList &interfaceNames) const +{ + if (interfaceNames.contains(stateEvaluatorTemplate->stateDescriptorTemplate()->interfaceName())) { + return true; + } + for (int i = 0; i < stateEvaluatorTemplate->childEvaluatorTemplates()->rowCount(); i++) { + if (stateEvaluatorTemplateContainsInterface(stateEvaluatorTemplate->childEvaluatorTemplates()->get(i), interfaceNames)) { + return true; + } + } + return false; +} diff --git a/libnymea-app-core/ruletemplates/ruletemplates.h b/libnymea-app-core/ruletemplates/ruletemplates.h new file mode 100644 index 00000000..0a25dd3e --- /dev/null +++ b/libnymea-app-core/ruletemplates/ruletemplates.h @@ -0,0 +1,64 @@ +#ifndef RULETEMPLATES_H +#define RULETEMPLATES_H + +#include + +class RuleTemplate; +class StateEvaluatorTemplate; + +class RuleTemplates : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RoleDescription + }; + Q_ENUM(Roles) + + explicit RuleTemplates(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + Q_INVOKABLE RuleTemplate* get(int index) const; + +signals: + void countChanged(); + +private: + QList m_list; + +}; + +#include + +class RuleTemplatesFilterModel: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(RuleTemplates* ruleTemplates READ ruleTemplates WRITE setRuleTemplates NOTIFY ruleTemplatesChanged) + Q_PROPERTY(QStringList filterInterfaceNames READ filterInterfaceNames WRITE setFilterInterfaceNames NOTIFY filterInterfaceNamesChanged) +public: + RuleTemplatesFilterModel(QObject *parent = nullptr): QSortFilterProxyModel(parent) {} + RuleTemplates* ruleTemplates() const { return m_ruleTemplates; } + void setRuleTemplates(RuleTemplates* ruleTemplates) { if (m_ruleTemplates != ruleTemplates) { m_ruleTemplates = ruleTemplates; setSourceModel(ruleTemplates); emit ruleTemplatesChanged(); invalidateFilter();}} + QStringList filterInterfaceNames() const { return m_filterInterfaceNames; } + void setFilterInterfaceNames(const QStringList &filterInterfaceNames) { if (m_filterInterfaceNames != filterInterfaceNames) m_filterInterfaceNames = filterInterfaceNames; emit filterInterfaceNamesChanged(); invalidateFilter();} + Q_INVOKABLE RuleTemplate* get(int index) { + if (index < 0 || index >= rowCount()) { + return nullptr; + } + return m_ruleTemplates->get(mapToSource(this->index(index, 0)).row()); + } +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + bool stateEvaluatorTemplateContainsInterface(StateEvaluatorTemplate *stateEvaluatorTemplate, const QStringList &interfaceNames) const; +signals: + void ruleTemplatesChanged(); + void filterInterfaceNamesChanged(); +private: + RuleTemplates* m_ruleTemplates = nullptr; + QStringList m_filterInterfaceNames; +}; + +#endif // RULETEMPLATES_H diff --git a/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp b/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp new file mode 100644 index 00000000..25872431 --- /dev/null +++ b/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp @@ -0,0 +1,37 @@ +#include "statedescriptortemplate.h" + +StateDescriptorTemplate::StateDescriptorTemplate(const QString &interfaceName, const QString &interfaceState, int selectionId, StateDescriptorTemplate::ValueOperator valueOperator, const QVariant &value, QObject *parent): + QObject(parent), + m_interfaceName(interfaceName), + m_interfaceState(interfaceState), + m_selectionId(selectionId), + m_valueOperator(valueOperator), + m_value(value) +{ + +} + +QString StateDescriptorTemplate::interfaceName() const +{ + return m_interfaceName; +} + +QString StateDescriptorTemplate::interfaceState() const +{ + return m_interfaceState; +} + +int StateDescriptorTemplate::selectionId() const +{ + return m_selectionId; +} + +StateDescriptorTemplate::ValueOperator StateDescriptorTemplate::valueOperator() const +{ + return m_valueOperator; +} + +QVariant StateDescriptorTemplate::value() const +{ + return m_value; +} diff --git a/libnymea-app-core/ruletemplates/statedescriptortemplate.h b/libnymea-app-core/ruletemplates/statedescriptortemplate.h new file mode 100644 index 00000000..b6906c94 --- /dev/null +++ b/libnymea-app-core/ruletemplates/statedescriptortemplate.h @@ -0,0 +1,43 @@ +#ifndef STATEDESCRIPTORTEMPLATE_H +#define STATEDESCRIPTORTEMPLATE_H + +#include +#include + +class StateDescriptorTemplate : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString interfaceName READ interfaceName CONSTANT) + Q_PROPERTY(QString interfaceState READ interfaceState CONSTANT) + Q_PROPERTY(int selectionId READ selectionId CONSTANT) + Q_PROPERTY(ValueOperator valueOperator READ valueOperator CONSTANT) + Q_PROPERTY(QVariant value READ value CONSTANT) + +public: + enum ValueOperator { + ValueOperatorEquals, + ValueOperatorNotEquals, + ValueOperatorLess, + ValueOperatorGreater, + ValueOperatorLessOrEqual, + ValueOperatorGreaterOrEqual + }; + Q_ENUM(ValueOperator) + + explicit StateDescriptorTemplate(const QString &interfaceName, const QString &interfaceState, int selectionId, ValueOperator valueOperator = ValueOperatorEquals, const QVariant &value = QVariant(), QObject *parent = nullptr); + + QString interfaceName() const; + QString interfaceState() const; + int selectionId() const; + ValueOperator valueOperator() const; + QVariant value() const; + +private: + QString m_interfaceName; + QString m_interfaceState; + ValueOperator m_valueOperator = ValueOperatorEquals; + QVariant m_value; + int m_selectionId = 0; +}; + +#endif // STATEDESCRIPTORTEMPLATE_H diff --git a/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp b/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp new file mode 100644 index 00000000..7ecd0cca --- /dev/null +++ b/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp @@ -0,0 +1,25 @@ +#include "stateevaluatortemplate.h" + +StateEvaluatorTemplate::StateEvaluatorTemplate(StateDescriptorTemplate *stateDescriptorTemplate, StateOperator stateOperator, QObject *parent): + QObject(parent), + m_stateDescriptorTemplate(stateDescriptorTemplate), + m_stateOperator(stateOperator), + m_childEvaluatorTemplates(new StateEvaluatorTemplates(this)) +{ + stateDescriptorTemplate->setParent(this); +} + +StateDescriptorTemplate *StateEvaluatorTemplate::stateDescriptorTemplate() const +{ + return m_stateDescriptorTemplate; +} + +StateEvaluatorTemplate::StateOperator StateEvaluatorTemplate::stateOperator() const +{ + return m_stateOperator; +} + +StateEvaluatorTemplates *StateEvaluatorTemplate::childEvaluatorTemplates() const +{ + return m_childEvaluatorTemplates; +} diff --git a/libnymea-app-core/ruletemplates/stateevaluatortemplate.h b/libnymea-app-core/ruletemplates/stateevaluatortemplate.h new file mode 100644 index 00000000..22b02c51 --- /dev/null +++ b/libnymea-app-core/ruletemplates/stateevaluatortemplate.h @@ -0,0 +1,65 @@ +#ifndef STATEEVALUATORTEMPLATE_H +#define STATEEVALUATORTEMPLATE_H + +#include "statedescriptortemplate.h" + +#include + +class StateEvaluatorTemplates; + +class StateEvaluatorTemplate : public QObject +{ + Q_OBJECT + Q_PROPERTY(StateDescriptorTemplate* stateDescriptorTemplate READ stateDescriptorTemplate CONSTANT) + Q_PROPERTY(StateOperator stateOperator READ stateOperator CONSTANT) + Q_PROPERTY(StateEvaluatorTemplates* childEvaluatorTemplates READ childEvaluatorTemplates CONSTANT) + +public: + enum StateOperator { + StateOperatorAnd, + StateOperatorOr + }; + Q_ENUM(StateOperator) + + explicit StateEvaluatorTemplate(StateDescriptorTemplate* stateDescriptorTemplate, StateOperator stateOperator = StateOperatorAnd, QObject *parent = nullptr); + + StateDescriptorTemplate* stateDescriptorTemplate() const; + StateOperator stateOperator() const; + StateEvaluatorTemplates* childEvaluatorTemplates() const; + +private: + StateDescriptorTemplate* m_stateDescriptorTemplate = nullptr; + StateOperator m_stateOperator = StateOperatorAnd; + StateEvaluatorTemplates *m_childEvaluatorTemplates = nullptr; +}; + +#include + +class StateEvaluatorTemplates: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount CONSTANT) + +public: + StateEvaluatorTemplates(QObject *parent = nullptr): QAbstractListModel(parent) {} + int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_list.count(); } + QVariant data(const QModelIndex &index, int role) const override { Q_UNUSED(index); Q_UNUSED(role); return QVariant(); } + + Q_INVOKABLE StateEvaluatorTemplate* get(int index) const { + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); + } + + void addStateEvaluatorTemplate(StateEvaluatorTemplate *stateEvaluatorTemplate) { + stateEvaluatorTemplate->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(stateEvaluatorTemplate); + endInsertRows(); + } +private: + QList m_list; +}; + +#endif // STATEEVALUATORTEMPLATE_H diff --git a/libnymea-app-core/tagsmanager.cpp b/libnymea-app-core/tagsmanager.cpp index a0a3fa2c..d7b34583 100644 --- a/libnymea-app-core/tagsmanager.cpp +++ b/libnymea-app-core/tagsmanager.cpp @@ -111,7 +111,6 @@ void TagsManager::handleTagsNotification(const QVariantMap ¶ms) void TagsManager::getTagsReply(const QVariantMap ¶ms) { - qDebug() << "Have tags" << params; foreach (const QVariant &tagVariant, params.value("params").toMap().value("tags").toList()) { addTagInternal(tagVariant.toMap()); } diff --git a/libnymea-common/types/eventdescriptors.cpp b/libnymea-common/types/eventdescriptors.cpp index 28b34cf5..056df5e3 100644 --- a/libnymea-common/types/eventdescriptors.cpp +++ b/libnymea-common/types/eventdescriptors.cpp @@ -42,7 +42,7 @@ EventDescriptor *EventDescriptors::get(int index) const EventDescriptor *EventDescriptors::createNewEventDescriptor() { - return new EventDescriptor(); + return new EventDescriptor(this); } void EventDescriptors::addEventDescriptor(EventDescriptor *eventDescriptor) diff --git a/libnymea-common/types/statetypes.cpp b/libnymea-common/types/statetypes.cpp index 745b2eec..0875d14f 100644 --- a/libnymea-common/types/statetypes.cpp +++ b/libnymea-common/types/statetypes.cpp @@ -103,7 +103,6 @@ StateType *StateTypes::findByName(const QString &name) const void StateTypes::clearModel() { beginResetModel(); - qDebug() << "StateTypes: delete all stateTypes"; qDeleteAll(m_stateTypes); m_stateTypes.clear(); endResetModel(); diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 1e6cfb29..ce539406 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -90,3 +90,6 @@ BR=$$BRANDING target.path = /usr/bin INSTALLS += target + +DISTFILES += \ + ui/magic/NewThingMagicPage.qml diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index b5fafe89..6fa57de9 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -226,5 +226,6 @@ ui/images/awning/awning-070.svg ui/images/awning/awning-080.svg ui/images/awning/awning-090.svg + ui/magic/NewThingMagicPage.qml diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index dd2da443..70acecc4 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -109,6 +109,9 @@ ApplicationWindow { objectName: "pageStack" anchors.fill: parent initialItem: Page {} + onDepthChanged: { + print("stackview depth changed", pageStack.depth) + } } onClosing: { @@ -244,6 +247,17 @@ ApplicationWindow { return "grey"; } + function interfaceToDisplayName(name) { + switch (name) { + case "light": + return qsTr("light") + case "button": + return "button"; + case "sensor": + return qsTr("sensor") + } + } + function interfaceListToDevicePage(interfaceList) { var page; if (interfaceList.indexOf("media") >= 0) { diff --git a/nymea-app/ui/magic/DeviceRulesPage.qml b/nymea-app/ui/magic/DeviceRulesPage.qml index 6f80a65c..886ae985 100644 --- a/nymea-app/ui/magic/DeviceRulesPage.qml +++ b/nymea-app/ui/magic/DeviceRulesPage.qml @@ -9,6 +9,9 @@ Page { property var device: null + Component.onCompleted: print("+++ created devicerulespage") + Component.onDestruction: print("--- destroying devicerulespage") + header: GuhHeader { text: qsTr("Magic involving %1").arg(root.device.name) onBackPressed: pageStack.pop() @@ -24,7 +27,14 @@ Page { // This Page will take ownership of the rule and delete it eventually. function addRule(rule) { if (rule === null || rule === undefined) { - rule = Engine.ruleManager.createNewRule(); + d.editRulePage = pageStack.push(Qt.resolvedUrl("NewThingMagicPage.qml"), {device: root.device}); + d.editRulePage.manualCreation.connect(function() { + pageStack.pop(); + rule = Engine.ruleManager.createNewRule(); + addRule(rule) + }) + d.editRulePage.done.connect(function() {pageStack.pop(root);}); + return; } d.editRulePage = pageStack.push(Qt.resolvedUrl("EditRulePage.qml"), {rule: rule}); d.editRulePage.StackView.onRemoved.connect(function() { @@ -95,8 +105,11 @@ Page { onDeleteClicked: Engine.ruleManager.removeRule(model.id) onClicked: { + print("clicked") var newRule = rulesFilterModel.get(index).clone(); + print("rule cloned") d.editRulePage = pageStack.push(Qt.resolvedUrl("EditRulePage.qml"), {rule: newRule }) + print("page pushed") d.editRulePage.StackView.onRemoved.connect(function() { newRule.destroy(); }) diff --git a/nymea-app/ui/magic/EditRulePage.qml b/nymea-app/ui/magic/EditRulePage.qml index 76f7e0ea..125674d5 100644 --- a/nymea-app/ui/magic/EditRulePage.qml +++ b/nymea-app/ui/magic/EditRulePage.qml @@ -13,7 +13,7 @@ Page { readonly property bool isEventBased: rule.eventDescriptors.count > 0 || rule.timeDescriptor.timeEventItems.count > 0 readonly property bool isStateBased: (rule.stateEvaluator !== null || rule.timeDescriptor.calendarItems.count > 0) && !isEventBased readonly property bool actionsVisible: true - readonly property bool exitActionsVisible: actionsVisible && isStateBased + readonly property bool exitActionsVisible: (Engine.jsonRpcClient.ensureServerVersion(1.7) && !isEmpty) || isStateBased readonly property bool hasActions: rule.actions.count > 0 readonly property bool hasExitActions: rule.exitActions.count > 0 readonly property bool isEmpty: !isEventBased && !isStateBased && !hasActions @@ -24,6 +24,9 @@ Page { signal accept(); signal cancel(); + Component.onCompleted: print("+++ created editrulepage") + Component.onDestruction: print("--- destroying editrulepage") + function addEventDescriptor(interfaceMode) { if (interfaceMode === undefined) { interfaceMode = false; @@ -396,9 +399,10 @@ Page { Repeater { id: eventsRepeater - model: root.hasExitActions ? null : root.rule.eventDescriptors + model: root.rule.eventDescriptors delegate: EventDescriptorDelegate { Layout.fillWidth: true + implicitWidth: parent.width eventDescriptor: root.rule.eventDescriptors.get(index) onRemoveEventDescriptor: root.rule.eventDescriptors.removeEventDescriptor(index) } @@ -552,6 +556,7 @@ Page { model: root.actionsVisible ? root.rule.actions : null delegate: RuleActionDelegate { Layout.fillWidth: true + implicitWidth: parent.width ruleAction: root.rule.actions.get(index) onRemoveRuleAction: root.rule.actions.removeRuleAction(index) } @@ -574,7 +579,8 @@ Page { ThinDivider { visible: root.exitActionsVisible } Label { - text: qsTr("...isn't met any more, execute those actions:") + text: root.isStateBased ? qsTr("...isn't met any more, execute those actions:") : + qsTr("If the condition isn't met, execute those actions instead:") Layout.fillWidth: true Layout.margins: app.margins wrapMode: Text.WordWrap @@ -589,6 +595,7 @@ Page { model: root.exitActionsVisible ? root.rule.exitActions : null delegate: RuleActionDelegate { Layout.fillWidth: true + implicitWidth: parent.width ruleAction: root.rule.exitActions.get(index) onClicked: root.rule.exitActions.removeRuleAction(index) } diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml new file mode 100644 index 00000000..28cb2d79 --- /dev/null +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -0,0 +1,297 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 +import "../components" + +Page { + id: root + + property var device: null + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + property bool busy: false + + signal done(); + signal manualCreation(); + + function fillRuleFromTemplate(rule, ruleTemplate, selectedThings) { + if (selectedThings === undefined) { + selectedThings = []; + } + + // Fill in all EventDescriptors + for (var i = rule.eventDescriptors.count; i < ruleTemplate.eventDescriptorTemplates.count; i++) { + var eventDescriptorTemplate = ruleTemplate.eventDescriptorTemplates.get(i); + // If we already have a thing selected for this selectionIndex, use that + if (selectedThings.length > eventDescriptorTemplate.selectionId) { + var device = Engine.deviceManager.devices.getDevice(selectedThings[eventDescriptorTemplate.selectionId]); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.deviceId = device.id + eventDescriptor.eventTypeId = deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + } + // Ok, we didn't pick a thing for this selectionId before. Did we already use the one we opened this page from? + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0) { + var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.deviceId = root.device.id; + eventDescriptor.eventTypeId = root.deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id + rule.eventDescriptors.addEventDescriptor(eventDescriptor); + selectedThings.push(root.device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + } + + // We need to pick a thing + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]}); + page.thingSelected.connect(function(device) { + var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.deviceId = device.id; + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + eventDescriptor.eventTypeId = deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id; + rule.eventDescriptors.addEventDescriptor(eventDescriptor); + selectedThings.push(device.id); + fullRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + }) + page.backPressed.connect(function() {rule.destroy(); root.done();}) + return; + } + + // Fill in StateEvaluator + if (ruleTemplate.stateEvaluatorTemplate !== null) { + if (rule.stateEvaluator === null) { + var stateEvaluator = rule.createStateEvaluator(); + rule.setStateEvaluator(stateEvaluator); + fillStateEvaluatorFromTemplate(rule, ruleTemplate, stateEvaluator, ruleTemplate.stateEvaluatorTemplate, selectedThings); + return; + } + var more = fillStateEvaluatorFromTemplate(rule, ruleTemplate, rule.stateEvaluator, ruleTemplate.stateEvaluatorTemplate, selectedThings); + if (more) { + return; + } + } + + for (var i = rule.actions.count; i < ruleTemplate.ruleActionTemplates.count; i++) { + var ruleActionTemplate = ruleTemplate.ruleActionTemplates.get(i); + + // Did we pick a thing for this index before? + if (selectedThings.length > ruleActionTemplate.selectionId) { + var ruleAction = rule.actions.createNewRuleAction(); + var deviceId = selectedThings[ruleActionTemplate.selectionId]; + var device = Engine.deviceManager.devices.getDevice(deviceId); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + ruleAction.deviceId = deviceId; + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id + for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + rule.actions.addRuleAction(ruleAction); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + } + + // Did we already use the thing we opened this page from? + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0) { + var ruleAction = rule.actions.createNewRuleAction(); + ruleAction.deviceId = root.device.id; + ruleAction.actionTypeId = root.deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id + for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + rule.actions.addRuleAction(ruleAction); + selectedThings.push(root.device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + } + + // Ok, we need to pick a thing + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName]}); + page.thingSelected.connect(function(device) { + var ruleAction = rule.actions.createNewRuleAction(); + ruleAction.deviceId = device.id; + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id; + for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + rule.actions.addRuleAction(ruleAction); + selectedThings.push(device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + }) + page.backPressed.connect(function() {rule.destroy(); root.done();}) + return; + } + + + for (var i = rule.exitActions.count; i < ruleTemplate.ruleExitActionTemplates.count; i++) { + var ruleExitActionTemplate = ruleTemplate.ruleExitActionTemplates.get(i); + + // Did we pick a thing for this index before? + if (selectedThings.length > ruleExitActionTemplate.selectionId) { + var ruleAction = rule.exitActions.createNewRuleAction(); + var deviceId = selectedThings[ruleExitActionTemplate.selectionId]; + var device = Engine.deviceManager.devices.getDevice(deviceId); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + ruleAction.deviceId = deviceId; + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleExitActionTemplate.interfaceAction).id + for (var j = 0; j < ruleExitActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleExitActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + rule.exitActions.addRuleAction(ruleAction); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + } + + // Did we already use the thing we opened this page from? + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0) { + var ruleAction = rule.exitActions.createNewRuleAction(); + ruleAction.deviceId = root.device.id; + ruleAction.actionTypeId = root.deviceClass.actionTypes.findByName(ruleExitActionTemplate.interfaceAction).id + for (var j = 0; j < ruleExitActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleExitActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + rule.exitActions.addRuleAction(ruleAction); + selectedThings.push(root.device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + } + + // Ok, we need to pick a thing + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleExitActionTemplate.interfaceName]}); + page.thingSelected.connect(function(device) { + var ruleAction = rule.exitActions.createNewRuleAction(); + ruleAction.deviceId = device.id; + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleExitActionTemplate.interfaceAction).id; + for (var j = 0; j < ruleExitActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleExitActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + rule.exitActions.addRuleAction(ruleAction); + selectedThings.push(device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return; + }) + page.backPressed.connect(function() {rule.destroy(); root.done();}) + return; + } + + + rule.name = ruleTemplate.ruleNameTemplate; + for (var i = 0; i < selectedThings.length; i++) { + var device = Engine.deviceManager.devices.getDevice(selectedThings[i]); + rule.name = rule.name.arg(device.name) + } + + print("Rule complete!") + Engine.ruleManager.addRule(rule); + rule.destroy(); + root.done(); + } + + function fillStateEvaluatorFromTemplate(rule, ruleTemplate, stateEvaluator, stateEvaluatorTemplate, selectedThings) { + if (stateEvaluatorTemplate.stateDescriptorTemplate !== null && selectedThings.indexOf(stateEvaluator.stateDescriptor.deviceId) === -1) { + // need to fill stateDescriptor + // did we pick a thing for this index before? + if (selectedThings.length > stateEvaluatorTemplate.stateDescriptorTemplate.selectionId) { + var deviceId = selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] + var device = Engine.deviceManager.devices.getDevice(deviceId) + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + stateEvaluator.stateDescriptor.deviceId = deviceId; + stateEvaluator.stateDescriptor.stateTypeId = deviceClass.stateTypes.findByName(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState).id + stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; + stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return true; + } + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName) >= 0) { + stateEvaluator.stateDescriptor.deviceId = root.device.id; + stateEvaluator.stateDescriptor.stateTypeId = root.deviceClass.stateTypes.findByName(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState).id + stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; + stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; + selectedThings.push(root.device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return true; + } + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName]}); + page.thingSelected.connect(function(device) { + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + stateEvaluator.stateDescriptor.deviceId = device.id; + stateEvaluator.stateDescriptor.stateTypeId = deviceClass.stateTypes.findByName(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState).id; + stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; + stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; + selectedThings.push(device.id); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings) + }) + page.backPressed.connect(function() {rule.destroy(); root.done();}) + return true; + } + stateEvaluator.stateOperator = stateEvaluatorTemplate.stateOperator; + if (stateEvaluatorTemplate.childEvaluatorTemplates.count > stateEvaluator.childEvaluators.count) { + var childEvaluator = rule.createStateEvaluator(); + var more = fillStateEvaluatorFromTemplate(rule, ruleTemplate, childEvaluator, stateEvaluatorTemplate.childEvaluatorTemplates.get(stateEvaluator.childEvaluators.count)) + stateEvaluator.childEvaluators.addStateEvaluator(childEvaluator); + return more; + } + return false; + } + + header: GuhHeader { + text: qsTr("New magic") + onBackPressed: root.done() + } + + ColumnLayout { + anchors.fill: parent + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: RuleTemplatesFilterModel { + id: ruleTemplatesModel + ruleTemplates: RuleTemplates {} + filterInterfaceNames: root.deviceClass ? root.deviceClass.interfaces : [] + } + delegate: MeaListItemDelegate { + width: parent.width + text: model.description + + onClicked: { + var ruleTemplate = ruleTemplatesModel.get(index); + var rule = Engine.ruleManager.createNewRule(); + root.fillRuleFromTemplate(rule, ruleTemplate) + } + } + } + + ThinDivider {} + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Create some magic manually") + onClicked: { + root.manualCreation(); + } + } + } +} diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index 29ead27b..e529274e 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -15,9 +15,12 @@ Page { property alias showEvents: interfacesProxy.showEvents property alias showActions: interfacesProxy.showActions property alias showStates: interfacesProxy.showStates + property alias shownInterfaces: devicesProxy.shownInterfaces header: GuhHeader { - text: root.selectInterface ? qsTr("Select a kind of things") : qsTr("Select a thing") + text: root.selectInterface ? + qsTr("Select a kind of things") : + root.shownInterfaces.length > 0 ? qsTr("Select a %1").arg(app.interfaceToDisplayName(root.shownInterfaces[0])) : qsTr("Select a thing") onBackPressed: root.backPressed() } @@ -26,13 +29,18 @@ Page { devicesFilter: Engine.deviceManager.devices } + DevicesProxy { + id: devicesProxy + devices: Engine.deviceManager.devices + } + ColumnLayout { anchors.fill: parent ListView { Layout.fillWidth: true Layout.fillHeight: true - model: root.selectInterface ? interfacesProxy : Engine.deviceManager.devices + model: root.selectInterface ? interfacesProxy : devicesProxy clip: true delegate: MeaListItemDelegate { width: parent.width @@ -42,7 +50,7 @@ Page { if (root.selectInterface) { root.interfaceSelected(interfacesProxy.get(index).name) } else { - root.thingSelected(Engine.deviceManager.devices.get(index)) + root.thingSelected(devicesProxy.get(index)) } } }