diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index 8e035da2..62efb381 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -150,6 +150,7 @@ void DeviceManager::getVendorsResponse(const QVariantMap ¶ms) void DeviceManager::getSupportedDevicesResponse(const QVariantMap ¶ms) { + qDebug() << "DeviceClass received:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); if (params.value("params").toMap().keys().contains("deviceClasses")) { QVariantList deviceClassList = params.value("params").toMap().value("deviceClasses").toList(); foreach (QVariant deviceClassVariant, deviceClassList) { diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index 91773949..edf18a71 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -279,7 +279,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // qWarning() << "Could not parse json data from nymea" << m_receiveBuffer.left(splitIndex) << error.errorString(); return; } -// qDebug() << "received response" << m_receiveBuffer.left(splitIndex); +// qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1); if (!m_receiveBuffer.isEmpty()) { staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray())); diff --git a/libnymea-app-core/jsonrpc/jsontypes.cpp b/libnymea-app-core/jsonrpc/jsontypes.cpp index bb620de4..c99d82d3 100644 --- a/libnymea-app-core/jsonrpc/jsontypes.cpp +++ b/libnymea-app-core/jsonrpc/jsontypes.cpp @@ -157,7 +157,9 @@ StateType *JsonTypes::unpackStateType(const QVariantMap &stateTypeMap, QObject * stateType->setDisplayName(stateTypeMap.value("displayName").toString()); stateType->setIndex(stateTypeMap.value("index").toInt()); stateType->setDefaultValue(stateTypeMap.value("defaultValue")); + stateType->setAllowedValues(stateTypeMap.value("possibleValues").toList()); stateType->setType(stateTypeMap.value("type").toString()); + QPair unit = stringToUnit(stateTypeMap.value("unit").toString()); stateType->setUnit(unit.first); stateType->setUnitString(unit.second); 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..14970864 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/actiontype.cpp b/libnymea-common/types/actiontype.cpp index f90518e8..88dbea5c 100644 --- a/libnymea-common/types/actiontype.cpp +++ b/libnymea-common/types/actiontype.cpp @@ -74,6 +74,10 @@ ParamTypes *ActionType::paramTypes() const void ActionType::setParamTypes(ParamTypes *paramTypes) { + if (m_paramTypes && m_paramTypes->parent() == this) { + m_paramTypes->deleteLater(); + } m_paramTypes = paramTypes; + m_paramTypes->setParent(this); emit paramTypesChanged(); } diff --git a/libnymea-common/types/actiontype.h b/libnymea-common/types/actiontype.h index 9347e0ca..8f1345a4 100644 --- a/libnymea-common/types/actiontype.h +++ b/libnymea-common/types/actiontype.h @@ -60,7 +60,7 @@ private: QString m_name; QString m_displayName; int m_index; - ParamTypes *m_paramTypes; + ParamTypes *m_paramTypes = nullptr; signals: void paramTypesChanged(); 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/interfaces.cpp b/libnymea-common/types/interfaces.cpp index e49ddc98..d25beef6 100644 --- a/libnymea-common/types/interfaces.cpp +++ b/libnymea-common/types/interfaces.cpp @@ -38,6 +38,9 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent) addStateType("temperaturesensor", "temperature", QVariant::Double, false, tr("Temperature"), tr("Temperature has changed")); + + addInterface("simpleclosable", tr("Closable things")); + addActionType("simpleclosable", "close", "Close", new ParamTypes()); } int Interfaces::rowCount(const QModelIndex &parent) const diff --git a/libnymea-common/types/paramtype.cpp b/libnymea-common/types/paramtype.cpp index ea7c7e84..b2fcc5aa 100644 --- a/libnymea-common/types/paramtype.cpp +++ b/libnymea-common/types/paramtype.cpp @@ -147,7 +147,7 @@ void ParamType::setUnit(const Types::Unit &unit) m_unit = unit; } -QList ParamType::allowedValues() const +QVariantList ParamType::allowedValues() const { return m_allowedValues; } diff --git a/libnymea-common/types/paramtype.h b/libnymea-common/types/paramtype.h index 7ecf1f6a..c7007c4e 100644 --- a/libnymea-common/types/paramtype.h +++ b/libnymea-common/types/paramtype.h @@ -83,7 +83,7 @@ public: QString unitString() const; void setUnitString(const QString &unitString); - QList allowedValues() const; + QVariantList allowedValues() const; void setAllowedValues(const QList allowedValues); bool readOnly() const; diff --git a/libnymea-common/types/statetype.cpp b/libnymea-common/types/statetype.cpp index c4a546bd..99888e2e 100644 --- a/libnymea-common/types/statetype.cpp +++ b/libnymea-common/types/statetype.cpp @@ -92,6 +92,16 @@ void StateType::setDefaultValue(const QVariant &defaultValue) m_defaultValue = defaultValue; } +QVariantList StateType::allowedValues() const +{ + return m_allowedValues; +} + +void StateType::setAllowedValues(const QVariantList &allowedValues) +{ + m_allowedValues = allowedValues; +} + Types::Unit StateType::unit() const { return m_unit; diff --git a/libnymea-common/types/statetype.h b/libnymea-common/types/statetype.h index 8ab87380..85bdea34 100644 --- a/libnymea-common/types/statetype.h +++ b/libnymea-common/types/statetype.h @@ -38,6 +38,7 @@ class StateType : public QObject Q_PROPERTY(QString type READ type CONSTANT) Q_PROPERTY(int index READ index CONSTANT) Q_PROPERTY(QVariant defaultValue READ defaultValue CONSTANT) + Q_PROPERTY(QVariantList allowedValues READ allowedValues WRITE setAllowedValues CONSTANT) Q_PROPERTY(Types::Unit unit READ unit CONSTANT) Q_PROPERTY(QString unitString READ unitString CONSTANT) @@ -63,6 +64,9 @@ public: QVariant defaultValue() const; void setDefaultValue(const QVariant &defaultValue); + QVariantList allowedValues() const; + void setAllowedValues(const QVariantList &allowedValues); + Types::Unit unit() const; void setUnit(const Types::Unit &unit); @@ -76,6 +80,7 @@ private: QString m_type; int m_index; QVariant m_defaultValue; + QVariantList m_allowedValues; Types::Unit m_unit; QString m_unitString; 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 ed343a79..ce539406 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -66,7 +66,7 @@ macx: { ios: { message("iOS build") QMAKE_TARGET_BUNDLE_PREFIX = io.guh - QMAKE_BUNDLE = $$TARGET + QMAKE_BUNDLE = nymeaApp # Configure generated xcode project to have our bundle id xcode_product_bundle_identifier_setting.value = $${QMAKE_TARGET_BUNDLE_PREFIX}.$${QMAKE_BUNDLE} plist.input = ../packaging/ios/Info.plist.in @@ -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 06696919..7150e544 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -24,7 +24,8 @@ ui/devicelistpages/LightsDeviceListPage.qml ui/customviews/ExtendedVolumeController.qml ui/components/ColorIcon.qml - ui/images/torch-on.svg + ui/images/light-on.svg + ui/images/light-off.svg ui/images/media-preview-start.svg ui/MagicPage.qml ui/images/mediaplayer-app-symbolic.svg @@ -59,7 +60,6 @@ ui/images/weathericons/wind.svg ui/devicepages/WeatherDevicePage.qml ui/devicepages/ColorLightDevicePage.qml - ui/images/torch-off.svg ui/components/ThrottledSlider.qml ui/components/ColorPickerCt.qml ui/images/navigation-menu.svg @@ -130,13 +130,12 @@ ui/PushButtonAuthPage.qml ui/images/dialog-error-symbolic.svg ui/images/send.svg - ui/images/mail-mark-important.svg + ui/images/attention.svg ui/devicepages/InputTriggerDevicePage.qml ui/images/clock-app-symbolic.svg ui/devicepages/StateLogPage.qml ui/customviews/GenericTypeLogView.qml qtquickcontrols2.conf - ui/BluetoothDiscoveryPage.qml ui/images/bluetooth.svg ui/images/refresh.svg ui/WirelessControlerPage.qml @@ -161,21 +160,21 @@ ui/AboutPage.qml ui/images/sort-listitem.svg ui/devicepages/ShutterDevicePage.qml - ui/images/shutter-1.svg - ui/images/shutter-2.svg - ui/images/shutter-3.svg - ui/images/shutter-4.svg - ui/images/shutter-5.svg - ui/images/shutter-6.svg - ui/images/shutter-7.svg - ui/images/shutter-8.svg - ui/images/shutter-9.svg - ui/images/shutter-10.svg + ui/images/shutter/shutter-000.svg + ui/images/shutter/shutter-010.svg + ui/images/shutter/shutter-020.svg + ui/images/shutter/shutter-030.svg + ui/images/shutter/shutter-040.svg + ui/images/shutter/shutter-050.svg + ui/images/shutter/shutter-060.svg + ui/images/shutter/shutter-070.svg + ui/images/shutter/shutter-080.svg + ui/images/shutter/shutter-090.svg + ui/images/shutter/shutter-100.svg ui/images/down.svg ui/images/up.svg ui/devicepages/GarageGateDevicePage.qml ui/images/remove.svg - ui/images/shutter-0.svg ui/components/ShutterControls.qml ../LICENSE ui/images/Built_with_Qt_RGB_logo.svg @@ -213,5 +212,22 @@ ui/components/EmptyViewPlaceholder.qml ui/components/RemoveDeviceMethodDialog.qml ui/components/FancyHeader.qml + ui/connection/ManualConnectPage.qml + ui/connection/BluetoothDiscoveryPage.qml + ui/images/awning/awning-100.svg + ui/devicepages/AwningDevicePage.qml + ui/images/awning/awning-000.svg + ui/images/awning/awning-010.svg + ui/images/awning/awning-020.svg + ui/images/awning/awning-030.svg + ui/images/awning/awning-040.svg + ui/images/awning/awning-050.svg + ui/images/awning/awning-060.svg + ui/images/awning/awning-070.svg + ui/images/awning/awning-080.svg + ui/images/awning/awning-090.svg + ui/magic/NewThingMagicPage.qml + ui/images/DeviceIconBlind.svg + ui/images/DeviceIconRollerShutter.svg diff --git a/nymea-app/ui/ConnectPage.qml b/nymea-app/ui/ConnectPage.qml index d2715c50..9f4d68f2 100644 --- a/nymea-app/ui/ConnectPage.qml +++ b/nymea-app/ui/ConnectPage.qml @@ -76,11 +76,27 @@ Page { Page { objectName: "discoveryPage" - header: GuhHeader { - text: qsTr("Connect %1").arg(app.systemName) - backButtonVisible: false - menuButtonVisible: true - onMenuPressed: connectionMenu.open() + header: FancyHeader { + title: qsTr("Connect %1").arg(app.systemName) + model: ListModel { + ListElement { iconSource: "../images/network-vpn.svg"; text: qsTr("Manual connection"); page: "connection/ManualConnectPage.qml" } + ListElement { iconSource: "../images/bluetooth.svg"; text: qsTr("Wireless setup"); page: "connection/BluetoothDiscoveryPage.qml" } + ListElement { iconSource: "../images/private-browsing.svg"; text: qsTr("Demo mode"); page: "" } + ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "AppSettingsPage.qml" } + } + onClicked: { + switch (index) { + case 0: + case 1: + case 3: + pageStack.push(model.get(index).page); + break; + case 2: + Engine.connection.connect("nymea://nymea.nymea.io:2222") + pageStack.push(connectingPage) + break; + } + } } Timer { @@ -90,41 +106,6 @@ Page { running: true } - Menu { - id: connectionMenu - objectName: "connectionMenu" - width: implicitWidth + app.margins - - IconMenuItem { - objectName: "manualConnectMenuItem" - iconSource: "../images/network-vpn.svg" - text: qsTr("Manual connection") - onTriggered: pageStack.push(manualConnectPage) - } - - IconMenuItem { - iconSource: "../images/bluetooth.svg" - text: qsTr("Wireless setup") - onTriggered: pageStack.push(Qt.resolvedUrl("BluetoothDiscoveryPage.qml")) - } - - IconMenuItem { - iconSource: "../images/private-browsing.svg" - text: qsTr("Demo mode") - onTriggered: { - pageStack.push(connectingPage) - Engine.connection.connect("nymea://nymea.nymea.io:2222") - } - } - - MenuSeparator { } - - IconMenuItem { - iconSource: "../images/stock_application.svg" - text: qsTr("App settings") - onTriggered: pageStack.push(Qt.resolvedUrl("AppSettingsPage.qml")) - } - } ColumnLayout { anchors.fill: parent @@ -304,107 +285,6 @@ Page { } } - - Component { - id: manualConnectPage - - Page { - objectName: "manualConnectPage" - header: GuhHeader { - text: qsTr("Manual connection") - onBackPressed: pageStack.pop() - } - - ColumnLayout { - anchors { left: parent.left; top: parent.top; right: parent.right } - anchors.margins: app.margins - spacing: app.margins - - GridLayout { - columns: 2 - - Label { - text: qsTr("Protocol") - } - - ComboBox { - id: connectionTypeComboBox - Layout.fillWidth: true - model: [ qsTr("TCP"), qsTr("Websocket") ] - } - - Label { text: qsTr("Address:") } - TextField { - id: addressTextInput - objectName: "addressTextInput" - Layout.fillWidth: true - placeholderText: "127.0.0.1" - } - - Label { text: qsTr("Port:") } - TextField { - id: portTextInput - Layout.fillWidth: true - placeholderText: connectionTypeComboBox.currentIndex === 0 ? "2222" : "4444" - validator: IntValidator{bottom: 1; top: 65535;} - } - - Label { - Layout.fillWidth: true - text: qsTr("Encrypted connection:") - } - CheckBox { - id: secureCheckBox - checked: true - } - } - - - Button { - text: qsTr("Connect") - objectName: "connectButton" - Layout.fillWidth: true - onClicked: { - var rpcUrl - var hostAddress - var port - - // Set default to placeholder - if (addressTextInput.text === "") { - hostAddress = addressTextInput.placeholderText - } else { - hostAddress = addressTextInput.text - } - - if (portTextInput.text === "") { - port = portTextInput.placeholderText - } else { - port = portTextInput.text - } - - if (connectionTypeComboBox.currentIndex == 0) { - if (secureCheckBox.checked) { - rpcUrl = "nymeas://" + hostAddress + ":" + port - } else { - rpcUrl = "nymea://" + hostAddress + ":" + port - } - } else if (connectionTypeComboBox.currentIndex == 1) { - if (secureCheckBox.checked) { - rpcUrl = "wss://" + hostAddress + ":" + port - } else { - rpcUrl = "ws://" + hostAddress + ":" + port - } - } - - print("Try to connect ", rpcUrl) - Engine.connection.connect(rpcUrl) - pageStack.push(connectingPage) - } - } - } - } - } - Component { id: connectingPage Page { diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 1b989aef..23ee8bd6 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -14,10 +14,14 @@ Page { title: swipeView.currentItem.title model: ListModel { - ListElement { iconSource: "../images/share.svg"; text: qsTr("Configure things"); page: "../EditDevicesPage.qml" } - ListElement { iconSource: "../images/magic.svg"; text: qsTr("Magic"); page: "../MagicPage.qml" } - ListElement { iconSource: "../images/settings.svg"; text: qsTr("System settings"); page: "../SettingsPage.qml" } - ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "../AppSettingsPage.qml" } + ListElement { iconSource: "../images/share.svg"; text: qsTr("Configure things"); page: "EditDevicesPage.qml" } + ListElement { iconSource: "../images/magic.svg"; text: qsTr("Magic"); page: "MagicPage.qml" } + ListElement { iconSource: "../images/settings.svg"; text: qsTr("System settings"); page: "SettingsPage.qml" } + ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "AppSettingsPage.qml" } + } + + onClicked: { + pageStack.push(model.get(index).page) } } diff --git a/nymea-app/ui/NewDeviceWizard.qml b/nymea-app/ui/NewDeviceWizard.qml index 31695ce9..b72b000f 100644 --- a/nymea-app/ui/NewDeviceWizard.qml +++ b/nymea-app/ui/NewDeviceWizard.qml @@ -97,6 +97,7 @@ Page { deviceClasses: Engine.deviceManager.deviceClasses } delegate: MeaListItemDelegate { + id: deviceClassDelegate width: parent.width text: model.displayName iconName: app.interfacesToIcon(deviceClass.interfaces) @@ -118,6 +119,27 @@ Page { print("should setup", deviceClass.name, deviceClass.setupMethod, deviceClass.createMethods, deviceClass["discoveryParamTypes"].count) } + + swipe.enabled: deviceClass.createMethods.indexOf("CreateMethodUser") !== -1 + swipe.right: MouseArea { + height: deviceClassDelegate.height + width: height + anchors.right: parent.right + Rectangle { + anchors.fill: parent + color: "transparent" + } + + ColorIcon { + anchors.fill: parent + anchors.margins: app.margins + name: "../images/add.svg" + } + onClicked: { + d.deviceClass = deviceClass + internalPageStack.push(paramsPage) + } + } } } } diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index df8ee59b..f5cbcb69 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -110,6 +110,9 @@ ApplicationWindow { objectName: "pageStack" anchors.fill: parent initialItem: Page {} + onDepthChanged: { + print("stackview depth changed", pageStack.depth) + } } onClosing: { @@ -135,7 +138,7 @@ ApplicationWindow { } } - property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "shutter", "garagegate", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] + property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "extendedawning", "extendedshutter", "extendedblind", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] function interfaceToString(name) { switch(name) { case "light": @@ -161,9 +164,14 @@ ApplicationWindow { case "outputtrigger": return qsTr("Events"); case "shutter": + case "extendedshutter": return qsTr("Shutters"); case "blind": + case "extendedblind": return qsTr("Blinds"); + case "awning": + case "extendedawning": + return qsTr("Awnings"); case "garagegate": return qsTr("Garage gates"); case "uncategorized": @@ -186,7 +194,7 @@ ApplicationWindow { case "light": case "colorlight": case "dimmablelight": - return Qt.resolvedUrl("images/torch-on.svg") + return Qt.resolvedUrl("images/light-on.svg") case "sensor": case "temperaturesensor": case "humiditysensor": @@ -212,18 +220,27 @@ ApplicationWindow { case "connectable": return Qt.resolvedUrl("images/stock_link.svg") case "inputtrigger": - return Qt.resolvedUrl("images/mail-mark-important.svg") + return Qt.resolvedUrl("images/attention.svg") case "outputtrigger": return Qt.resolvedUrl("images/send.svg") case "shutter": + case "extendedshutter": + return Qt.resolvedUrl("images/DeviceIconRollerShutter.svg") case "blind": - return Qt.resolvedUrl("images/sort-listitem.svg") + case "extendedblind": + return Qt.resolvedUrl("images/DeviceIconBlind.svg") case "garagegate": - return Qt.resolvedUrl("images/shutter-10.svg") + return Qt.resolvedUrl("images/shutter/shutter-100.svg") + case "extendedawning": + return Qt.resolvedUrl("images/awning/awning-100.svg") case "battery": return Qt.resolvedUrl("images/battery/battery-050.svg") case "uncategorized": return Qt.resolvedUrl("images/select-none.svg") + case "simpleclosable": + return Qt.resolvedUrl("images/sort-listitem.svg") + default: + console.warn("InterfaceToIcon: Unhandled interface", name) } return ""; } @@ -238,6 +255,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) { @@ -250,12 +278,14 @@ ApplicationWindow { page = "SensorDevicePage.qml"; } else if (interfaceList.indexOf("inputtrigger") >= 0) { page = "InputTriggerDevicePage.qml"; - } else if (interfaceList.indexOf("shutter") >= 0 ) { - page = "ShutterDevicePage.qml"; } else if (interfaceList.indexOf("garagegate") >= 0 ) { page = "GarageGateDevicePage.qml"; } else if (interfaceList.indexOf("light") >= 0) { - page = "ColorLightDevicePage.qml" + page = "ColorLightDevicePage.qml"; + } else if (interfaceList.indexOf("extendedshutter") >= 0 ) { + page = "ShutterDevicePage.qml"; + } else if (interfaceList.indexOf("extendedawning") >= 0) { + page = "AwningDevicePage.qml"; } else { page = "GenericDevicePage.qml"; } @@ -263,6 +293,11 @@ ApplicationWindow { return page; } + function pad(num, size) { + var s = "000000000" + num; + return s.substr(s.length-size); + } + Component { id: invalidVersionComponent Popup { diff --git a/nymea-app/ui/components/ColorIcon.qml b/nymea-app/ui/components/ColorIcon.qml index 0624a1e3..16e61690 100644 --- a/nymea-app/ui/components/ColorIcon.qml +++ b/nymea-app/ui/components/ColorIcon.qml @@ -76,6 +76,8 @@ Item { property int margins: 0 + property alias status: image.status + Image { id: image diff --git a/nymea-app/ui/components/FancyHeader.qml b/nymea-app/ui/components/FancyHeader.qml index 86046168..4aebfd81 100644 --- a/nymea-app/ui/components/FancyHeader.qml +++ b/nymea-app/ui/components/FancyHeader.qml @@ -10,6 +10,8 @@ ToolBar { property string title property alias model: menuRepeater.model + signal clicked(int index); + QtObject { id: d property bool menuOpen: false @@ -87,7 +89,7 @@ ToolBar { onClicked: { d.menuOpen = false - pageStack.push(model.page) + root.clicked(index) } Rectangle { diff --git a/nymea-app/ui/components/ShutterControls.qml b/nymea-app/ui/components/ShutterControls.qml index 3751a058..21703243 100644 --- a/nymea-app/ui/components/ShutterControls.qml +++ b/nymea-app/ui/components/ShutterControls.qml @@ -14,54 +14,46 @@ RowLayout { readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null readonly property var openState: device ? device.states.getState(deviceClass.stateTypes.findByName("state").id) : null - Rectangle { + property bool invert: false + + ItemDelegate { Layout.preferredWidth: app.iconSize * 2 Layout.preferredHeight: width - color: root.openState && root.openState.value === "opening" ? Material.accent : Material.foreground - radius: height / 2 ColorIcon { anchors.fill: parent anchors.margins: app.margins - name: "../images/up.svg" - } - MouseArea { - anchors.fill: parent - onClicked: Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("open").id) + name: root.invert ? "../images/down.svg" : "../images/up.svg" + color: root.openState && root.openState.value === "opening" ? Material.accent : keyColor } + onClicked: Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("open").id) } - Rectangle { + + ItemDelegate { Layout.preferredWidth: app.iconSize * 2 Layout.preferredHeight: width - color: Material.foreground - radius: height / 2 +// color: Material.foreground +// radius: height / 2 ColorIcon { anchors.fill: parent anchors.margins: app.margins - name: "../images/remove.svg" - } - MouseArea { - anchors.fill: parent - onClicked: Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("stop").id) + name: "../images/media-playback-stop.svg" } + onClicked: Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("stop").id) } - Rectangle { + ItemDelegate { Layout.preferredWidth: app.iconSize * 2 Layout.preferredHeight: width - color: root.openState && root.openState.value === "closing" ? Material.accent : Material.foreground - radius: height / 2 ColorIcon { anchors.fill: parent anchors.margins: app.margins - name: "../images/down.svg" - } - MouseArea { - anchors.fill: parent - onClicked: Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("close").id) + name: root.invert ? "../images/up.svg" : "../images/down.svg" + color: root.openState && root.openState.value === "closing" ? Material.accent : keyColor } + onClicked: Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("close").id) } } diff --git a/nymea-app/ui/BluetoothDiscoveryPage.qml b/nymea-app/ui/connection/BluetoothDiscoveryPage.qml similarity index 96% rename from nymea-app/ui/BluetoothDiscoveryPage.qml rename to nymea-app/ui/connection/BluetoothDiscoveryPage.qml index a47aaeed..77fbaa42 100644 --- a/nymea-app/ui/BluetoothDiscoveryPage.qml +++ b/nymea-app/ui/connection/BluetoothDiscoveryPage.qml @@ -1,7 +1,7 @@ import QtQuick 2.4 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.2 -import "components" +import "../components" import Nymea 1.0 @@ -12,7 +12,7 @@ Page { onBackPressed: pageStack.pop() HeaderButton { - imageSource: Qt.resolvedUrl("images/refresh.svg") + imageSource: Qt.resolvedUrl("../images/refresh.svg") onClicked: Engine.bluetoothDiscovery.start() } } @@ -68,7 +68,7 @@ Page { delegate: MeaListItemDelegate { width: parent.width - iconName: Qt.resolvedUrl("images/bluetooth.svg") + iconName: Qt.resolvedUrl("../images/bluetooth.svg") text: model.name subText: model.address @@ -138,7 +138,7 @@ Page { sourceSize.height: 540 fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignHCenter - source: "images/rpi-setup.svg" + source: "../images/rpi-setup.svg" } ThinDivider {} Label { @@ -164,7 +164,7 @@ Page { sourceSize.height: width fillMode: Image.PreserveAspectFit Layout.alignment: Qt.AlignHCenter - source: "images/nymea-box-setup.svg" + source: "../images/nymea-box-setup.svg" } } } diff --git a/nymea-app/ui/connection/ManualConnectPage.qml b/nymea-app/ui/connection/ManualConnectPage.qml new file mode 100644 index 00000000..9d53e60f --- /dev/null +++ b/nymea-app/ui/connection/ManualConnectPage.qml @@ -0,0 +1,101 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 +import "../components" + +Page { + objectName: "manualConnectPage" + header: GuhHeader { + text: qsTr("Manual connection") + onBackPressed: pageStack.pop() + } + + ColumnLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + anchors.margins: app.margins + spacing: app.margins + + GridLayout { + columns: 2 + + Label { + text: qsTr("Protocol") + } + + ComboBox { + id: connectionTypeComboBox + Layout.fillWidth: true + model: [ qsTr("TCP"), qsTr("Websocket") ] + } + + Label { text: qsTr("Address:") } + TextField { + id: addressTextInput + objectName: "addressTextInput" + Layout.fillWidth: true + placeholderText: "127.0.0.1" + } + + Label { text: qsTr("Port:") } + TextField { + id: portTextInput + Layout.fillWidth: true + placeholderText: connectionTypeComboBox.currentIndex === 0 ? "2222" : "4444" + validator: IntValidator{bottom: 1; top: 65535;} + } + + Label { + Layout.fillWidth: true + text: qsTr("Encrypted connection:") + } + CheckBox { + id: secureCheckBox + checked: true + } + } + + + Button { + text: qsTr("Connect") + objectName: "connectButton" + Layout.fillWidth: true + onClicked: { + var rpcUrl + var hostAddress + var port + + // Set default to placeholder + if (addressTextInput.text === "") { + hostAddress = addressTextInput.placeholderText + } else { + hostAddress = addressTextInput.text + } + + if (portTextInput.text === "") { + port = portTextInput.placeholderText + } else { + port = portTextInput.text + } + + if (connectionTypeComboBox.currentIndex == 0) { + if (secureCheckBox.checked) { + rpcUrl = "nymeas://" + hostAddress + ":" + port + } else { + rpcUrl = "nymea://" + hostAddress + ":" + port + } + } else if (connectionTypeComboBox.currentIndex == 1) { + if (secureCheckBox.checked) { + rpcUrl = "wss://" + hostAddress + ":" + port + } else { + rpcUrl = "ws://" + hostAddress + ":" + port + } + } + + print("Try to connect ", rpcUrl) + Engine.connection.connect(rpcUrl) + pageStack.push(connectingPage) + } + } + } +} diff --git a/nymea-app/ui/customviews/MediaControllerView.qml b/nymea-app/ui/customviews/MediaControllerView.qml index c93a6309..2a1d368e 100644 --- a/nymea-app/ui/customviews/MediaControllerView.qml +++ b/nymea-app/ui/customviews/MediaControllerView.qml @@ -25,10 +25,10 @@ CustomViewBase { controlsModel.append({image: "../images/media-skip-backward.svg", action: "skipBack"}) controlsModel.append({image: "../images/media-seek-backward.svg", action: "rewind"}) controlsModel.append({image: "../images/media-playback-stop.svg", action: "stop"}) - if (playbackState.value === "PAUSED" || playbackState.value === "STOPPED") { + if (playbackState.value === "Paused" || playbackState.value === "Stopped") { controlsModel.append({image: "../images/media-playback-start.svg", action: "play"}) } - if (playbackState.value === "PLAYING") { + if (playbackState.value === "Playing") { controlsModel.append({image: "../images/media-playback-pause.svg", action: "pause"}) } diff --git a/nymea-app/ui/delegates/ParamDescriptorDelegate.qml b/nymea-app/ui/delegates/ParamDescriptorDelegate.qml index 68a1286e..ab02b16e 100644 --- a/nymea-app/ui/delegates/ParamDescriptorDelegate.qml +++ b/nymea-app/ui/delegates/ParamDescriptorDelegate.qml @@ -24,6 +24,7 @@ ItemDelegate { case "bool": case "string": case "qstring": + case "color": return [qsTr("is"), qsTr("is not")]; case "int": case "double": @@ -61,7 +62,7 @@ ItemDelegate { Layout.fillWidth: true sourceComponent: { - print("Datatye is:", paramType.type, paramType.minValue, paramType.maxValue, paramType.allowedValues) + print("Datatye is:", paramType.name, paramType.type, paramType.minValue, paramType.maxValue, paramType.allowedValues) switch (paramType.type.toLowerCase()) { case "bool": return boolComponent; @@ -73,6 +74,7 @@ ItemDelegate { return textFieldComponent; case "string": case "qstring": + case "color": if (paramType.allowedValues.length > 0) { return comboBoxComponent } diff --git a/nymea-app/ui/devicepages/AwningDevicePage.qml b/nymea-app/ui/devicepages/AwningDevicePage.qml new file mode 100644 index 00000000..1b8e38f3 --- /dev/null +++ b/nymea-app/ui/devicepages/AwningDevicePage.qml @@ -0,0 +1,82 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +DevicePageBase { + id: root + + readonly property bool landscape: width > height + readonly property bool isExtended: deviceClass.interfaces.indexOf("extendedawning") >= 0 + readonly property var percentageState: isExtended ? device.states.getState(deviceClass.stateTypes.findByName("percentage").id) : 0 + readonly property var movingState: isExtended ? device.states.getState(deviceClass.stateTypes.findByName("moving").id) : 0 + + GridLayout { + anchors.fill: parent + columns: root.landscape ? 2 : 1 + + ColorIcon { + id: shutterImage + Layout.preferredWidth: root.landscape ? Math.min(parent.width - shutterControlsContainer.width, parent.height) - app.margins : parent.width + Layout.preferredHeight: width + name: "../images/awning/awning-" + app.pad(Math.round(root.percentageState.value / 10) * 10, 3) + ".svg" + visible: isExtended + } + + + Item { + id: shutterControlsContainer + Layout.preferredWidth: root.landscape ? Math.max(parent.width / 2, shutterControls.implicitWidth) : parent.width + Layout.minimumWidth: shutterControls.implicitWidth + Layout.fillHeight: true + Layout.minimumHeight: app.iconSize * 2.5 + + Column { + anchors.centerIn: parent + width: parent.width - app.margins * 2 + spacing: app.margins + + Slider { + id: percentageSlider + width: parent.width + from: 0 + to: 100 + stepSize: 1 + visible: isExtended + + Binding { + target: percentageSlider + property: "value" + value: root.percentageState.value + when: root.movingState.value === false + } + + onPressedChanged: { + if (!pressed) { + return + } + + var actionType = root.deviceClass.actionTypes.findByName("percentage"); + var params = []; + var percentageParam = {} + percentageParam["paramTypeId"] = actionType.paramTypes.findByName("percentage").id; + percentageParam["value"] = value + params.push(percentageParam); + Engine.deviceManager.executeAction(root.device.id, actionType.id, params); + } + } + + ShutterControls { + id: shutterControls + device: root.device + invert: true + anchors.horizontalCenter: parent.horizontalCenter + } + } + + } + } +} diff --git a/nymea-app/ui/devicepages/ColorLightDevicePage.qml b/nymea-app/ui/devicepages/ColorLightDevicePage.qml index eccb95c0..41317395 100644 --- a/nymea-app/ui/devicepages/ColorLightDevicePage.qml +++ b/nymea-app/ui/devicepages/ColorLightDevicePage.qml @@ -28,7 +28,7 @@ DevicePageBase { height: width ColorIcon { anchors.fill: parent - name: "../images/torch-off.svg" + name: "../images/light-off.svg" color: powerRow.powerState.value === true ? keyColor : app.guhAccent } onClicked: { @@ -62,7 +62,7 @@ DevicePageBase { height: width ColorIcon { anchors.fill: parent - name: "../images/torch-on.svg" + name: "../images/light-on.svg" color: powerRow.powerState.value === true ? app.guhAccent : keyColor } onClicked: { diff --git a/nymea-app/ui/devicepages/GarageGateDevicePage.qml b/nymea-app/ui/devicepages/GarageGateDevicePage.qml index 2ae01c55..b0827fbf 100644 --- a/nymea-app/ui/devicepages/GarageGateDevicePage.qml +++ b/nymea-app/ui/devicepages/GarageGateDevicePage.qml @@ -23,9 +23,9 @@ DevicePageBase { id: shutterImage Layout.preferredWidth: root.landscape ? Math.min(parent.width - shutterControlsContainer.width, parent.height) - app.margins : parent.width Layout.preferredHeight: width - property int currentImage: root.openState.value === "closed" ? 10 : - root.openState.value === "open" && root.intermediatePositionState.value === false ? 0 : 5 - name: "../images/shutter-" + currentImage + ".svg" + property string currentImage: root.openState.value === "closed" ? "100" : + root.openState.value === "open" && root.intermediatePositionState.value === false ? "000" : "050" + name: "../images/shutter/shutter-" + currentImage + ".svg" Item { id: arrows @@ -85,29 +85,25 @@ DevicePageBase { device: root.device anchors.centerIn: parent - Rectangle { + ItemDelegate { Layout.preferredWidth: app.iconSize * 2 Layout.preferredHeight: width - color: root.lightState && root.lightState.value === true ? Material.accent : Material.foreground - radius: height / 2 visible: root.lightStateType !== null ColorIcon { anchors.fill: parent anchors.margins: app.margins - name: "../images/torch-" + (root.lightState && root.lightState.value === true ? "on" : "off") + ".svg" + name: "../images/light-" + (root.lightState && root.lightState.value === true ? "on" : "off") + ".svg" + color: root.lightState && root.lightState.value === true ? Material.accent : keyColor } - MouseArea { - anchors.fill: parent - onClicked: { - print("blabla", root.lightState, root.lightState.value, root.lightStateType.name, root.lightState.stateTypeId, root.lightStateType.id) - var params = []; - var param = {}; - param["paramTypeId"] = root.lightStateType.id; - param["value"] = !root.lightState.value; - params.push(param) - Engine.deviceManager.executeAction(root.device.id, root.lightStateType.id, params) - } + onClicked: { + print("blabla", root.lightState, root.lightState.value, root.lightStateType.name, root.lightState.stateTypeId, root.lightStateType.id) + var params = []; + var param = {}; + param["paramTypeId"] = root.lightStateType.id; + param["value"] = !root.lightState.value; + params.push(param) + Engine.deviceManager.executeAction(root.device.id, root.lightStateType.id, params) } } } diff --git a/nymea-app/ui/devicepages/ShutterDevicePage.qml b/nymea-app/ui/devicepages/ShutterDevicePage.qml index e4bb5c7c..af9cc202 100644 --- a/nymea-app/ui/devicepages/ShutterDevicePage.qml +++ b/nymea-app/ui/devicepages/ShutterDevicePage.qml @@ -1,5 +1,6 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" @@ -8,13 +9,71 @@ import "../customviews" DevicePageBase { id: root + readonly property bool landscape: width > height + readonly property bool isExtended: deviceClass.interfaces.indexOf("extendedshutter") >= 0 + readonly property var percentageState: isExtended ? device.states.getState(deviceClass.stateTypes.findByName("percentage").id) : 0 + readonly property var movingState: isExtended ? device.states.getState(deviceClass.stateTypes.findByName("moving").id) : 0 - ShutterControls { - anchors { - top: parent.top - topMargin: app.iconSize - horizontalCenter: parent.horizontalCenter + GridLayout { + anchors.fill: parent + columns: root.landscape ? 2 : 1 + + ColorIcon { + id: shutterImage + Layout.preferredWidth: root.landscape ? Math.min(parent.width - shutterControlsContainer.width, parent.height) - app.margins : parent.width + Layout.preferredHeight: width + name: "../images/shutter/shutter-" + app.pad(Math.round(root.percentageState.value / 10) * 10, 3) + ".svg" + visible: isExtended + } + + Item { + id: shutterControlsContainer + Layout.preferredWidth: root.landscape ? Math.max(parent.width / 2, shutterControls.implicitWidth) : parent.width + Layout.minimumWidth: shutterControls.implicitWidth + Layout.fillHeight: true + Layout.minimumHeight: app.iconSize * 2.5 + + Column { + anchors.centerIn: parent + width: parent.width - app.margins * 2 + spacing: app.margins + + Slider { + id: percentageSlider + width: parent.width + from: 0 + to: 100 + stepSize: 1 + visible: isExtended + + Binding { + target: percentageSlider + property: "value" + value: root.percentageState.value + when: root.movingState.value === false + } + + onPressedChanged: { + if (pressed) { + return; + } + + var actionType = root.deviceClass.actionTypes.findByName("percentage"); + var params = []; + var percentageParam = {} + percentageParam["paramTypeId"] = actionType.paramTypes.findByName("percentage").id; + percentageParam["value"] = value + params.push(percentageParam); + Engine.deviceManager.executeAction(root.device.id, actionType.id, params); + } + } + + ShutterControls { + id: shutterControls + device: root.device + anchors.horizontalCenter: parent.horizontalCenter + } + } } - device: root.device } } diff --git a/nymea-app/ui/images/DeviceIconBlind.svg b/nymea-app/ui/images/DeviceIconBlind.svg new file mode 100644 index 00000000..46c17f7c --- /dev/null +++ b/nymea-app/ui/images/DeviceIconBlind.svg @@ -0,0 +1,59 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nymea-app/ui/images/DeviceIconRollerShutter.svg b/nymea-app/ui/images/DeviceIconRollerShutter.svg new file mode 100644 index 00000000..e99ba932 --- /dev/null +++ b/nymea-app/ui/images/DeviceIconRollerShutter.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/nymea-app/ui/images/mail-mark-important.svg b/nymea-app/ui/images/attention.svg similarity index 100% rename from nymea-app/ui/images/mail-mark-important.svg rename to nymea-app/ui/images/attention.svg diff --git a/nymea-app/ui/images/awning/awning-000.svg b/nymea-app/ui/images/awning/awning-000.svg new file mode 100644 index 00000000..78f1fce6 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-000.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-010.svg b/nymea-app/ui/images/awning/awning-010.svg new file mode 100644 index 00000000..708e9f7f --- /dev/null +++ b/nymea-app/ui/images/awning/awning-010.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-020.svg b/nymea-app/ui/images/awning/awning-020.svg new file mode 100644 index 00000000..f844d3a6 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-020.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-030.svg b/nymea-app/ui/images/awning/awning-030.svg new file mode 100644 index 00000000..05c2d822 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-030.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-040.svg b/nymea-app/ui/images/awning/awning-040.svg new file mode 100644 index 00000000..1b5a8a97 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-040.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-050.svg b/nymea-app/ui/images/awning/awning-050.svg new file mode 100644 index 00000000..1f8b86b3 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-050.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-060.svg b/nymea-app/ui/images/awning/awning-060.svg new file mode 100644 index 00000000..ceb376c4 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-060.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-070.svg b/nymea-app/ui/images/awning/awning-070.svg new file mode 100644 index 00000000..23880103 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-070.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-080.svg b/nymea-app/ui/images/awning/awning-080.svg new file mode 100644 index 00000000..fc4ef304 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-080.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-090.svg b/nymea-app/ui/images/awning/awning-090.svg new file mode 100644 index 00000000..57fb2191 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-090.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/awning/awning-100.svg b/nymea-app/ui/images/awning/awning-100.svg new file mode 100644 index 00000000..3a470a26 --- /dev/null +++ b/nymea-app/ui/images/awning/awning-100.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/torch-off.svg b/nymea-app/ui/images/light-off.svg similarity index 100% rename from nymea-app/ui/images/torch-off.svg rename to nymea-app/ui/images/light-off.svg diff --git a/nymea-app/ui/images/torch-on.svg b/nymea-app/ui/images/light-on.svg similarity index 100% rename from nymea-app/ui/images/torch-on.svg rename to nymea-app/ui/images/light-on.svg diff --git a/nymea-app/ui/images/shutter-0.svg b/nymea-app/ui/images/shutter/shutter-000.svg similarity index 100% rename from nymea-app/ui/images/shutter-0.svg rename to nymea-app/ui/images/shutter/shutter-000.svg diff --git a/nymea-app/ui/images/shutter-1.svg b/nymea-app/ui/images/shutter/shutter-010.svg similarity index 100% rename from nymea-app/ui/images/shutter-1.svg rename to nymea-app/ui/images/shutter/shutter-010.svg diff --git a/nymea-app/ui/images/shutter-2.svg b/nymea-app/ui/images/shutter/shutter-020.svg similarity index 100% rename from nymea-app/ui/images/shutter-2.svg rename to nymea-app/ui/images/shutter/shutter-020.svg diff --git a/nymea-app/ui/images/shutter-3.svg b/nymea-app/ui/images/shutter/shutter-030.svg similarity index 100% rename from nymea-app/ui/images/shutter-3.svg rename to nymea-app/ui/images/shutter/shutter-030.svg diff --git a/nymea-app/ui/images/shutter-4.svg b/nymea-app/ui/images/shutter/shutter-040.svg similarity index 100% rename from nymea-app/ui/images/shutter-4.svg rename to nymea-app/ui/images/shutter/shutter-040.svg diff --git a/nymea-app/ui/images/shutter-5.svg b/nymea-app/ui/images/shutter/shutter-050.svg similarity index 100% rename from nymea-app/ui/images/shutter-5.svg rename to nymea-app/ui/images/shutter/shutter-050.svg diff --git a/nymea-app/ui/images/shutter-6.svg b/nymea-app/ui/images/shutter/shutter-060.svg similarity index 100% rename from nymea-app/ui/images/shutter-6.svg rename to nymea-app/ui/images/shutter/shutter-060.svg diff --git a/nymea-app/ui/images/shutter-7.svg b/nymea-app/ui/images/shutter/shutter-070.svg similarity index 100% rename from nymea-app/ui/images/shutter-7.svg rename to nymea-app/ui/images/shutter/shutter-070.svg diff --git a/nymea-app/ui/images/shutter-8.svg b/nymea-app/ui/images/shutter/shutter-080.svg similarity index 100% rename from nymea-app/ui/images/shutter-8.svg rename to nymea-app/ui/images/shutter/shutter-080.svg diff --git a/nymea-app/ui/images/shutter-9.svg b/nymea-app/ui/images/shutter/shutter-090.svg similarity index 100% rename from nymea-app/ui/images/shutter-9.svg rename to nymea-app/ui/images/shutter/shutter-090.svg diff --git a/nymea-app/ui/images/shutter-10.svg b/nymea-app/ui/images/shutter/shutter-100.svg similarity index 100% rename from nymea-app/ui/images/shutter-10.svg rename to nymea-app/ui/images/shutter/shutter-100.svg 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 1e698e8a..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; @@ -335,8 +338,7 @@ Page { } } Repeater { - model: ["torch-on", "torch-off", "alarm-clock", "media-preview-start", "network-secure", "notification", "sensors", "shutter-10", "mail-mark-important", "eye"] - + model: ["light-on", "light-off", "alarm-clock", "media-play", "network-secure", "notification", "sensors", "shutter-10", "attention", "eye"] delegate: Item { Layout.fillWidth: true Layout.preferredHeight: app.iconSize + app.margins @@ -397,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) } @@ -553,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) } @@ -575,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 @@ -590,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)) } } } diff --git a/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml b/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml index d06d6c55..a277ef23 100644 --- a/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml +++ b/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml @@ -40,6 +40,7 @@ SwipeDelegate { Label { Layout.fillWidth: true font.pixelSize: childEvaluatorsRepeater.count > 0 ? app.smallFont : app.mediumFont + wrapMode: Text.WordWrap property string operatorString: { if (!root.stateEvaluator) { return ""; diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml index 72fc4e50..321bdee0 100644 --- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml +++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml @@ -90,9 +90,15 @@ Item { case "light": case "media": case "garagegate": - case "shutter": case "blind": + case "extendedblind": + case "shutter": + case "extendedshutter": + case "awning": + case "extendedawning": return buttonComponent + default: + console.warn("DevicesPageDelegate, inlineControl: Unhandled interface", model.name) } } } @@ -138,10 +144,10 @@ Item { var actionName switch (state.value) { - case "PLAYING": + case "Playing": actionName = "pause"; break; - case "PAUSED": + case "Paused": actionName = "play"; break; } @@ -152,7 +158,12 @@ Item { Engine.deviceManager.executeAction(device.id, actionTypeId) case "garagegate": case "shutter": + case "extendedshutter": case "blind": + case "extendedblind": + case "awning": + case "extendedawning": + case "simpleclosable": for (var i = 0; i < devicesProxy.count; i++) { var device = devicesProxy.get(i); var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); @@ -160,6 +171,8 @@ Item { Engine.deviceManager.executeAction(device.id, actionType.id) } + default: + console.warn("DevicesPageDelegate, inlineButtonControl clicked: Unhandled interface", model.name) } } @@ -195,10 +208,15 @@ Item { } } return count === 0 ? qsTr("All closed") : qsTr("%1 open").arg(count) + case "blind": + case "extendedblind": + case "awning": + case "extendedawning": case "shutter": + case "extendedshutter": return qsTr("%1 installed").arg(devicesProxy.count) } - console.warn("Unhandled interface", model.name) + console.warn("DevicesPageDelegate, inlineButtonControl: Unhandled interface", model.name) } font.pixelSize: app.smallFont elide: Text.ElideRight @@ -216,15 +234,21 @@ Item { var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); var stateType = deviceClass.stateTypes.findByName("playbackStatus"); var state = device.states.getState(stateType.id) - return state.value === "PLAYING" ? "../images/media-playback-pause.svg" : + return state.value === "Playing" ? "../images/media-playback-pause.svg" : state.value === "PAUSED" ? "../images/media-playback-start.svg" : "" case "light": return "../images/system-shutdown.svg" case "garagegate": - case "shutter": case "blind": + case "extendedblind": + case "awning": + case "extendedawning": + case "shutter": + case "extendedshutter": return "../images/down.svg" + default: + console.warn("DevicesPageDelegate, inlineButtonControl image: Unhandled interface", model.name) } } } diff --git a/nymea-app/ui/mainviews/ScenesView.qml b/nymea-app/ui/mainviews/ScenesView.qml index 11e67b7e..04c7640a 100644 --- a/nymea-app/ui/mainviews/ScenesView.qml +++ b/nymea-app/ui/mainviews/ScenesView.qml @@ -44,26 +44,31 @@ Item { anchors.fill: parent anchors.margins: app.margins / 2 Material.elevation: 1 - - MouseArea { + padding: 0 + ItemDelegate { anchors.fill: parent onClicked: { Engine.ruleManager.executeActions(model.id) } - } + contentItem: ColumnLayout { + width: parent.width + anchors.centerIn: parent + spacing: app.margins - ColumnLayout { - width: parent.width - anchors.centerIn: parent - spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: height + Layout.alignment: Qt.AlignHCenter + name: scenesDelegate.iconTag ? "../images/" + scenesDelegate.iconTag.value + ".svg" : "../images/slideshow.svg"; + color: scenesDelegate.colorTag ? scenesDelegate.colorTag.value : app.guhAccent; - ColorIcon { - Layout.preferredHeight: app.iconSize * 2 - Layout.preferredWidth: height - Layout.alignment: Qt.AlignHCenter - name: scenesDelegate.iconTag ? "../images/" + scenesDelegate.iconTag.value + ".svg" : "../images/slideshow.svg"; - color: scenesDelegate.colorTag ? scenesDelegate.colorTag.value : app.guhAccent; - } + ColorIcon { + anchors.fill: parent + name: "../images/slideshow.svg" + color: app.guhAccent + visible: parent.status === Image.Error + } + } Label { Layout.fillWidth: true @@ -80,5 +85,4 @@ Item { } } } - } diff --git a/version.txt b/version.txt index 33af14ae..18d6719b 100644 --- a/version.txt +++ b/version.txt @@ -1,2 +1,2 @@ -1.0.36 -2 +1.0.42 +8