diff --git a/libmea-core/devicemanager.cpp b/libmea-core/devicemanager.cpp index cf0bfef7..1fd9f554 100644 --- a/libmea-core/devicemanager.cpp +++ b/libmea-core/devicemanager.cpp @@ -224,7 +224,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms) // set initial state values QVariantList stateVariantList = deviceVariant.toMap().value("states").toList(); foreach (const QVariant &stateMap, stateVariantList) { - QUuid stateTypeId = stateMap.toMap().value("stateTypeId").toUuid(); + QString stateTypeId = stateMap.toMap().value("stateTypeId").toString(); StateType *st = dc->stateTypes()->getStateType(stateTypeId); if (!st) { qWarning() << "Can't find a statetype for this state"; diff --git a/libmea-core/jsonrpc/jsonrpcclient.cpp b/libmea-core/jsonrpc/jsonrpcclient.cpp index f605d348..6a8938ae 100644 --- a/libmea-core/jsonrpc/jsonrpcclient.cpp +++ b/libmea-core/jsonrpc/jsonrpcclient.cpp @@ -134,6 +134,21 @@ bool JsonRpcClient::pushButtonAuthAvailable() const return m_pushButtonAuthAvailable; } +QString JsonRpcClient::serverVersion() const +{ + return m_serverVersion; +} + +QString JsonRpcClient::jsonRpcVersion() const +{ + return m_jsonRpcVersion.toString(); +} + +QString JsonRpcClient::serverUuid() const +{ + return m_serverUuid; +} + int JsonRpcClient::createUser(const QString &username, const QString &password) { QVariantMap params; @@ -168,6 +183,10 @@ int JsonRpcClient::requestPushButtonAuth(const QString &deviceName) return reply->commandId(); } +bool JsonRpcClient::ensureServerVersion(const QString &jsonRpcVersion) +{ + return QVersionNumber(m_jsonRpcVersion) >= QVersionNumber::fromString(jsonRpcVersion); +} void JsonRpcClient::processAuthenticate(const QVariantMap &data) { @@ -224,7 +243,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request) { QVariantMap newRequest = request; newRequest.insert("token", m_token); -// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); + qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson()); } @@ -259,7 +278,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // qWarning() << "Could not parse json data from mea" << data << error.errorString(); return; } -// qDebug() << "received response" << m_receiveBuffer.left(splitIndex); + qDebug() << "received response" << m_receiveBuffer.left(splitIndex); m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1); if (!m_receiveBuffer.isEmpty()) { staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray())); @@ -274,24 +293,28 @@ void JsonRpcClient::dataReceived(const QByteArray &data) m_initialSetupRequired = dataMap.value("initialSetupRequired").toBool(); m_authenticationRequired = dataMap.value("authenticationRequired").toBool(); m_pushButtonAuthAvailable = dataMap.value("pushButtonAuthAvailable").toBool(); + emit pushButtonAuthAvailableChanged(); + qDebug() << "Handshake received" << "initRequired:" << m_initialSetupRequired << "authRequired:" << m_authenticationRequired << "pushButtonAvailable:" << m_pushButtonAuthAvailable;; m_serverUuid = dataMap.value("uuid").toString(); - emit pushButtonAuthAvailableChanged(); + m_serverVersion = dataMap.value("version").toString(); QString protoVersionString = dataMap.value("protocol version").toString(); if (!protoVersionString.contains('.')) { protoVersionString.prepend("0."); } + m_jsonRpcVersion = QVersionNumber::fromString(protoVersionString); QVersionNumber minimumRequiredVersion = QVersionNumber(1, 0); - QVersionNumber protocolVersion = QVersionNumber::fromString(protoVersionString); - if (protocolVersion < minimumRequiredVersion) { - qWarning() << "Nymea box doesn't support minimum required version. Required:" << minimumRequiredVersion << "Found:" << protocolVersion; + if (m_jsonRpcVersion < minimumRequiredVersion) { + qWarning() << "Nymea box doesn't support minimum required version. Required:" << minimumRequiredVersion << "Found:" << m_jsonRpcVersion; m_connection->disconnect(); - emit invalidProtocolVersion(protocolVersion.toString(), minimumRequiredVersion.toString()); + emit invalidProtocolVersion(m_jsonRpcVersion.toString(), minimumRequiredVersion.toString()); return; } + emit handshakeReceived(); + if (m_initialSetupRequired) { emit initialSetupRequiredChanged(); return; diff --git a/libmea-core/jsonrpc/jsonrpcclient.h b/libmea-core/jsonrpc/jsonrpcclient.h index f8ced86b..8256dd19 100644 --- a/libmea-core/jsonrpc/jsonrpcclient.h +++ b/libmea-core/jsonrpc/jsonrpcclient.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "nymeaconnection.h" #include "jsonhandler.h" @@ -39,6 +40,9 @@ class JsonRpcClient : public JsonHandler Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged) Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged) Q_PROPERTY(bool pushButtonAuthAvailable READ pushButtonAuthAvailable NOTIFY pushButtonAuthAvailableChanged) + Q_PROPERTY(QString serverVersion READ serverVersion NOTIFY handshakeReceived) + Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived) + Q_PROPERTY(QString serverUuid READ serverUuid NOTIFY handshakeReceived) public: explicit JsonRpcClient(NymeaConnection *connection, QObject *parent = 0); @@ -56,13 +60,19 @@ public: bool authenticationRequired() const; bool pushButtonAuthAvailable() const; + QString serverVersion() const; + QString jsonRpcVersion() const; + QString serverUuid() const; + // ui methods Q_INVOKABLE int createUser(const QString &username, const QString &password); Q_INVOKABLE int authenticate(const QString &username, const QString &password, const QString &deviceName); Q_INVOKABLE int requestPushButtonAuth(const QString &deviceName); + Q_INVOKABLE bool ensureServerVersion(const QString &jsonRpcVersion); signals: + void handshakeReceived(); void initialSetupRequiredChanged(); void authenticationRequiredChanged(); void pushButtonAuthAvailableChanged(); @@ -94,6 +104,8 @@ private: bool m_pushButtonAuthAvailable = false; int m_pendingPushButtonTransaction = -1; QString m_serverUuid; + QVersionNumber m_jsonRpcVersion; + QString m_serverVersion; QByteArray m_token; QByteArray m_receiveBuffer; diff --git a/libmea-core/jsonrpc/jsontypes.cpp b/libmea-core/jsonrpc/jsontypes.cpp index e83f446e..1b1ecd92 100644 --- a/libmea-core/jsonrpc/jsontypes.cpp +++ b/libmea-core/jsonrpc/jsontypes.cpp @@ -152,7 +152,7 @@ ParamType *JsonTypes::unpackParamType(const QVariantMap ¶mTypeMap, QObject * StateType *JsonTypes::unpackStateType(const QVariantMap &stateTypeMap, QObject *parent) { StateType *stateType = new StateType(parent); - stateType->setId(stateTypeMap.value("id").toUuid()); + stateType->setId(stateTypeMap.value("id").toString()); stateType->setName(stateTypeMap.value("name").toString()); stateType->setDisplayName(stateTypeMap.value("displayName").toString()); stateType->setIndex(stateTypeMap.value("index").toInt()); @@ -267,18 +267,29 @@ QVariantList JsonTypes::packRuleActions(RuleActions *ruleActions) QVariantList ret; for (int i = 0; i < ruleActions->rowCount(); i++) { QVariantMap ruleAction; - ruleAction.insert("actionTypeId", ruleActions->get(i)->actionTypeId()); - ruleAction.insert("deviceId", ruleActions->get(i)->deviceId()); - if (ruleActions->get(i)->ruleActionParams()->rowCount() > 0) { + RuleAction *ra = ruleActions->get(i); + if (!ra->actionTypeId().isNull() && !ra->deviceId().isNull()) { + ruleAction.insert("deviceId", ra->deviceId()); + ruleAction.insert("actionTypeId", ra->actionTypeId()); + } else { + ruleAction.insert("interface", ra->interfaceName()); + ruleAction.insert("interfaceAction", ra->interfaceAction()); + } + if (ra->ruleActionParams()->rowCount() > 0) { QVariantList ruleActionParams; - for (int j = 0; j < ruleActions->get(i)->ruleActionParams()->rowCount(); j++) { + for (int j = 0; j < ra->ruleActionParams()->rowCount(); j++) { QVariantMap ruleActionParam; - ruleActionParam.insert("paramTypeId", ruleActions->get(i)->ruleActionParams()->get(j)->paramTypeId()); - if (!ruleActions->get(i)->ruleActionParams()->get(j)->eventTypeId().isEmpty() && !ruleActions->get(i)->ruleActionParams()->get(j)->eventParamTypeId().isEmpty()) { - ruleActionParam.insert("eventTypeId", ruleActions->get(i)->ruleActionParams()->get(j)->eventTypeId()); - ruleActionParam.insert("eventParamTypeId", ruleActions->get(i)->ruleActionParams()->get(j)->eventParamTypeId()); + RuleActionParam *rap = ruleActions->get(i)->ruleActionParams()->get(j); + if (!rap->paramTypeId().isNull()) { + ruleActionParam.insert("paramTypeId", rap->paramTypeId()); } else { - ruleActionParam.insert("value", ruleActions->get(i)->ruleActionParams()->get(j)->value()); + ruleActionParam.insert("paramName", rap->paramName()); + } + if (!rap->eventTypeId().isEmpty() && !rap->eventParamTypeId().isEmpty()) { + ruleActionParam.insert("eventTypeId", rap->eventTypeId()); + ruleActionParam.insert("eventParamTypeId", rap->eventParamTypeId()); + } else { + ruleActionParam.insert("value", rap->value()); } ruleActionParams.append(ruleActionParam); } @@ -307,7 +318,11 @@ QVariantList JsonTypes::packEventDescriptors(EventDescriptors *eventDescriptors) QVariantList paramDescriptors; for (int j = 0; j < eventDescriptor->paramDescriptors()->rowCount(); j++) { QVariantMap paramDescriptor; - paramDescriptor.insert("paramTypeId", eventDescriptor->paramDescriptors()->get(j)->paramTypeId()); + if (!eventDescriptor->paramDescriptors()->get(j)->paramTypeId().isEmpty()) { + paramDescriptor.insert("paramTypeId", eventDescriptor->paramDescriptors()->get(j)->paramTypeId()); + } else { + paramDescriptor.insert("paramName", eventDescriptor->paramDescriptors()->get(j)->paramName()); + } paramDescriptor.insert("value", eventDescriptor->paramDescriptors()->get(j)->value()); QMetaEnum operatorEnum = QMetaEnum::fromType(); paramDescriptor.insert("operator", operatorEnum.valueToKey(eventDescriptor->paramDescriptors()->get(j)->operatorType())); @@ -334,10 +349,15 @@ QVariantMap JsonTypes::packStateEvaluator(StateEvaluator *stateEvaluator) QMetaEnum stateOperatorEnum = QMetaEnum::fromType(); ret.insert("operator", stateOperatorEnum.valueToKey(stateEvaluator->stateOperator())); QVariantMap stateDescriptor; - stateDescriptor.insert("deviceId", stateEvaluator->stateDescriptor()->deviceId()); + if (!stateEvaluator->stateDescriptor()->deviceId().isNull() && !stateEvaluator->stateDescriptor()->stateTypeId().isNull()) { + stateDescriptor.insert("deviceId", stateEvaluator->stateDescriptor()->deviceId()); + stateDescriptor.insert("stateTypeId", stateEvaluator->stateDescriptor()->stateTypeId()); + } else { + stateDescriptor.insert("interface", stateEvaluator->stateDescriptor()->interfaceName()); + stateDescriptor.insert("interfaceState", stateEvaluator->stateDescriptor()->interfaceState()); + } QMetaEnum valueOperatorEnum = QMetaEnum::fromType(); stateDescriptor.insert("operator", valueOperatorEnum.valueToKeys(stateEvaluator->stateDescriptor()->valueOperator())); - stateDescriptor.insert("stateTypeId", stateEvaluator->stateDescriptor()->stateTypeId()); stateDescriptor.insert("value", stateEvaluator->stateDescriptor()->value()); ret.insert("stateDescriptor", stateDescriptor); QVariantList childEvaluators; diff --git a/libmea-core/libmea-core.h b/libmea-core/libmea-core.h index cf834c60..1aa82ac6 100644 --- a/libmea-core/libmea-core.h +++ b/libmea-core/libmea-core.h @@ -34,6 +34,7 @@ #include "models/logsmodelng.h" #include "models/valuelogsproxymodel.h" #include "models/eventdescriptorparamsfiltermodel.h" +#include "models/interfacesproxy.h" #include "basicconfiguration.h" #include "wifisetup/networkmanagercontroler.h" @@ -119,6 +120,7 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "Interface", "Uncreatable"); qmlRegisterSingletonType(uri, 1, 0, "Interfaces", interfacesModel_provider); + qmlRegisterType(uri, 1, 0, "InterfacesProxy"); qmlRegisterUncreatableType(uri, 1, 0, "Plugin", "Can't create this in QML. Get it from the Plugins."); qmlRegisterUncreatableType(uri, 1, 0, "Plugins", "Can't create this in QML. Get it from the DeviceManager."); diff --git a/libmea-core/libmea-core.pro b/libmea-core/libmea-core.pro index 9677b28b..5c60c40c 100644 --- a/libmea-core/libmea-core.pro +++ b/libmea-core/libmea-core.pro @@ -56,7 +56,8 @@ SOURCES += \ wifisetup/wirelessaccesspoints.cpp \ wifisetup/wirelesssetupmanager.cpp \ wifisetup/networkmanagercontroler.cpp \ - models/logsmodelng.cpp + models/logsmodelng.cpp \ + models/interfacesproxy.cpp HEADERS += \ engine.h \ @@ -99,7 +100,8 @@ HEADERS += \ wifisetup/wirelesssetupmanager.h \ wifisetup/networkmanagercontroler.h \ libmea-core.h \ - models/logsmodelng.h + models/logsmodelng.h \ + models/interfacesproxy.h unix { target.path = /usr/lib diff --git a/libmea-core/models/interfacesproxy.cpp b/libmea-core/models/interfacesproxy.cpp new file mode 100644 index 00000000..972bfded --- /dev/null +++ b/libmea-core/models/interfacesproxy.cpp @@ -0,0 +1,113 @@ +#include "interfacesproxy.h" + +#include "types/interface.h" +#include "types/interfaces.h" +#include "types/device.h" + +#include "devices.h" +#include "engine.h" + +InterfacesProxy::InterfacesProxy(QObject *parent): QSortFilterProxyModel(parent) +{ + m_interfaces = new Interfaces(this); + setSourceModel(m_interfaces); +} + +bool InterfacesProxy::showEvents() const +{ + return m_showEvents; +} + +void InterfacesProxy::setShowEvents(bool showEvents) +{ + if (m_showEvents != showEvents) { + m_showEvents = showEvents; + emit showEventsChanged(); + invalidateFilter(); + } +} + +bool InterfacesProxy::showActions() const +{ + return m_showActions; +} + +void InterfacesProxy::setShowActions(bool showActions) +{ + if (m_showActions != showActions) { + m_showActions = showActions; + emit showActionsChanged(); + invalidateFilter(); + } +} + +bool InterfacesProxy::showStates() const +{ + return m_showStates; +} + +void InterfacesProxy::setShowStates(bool showStates) +{ + if (m_showStates != showStates) { + m_showStates = showStates; + emit showStatesChanged(); + invalidateFilter(); + } +} + +bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + qDebug() << "filterAcceptsRow"; + QString interfaceName = m_interfaces->get(source_row)->name(); + if (!m_shownInterfaces.isEmpty()) { + if (!m_shownInterfaces.contains(interfaceName)) { + return false; + } + } + + if (m_devicesFilter != nullptr) { + // TODO: This could be improved *a lot* by caching interfaces in the devices model... + bool found = false; + for (int i = 0; i < m_devicesFilter->rowCount(); i++) { + Device *d = m_devicesFilter->get(i); + DeviceClass *dc = Engine::instance()->deviceManager()->deviceClasses()->getDeviceClass(d->deviceClassId()); + if (!dc) { + qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name(); + return false; + } + if (dc->interfaces().contains(interfaceName)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + + Interface* iface = m_interfaces->get(source_row); + if (m_showEvents) { + if (iface->eventTypes()->rowCount() > 0) { + return true; + } + } + + if (m_showActions) { + if (iface->actionTypes()->rowCount() > 0) { + return true; + } + } + if (m_showStates) { + if (iface->stateTypes()->rowCount() > 0) { + return true; + } + } + + return false; +} + +Interface *InterfacesProxy::get(int index) const +{ + return m_interfaces->get(mapToSource(this->index(index, 0)).row()); +} diff --git a/libmea-core/models/interfacesproxy.h b/libmea-core/models/interfacesproxy.h new file mode 100644 index 00000000..2ae59058 --- /dev/null +++ b/libmea-core/models/interfacesproxy.h @@ -0,0 +1,58 @@ +#ifndef INTERFACESPROXY_H +#define INTERFACESPROXY_H + +#include + +class Devices; +class Interface; +class Interfaces; + +class InterfacesProxy: public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged) + Q_PROPERTY(Devices* devicesFilter READ devicesFilter WRITE setDevicesFilter NOTIFY devicesFilterChanged) + Q_PROPERTY(bool showEvents READ showEvents WRITE setShowEvents NOTIFY showEventsChanged) + Q_PROPERTY(bool showActions READ showActions WRITE setShowActions NOTIFY showActionsChanged) + Q_PROPERTY(bool showStates READ showStates WRITE setShowStates NOTIFY showStatesChanged) + +public: + InterfacesProxy(QObject *parent = nullptr); + + QStringList shownInterfaces() const { return m_shownInterfaces; } + void setShownInterfaces(const QStringList &shownInterfaces) { m_shownInterfaces = shownInterfaces; emit shownInterfacesChanged(); invalidateFilter(); } + + Devices* devicesFilter() const { return m_devicesFilter; } + void setDevicesFilter(Devices *devices) { m_devicesFilter = devices; emit devicesFilterChanged(); invalidateFilter(); } + + bool showEvents() const; + void setShowEvents(bool showEvents); + + bool showActions() const; + void setShowActions(bool showActions); + + bool showStates() const; + void setShowStates(bool showStates); + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + + Q_INVOKABLE Interface* get(int index) const; + +signals: + void shownInterfacesChanged(); + void devicesFilterChanged(); + void showEventsChanged(); + void showActionsChanged(); + void showStatesChanged(); + +private: + Interfaces *m_interfaces = nullptr; + QStringList m_shownInterfaces; + Devices* m_devicesFilter = nullptr; + bool m_showEvents = false; + bool m_showActions = false; + bool m_showStates = false; +}; + +#endif // INTERFACESPROXY_H diff --git a/libmea-core/rulemanager.cpp b/libmea-core/rulemanager.cpp index 78f23c96..bbad1c58 100644 --- a/libmea-core/rulemanager.cpp +++ b/libmea-core/rulemanager.cpp @@ -198,7 +198,10 @@ void RuleManager::parseEventDescriptors(const QVariantList &eventDescriptorList, eventDescriptor->setInterfaceName(eventDescriptorVariant.toMap().value("interface").toString()); eventDescriptor->setInterfaceEvent(eventDescriptorVariant.toMap().value("interfaceEvent").toString()); foreach (const QVariant ¶mDescriptorVariant, eventDescriptorVariant.toMap().value("paramDescriptors").toList()) { - ParamDescriptor *paramDescriptor = new ParamDescriptor(paramDescriptorVariant.toMap().value("paramTypeId").toString(), paramDescriptorVariant.toMap().value("value")); + ParamDescriptor *paramDescriptor = new ParamDescriptor(); + paramDescriptor->setParamTypeId(paramDescriptorVariant.toMap().value("paramTypeId").toString()); + paramDescriptor->setParamName(paramDescriptorVariant.toMap().value("paramName").toString()); + paramDescriptor->setValue(paramDescriptorVariant.toMap().value("value")); QMetaEnum operatorEnum = QMetaEnum::fromType(); paramDescriptor->setOperatorType((ParamDescriptor::ValueOperator)operatorEnum.keyToValue(paramDescriptorVariant.toMap().value("operator").toString().toLocal8Bit())); eventDescriptor->paramDescriptors()->addParamDescriptor(paramDescriptor); @@ -216,7 +219,14 @@ StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluat QVariantMap sdMap = stateEvaluatorMap.value("stateDescriptor").toMap(); QMetaEnum operatorEnum = QMetaEnum::fromType(); StateDescriptor::ValueOperator op = (StateDescriptor::ValueOperator)operatorEnum.keyToValue(sdMap.value("operator").toByteArray()); - StateDescriptor *sd = new StateDescriptor(sdMap.value("deviceId").toUuid(), op, sdMap.value("stateTypeId").toUuid(), sdMap.value("value"), stateEvaluator); + + StateDescriptor *sd = nullptr; + if (sdMap.contains("deviceId") && sdMap.contains("stateTypeId")) { + sd = new StateDescriptor(sdMap.value("deviceId").toUuid(), sdMap.value("stateTypeId").toUuid(), op, sdMap.value("value"), stateEvaluator); + } 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()) { @@ -230,39 +240,39 @@ StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluat void RuleManager::parseRuleActions(const QVariantList &ruleActions, Rule *rule) { foreach (const QVariant &ruleActionVariant, ruleActions) { - RuleAction *ruleAction = new RuleAction(); - ruleAction->setDeviceId(ruleActionVariant.toMap().value("deviceId").toUuid()); - ruleAction->setActionTypeId(ruleActionVariant.toMap().value("actionTypeId").toUuid()); - foreach (const QVariant &ruleActionParamVariant, ruleActionVariant.toMap().value("ruleActionParams").toList()) { - RuleActionParam *param = new RuleActionParam(); - param->setParamTypeId(ruleActionParamVariant.toMap().value("paramTypeId").toUuid()); - param->setValue(ruleActionParamVariant.toMap().value("value")); - param->setEventTypeId(ruleActionParamVariant.toMap().value("eventTypeId").toString()); - param->setEventParamTypeId(ruleActionParamVariant.toMap().value("eventParamTypeId").toString()); - ruleAction->ruleActionParams()->addRuleActionParam(param); - } - rule->actions()->addRuleAction(ruleAction); + rule->actions()->addRuleAction(parseRuleAction(ruleActionVariant.toMap())); } } void RuleManager::parseRuleExitActions(const QVariantList &ruleActions, Rule *rule) { foreach (const QVariant &ruleActionVariant, ruleActions) { - RuleAction *ruleAction = new RuleAction(); - ruleAction->setDeviceId(ruleActionVariant.toMap().value("deviceId").toUuid()); - ruleAction->setActionTypeId(ruleActionVariant.toMap().value("actionTypeId").toUuid()); - foreach (const QVariant &ruleActionParamVariant, ruleActionVariant.toMap().value("ruleActionParams").toList()) { - RuleActionParam *param = new RuleActionParam(); - param->setParamTypeId(ruleActionParamVariant.toMap().value("paramTypeId").toUuid()); - param->setValue(ruleActionParamVariant.toMap().value("value")); - param->setEventTypeId(ruleActionParamVariant.toMap().value("eventTypeId").toString()); - param->setEventParamTypeId(ruleActionParamVariant.toMap().value("eventParamTypeId").toString()); - ruleAction->ruleActionParams()->addRuleActionParam(param); - } - rule->exitActions()->addRuleAction(ruleAction); + rule->exitActions()->addRuleAction(parseRuleAction(ruleActionVariant.toMap())); } } +RuleAction *RuleManager::parseRuleAction(const QVariantMap &ruleAction) +{ + RuleAction *ret = new RuleAction(); + if (ruleAction.contains("deviceId") && ruleAction.contains("actionTypeId")) { + ret->setDeviceId(ruleAction.value("deviceId").toUuid()); + ret->setActionTypeId(ruleAction.value("actionTypeId").toUuid()); + } else { + ret->setInterfaceName(ruleAction.value("interface").toString()); + ret->setInterfaceAction(ruleAction.value("interfaceAction").toString()); + } + foreach (const QVariant &ruleActionParamVariant, ruleAction.value("ruleActionParams").toList()) { + RuleActionParam *param = new RuleActionParam(); + param->setParamTypeId(ruleActionParamVariant.toMap().value("paramTypeId").toString()); + param->setParamName(ruleActionParamVariant.toMap().value("paramName").toString()); + param->setValue(ruleActionParamVariant.toMap().value("value")); + param->setEventTypeId(ruleActionParamVariant.toMap().value("eventTypeId").toString()); + param->setEventParamTypeId(ruleActionParamVariant.toMap().value("eventParamTypeId").toString()); + ret->ruleActionParams()->addRuleActionParam(param); + } + return ret; +} + void RuleManager::parseTimeDescriptor(const QVariantMap &timeDescriptor, Rule *rule) { foreach (const QVariant &timeEventItemVariant, timeDescriptor.value("timeEventItems").toList()) { diff --git a/libmea-core/rulemanager.h b/libmea-core/rulemanager.h index 297f7e72..fad4a8ff 100644 --- a/libmea-core/rulemanager.h +++ b/libmea-core/rulemanager.h @@ -8,6 +8,7 @@ class JsonRpcClient; class StateEvaluator; +class RuleAction; class RuleManager : public JsonHandler { @@ -45,6 +46,7 @@ private: StateEvaluator* parseStateEvaluator(const QVariantMap &stateEvaluatorMap); void parseRuleActions(const QVariantList &ruleActions, Rule *rule); void parseRuleExitActions(const QVariantList &ruleActions, Rule *rule); + RuleAction* parseRuleAction(const QVariantMap &ruleAction); void parseTimeDescriptor(const QVariantMap &timeDescriptor, Rule *rule); signals: diff --git a/libnymea-common/types/actiontypes.cpp b/libnymea-common/types/actiontypes.cpp index afd01f87..6ce63844 100644 --- a/libnymea-common/types/actiontypes.cpp +++ b/libnymea-common/types/actiontypes.cpp @@ -56,9 +56,6 @@ int ActionTypes::rowCount(const QModelIndex &parent) const QVariant ActionTypes::data(const QModelIndex &index, int role) const { - if (index.row() < 0 || index.row() >= m_actionTypes.count()) - return QVariant(); - ActionType *actionType = m_actionTypes.at(index.row()); switch (role) { case RoleId: diff --git a/libnymea-common/types/interfaces.cpp b/libnymea-common/types/interfaces.cpp index aef768a3..e49ddc98 100644 --- a/libnymea-common/types/interfaces.cpp +++ b/libnymea-common/types/interfaces.cpp @@ -5,61 +5,39 @@ #include "eventtype.h" #include "actiontypes.h" #include "actiontype.h" +#include "statetype.h" +#include "statetypes.h" + +#include "device.h" + +#include "paramtypes.h" Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent) { - - Interface* iface = nullptr; - EventType* et = nullptr; - ActionType* at = nullptr; - ParamType* pt = nullptr; ParamTypes *pts = nullptr; - iface = new Interface("battery", "Battery powered devices", this); - et = new EventType(); - pts = new ParamTypes(et); - et->setParamTypes(pts); - - et->setName("batteryLevel"); - et->setDisplayName("Battery level changed"); - pt = new ParamType("batteryLevel", QVariant::Int, 50); - pt->setDisplayName("Battery Level"); - qDebug() << "added param" << pt->type(); - pt->setMinValue(0); - pt->setMaxValue(100); - et->paramTypes()->addParamType(pt); - iface->eventTypes()->addEventType(et); - - et = new EventType(); - pts = new ParamTypes(et); - et->setParamTypes(pts); - et->setName("batteryCritical"); - et->setDisplayName("Battery level critical"); - pt = new ParamType("batteryCritical", QVariant::Bool, true); - pt->setDisplayName("Battery critical"); - et->paramTypes()->addParamType(pt); - iface->eventTypes()->addEventType(et); - - m_list.append(iface); + addInterface("battery", tr("Battery powered devices")); + addStateType("battery", "batteryCritical", QVariant::Bool, false, + tr("Battery level is critical"), + tr("Battery level entered critical state")); - iface = new Interface("notification", "Notification services", this); - at = new ActionType(); - pts = new ParamTypes(at); - at->setParamTypes(pts); + addInterface("notifications", tr("Notification services")); + pts = createParamTypes("title", tr("Title"), QVariant::String); + addParamType(pts, "body", tr("Message body"), QVariant::String); + addActionType("notifications", "notify", tr("Send notification"), pts); - at->setName("notify"); - at->setDisplayName("Send notification"); - pt = new ParamType("title", QVariant::String); - pt->setDisplayName("Title"); - at->paramTypes()->addParamType(pt); - pt = new ParamType("body", QVariant::String); - pt->setDisplayName("Message body"); - at->paramTypes()->addParamType(pt); - iface->actionTypes()->addActionType(at); - m_list.append(iface); + addInterface("light", tr("Lights")); + addStateType("light", "power", QVariant::Bool, true, + tr("Light is turned on"), + tr("A light is turned on or off"), + tr("Turn lights on or off")); + addInterface("temperaturesensor", tr("Temperature sensors")); + addStateType("temperaturesensor", "temperature", QVariant::Double, false, + tr("Temperature"), + tr("Temperature has changed")); } int Interfaces::rowCount(const QModelIndex &parent) const @@ -101,3 +79,85 @@ Interface *Interfaces::findByName(const QString &name) const } return nullptr; } + +void Interfaces::addInterface(const QString &name, const QString &displayName) +{ + Interface *iface = new Interface(name, displayName, this); + m_list.append(iface); +} + +void Interfaces::addEventType(const QString &interfaceName, const QString &name, const QString &displayName, ParamTypes *paramTypes) +{ + Interface *iface = nullptr; + foreach (Interface* i, m_list) { + if (i->name() == interfaceName) { + iface = i; + break; + } + } + Q_ASSERT_X(iface != nullptr, "Interfaces", "Interface not found"); + EventType *et = new EventType(); + et->setName(name); + et->setDisplayName(displayName); + et->setParamTypes(paramTypes); + iface->eventTypes()->addEventType(et); +} + +void Interfaces::addActionType(const QString &interfaceName, const QString &name, const QString &displayName, ParamTypes *paramTypes) +{ + Interface *iface = nullptr; + foreach (Interface* i, m_list) { + if (i->name() == interfaceName) { + iface = i; + break; + } + } + Q_ASSERT_X(iface != nullptr, "Interfaces", "Interface not found"); + ActionType *at = new ActionType(); + at->setName(name); + at->setDisplayName(displayName); + at->setParamTypes(paramTypes); + iface->actionTypes()->addActionType(at); +} + +void Interfaces::addStateType(const QString &interfaceName, const QString &name, QVariant::Type type, bool writable, const QString &displayName, const QString &displayNameEvent, const QString &displayNameAction) +{ + Interface *iface = nullptr; + foreach (Interface* i, m_list) { + if (i->name() == interfaceName) { + iface = i; + break; + } + } + Q_ASSERT_X(iface != nullptr, "Interfaces", "Interface not found"); + StateType *st = new StateType(); + st->setName(name); + st->setDisplayName(displayName); + st->setType(type); + iface->stateTypes()->addStateType(st); + ParamTypes *pts = createParamTypes(name, displayName, type); + addEventType(interfaceName, name, displayNameEvent, pts); + if (writable) { + addActionType(interfaceName, name, displayNameAction, pts); + } +} + +ParamTypes *Interfaces::createParamTypes(const QString &name, const QString &displayName, QVariant::Type type, const QVariant &defaultValue, const QVariant &minValue, const QVariant &maxValue) +{ + ParamTypes *pts = new ParamTypes(); + ParamType *pt = new ParamType(name, type, defaultValue); + pt->setDisplayName(displayName); + pt->setMinValue(minValue); + pt->setMaxValue(maxValue); + pts->addParamType(pt); + return pts; +} + +void Interfaces::addParamType(ParamTypes *paramTypes, const QString &name, const QString &displayName, QVariant::Type type, const QVariant &defaultValue, const QVariant &minValue, const QVariant &maxValue) +{ + ParamType *pt = new ParamType(name, type, defaultValue); + pt->setDisplayName(displayName); + pt->setMinValue(minValue); + pt->setMaxValue(maxValue); + paramTypes->addParamType(pt); +} diff --git a/libnymea-common/types/interfaces.h b/libnymea-common/types/interfaces.h index 328043e8..3a6b35ef 100644 --- a/libnymea-common/types/interfaces.h +++ b/libnymea-common/types/interfaces.h @@ -2,8 +2,13 @@ #define INTERFACES_H #include +#include +#include class Interface; +class ParamType; +class ParamTypes; +class Devices; class Interfaces : public QAbstractListModel { @@ -26,6 +31,16 @@ public: private: QList m_list; + + // helpers to populate the model + void addInterface(const QString &name, const QString &displayName); + void addEventType(const QString &interfaceName, const QString &name, const QString &displayName, ParamTypes *paramTypes); + void addActionType(const QString &interfaceName, const QString &name, const QString &displayName, ParamTypes *paramTypes); + void addStateType(const QString &interfaceName, const QString &name, QVariant::Type type, bool writable, const QString &displayName, const QString &displayNameEvent, const QString &displayNameAction = QString()); + + ParamTypes* createParamTypes(const QString &name, const QString &displayName, QVariant::Type type, const QVariant &defaultValue = QVariant(), const QVariant &minValue = QVariant(), const QVariant &maxValue = QVariant()); + void addParamType(ParamTypes* paramTypes, const QString &name, const QString &displayName, QVariant::Type type, const QVariant &defaultValue = QVariant(), const QVariant &minValue = QVariant(), const QVariant &maxValue = QVariant()); }; + #endif // INTERFACES_H diff --git a/libnymea-common/types/param.cpp b/libnymea-common/types/param.cpp index e69316f8..e93b85b6 100644 --- a/libnymea-common/types/param.cpp +++ b/libnymea-common/types/param.cpp @@ -29,6 +29,12 @@ Param::Param(const QString ¶mTypeId, const QVariant &value, QObject *parent) { } +Param::Param(QObject *parent): + QObject(parent) +{ + +} + QString Param::paramTypeId() const { return m_paramTypeId; diff --git a/libnymea-common/types/param.h b/libnymea-common/types/param.h index 31521cc0..6b38edd0 100644 --- a/libnymea-common/types/param.h +++ b/libnymea-common/types/param.h @@ -35,6 +35,7 @@ class Param : public QObject public: Param(const QString ¶mTypeId = QString(), const QVariant &value = QVariant(), QObject *parent = 0); + Param(QObject *parent); QString paramTypeId() const; void setParamTypeId(const QString ¶mTypeId); @@ -42,14 +43,13 @@ public: QVariant value() const; void setValue(const QVariant &value); -private: - QString m_paramTypeId; - QVariant m_value; - signals: void paramTypeIdChanged(); void valueChanged(); +private: + QString m_paramTypeId; + QVariant m_value; }; #endif // PARAM_H diff --git a/libnymea-common/types/paramdescriptor.cpp b/libnymea-common/types/paramdescriptor.cpp index 32f925cf..6eb0f1f7 100644 --- a/libnymea-common/types/paramdescriptor.cpp +++ b/libnymea-common/types/paramdescriptor.cpp @@ -1,10 +1,23 @@ #include "paramdescriptor.h" -ParamDescriptor::ParamDescriptor(const QString &id, const QVariant &value, QObject *parent) : Param(id, value, parent) +ParamDescriptor::ParamDescriptor(QObject *parent) : Param(parent) { } +QString ParamDescriptor::paramName() const +{ + return m_paramName; +} + +void ParamDescriptor::setParamName(const QString ¶mName) +{ + if (m_paramName != paramName) { + m_paramName = paramName; + emit paramNameChanged(); + } +} + ParamDescriptor::ValueOperator ParamDescriptor::operatorType() const { return m_operator; @@ -20,7 +33,10 @@ void ParamDescriptor::setOperatorType(ParamDescriptor::ValueOperator operatorTyp ParamDescriptor *ParamDescriptor::clone() const { - ParamDescriptor *ret = new ParamDescriptor(this->paramTypeId(), this->value()); + ParamDescriptor *ret = new ParamDescriptor(); + ret->setParamTypeId(this->paramTypeId()); + ret->setParamName(this->paramName()); + ret->setValue(this->value()); ret->setOperatorType(this->operatorType()); return ret; } diff --git a/libnymea-common/types/paramdescriptor.h b/libnymea-common/types/paramdescriptor.h index 1521f227..33ae0d4f 100644 --- a/libnymea-common/types/paramdescriptor.h +++ b/libnymea-common/types/paramdescriptor.h @@ -6,6 +6,7 @@ class ParamDescriptor : public Param { Q_OBJECT + Q_PROPERTY(QString paramName READ paramName WRITE setParamName NOTIFY paramNameChanged) Q_PROPERTY(ValueOperator operatorType READ operatorType WRITE setOperatorType NOTIFY operatorTypeChanged) public: enum ValueOperator { @@ -18,16 +19,22 @@ public: }; Q_ENUM(ValueOperator) - explicit ParamDescriptor(const QString &id = QString(), const QVariant &value = QVariant(), QObject *parent = nullptr); + explicit ParamDescriptor(QObject *parent = nullptr); + + QString paramName() const; + void setParamName(const QString ¶mName); ValueOperator operatorType() const; void setOperatorType(ValueOperator operatorType); ParamDescriptor* clone() const; + signals: + void paramNameChanged(); void operatorTypeChanged(); private: + QString m_paramName; ValueOperator m_operator; }; diff --git a/libnymea-common/types/paramdescriptors.cpp b/libnymea-common/types/paramdescriptors.cpp index fcb47fe5..ee4c0d72 100644 --- a/libnymea-common/types/paramdescriptors.cpp +++ b/libnymea-common/types/paramdescriptors.cpp @@ -72,3 +72,29 @@ void ParamDescriptors::setParamDescriptor(const QString ¶mTypeId, const QVar paramDescriptor->setOperatorType((ParamDescriptor::ValueOperator)operatorType); addParamDescriptor(paramDescriptor); } + +void ParamDescriptors::setParamDescriptorByName(const QString ¶mName, const QVariant &value, ParamDescriptors::ValueOperator operatorType) +{ + foreach (ParamDescriptor* paramDescriptor, m_list) { + if (paramDescriptor->paramName() == paramName) { + paramDescriptor->setValue(value); + paramDescriptor->setOperatorType((ParamDescriptor::ValueOperator)operatorType); + return; + } + } + // Still here? need to add a new one + ParamDescriptor* paramDescriptor = createNewParamDescriptor(); + paramDescriptor->setParamName(paramName); + paramDescriptor->setValue(value); + paramDescriptor->setOperatorType((ParamDescriptor::ValueOperator)operatorType); + addParamDescriptor(paramDescriptor); +} + +void ParamDescriptors::clear() +{ + beginResetModel(); + qDeleteAll(m_list); + m_list.clear(); + endResetModel(); + emit countChanged(); +} diff --git a/libnymea-common/types/paramdescriptors.h b/libnymea-common/types/paramdescriptors.h index b3d2d161..d3c62696 100644 --- a/libnymea-common/types/paramdescriptors.h +++ b/libnymea-common/types/paramdescriptors.h @@ -39,6 +39,8 @@ public: void addParamDescriptor(ParamDescriptor* paramDescriptor); Q_INVOKABLE void setParamDescriptor(const QString ¶mTypeId, const QVariant &value, ValueOperator operatorType); + Q_INVOKABLE void setParamDescriptorByName(const QString ¶mName, const QVariant &value, ValueOperator operatorType); + Q_INVOKABLE void clear(); signals: void countChanged(); diff --git a/libnymea-common/types/rule.cpp b/libnymea-common/types/rule.cpp index ae44e3c3..039847a7 100644 --- a/libnymea-common/types/rule.cpp +++ b/libnymea-common/types/rule.cpp @@ -113,9 +113,9 @@ void Rule::setStateEvaluator(StateEvaluator *stateEvaluator) emit stateEvaluatorChanged(); } -void Rule::createStateEvaluator() +StateEvaluator* Rule::createStateEvaluator() const { - setStateEvaluator(new StateEvaluator(this)); + return new StateEvaluator(); } Rule *Rule::clone() const diff --git a/libnymea-common/types/rule.h b/libnymea-common/types/rule.h index 4c1f4c27..a44472dd 100644 --- a/libnymea-common/types/rule.h +++ b/libnymea-common/types/rule.h @@ -17,7 +17,7 @@ class Rule : public QObject Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool active READ active NOTIFY activeChanged) Q_PROPERTY(EventDescriptors* eventDescriptors READ eventDescriptors CONSTANT) - Q_PROPERTY(StateEvaluator* stateEvaluator READ stateEvaluator NOTIFY stateEvaluatorChanged) + Q_PROPERTY(StateEvaluator* stateEvaluator READ stateEvaluator WRITE setStateEvaluator NOTIFY stateEvaluatorChanged) Q_PROPERTY(RuleActions* actions READ actions CONSTANT) Q_PROPERTY(RuleActions* exitActions READ exitActions CONSTANT) Q_PROPERTY(TimeDescriptor* timeDescriptor READ timeDescriptor CONSTANT) @@ -42,9 +42,9 @@ public: RuleActions* exitActions() const; TimeDescriptor* timeDescriptor() const; - void setStateEvaluator(StateEvaluator* stateEvaluator); + Q_INVOKABLE StateEvaluator* createStateEvaluator() const; - Q_INVOKABLE void createStateEvaluator(); + Q_INVOKABLE void setStateEvaluator(StateEvaluator* stateEvaluator); Q_INVOKABLE Rule *clone() const; diff --git a/libnymea-common/types/ruleaction.cpp b/libnymea-common/types/ruleaction.cpp index 97d9fbbe..4c78cbf1 100644 --- a/libnymea-common/types/ruleaction.cpp +++ b/libnymea-common/types/ruleaction.cpp @@ -34,6 +34,32 @@ void RuleAction::setActionTypeId(const QUuid &actionTypeId) } } +QString RuleAction::interfaceName() const +{ + return m_interfaceName; +} + +void RuleAction::setInterfaceName(const QString &interfaceName) +{ + if (m_interfaceName != interfaceName) { + m_interfaceName = interfaceName; + emit interfaceNameChanged(); + } +} + +QString RuleAction::interfaceAction() const +{ + return m_interfaceAction; +} + +void RuleAction::setInterfaceAction(const QString &interfaceAction) +{ + if (m_interfaceAction != interfaceAction) { + m_interfaceAction = interfaceAction; + emit interfaceActionChanged(); + } +} + RuleActionParams *RuleAction::ruleActionParams() const { return m_ruleActionParams; @@ -42,8 +68,10 @@ RuleActionParams *RuleAction::ruleActionParams() const RuleAction *RuleAction::clone() const { RuleAction *ret = new RuleAction(); - ret->setActionTypeId(actionTypeId()); ret->setDeviceId(deviceId()); + ret->setActionTypeId(actionTypeId()); + ret->setInterfaceName(interfaceName()); + ret->setInterfaceAction(interfaceAction()); for (int i = 0; i < ruleActionParams()->rowCount(); i++) { ret->ruleActionParams()->addRuleActionParam(ruleActionParams()->get(i)->clone()); } diff --git a/libnymea-common/types/ruleaction.h b/libnymea-common/types/ruleaction.h index d2717652..d9d85614 100644 --- a/libnymea-common/types/ruleaction.h +++ b/libnymea-common/types/ruleaction.h @@ -11,6 +11,8 @@ class RuleAction : public QObject Q_OBJECT Q_PROPERTY(QUuid deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) Q_PROPERTY(QUuid actionTypeId READ actionTypeId WRITE setActionTypeId NOTIFY actionTypeIdChanged) + Q_PROPERTY(QString interfaceName READ interfaceName WRITE setInterfaceName NOTIFY interfaceNameChanged) + Q_PROPERTY(QString interfaceAction READ interfaceAction WRITE setInterfaceAction NOTIFY interfaceActionChanged) Q_PROPERTY(RuleActionParams* ruleActionParams READ ruleActionParams CONSTANT) public: @@ -22,6 +24,12 @@ public: QUuid actionTypeId() const; void setActionTypeId(const QUuid &actionTypeId); + QString interfaceName() const; + void setInterfaceName(const QString &interfaceName); + + QString interfaceAction() const; + void setInterfaceAction(const QString &interfaceAction); + RuleActionParams* ruleActionParams() const; RuleAction *clone() const; @@ -29,10 +37,14 @@ public: signals: void deviceIdChanged(); void actionTypeIdChanged(); + void interfaceNameChanged(); + void interfaceActionChanged(); private: QUuid m_deviceId; QUuid m_actionTypeId; + QString m_interfaceName; + QString m_interfaceAction; RuleActionParams *m_ruleActionParams; }; diff --git a/libnymea-common/types/ruleactionparam.cpp b/libnymea-common/types/ruleactionparam.cpp index 7558c8f7..a5dd8aca 100644 --- a/libnymea-common/types/ruleactionparam.cpp +++ b/libnymea-common/types/ruleactionparam.cpp @@ -1,33 +1,20 @@ #include "ruleactionparam.h" -RuleActionParam::RuleActionParam(QObject *parent) : QObject(parent) +RuleActionParam::RuleActionParam(QObject *parent) : Param(parent) { } -QUuid RuleActionParam::paramTypeId() const +QString RuleActionParam::paramName() const { - return m_paramTypeId; + return m_paramName; } -void RuleActionParam::setParamTypeId(const QUuid ¶mTypeId) +void RuleActionParam::setParamName(const QString ¶mName) { - if (m_paramTypeId != paramTypeId) { - m_paramTypeId = paramTypeId; - emit paramTypeIdChanged(); - } -} - -QVariant RuleActionParam::value() const -{ - return m_value; -} - -void RuleActionParam::setValue(const QVariant &value) -{ - if (m_value != value) { - m_value = value; - emit valueChanged(); + if (m_paramName != paramName) { + m_paramName = paramName; + emit paramNameChanged(); } } @@ -61,6 +48,7 @@ RuleActionParam *RuleActionParam::clone() const { RuleActionParam *ret = new RuleActionParam(); ret->setParamTypeId(paramTypeId()); + ret->setParamName(paramName()); ret->setValue(value()); return ret; } diff --git a/libnymea-common/types/ruleactionparam.h b/libnymea-common/types/ruleactionparam.h index 57ef93c7..45bb3c98 100644 --- a/libnymea-common/types/ruleactionparam.h +++ b/libnymea-common/types/ruleactionparam.h @@ -5,21 +5,19 @@ #include #include -class RuleActionParam : public QObject +#include "param.h" + +class RuleActionParam : public Param { Q_OBJECT - Q_PROPERTY(QUuid paramTypeId READ paramTypeId NOTIFY paramTypeIdChanged) - Q_PROPERTY(QVariant value READ value NOTIFY valueChanged) + Q_PROPERTY(QString paramName READ paramName WRITE setParamName NOTIFY paramNameChanged) Q_PROPERTY(QString eventTypeId READ eventTypeId WRITE setEventTypeId NOTIFY eventTypeIdChanged) Q_PROPERTY(QString eventParamTypeId READ eventParamTypeId WRITE setEventParamTypeId NOTIFY eventParamTypeIdChanged) public: explicit RuleActionParam(QObject *parent = nullptr); - QUuid paramTypeId() const; - void setParamTypeId(const QUuid ¶mTypeId); - - QVariant value() const; - void setValue(const QVariant &value); + QString paramName() const; + void setParamName(const QString ¶mName); QString eventTypeId() const; void setEventTypeId(const QString &eventTypeId); @@ -29,15 +27,12 @@ public: RuleActionParam* clone() const; signals: - void paramTypeIdChanged(); - void valueChanged(); + void paramNameChanged(); void eventTypeIdChanged(); void eventParamTypeIdChanged(); - private: - QUuid m_paramTypeId; - QVariant m_value; + QString m_paramName; QString m_eventTypeId; QString m_eventParamTypeId; }; diff --git a/libnymea-common/types/ruleactionparams.cpp b/libnymea-common/types/ruleactionparams.cpp index 24b0b28b..cc18816f 100644 --- a/libnymea-common/types/ruleactionparams.cpp +++ b/libnymea-common/types/ruleactionparams.cpp @@ -62,6 +62,22 @@ void RuleActionParams::setRuleActionParam(const QString ¶mTypeId, const QVar addRuleActionParam(rap); } +void RuleActionParams::setRuleActionParamByName(const QString ¶mName, const QVariant &value) +{ + foreach (RuleActionParam *rap, m_list) { + if (rap->paramName() == paramName) { + rap->setValue(value); + return; + } + } + + // Still here? Need to add it + RuleActionParam *rap = new RuleActionParam(this); + rap->setParamName(paramName); + rap->setValue(value); + addRuleActionParam(rap); +} + void RuleActionParams::setRuleActionParamEvent(const QString ¶mTypeId, const QString &eventTypeId, const QString &eventParamTypeId) { foreach (RuleActionParam *rap, m_list) { diff --git a/libnymea-common/types/ruleactionparams.h b/libnymea-common/types/ruleactionparams.h index c3a0b6d0..03a18120 100644 --- a/libnymea-common/types/ruleactionparams.h +++ b/libnymea-common/types/ruleactionparams.h @@ -27,6 +27,7 @@ public: void addRuleActionParam(RuleActionParam* ruleActionParam); Q_INVOKABLE void setRuleActionParam(const QString ¶mTypeId, const QVariant &value); + Q_INVOKABLE void setRuleActionParamByName(const QString ¶mName, const QVariant &value); Q_INVOKABLE void setRuleActionParamEvent(const QString ¶mTypeId, const QString &eventTypeId, const QString &eventParamTypeId); Q_INVOKABLE RuleActionParam* get(int index) const; diff --git a/libnymea-common/types/statedescriptor.cpp b/libnymea-common/types/statedescriptor.cpp index 104e17c1..102c4a84 100644 --- a/libnymea-common/types/statedescriptor.cpp +++ b/libnymea-common/types/statedescriptor.cpp @@ -1,10 +1,20 @@ #include "statedescriptor.h" -StateDescriptor::StateDescriptor(const QUuid &deviceId, StateDescriptor::ValueOperator valueOperator, const QUuid &stateTypeId, const QVariant &value, QObject *parent): +StateDescriptor::StateDescriptor(const QUuid &deviceId, const QUuid &stateTypeId, StateDescriptor::ValueOperator valueOperator, const QVariant &value, QObject *parent): QObject(parent), m_deviceId(deviceId), - m_operator(valueOperator), m_stateTypeId(stateTypeId), + m_operator(valueOperator), + m_value(value) +{ + +} + +StateDescriptor::StateDescriptor(const QString &interfaceName, const QString &interfaceState, StateDescriptor::ValueOperator valueOperator, const QVariant &value, QObject *parent): + QObject(parent), + m_interfaceName(interfaceName), + m_interfaceState(interfaceState), + m_operator(valueOperator), m_value(value) { @@ -54,6 +64,32 @@ void StateDescriptor::setStateTypeId(const QUuid &stateTypeId) } } +QString StateDescriptor::interfaceName() const +{ + return m_interfaceName; +} + +void StateDescriptor::setInterfaceName(const QString &interfaceName) +{ + if (m_interfaceName != interfaceName) { + m_interfaceName = interfaceName; + emit interfaceNameChanged(); + } +} + +QString StateDescriptor::interfaceState() const +{ + return m_interfaceState; +} + +void StateDescriptor::setInterfaceState(const QString &interfaceState) +{ + if (m_interfaceState != interfaceState) { + m_interfaceState = interfaceState; + emit interfaceStateChanged(); + } +} + QVariant StateDescriptor::value() const { return m_value; @@ -69,6 +105,8 @@ void StateDescriptor::setValue(const QVariant &value) StateDescriptor *StateDescriptor::clone() const { - StateDescriptor *ret = new StateDescriptor(deviceId(), valueOperator(), stateTypeId(), value()); + StateDescriptor *ret = new StateDescriptor(deviceId(), stateTypeId(), valueOperator(), value()); + ret->setInterfaceName(interfaceName()); + ret->setInterfaceState(interfaceState()); return ret; } diff --git a/libnymea-common/types/statedescriptor.h b/libnymea-common/types/statedescriptor.h index 34e726bd..796e7a74 100644 --- a/libnymea-common/types/statedescriptor.h +++ b/libnymea-common/types/statedescriptor.h @@ -9,8 +9,10 @@ class StateDescriptor : public QObject { Q_OBJECT Q_PROPERTY(QUuid deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) - Q_PROPERTY(ValueOperator valueOperator READ valueOperator WRITE setValueOperator NOTIFY valueOperatorChanged) Q_PROPERTY(QUuid stateTypeId READ stateTypeId WRITE setStateTypeId NOTIFY stateTypeIdChanged) + Q_PROPERTY(QString interfaceName READ interfaceName WRITE setInterfaceName NOTIFY interfaceNameChanged) + Q_PROPERTY(QString interfaceState READ interfaceState WRITE setInterfaceState NOTIFY interfaceStateChanged) + Q_PROPERTY(ValueOperator valueOperator READ valueOperator WRITE setValueOperator NOTIFY valueOperatorChanged) Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) public: @@ -24,18 +26,25 @@ public: }; Q_ENUM(ValueOperator) - explicit StateDescriptor(const QUuid &deviceId, ValueOperator valueOperator, const QUuid &stateTypeId, const QVariant &value, QObject *parent = nullptr); + explicit StateDescriptor(const QUuid &deviceId, const QUuid &stateTypeId, ValueOperator valueOperator, const QVariant &value, QObject *parent = nullptr); + explicit StateDescriptor(const QString &interfaceName, const QString &interfaceState, ValueOperator valueOperator, const QVariant &value, QObject *parent = nullptr); StateDescriptor(QObject *parent = nullptr); QUuid deviceId() const; void setDeviceId(const QUuid &deviceId); - ValueOperator valueOperator() const; - void setValueOperator(ValueOperator valueOperator); - QUuid stateTypeId() const; void setStateTypeId(const QUuid &stateTypeId); + QString interfaceName() const; + void setInterfaceName(const QString &interfaceName); + + QString interfaceState() const; + void setInterfaceState(const QString &interfaceState); + + ValueOperator valueOperator() const; + void setValueOperator(ValueOperator valueOperator); + QVariant value() const; void setValue(const QVariant &value); @@ -43,14 +52,18 @@ public: signals: void deviceIdChanged(); - void valueOperatorChanged(); void stateTypeIdChanged(); + void interfaceNameChanged(); + void interfaceStateChanged(); + void valueOperatorChanged(); void valueChanged(); private: QUuid m_deviceId; - ValueOperator m_operator = ValueOperatorEquals; QUuid m_stateTypeId; + QString m_interfaceName; + QString m_interfaceState; + ValueOperator m_operator = ValueOperatorEquals; QVariant m_value; }; diff --git a/libnymea-common/types/stateevaluator.cpp b/libnymea-common/types/stateevaluator.cpp index 2ba1444b..ba0c4de2 100644 --- a/libnymea-common/types/stateevaluator.cpp +++ b/libnymea-common/types/stateevaluator.cpp @@ -66,6 +66,8 @@ StateEvaluator *StateEvaluator::clone() const ret->m_operator = this->m_operator; ret->m_stateDescriptor->setDeviceId(this->m_stateDescriptor->deviceId()); ret->m_stateDescriptor->setStateTypeId(this->m_stateDescriptor->stateTypeId()); + ret->m_stateDescriptor->setInterfaceName(this->m_stateDescriptor->interfaceName()); + ret->m_stateDescriptor->setInterfaceState(this->m_stateDescriptor->interfaceState()); ret->m_stateDescriptor->setValueOperator(this->m_stateDescriptor->valueOperator()); ret->m_stateDescriptor->setValue(this->m_stateDescriptor->value()); for (int i = 0; i < this->m_childEvaluators->rowCount(); i++) { diff --git a/libnymea-common/types/stateevaluators.cpp b/libnymea-common/types/stateevaluators.cpp index 49d98ab3..c2086ceb 100644 --- a/libnymea-common/types/stateevaluators.cpp +++ b/libnymea-common/types/stateevaluators.cpp @@ -1,4 +1,5 @@ #include "stateevaluators.h" +#include "stateevaluator.h" StateEvaluators::StateEvaluators(QObject *parent) : QAbstractListModel(parent) { @@ -34,13 +35,23 @@ void StateEvaluators::addStateEvaluator(StateEvaluator *stateEvaluator) StateEvaluator *StateEvaluators::get(int index) const { + if (index < 0 || index >= m_list.count()) { + return nullptr; + } return m_list.at(index); } StateEvaluator *StateEvaluators::take(int index) { beginRemoveRows(QModelIndex(), index, index); - return m_list.takeAt(index); - endInsertRows(); + StateEvaluator* ret = m_list.takeAt(index); + endRemoveRows(); emit countChanged(); + ret->setParent(nullptr); + return ret; +} + +void StateEvaluators::remove(int index) +{ + take(index)->deleteLater(); } diff --git a/libnymea-common/types/stateevaluators.h b/libnymea-common/types/stateevaluators.h index ec64681d..f2646fc8 100644 --- a/libnymea-common/types/stateevaluators.h +++ b/libnymea-common/types/stateevaluators.h @@ -19,7 +19,12 @@ public: void addStateEvaluator(StateEvaluator* stateEvaluator); Q_INVOKABLE StateEvaluator* get(int index) const; - StateEvaluator* take(int index); + + // Caller takes ownership, is responsible for deleting + Q_INVOKABLE StateEvaluator* take(int index); + + // StateEvaluator will be deleted + Q_INVOKABLE void remove(int index); signals: void countChanged(); diff --git a/libnymea-common/types/statetype.cpp b/libnymea-common/types/statetype.cpp index 50c4dd80..26b6dc8a 100644 --- a/libnymea-common/types/statetype.cpp +++ b/libnymea-common/types/statetype.cpp @@ -27,12 +27,12 @@ StateType::StateType(QObject *parent) : { } -QUuid StateType::id() const +QString StateType::id() const { return m_id; } -void StateType::setId(const QUuid &id) +void StateType::setId(const QString &id) { m_id = id; } @@ -67,6 +67,11 @@ void StateType::setType(const QString &type) m_type = type; } +void StateType::setType(QVariant::Type type) +{ + m_type = QVariant::typeToName(type); +} + int StateType::index() const { return m_index; diff --git a/libnymea-common/types/statetype.h b/libnymea-common/types/statetype.h index d4706723..9fe2fe0e 100644 --- a/libnymea-common/types/statetype.h +++ b/libnymea-common/types/statetype.h @@ -32,7 +32,7 @@ class StateType : public QObject { Q_OBJECT - Q_PROPERTY(QUuid id READ id CONSTANT) + Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(QString type READ type CONSTANT) @@ -44,8 +44,8 @@ class StateType : public QObject public: StateType(QObject *parent = 0); - QUuid id() const; - void setId(const QUuid &id); + QString id() const; + void setId(const QString &id); QString name() const; void setName(const QString &name); @@ -55,6 +55,7 @@ public: QString type() const; void setType(const QString &type); + void setType(QVariant::Type type); int index() const; void setIndex(const int &index); @@ -69,7 +70,7 @@ public: void setUnitString(const QString &unitString); private: - QUuid m_id; + QString m_id; QString m_name; QString m_displayName; QString m_type; diff --git a/libnymea-common/types/statetypes.cpp b/libnymea-common/types/statetypes.cpp index 433d2b07..20b3c11e 100644 --- a/libnymea-common/types/statetypes.cpp +++ b/libnymea-common/types/statetypes.cpp @@ -34,17 +34,13 @@ QList StateTypes::stateTypes() return m_stateTypes; } -int StateTypes::count() const -{ - return m_stateTypes.count(); -} - StateType *StateTypes::get(int index) const { + qDebug() << "returning" << m_stateTypes.at(index); return m_stateTypes.at(index); } -StateType *StateTypes::getStateType(const QUuid &stateTypeId) const +StateType *StateTypes::getStateType(const QString &stateTypeId) const { foreach (StateType *stateType, m_stateTypes) { if (stateType->id() == stateTypeId) { @@ -68,7 +64,7 @@ QVariant StateTypes::data(const QModelIndex &index, int role) const StateType *stateType = m_stateTypes.at(index.row()); switch (role) { case RoleId: - return stateType->id().toString(); + return stateType->id(); case RoleName: return stateType->name(); case RoleDisplayName: @@ -87,10 +83,11 @@ QVariant StateTypes::data(const QModelIndex &index, int role) const void StateTypes::addStateType(StateType *stateType) { + stateType->setParent(this); beginInsertRows(QModelIndex(), m_stateTypes.count(), m_stateTypes.count()); - //qDebug() << "StateTypes: loaded stateType" << stateType->name(); m_stateTypes.append(stateType); endInsertRows(); + emit countChanged(); } StateType *StateTypes::findByName(const QString &name) const @@ -110,6 +107,7 @@ void StateTypes::clearModel() qDeleteAll(m_stateTypes); m_stateTypes.clear(); endResetModel(); + emit countChanged(); } QHash StateTypes::roleNames() const diff --git a/libnymea-common/types/statetypes.h b/libnymea-common/types/statetypes.h index 09fc7cd3..979efa70 100644 --- a/libnymea-common/types/statetypes.h +++ b/libnymea-common/types/statetypes.h @@ -31,6 +31,7 @@ class StateTypes : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: enum Role { @@ -47,9 +48,8 @@ public: QList stateTypes(); - Q_INVOKABLE int count() const; Q_INVOKABLE StateType *get(int index) const; - Q_INVOKABLE StateType *getStateType(const QUuid &stateTypeId) const; + Q_INVOKABLE StateType *getStateType(const QString &stateTypeId) const; int rowCount(const QModelIndex & parent = QModelIndex()) const; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; @@ -63,6 +63,9 @@ public: protected: QHash roleNames() const; +signals: + void countChanged(); + private: QList m_stateTypes; diff --git a/mea/resources.qrc b/mea/resources.qrc index 4a132cf9..acdddf38 100644 --- a/mea/resources.qrc +++ b/mea/resources.qrc @@ -198,5 +198,12 @@ ui/images/action.svg ui/images/event.svg ui/images/state.svg + ui/images/event-interface.svg + ui/components/MeaListItemDelegate.qml + ui/images/state-interface.svg + ui/images/action-interface.svg + ui/system/AboutNymeaPage.qml + ui/images/logs.svg + ui/images/plugin.svg diff --git a/mea/ui/AppSettingsPage.qml b/mea/ui/AppSettingsPage.qml index 9dec1e83..e44057f3 100644 --- a/mea/ui/AppSettingsPage.qml +++ b/mea/ui/AppSettingsPage.qml @@ -110,24 +110,11 @@ Page { } } ThinDivider {} - ItemDelegate { + MeaListItemDelegate { Layout.fillWidth: true - - contentItem: RowLayout { - Label { - Layout.fillWidth: true - text: qsTr("About %1").arg(app.appName) - } - Image { - source: "images/next.svg" - Layout.preferredHeight: parent.height - Layout.preferredWidth: height - } - } - - onClicked: { - pageStack.push(Qt.resolvedUrl("AboutPage.qml")) - } + text: qsTr("About %1").arg(app.appName) + iconName: "../images/info.svg" + onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml")) } } diff --git a/mea/ui/MagicPage.qml b/mea/ui/MagicPage.qml index 2b4ae3cb..f9de8c31 100644 --- a/mea/ui/MagicPage.qml +++ b/mea/ui/MagicPage.qml @@ -61,24 +61,15 @@ Page { anchors.fill: parent model: Engine.ruleManager.rules - delegate: SwipeDelegate { + delegate: MeaListItemDelegate { id: ruleDelegate width: parent.width + iconName: "../images/magic.svg" + iconColor: !model.enabled ? "red" : (model.active ? app.guhAccent : "grey") + text: model.name + canDelete: true - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - height: app.iconSize - width: height - name: "../images/magic.svg" - color: !model.enabled ? "red" : (model.active ? app.guhAccent : "grey") - } - - Label { - Layout.fillWidth: true - text: model.name - } - } + onDeleteClicked: Engine.ruleManager.removeRule(model.id) onClicked: { d.editRulePage = pageStack.push(Qt.resolvedUrl("magic/EditRulePage.qml"), {rule: Engine.ruleManager.rules.get(index).clone()}) @@ -88,25 +79,12 @@ Page { }) d.editRulePage.onAccept.connect(function() { d.editRulePage.busy = true; - Engine.ruleManager.editRule(editRulePage.rule); + Engine.ruleManager.editRule(d.editRulePage.rule); }) d.editRulePage.onCancel.connect(function() { pageStack.pop(); }) } - - swipe.right: MouseArea { - height: ruleDelegate.height - width: height - anchors.right: parent.right - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/delete.svg" - color: "red" - } - onClicked: Engine.ruleManager.removeRule(model.id) - } } } diff --git a/mea/ui/Mea.qml b/mea/ui/Mea.qml index 17a7db5f..05de6763 100644 --- a/mea/ui/Mea.qml +++ b/mea/ui/Mea.qml @@ -16,7 +16,7 @@ ApplicationWindow { property int margins: 14 property int bigMargins: 20 - property int smallFont: 14 + property int smallFont: 13 property int mediumFont: 16 property int largeFont: 20 property int iconSize: 30 @@ -220,6 +220,8 @@ ApplicationWindow { return Qt.resolvedUrl("images/sort-listitem.svg") case "garagegate": return Qt.resolvedUrl("images/shutter-10.svg") + case "battery": + return Qt.resolvedUrl("images/battery/battery-050.svg") } return ""; } diff --git a/mea/ui/SettingsPage.qml b/mea/ui/SettingsPage.qml index 4a928968..74462dac 100644 --- a/mea/ui/SettingsPage.qml +++ b/mea/ui/SettingsPage.qml @@ -89,41 +89,25 @@ Page { } - ItemDelegate { + MeaListItemDelegate { Layout.fillWidth: true - contentItem: RowLayout { - Label { - Layout.fillWidth: true - text: qsTr("Plugins") - } - Image { - source: "images/next.svg" - Layout.preferredHeight: parent.height - Layout.preferredWidth: height - } - } - onClicked: { - pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml")) - } + iconName: "../images/plugin.svg" + text: qsTr("Plugins") + onClicked:pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml")) } - ItemDelegate { + MeaListItemDelegate { Layout.fillWidth: true - contentItem: RowLayout { - Label { - text: qsTr("Log viewer") - Layout.fillWidth: true - } - Image { - source: "images/next.svg" - Layout.preferredHeight: parent.height - Layout.preferredWidth: height - } - } - + iconName: "../images/logs.svg" + text: qsTr("Log viewer") onClicked: pageStack.push(Qt.resolvedUrl("system/LogViewerPage.qml")) } - + MeaListItemDelegate { + Layout.fillWidth: true + iconName: "../images/info.svg" + text: qsTr("About nymea") + onClicked: pageStack.push(Qt.resolvedUrl("system/AboutNymeaPage.qml")) + } } Component { diff --git a/mea/ui/components/GuhHeader.qml b/mea/ui/components/GuhHeader.qml index 52c4b3d8..41aea2ef 100644 --- a/mea/ui/components/GuhHeader.qml +++ b/mea/ui/components/GuhHeader.qml @@ -10,7 +10,7 @@ ToolBar { property string text property alias backButtonVisible: backButton.visible property alias menuButtonVisible: menuButton.visible - default property alias data: layout.data + default property alias children: layout.data signal backPressed(); signal menuPressed(); diff --git a/mea/ui/components/MeaListItemDelegate.qml b/mea/ui/components/MeaListItemDelegate.qml new file mode 100644 index 00000000..55131509 --- /dev/null +++ b/mea/ui/components/MeaListItemDelegate.qml @@ -0,0 +1,79 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +SwipeDelegate { + id: root + + property string subText + property bool progressive: true + property bool canDelete: false + + property string iconName + property int iconSize: app.iconSize + property color iconColor: app.guhAccent + + signal deleteClicked() + + contentItem: RowLayout { + spacing: app.margins + ColorIcon { + id: icon + Layout.preferredHeight: root.iconSize + Layout.preferredWidth: height + name: root.iconName + color: root.iconColor + visible: root.iconName + } + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: root.text + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + } + Label { + Layout.fillWidth: true + Layout.fillHeight: true + text: root.subText + font.pixelSize: app.smallFont + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + visible: root.subText.length > 0 + } + } + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + name: "../images/next.svg" + visible: root.progressive + } + } + + swipe.enabled: canDelete + swipe.right: MouseArea { + height: root.height + width: height + anchors.right: parent.right + Rectangle { + anchors.fill: parent + color: "red" + } + + ColorIcon { + anchors.fill: parent + anchors.margins: app.margins + name: "../images/delete.svg" + color: "white" + } + onClicked: { + swipe.close(); + root.deleteClicked() + } + } +} diff --git a/mea/ui/delegates/ThingDelegate.qml b/mea/ui/delegates/ThingDelegate.qml index 608653a9..8d88d359 100644 --- a/mea/ui/delegates/ThingDelegate.qml +++ b/mea/ui/delegates/ThingDelegate.qml @@ -3,30 +3,13 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 import "../components" -ItemDelegate { +MeaListItemDelegate { id: root width: parent.width + iconName: app.interfacesToIcon(root.interfaces) + text: root.name + progressive: true property var interfaces: [] - property var name: "" - - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - height: app.iconSize - width: height - name: app.interfacesToIcon(root.interfaces) - color: app.guhAccent - } - - Label { - Layout.fillWidth: true - text: root.name - } - Image { - source: "../images/next.svg" - Layout.preferredHeight: parent.height - Layout.preferredWidth: height - } - } + property string name: "" } diff --git a/mea/ui/devicepages/StateLogPage.qml b/mea/ui/devicepages/StateLogPage.qml index 9b44cc8a..9c4ec3a3 100644 --- a/mea/ui/devicepages/StateLogPage.qml +++ b/mea/ui/devicepages/StateLogPage.qml @@ -78,11 +78,12 @@ Page { onAddRuleClicked: { var rule = Engine.ruleManager.createNewRule(); - rule.createStateEvaluator(); - rule.stateEvaluator.stateDescriptor.deviceId = device.id; - rule.stateEvaluator.stateDescriptor.stateTypeId = root.stateType.id; - rule.stateEvaluator.stateDescriptor.value = value; - rule.stateEvaluator.stateDescriptor.valueOperator = StateDescriptor.ValueOperatorEquals; + var stateEvaluator = rule.createStateEvaluator(); + stateEvaluator.stateDescriptor.deviceId = device.id; + stateEvaluator.stateDescriptor.stateTypeId = root.stateType.id; + stateEvaluator.stateDescriptor.value = value; + stateEvaluator.stateDescriptor.valueOperator = StateDescriptor.ValueOperatorEquals; + rule.setStateEvaluator(stateEvaluator); rule.name = root.device.name + " - " + stateType.displayName + " = " + value; var rulePage = pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device}); diff --git a/mea/ui/images/action-interface.svg b/mea/ui/images/action-interface.svg new file mode 100644 index 00000000..80407d8a --- /dev/null +++ b/mea/ui/images/action-interface.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/mea/ui/images/event-interface.svg b/mea/ui/images/event-interface.svg new file mode 100644 index 00000000..368b3da5 --- /dev/null +++ b/mea/ui/images/event-interface.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/mea/ui/images/logs.svg b/mea/ui/images/logs.svg new file mode 100644 index 00000000..4110f70e --- /dev/null +++ b/mea/ui/images/logs.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/mea/ui/images/plugin.svg b/mea/ui/images/plugin.svg new file mode 100644 index 00000000..8a885ea0 --- /dev/null +++ b/mea/ui/images/plugin.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/mea/ui/images/state-interface.svg b/mea/ui/images/state-interface.svg new file mode 100644 index 00000000..6d046a74 --- /dev/null +++ b/mea/ui/images/state-interface.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/mea/ui/magic/CalendarItemDelegate.qml b/mea/ui/magic/CalendarItemDelegate.qml index 1a5e8612..6de94570 100644 --- a/mea/ui/magic/CalendarItemDelegate.qml +++ b/mea/ui/magic/CalendarItemDelegate.qml @@ -4,9 +4,11 @@ import QtQuick.Layouts 1.3 import Mea 1.0 import "../components" -SwipeDelegate { +MeaListItemDelegate { id: root implicitHeight: app.delegateHeight + progressive: false + canDelete: true property var calendarItem: null @@ -15,90 +17,7 @@ SwipeDelegate { signal removeCalendarItem(); - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: app.iconSize - name: "../images/clock-app-symbolic.svg" - color: app.guhAccent - } - - ColumnLayout { - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: qsTr("From %1 to %2") - .arg(root.isDateBased ? Qt.formatDateTime(root.calendarItem.dateTime) : Qt.formatTime(root.calendarItem.startTime)) - .arg(root.isDateBased ? Qt.formatDateTime(new Date(root.calendarItem.dateTime.getTime() + root.calendarItem.duration * 60000)) : Qt.formatTime(new Date(root.calendarItem.startTime.getTime() + root.calendarItem.duration * 60000))) - } - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - font.pixelSize: app.smallFont - text: qsTr("repeated %3") - .arg(repeatingString) - - property string repeatingString: { - switch (root.calendarItem.repeatingOption.repeatingMode) { - case RepeatingOption.RepeatingModeNone: - return qsTr("never"); - case RepeatingOption.RepeatingModeHourly: - return qsTr("hourly"); - case RepeatingOption.RepeatingModeDaily: - return qsTr("daily"); - case RepeatingOption.RepeatingModeWeekly: - var weekdays = [] - for (var i = 0; i < root.calendarItem.repeatingOption.weekDays.length; i++) { - switch (root.calendarItem.repeatingOption.weekDays[i]) { - case 1: - weekdays.push(qsTr("Mon")); - break; - case 2: - weekdays.push(qsTr("Tue")); - break; - case 3: - weekdays.push(qsTr("Wed")); - break; - case 4: - weekdays.push(qsTr("Thu")); - break; - case 5: - weekdays.push(qsTr("Fri")); - break; - case 6: - weekdays.push(qsTr("Sat")); - break; - case 7: - weekdays.push(qsTr("Sun")); - break; - } - } - - return qsTr("weekly on %1").arg(weekdays.join(', ')); - case RepeatingOption.RepeatingModeMonthly: - return qsTr("monthly on the %1").arg(root.calendarItem.repeatingOption.monthDays.join(', ')); - case RepeatingOption.RepeatingModeYearly: - return qsTr("every year"); - } - } - } - } - } - - swipe.right: MouseArea { - height: root.height - width: height - anchors.right: parent.right - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/delete.svg" - color: "red" - } - onClicked: root.removeCalendarItem() - } + onDeleteClicked: root.removeCalendarItem() onClicked: { var page = pageStack.push(Qt.resolvedUrl("EditCalendarItemPage.qml"), {calendarItem: root.calendarItem}) @@ -108,4 +27,59 @@ SwipeDelegate { print("calendarItem.time is now", root.calendarItem.time) }) } + + + iconName: "../images/clock-app-symbolic.svg" + + text: qsTr("From %1 to %2") + .arg(root.isDateBased ? Qt.formatDateTime(root.calendarItem.dateTime) : Qt.formatTime(root.calendarItem.startTime)) + .arg(root.isDateBased ? Qt.formatDateTime(new Date(root.calendarItem.dateTime.getTime() + root.calendarItem.duration * 60000)) : Qt.formatTime(new Date(root.calendarItem.startTime.getTime() + root.calendarItem.duration * 60000))) + + subText: qsTr("repeated %3") + .arg(repeatingString) + + property string repeatingString: { + switch (root.calendarItem.repeatingOption.repeatingMode) { + case RepeatingOption.RepeatingModeNone: + return qsTr("never"); + case RepeatingOption.RepeatingModeHourly: + return qsTr("hourly"); + case RepeatingOption.RepeatingModeDaily: + return qsTr("daily"); + case RepeatingOption.RepeatingModeWeekly: + var weekdays = [] + for (var i = 0; i < root.calendarItem.repeatingOption.weekDays.length; i++) { + switch (root.calendarItem.repeatingOption.weekDays[i]) { + case 1: + weekdays.push(qsTr("Mon")); + break; + case 2: + weekdays.push(qsTr("Tue")); + break; + case 3: + weekdays.push(qsTr("Wed")); + break; + case 4: + weekdays.push(qsTr("Thu")); + break; + case 5: + weekdays.push(qsTr("Fri")); + break; + case 6: + weekdays.push(qsTr("Sat")); + break; + case 7: + weekdays.push(qsTr("Sun")); + break; + } + } + + return qsTr("weekly on %1").arg(weekdays.join(', ')); + case RepeatingOption.RepeatingModeMonthly: + return qsTr("monthly on the %1").arg(root.calendarItem.repeatingOption.monthDays.join(', ')); + case RepeatingOption.RepeatingModeYearly: + return qsTr("every year"); + } + } + } diff --git a/mea/ui/magic/EditRulePage.qml b/mea/ui/magic/EditRulePage.qml index cfb740d4..9443da3c 100644 --- a/mea/ui/magic/EditRulePage.qml +++ b/mea/ui/magic/EditRulePage.qml @@ -20,23 +20,39 @@ Page { signal accept(); signal cancel(); - function addEventDescriptor() { - var eventDescriptor = root.rule.eventDescriptors.createNewEventDescriptor(); - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); - page.onBackPressed.connect(function() { pageStack.pop(); }); + function addEventDescriptor(interfaceMode) { + if (interfaceMode === undefined) { + interfaceMode = false; + } + + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {selectInterface: interfaceMode, showEvents: true}); + page.onBackPressed.connect(function() { + pageStack.pop(); + }); page.onThingSelected.connect(function(device) { + var eventDescriptor = root.rule.eventDescriptors.createNewEventDescriptor(); eventDescriptor.deviceId = device.id; - selectEventDescriptorData(eventDescriptor) + selectEventDescriptorData(eventDescriptor); }) page.onInterfaceSelected.connect(function(interfaceName) { + var eventDescriptor = root.rule.eventDescriptors.createNewEventDescriptor(); eventDescriptor.interfaceName = interfaceName; - selectEventDescriptorData(eventDescriptor) + selectEventDescriptorData(eventDescriptor); }) } + function addInterfaceEventDescriptor() { + addEventDescriptor(true); + } + function selectEventDescriptorData(eventDescriptor) { var eventPage = pageStack.push(Qt.resolvedUrl("SelectEventDescriptorPage.qml"), {text: "Select event", eventDescriptor: eventDescriptor}); - eventPage.onBackPressed.connect(function() {pageStack.pop()}) + eventPage.onBackPressed.connect(function() { + eventPage.StackView.onRemoved.connect(function() { + eventDescriptor.destroy(); + }); + pageStack.pop(); + }) eventPage.onDone.connect(function() { root.rule.eventDescriptors.addEventDescriptor(eventPage.eventDescriptor); pageStack.pop(root) @@ -47,8 +63,10 @@ Page { var timeEventItem = root.rule.timeDescriptor.timeEventItems.createNewTimeEventItem(); var page = pageStack.push(Qt.resolvedUrl("EditTimeEventItemPage.qml"), {timeEventItem: timeEventItem}); page.onBackPressed.connect(function() { + page.StackView.onRemoved.connect(function() { + timeEventItem.destroy(); + }); pageStack.pop() - timeEventItem.destroy(); }) page.onDone.connect(function() { root.rule.timeDescriptor.timeEventItems.addTimeEventItem(timeEventItem); @@ -60,8 +78,10 @@ Page { var calendarItem = root.rule.timeDescriptor.calendarItems.createNewCalendarItem(); var page = pageStack.push(Qt.resolvedUrl("EditCalendarItemPage.qml"), {calendarItem: calendarItem}); page.onBackPressed.connect(function() { + page.StackView.onRemoved.connect(function() { + calendarItem.destroy(); + }); pageStack.pop(); - calendarItem.destroy(); }) page.onDone.connect(function() { root.rule.timeDescriptor.calendarItems.addCalendarItem(calendarItem); @@ -69,48 +89,109 @@ Page { }) } + function createStateEvaluator(interfaceMode) { + if (interfaceMode === undefined) { + interfaceMode = false; + } + + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {selectInterface: interfaceMode, showStates: true}); + page.backPressed.connect(function() { + pageStack.pop(); + }); + page.interfaceSelected.connect(function(interfaceName) { + var stateEvaluator = root.rule.createStateEvaluator(); + stateEvaluator.stateDescriptor.interfaceName = interfaceName; + selectStateDescriptorData(stateEvaluator) + }); + page.thingSelected.connect(function(device) { + var stateEvaluator = root.rule.createStateEvaluator(); + stateEvaluator.stateDescriptor.deviceId = device.id + selectStateDescriptorData(stateEvaluator) + }) + } + + function selectStateDescriptorData(stateEvaluator) { + print("Selecting stateDescriptorData for", stateEvaluator.stateDescriptor.deviceId, stateEvaluator.stateDescriptor.interfaceName) + var statePage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorPage.qml"), {text: "Select state", stateDescriptor: stateEvaluator.stateDescriptor}) + statePage.backPressed.connect(function() { + statePage.StackView.onRemoved.connect(function() { + stateEvaluator.destroy(); + }) + pageStack.pop() + }) + statePage.done.connect(function() { + root.rule.setStateEvaluator(stateEvaluator) + pageStack.pop(); + pageStack.pop(); + pageStack.pop(); + }) + } + + function createInterfaceStateEvaluator() { + createStateEvaluator(true) + } + function editStateEvaluator() { print("opening page", root.rule.stateEvaluator) var page = pageStack.push(Qt.resolvedUrl("EditStateEvaluatorPage.qml"), { stateEvaluator: root.rule.stateEvaluator }) } - function addAction() { - var ruleAction = root.rule.actions.createNewRuleAction(); - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); - page.onBackPressed.connect(function() { pageStack.pop() }) + function addRuleAction(interfaceMode) { + if (interfaceMode === undefined) { + interfaceMode = false; + } + + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {selectInterface: interfaceMode, showActions: true}); + page.onBackPressed.connect(function() { + pageStack.pop(); + }) page.onThingSelected.connect(function(device) { - print("thing selected", device.name, device.id) + var ruleAction = root.rule.actions.createNewRuleAction(); ruleAction.deviceId = device.id; selectRuleActionData(root.rule.actions, ruleAction) }) page.onInterfaceSelected.connect(function(interfaceName) { - print("interface selected", interfaceName) + var ruleAction = root.rule.actions.createNewRuleAction(); ruleAction.interfaceName = interfaceName; selectRuleActionData(root.rule.actions, ruleAction) }) } - function addExitAction() { - var ruleAction = root.rule.exitActions.createNewRuleAction(); - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); - page.onBackPressed.connect(function() { pageStack.pop() }) + function addInterfaceRuleAction() { + addRuleAction(true); + } + + function addRuleExitAction(interfaceMode) { + if (interfaceMode === undefined) { + interfaceMode = false; + } + + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {selectInterface: interfaceMode, showActions: true}); + page.onBackPressed.connect(function() { + pageStack.pop(); + }) page.onThingSelected.connect(function(device) { - print("thing selected", device.name, device.id) + var ruleAction = root.rule.exitActions.createNewRuleAction(); ruleAction.deviceId = device.id; selectRuleActionData(root.rule.exitActions, ruleAction) }) page.onInterfaceSelected.connect(function(interfaceName) { - print("interface selected", interfaceName) + var ruleAction = root.rule.exitActions.createNewRuleAction(); ruleAction.interfaceName = interfaceName; selectRuleActionData(root.rule.exitActions, ruleAction) }) } + function addInterfaceRuleExitAction() { + addRuleExitAction(true); + } function selectRuleActionData(ruleActions, ruleAction) { - print("opening with ruleAction", ruleAction) + print("opening with ruleAction", ruleAction, ruleAction.interfaceName) var ruleActionPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionPage.qml"), {text: "Select action", ruleAction: ruleAction, rule: rule }); ruleActionPage.onBackPressed.connect(function() { - pageStack.pop(root); - ruleAction.destroy(); + ruleActionPage.StackView.onRemoved.connect(function() { + ruleAction.destroy(); + }); + pageStack.pop(); }) ruleActionPage.onDone.connect(function() { ruleActions.addRuleAction(ruleAction) @@ -246,8 +327,7 @@ Page { if (root.rule.timeDescriptor.calendarItems.count > 0) { root.addEventDescriptor() } else { - var popup = eventQuestionDialogComponent.createObject(root) - popup.open(); + pageStack.push(eventQuestionPageComponent) } } } @@ -298,6 +378,9 @@ Page { Layout.fillWidth: true stateEvaluator: root.rule.stateEvaluator visible: root.rule.stateEvaluator !== null + onDeleteClicked: { + root.rule.stateEvaluator = null + } } Label { @@ -331,12 +414,11 @@ Page { visible: root.rule.timeDescriptor.timeEventItems.count === 0 || root.rule.stateEvaluator === null onClicked: { if (root.rule.timeDescriptor.timeEventItems.count > 0) { - root.rule.createStateEvaluator() + root.rule.setStateEvaluator(root.rule.createStateEvaluator()); } else if (root.rule.stateEvaluator !== null) { root.addCalendarItem(); } else { - var popup = stateQuestionDialogComponent.createObject(root) - popup.open() + pageStack.push(stateQuestionPageComponent) } } } @@ -369,7 +451,9 @@ Page { Layout.fillWidth: true Layout.margins: app.margins text: actionsRepeater.count == 0 ? qsTr("Add an action...") : qsTr("Add another action...") - onClicked: root.addAction(); + onClicked: { + var page = pageStack.push(ruleActionQuestionPageComponent, {exitAction: false}); + } visible: root.actionsVisible } @@ -400,7 +484,9 @@ Page { Layout.fillWidth: true Layout.margins: app.margins text: actionsRepeater.count == 0 ? qsTr("Add an action...") : qsTr("Add another action...") - onClicked: root.addExitAction(); + onClicked: { + var page = pageStack.push(ruleActionQuestionPageComponent, {exitAction: true}); + } visible: root.exitActionsVisible } } @@ -419,134 +505,162 @@ Page { } Component { - id: eventQuestionDialogComponent - MeaDialog { - id: questionDialog - title: qsTr("Add event...") - standardButtons: Dialog.Cancel - - Button { - Layout.fillWidth: true - Layout.preferredHeight: (app.largeFont * 2) + (app.margins * 3) - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - name: "../images/event.svg" - color: "black" - } - Label { - Layout.fillWidth: true - Layout.fillHeight: true - text: qsTr("When one of my things triggers an event") - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - } - } - onClicked: { - root.addEventDescriptor() - questionDialog.close() - } + id: eventQuestionPageComponent + Page { + header: GuhHeader { + text: qsTr("Add event") + onBackPressed: pageStack.pop() } - Label { - text: qsTr("or") - Layout.fillWidth: true - Layout.margins: app.margins - horizontalAlignment: Text.AlignHCenter - } + ColumnLayout { + anchors.fill: parent - Button { - Layout.fillWidth: true - Layout.preferredHeight: (app.largeFont * 2) + (app.margins * 3) - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - name: "../images/alarm-clock.svg" - color: "black" + Repeater { + model: ListModel { + ListElement { + iconName: "../images/event.svg" + text: qsTr("When one of my things triggers an event") + method: "addEventDescriptor" + minimumJsonRpcVersion: "1.0" + } + ListElement { + iconName: "../images/event-interface.svg" + text: qsTr("When a thing of a given type triggers an event") + method: "addInterfaceEventDescriptor" + minimumJsonRpcVersion: "1.5" + } + ListElement { + iconName: "../images/alarm-clock.svg" + text: qsTr("At a particular time or date") + method: "addTimeEventItem" + minimumJsonRpcVersion: "1.0" + } } - Label { + delegate: MeaListItemDelegate { Layout.fillWidth: true - Layout.fillHeight: true - text: qsTr("At a particular time or date") - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter + iconName: model.iconName + text: model.text + progressive: true + iconSize: app.iconSize * 2 + visible: Engine.jsonRpcClient.ensureServerVersion(model.minimumJsonRpcVersion) + + onClicked: { + root[model.method]() + } } } - onClicked: { - root.addTimeEventItem() - questionDialog.close() - } } } } Component { - id: stateQuestionDialogComponent - MeaDialog { - id: questionDialog - title: qsTr("Add condition...") - standardButtons: Dialog.Cancel + id: stateQuestionPageComponent + Page { + header: GuhHeader { + text: qsTr("Add condition...") - - Button { - Layout.fillWidth: true - Layout.preferredHeight: (app.largeFont * 2) + (app.margins * 3) - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - name: "../images/state.svg" - color: "black" - } - - Label { - Layout.fillWidth: true - Layout.fillHeight: true - text: qsTr("When one of my things is in a certain state") - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter - } - } - onClicked: { - root.rule.createStateEvaluator() - questionDialog.close() - } + onBackPressed: pageStack.pop() } - Label { - text: qsTr("or") - Layout.fillWidth: true - Layout.margins: app.margins - horizontalAlignment: Text.AlignHCenter - } + ColumnLayout { + anchors.fill: parent - Button { - Layout.fillWidth: true - Layout.preferredHeight: (app.largeFont * 2) + (app.margins * 3) - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - name: "../images/clock-app-symbolic.svg" - color: "black" + Repeater { + model: ListModel { + ListElement { + iconName: "../images/state.svg" + text: qsTr("When one of my things is in a certain state") + method: "createStateEvaluator" + minimumJsonRpcVersion: "1.0" + } + ListElement { + iconName: "../images/state-interface.svg" + text: qsTr("When a thing of a given type enters a state") + method: "createInterfaceStateEvaluator" + minimumJsonRpcVersion: "1.5" + } + ListElement { + iconName: "../images/clock-app-symbolic.svg" + text: qsTr("During a given time") + method: "addCalendarItem" + minimumJsonRpcVersion: "1.0" + } } - Label { + delegate: MeaListItemDelegate { Layout.fillWidth: true - Layout.fillHeight: true - text: qsTr("During a given time") - wrapMode: Text.WordWrap - verticalAlignment: Text.AlignVCenter + iconName: model.iconName + text: model.text + progressive: true + iconSize: app.iconSize * 2 + visible: Engine.jsonRpcClient.ensureServerVersion(model.minimumJsonRpcVersion) + + onClicked: { + root[model.method]() + } } } - onClicked: { - root.addCalendarItem() - questionDialog.close() + } + } + } + + Component { + id: ruleActionQuestionPageComponent + Page { + id: ruleActionQuestionPage + property bool exitAction: false + + header: GuhHeader { + text: qsTr("Add action...") + + onBackPressed: pageStack.pop() + } + + ColumnLayout { + anchors.fill: parent + + Repeater { + model: ListModel { + ListElement { + iconName: "../images/action.svg" + text: qsTr("Execute an action on of my things") + method: "addRuleAction" + isExitAction: false + minimumJsonRpcVersion: "1.0" + } + ListElement { + iconName: "../images/action-interface.svg" + text: qsTr("Execute an action on an entire kind of things") + method: "addInterfaceRuleAction" + isExitAction: false + minimumJsonRpcVersion: "1.5" + } + ListElement { + iconName: "../images/action.svg" + text: qsTr("Execute an action on of my things") + method: "addRuleExitAction" + isExitAction: true + minimumJsonRpcVersion: "1.0" + } + ListElement { + iconName: "../images/action-interface.svg" + text: qsTr("Execute an action on an entire kind of things") + method: "addInterfaceRuleExitAction" + isExitAction: true + minimumJsonRpcVersion: "1.5" + } + } + delegate: MeaListItemDelegate { + Layout.fillWidth: true + iconName: model.iconName + text: model.text + progressive: true + iconSize: app.iconSize * 2 + visible: ruleActionQuestionPage.exitAction === model.isExitAction && Engine.jsonRpcClient.ensureServerVersion(model.minimumJsonRpcVersion) + + onClicked: { + root[model.method]() + } + } } } } diff --git a/mea/ui/magic/EditStateEvaluatorPage.qml b/mea/ui/magic/EditStateEvaluatorPage.qml index 26b0e389..e746e216 100644 --- a/mea/ui/magic/EditStateEvaluatorPage.qml +++ b/mea/ui/magic/EditStateEvaluatorPage.qml @@ -16,5 +16,6 @@ Page { StateEvaluatorDelegate { width: parent.width stateEvaluator: root.stateEvaluator + canDelete: false } } diff --git a/mea/ui/magic/EventDescriptorDelegate.qml b/mea/ui/magic/EventDescriptorDelegate.qml index 27e99d7c..ece75a97 100644 --- a/mea/ui/magic/EventDescriptorDelegate.qml +++ b/mea/ui/magic/EventDescriptorDelegate.qml @@ -4,96 +4,68 @@ import QtQuick.Layouts 1.3 import Mea 1.0 import "../components" -SwipeDelegate { +MeaListItemDelegate { id: root implicitHeight: app.delegateHeight + canDelete: true + progressive: false property var eventDescriptor: null readonly property var device: eventDescriptor ? Engine.deviceManager.devices.getDevice(eventDescriptor.deviceId) : null readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null readonly property var iface: eventDescriptor.interfaceName ? Interfaces.findByName(eventDescriptor.interfaceName) : null readonly property var eventType: deviceClass ? deviceClass.eventTypes.getEventType(eventDescriptor.eventTypeId) - : iface ? iface.eventTypes.findByName(eventDescriptor.interfaceEvent) : null + : iface ? iface.eventTypes.findByName(eventDescriptor.interfaceEvent) : null signal removeEventDescriptor() - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: app.iconSize - name: "../images/event.svg" - color: app.guhAccent - } + onDeleteClicked: root.removeEventDescriptor() - ColumnLayout { - Label { - text: qsTr("%1 - %2").arg(root.device ? root.device.name : root.iface.displayName).arg(root.eventType.displayName) - Layout.fillWidth: true - elide: Text.ElideRight + iconName: root.device ? "../images/event.svg" : "../images/event-interface.svg" + text: qsTr("%1 - %2").arg(root.device ? root.device.name : root.iface.displayName).arg(root.eventType.displayName) + subText: { + var ret = qsTr("anytime"); + for (var i = 0; i < root.eventDescriptor.paramDescriptors.count; i++) { + var paramDescriptor = root.eventDescriptor.paramDescriptors.get(i) + var operatorString; + switch (paramDescriptor.operatorType) { + case ParamDescriptor.ValueOperatorEquals: + operatorString = " = "; + break; + case ParamDescriptor.ValueOperatorNotEquals: + operatorString = " != "; + break; + case ParamDescriptor.ValueOperatorGreater: + operatorString = " > "; + break; + case ParamDescriptor.ValueOperatorGreaterOrEqual: + operatorString = " >= "; + break; + case ParamDescriptor.ValueOperatorLess: + operatorString = " < "; + break; + case ParamDescriptor.ValueOperatorLessOrEqual: + operatorString = " <= "; + break; + default: + operatorString = " ? "; } - Label { - Layout.fillWidth: true - elide: Text.ElideRight - font.pixelSize: app.smallFont - text: { - var ret = qsTr("anytime"); - for (var i = 0; i < root.eventDescriptor.paramDescriptors.count; i++) { - var paramDescriptor = root.eventDescriptor.paramDescriptors.get(i) - var operatorString; - switch (paramDescriptor.operatorType) { - case ParamDescriptor.ValueOperatorEquals: - operatorString = " = "; - break; - case ParamDescriptor.ValueOperatorNotEquals: - operatorString = " != "; - break; - case ParamDescriptor.ValueOperatorGreater: - operatorString = " > "; - break; - case ParamDescriptor.ValueOperatorGreaterOrEqual: - operatorString = " >= "; - break; - case ParamDescriptor.ValueOperatorLess: - operatorString = " < "; - break; - case ParamDescriptor.ValueOperatorLessOrEqual: - operatorString = " <= "; - break; - default: - operatorString = " ? "; - } - if (i === 0) { - // TRANSLATORS: example: "only if temperature > 5" - ret = qsTr("only if %1 %2 %3") - .arg(root.eventType.paramTypes.getParamType(paramDescriptor.paramTypeId).displayName) - .arg(operatorString) - .arg(paramDescriptor.value) - } else { - // TRANSLATORS: example: "and temperature > 5" - ret += " " + qsTr("and %1 %2 %3") - .arg(root.eventType.paramTypes.getParamType(paramDescriptor.paramTypeId).displayName) - .arg(operatorString) - .arg(model.value) - } - } - - return ret; - } + if (i === 0) { + // TRANSLATORS: example: "only if temperature > 5" + ret = qsTr("only if %1 %2 %3") + .arg(root.eventType.paramTypes.getParamType(paramDescriptor.paramTypeId).displayName) + .arg(operatorString) + .arg(paramDescriptor.value) + } else { + // TRANSLATORS: example: "and temperature > 5" + ret += " " + qsTr("and %1 %2 %3") + .arg(root.eventType.paramTypes.getParamType(paramDescriptor.paramTypeId).displayName) + .arg(operatorString) + .arg(model.value) } } - } - swipe.right: MouseArea { - height: root.height - width: height - anchors.right: parent.right - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/delete.svg" - color: "red" - } - onClicked: root.removeEventDescriptor() + + return ret; } } diff --git a/mea/ui/magic/RuleActionDelegate.qml b/mea/ui/magic/RuleActionDelegate.qml index e357a97c..6e21c2c9 100644 --- a/mea/ui/magic/RuleActionDelegate.qml +++ b/mea/ui/magic/RuleActionDelegate.qml @@ -4,9 +4,12 @@ import QtQuick.Layouts 1.3 import Mea 1.0 import "../components" -SwipeDelegate { +MeaListItemDelegate { id: root implicitHeight: app.delegateHeight + canDelete: true + progressive: false + property var ruleAction: null property var device: ruleAction.deviceId ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null @@ -17,50 +20,19 @@ SwipeDelegate { signal removeRuleAction() - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: app.iconSize - name: "../images/action.svg" - color: app.guhAccent - } + onDeleteClicked: root.removeRuleAction() - ColumnLayout { - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: qsTr("%1 - %2").arg(root.device ? root.device.name : root.iface.displayName).arg(root.actionType.displayName) - } - Label { - Layout.fillWidth: true - elide: Text.ElideRight - font.pixelSize: app.smallFont - text: { - var ret = []; - for (var i = 0; i < root.ruleAction.ruleActionParams.count; i++) { - var ruleActionParam = root.ruleAction.ruleActionParams.get(i) - var paramString = qsTr("%1: %2") - .arg(root.actionType.paramTypes.getParamType(ruleActionParam.paramTypeId).displayName) - .arg(ruleActionParam.eventParamTypeId.length > 0 ? qsTr("value from event") : ruleActionParam.value) - ret.push(paramString) - } - return ret.join(', ') - } - - } + iconName: root.device ? "../images/action.svg" : "../images/action-interface.svg" + text: qsTr("%1 - %2").arg(root.device ? root.device.name : root.iface.displayName).arg(root.actionType.displayName) + subText: { + var ret = []; + for (var i = 0; i < root.ruleAction.ruleActionParams.count; i++) { + var ruleActionParam = root.ruleAction.ruleActionParams.get(i) + var paramString = qsTr("%1: %2") + .arg(root.actionType.paramTypes.getParamType(ruleActionParam.paramTypeId).displayName) + .arg(ruleActionParam.eventParamTypeId.length > 0 ? qsTr("value from event") : ruleActionParam.value) + ret.push(paramString) } - } - swipe.right: MouseArea { - height: root.height - width: height - anchors.right: parent.right - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/delete.svg" - color: "red" - } - onClicked: root.removeRuleAction() + return ret.join(', ') } } diff --git a/mea/ui/magic/SelectEventDescriptorPage.qml b/mea/ui/magic/SelectEventDescriptorPage.qml index 4e731617..34cfaf0f 100644 --- a/mea/ui/magic/SelectEventDescriptorPage.qml +++ b/mea/ui/magic/SelectEventDescriptorPage.qml @@ -34,20 +34,25 @@ Page { } ListModel { - id: eventTemplateModel - ListElement { interfaceName: "temperaturesensor"; text: qsTr("When it's freezing..."); event: "freeze"} - ListElement { interfaceName: "battery"; text: qsTr("When the device runs out of battery..."); event: "lowBattery"} - ListElement { interfaceName: "weather"; text: qsTr("When it starts raining..."); event: "rain" } + id: generatedModel + ListElement { displayName: ""; eventTypeId: "" } } function buildInterface() { if (header.interfacesMode) { if (root.device) { + generatedModel.clear(); for (var i = 0; i < Interfaces.count; i++) { - if (deviceClass.interfaces.indexOf(Interfaces.get(i).name) >= 0) { - actualModel.append(Interfaces.get(i)) + var iface = Interfaces.get(i); + if (root.deviceClass.interfaces.indexOf(iface.name) >= 0) { + for (var j = 0; j < iface.eventTypes.count; j++) { + var ifaceEt = iface.eventTypes.get(j); + var dcEt = root.deviceClass.eventTypes.findByName(ifaceEt.name) + generatedModel.append({displayName: ifaceEt.displayName, eventTypeId: dcEt.id}) + } } } + listView.model = generatedModel } else if (root.eventDescriptor.interfaceName !== "") { listView.model = Interfaces.findByName(root.eventDescriptor.interfaceName).eventTypes } else { @@ -70,16 +75,17 @@ Page { onClicked: { if (header.interfacesMode) { if (root.device) { - print("selected:", model.event) - switch (model.event) { - case "lowBattery": - var eventType = root.deviceClass.eventTypes.findByName("batteryCritical") - root.eventDescriptor.eventTypeId = eventType.id; -// root.eventDescriptor.paramDescriptors.setParamDescriptor(eventType.paramTypes.get(0).paramTypeId, 0, ParamDescriptors.ValueOperatorLessOrEqual) + root.eventDescriptor.eventTypeId = model.eventTypeId; + var eventType = root.deviceClass.eventTypes.getEventType(model.eventTypeId) + if (eventType.paramTypes.count > 0) { + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectEventDescriptorParamsPage.qml"), {eventDescriptor: root.eventDescriptor}) + paramsPage.onBackPressed.connect(function() {pageStack.pop()}); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + } else { root.done(); - break; - default: - console.warn("FIXME: Unhandled interface event"); } } else if (root.eventDescriptor.interfaceName !== "") { root.eventDescriptor.interfaceEvent = model.name; diff --git a/mea/ui/magic/SelectEventDescriptorParamsPage.qml b/mea/ui/magic/SelectEventDescriptorParamsPage.qml index f84c42fe..271000e4 100644 --- a/mea/ui/magic/SelectEventDescriptorParamsPage.qml +++ b/mea/ui/magic/SelectEventDescriptorParamsPage.qml @@ -19,7 +19,7 @@ Page { signal completed(); header: GuhHeader { - text: "params" + text: "Options" onBackPressed: root.backPressed(); } @@ -60,11 +60,16 @@ Page { Layout.fillWidth: true Layout.margins: app.margins onClicked: { - var params = []; + root.eventDescriptor.paramDescriptors.clear(); for (var i = 0; i < delegateRepeater.count; i++) { var paramDelegate = delegateRepeater.itemAt(i); if (paramDelegate.considerParam) { - root.eventDescriptor.paramDescriptors.setParamDescriptor(paramDelegate.paramType.id, paramDelegate.value, paramDelegate.operatorType) + if (root.device) { + root.eventDescriptor.paramDescriptors.setParamDescriptor(paramDelegate.paramType.id, paramDelegate.value, paramDelegate.operatorType) + } else if (root.iface) { + print("setting param descriptors by name", root.eventType.paramTypes.get(i), root.eventType.paramTypes.get(i).name) + root.eventDescriptor.paramDescriptors.setParamDescriptorByName(root.eventType.paramTypes.get(i).name, paramDelegate.value, paramDelegate.operatorType) + } } } root.completed() diff --git a/mea/ui/magic/SelectRuleActionPage.qml b/mea/ui/magic/SelectRuleActionPage.qml index 78e179de..54009821 100644 --- a/mea/ui/magic/SelectRuleActionPage.qml +++ b/mea/ui/magic/SelectRuleActionPage.qml @@ -26,38 +26,39 @@ Page { id: header onBackPressed: root.backPressed(); - property bool interfacesMode: false//root.ruleAction.interfaceName !== "" + property bool interfacesMode: root.ruleAction.interfaceName !== "" onInterfacesModeChanged: root.buildInterface() HeaderButton { imageSource: header.interfacesMode ? "../images/view-expand.svg" : "../images/view-collapse.svg" - visible: root.ruleAction.deviceId || root.ruleAction.interfaceName === "" + visible: root.ruleAction.interfaceName === "" onClicked: header.interfacesMode = !header.interfacesMode } } -// ListModel { -// id: actionTemplateModel -// ListElement { interfaceName: "light"; text: "Switch light"; identifier: "switchLight"} -// ListElement { interfaceName: "dimmablelight"; text: "Dim light"; identifier: "dimLight"} -// ListElement { interfaceName: "colorlight"; text: "Set light color"; identifier: "colorLight" } -// ListElement { interfaceName: "mediacontroller"; text: "Pause playback"; identifier: "pausePlayback" } -// ListElement { interfaceName: "mediacontroller"; text: "Resume playback"; identifier: "resumePlayback" } -// ListElement { interfaceName: "extendedvolumecontroller"; text: "Set volume"; identifier: "setVolume" } -// ListElement { interfaceName: "extendedvolumecontroller"; text: "Mute"; identifier: "mute" } -// ListElement { interfaceName: "extendedvolumecontroller"; text: "Unmute"; identifier: "unmute" } -// ListElement { interfaceName: "notifications"; text: "Notify me"; identifier: "notify" } -// } + ListModel { + id: generatedModel + ListElement { displayName: ""; actionTypeId: "" } + } function buildInterface() { + print("building iface", root.ruleAction, root.ruleAction.interfaceName, header.interfacesMode, root.ruleAction.interfaceName === "") if (header.interfacesMode) { if (root.device) { + generatedModel.clear(); for (var i = 0; i < Interfaces.count; i++) { - if (deviceClass.interfaces.indexOf(Interfaces.get(i).interfaceName) >= 0) { - actualModel.append(Interfaces.get(i)) + var iface = Interfaces.get(i); + if (root.deviceClass.interfaces.indexOf(iface.name) >= 0) { + for (var j = 0; j < iface.actionTypes.count; j++) { + var ifaceAt = iface.actionTypes.get(j); + var dcAt = root.deviceClass.actionTypes.findByName(ifaceAt.name) + generatedModel.append({displayName: ifaceAt.displayName, actionTypeId: dcAt.id}) + } } } + listView.model = generatedModel } else if (root.ruleAction.interfaceName !== "") { + print("showing actions for interface", root.ruleAction.interfaceName) listView.model = Interfaces.findByName(root.ruleAction.interfaceName).actionTypes } else { console.warn("You need to set device or interfaceName"); @@ -79,13 +80,17 @@ Page { onClicked: { if (header.interfacesMode) { if (root.device) { - print("selected:", model.identifier) - switch (model.identfier) { - case "switchLight": + root.ruleAction.actionTypeId = model.actionTypeId; + var actionType = root.deviceClass.actionTypes.getActionType(model.actionTypeId) + if (actionType.paramTypes.count > 0) { + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionParamsPage.qml"), {ruleAction: root.ruleAction, rule: root.rule}) + paramsPage.onBackPressed.connect(function() {pageStack.pop()}); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + } else { root.done(); - break; - default: - console.warn("FIXME: Unhandled interface action"); } } else if (root.ruleAction.interfaceName !== "") { root.ruleAction.interfaceAction = model.name; diff --git a/mea/ui/magic/SelectRuleActionParamsPage.qml b/mea/ui/magic/SelectRuleActionParamsPage.qml index 7f8b09ac..b41b0844 100644 --- a/mea/ui/magic/SelectRuleActionParamsPage.qml +++ b/mea/ui/magic/SelectRuleActionParamsPage.qml @@ -10,7 +10,7 @@ Page { // Needs to be set and have rule.ruleActions filled in with deviceId and actionTypeId or interfaceName and interfaceAction property var ruleAction: null - // optionally a rule which will be used to propse event's params as param values + // optionally a rule which will be used to propose event's params as param values property var rule: null readonly property var device: ruleAction && ruleAction.deviceId ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null @@ -81,9 +81,9 @@ Page { model: EventDescriptorParamsFilterModel { id: eventDescriptorParamsFilterModel eventDescriptor: root.rule.eventDescriptors.count === 1 ? root.rule.eventDescriptors.get(0) : null - property var device: Engine.deviceManager.devices.getDevice(eventDescriptor.deviceId) - property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) - property var eventType: deviceClass.eventTypes.getEventType(eventDescriptor.eventTypeId) + property var device: eventDescriptor ? Engine.deviceManager.devices.getDevice(eventDescriptor.deviceId) : null + property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + property var eventType: deviceClass ? deviceClass.eventTypes.getEventType(eventDescriptor.eventTypeId) : null property var paramDescriptor: eventDescriptorParamsFilterModel.eventType.paramTypes.get(eventParamsComboBox.currentIndex) property var paramTypeId: paramDescriptor.id } @@ -116,7 +116,11 @@ Page { for (var i = 0; i < delegateRepeater.count; i++) { var paramDelegate = delegateRepeater.itemAt(i); if (paramDelegate.type === "static") { - root.ruleAction.ruleActionParams.setRuleActionParam(paramDelegate.paramType.id, paramDelegate.value) + if (root.device) { + root.ruleAction.ruleActionParams.setRuleActionParam(paramDelegate.paramType.id, paramDelegate.value) + } else if (root.iface) { + root.ruleAction.ruleActionParams.setRuleActionParamByName(root.actionType.paramTypes.get(i).name, paramDelegate.value) + } } else if (paramDelegate.type === "event") { print("adding event based rule action param", paramDelegate.paramType.id, paramDelegate.eventType.id, paramDelegate.eventParamTypeId) root.ruleAction.ruleActionParams.setRuleActionParamEvent(paramDelegate.paramType.id, paramDelegate.eventType.id, paramDelegate.eventParamTypeId) diff --git a/mea/ui/magic/SelectStateDescriptorPage.qml b/mea/ui/magic/SelectStateDescriptorPage.qml index b5cce51d..8a89c8b1 100644 --- a/mea/ui/magic/SelectStateDescriptorPage.qml +++ b/mea/ui/magic/SelectStateDescriptorPage.qml @@ -16,29 +16,105 @@ Page { signal backPressed(); signal done(); + onStateDescriptorChanged: buildInterface() + Component.onCompleted: buildInterface() + header: GuhHeader { id: header onBackPressed: root.backPressed(); + + property bool interfacesMode: root.stateDescriptor && root.stateDescriptor.interfaceName && root.stateDescriptor.interfaceName.length > 0 + onInterfacesModeChanged: root.buildInterface() + + HeaderButton { + imageSource: header.interfacesMode ? "../images/view-expand.svg" : "../images/view-collapse.svg" + visible: root.stateDescriptor && root.stateDescriptor.interfaceName.length === 0 + onClicked: header.interfacesMode = !header.interfacesMode + } + } + + ListModel { + id: generatedModel + ListElement { displayName: ""; stateTypeId: "" } + } + + function buildInterface() { + print("building interface:", header.interfacesMode, root.stateDescriptor, root.stateDescriptor.interfaceName) + if (header.interfacesMode) { + if (root.device) { + generatedModel.clear(); + for (var i = 0; i < Interfaces.count; i++) { + var iface = Interfaces.get(i); + if (root.deviceClass.interfaces.indexOf(iface.name) >= 0) { + print("root has device class:", iface.name, iface.stateTypes.count) + for (var j = 0; j < iface.stateTypes.count; j++) { + var ifaceSt = iface.stateTypes.get(j); + print("ifaceSt:", ifaceSt, j, iface.stateTypes.count) + var dcSt = root.deviceClass.stateTypes.findByName(ifaceSt.name) + print("adding:", ifaceSt.displayName, dcSt.id) + generatedModel.append({displayName: ifaceSt.displayName, stateTypeId: dcSt.id}) + } + } + } + listView.model = generatedModel + } else if (root.stateDescriptor.interfaceName !== "") { + listView.model = Interfaces.findByName(root.stateDescriptor.interfaceName).stateTypes + } else { + console.warn("You need to set device or interfaceName"); + } + } else { + if (root.device) { + listView.model = deviceClass.stateTypes; + } + } } ListView { id: listView anchors.fill: parent - model: root.deviceClass.stateTypes delegate: ItemDelegate { text: model.displayName width: parent.width onClicked: { - var stateType = root.deviceClass.stateTypes.getStateType(model.id); - console.log("StateType", stateType.id, "selected.") - root.stateDescriptor.stateTypeId = stateType.id; - var paramsPage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorParamsPage.qml"), {stateDescriptor: root.stateDescriptor}) - paramsPage.onBackPressed.connect(function() { pageStack.pop(); }); - paramsPage.onCompleted.connect(function() { - pageStack.pop(); - root.done(); - }) + if (header.interfacesMode) { + if (root.device) { + print("selected:", model.stateTypeId) + root.stateDescriptor.stateTypeId = model.stateTypeId; + var stateType = root.deviceClass.stateTypes.getStateType(model.stateTypeId) + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorParamsPage.qml"), {stateDescriptor: root.stateDescriptor}) + paramsPage.onBackPressed.connect(function() {pageStack.pop()}); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + } else if (root.stateDescriptor.interfaceName !== "") { + root.stateDescriptor.interfaceState = model.name; + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorParamsPage.qml"), {stateDescriptor: root.stateDescriptor}) + paramsPage.onBackPressed.connect(function() {pageStack.pop()}); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + } else { + console.warn("Neither deviceId not interfaceName set. Cannot continue..."); + } + } else { + if (root.device) { + var stateType = root.deviceClass.stateTypes.getStateType(model.id); + root.stateDescriptor.stateTypeId = model.id; + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorParamsPage.qml"), {stateDescriptor: root.stateDescriptor}) + paramsPage.onBackPressed.connect(function() {pageStack.pop()}); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + + print("have type", stateType.id) + } else { + console.warn("FIXME: not implemented yet"); + } + } } } } diff --git a/mea/ui/magic/SelectStateDescriptorParamsPage.qml b/mea/ui/magic/SelectStateDescriptorParamsPage.qml index c85c7924..aef46289 100644 --- a/mea/ui/magic/SelectStateDescriptorParamsPage.qml +++ b/mea/ui/magic/SelectStateDescriptorParamsPage.qml @@ -11,13 +11,15 @@ Page { property var stateDescriptor: null readonly property var device: stateDescriptor && stateDescriptor.deviceId ? Engine.deviceManager.devices.getDevice(stateDescriptor.deviceId) : null - readonly property var stateType: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId).stateTypes.getStateType(stateDescriptor.stateTypeId) : null + readonly property var iface: stateDescriptor && stateDescriptor.interfaceName ? Interfaces.findByName(stateDescriptor.interfaceName) : null + readonly property var stateType: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId).stateTypes.getStateType(stateDescriptor.stateTypeId) + : iface ? iface.stateTypes.findByName(stateDescriptor.interfaceState) : null signal backPressed(); signal completed(); header: GuhHeader { - text: qsTr("params") + text: qsTr("Options") onBackPressed: root.backPressed(); } diff --git a/mea/ui/magic/SelectThingPage.qml b/mea/ui/magic/SelectThingPage.qml index 7cfeacac..5fd7ef48 100644 --- a/mea/ui/magic/SelectThingPage.qml +++ b/mea/ui/magic/SelectThingPage.qml @@ -8,45 +8,41 @@ import Mea 1.0 Page { id: root + property bool selectInterface: false signal backPressed(); signal thingSelected(var device); signal interfaceSelected(string interfaceName); + property alias showEvents: interfacesProxy.showEvents + property alias showActions: interfacesProxy.showActions + property alias showStates: interfacesProxy.showStates header: GuhHeader { - text: qsTr("Select a thing") + text: root.selectInterface ? qsTr("Select a kind of things") : qsTr("Select a thing") onBackPressed: root.backPressed() } + + InterfacesProxy { + id: interfacesProxy + devicesFilter: Engine.deviceManager.devices + } + ColumnLayout { anchors.fill: parent - RowLayout { - Layout.fillWidth: true - // TODO: unfinished, disabled for now - visible: false - RadioButton { - id: thingButton - text: qsTr("A specific thing") - checked: true - } - RadioButton { - id: interfacesButton - text: qsTr("A group of things") - } - } - ListView { Layout.fillWidth: true Layout.fillHeight: true - model: thingButton.checked ? Engine.deviceManager.devices : Interfaces + model: root.selectInterface ? interfacesProxy : Engine.deviceManager.devices clip: true - delegate: ThingDelegate { - name: thingButton.checked ? model.name : model.displayName - interfaces: model.interfaces + delegate: MeaListItemDelegate { + width: parent.width + text: root.selectInterface ? model.displayName : model.name + iconName: root.selectInterface ? app.interfaceToIcon(model.name) : app.interfacesToIcon(model.interfaces) onClicked: { - if (thingButton.checked) { - root.thingSelected(Engine.deviceManager.devices.get(index)) + if (root.selectInterface) { + root.interfaceSelected(interfacesProxy.get(index).name) } else { - root.interfaceSelected(Interfaces.get(index).name) + root.thingSelected(Engine.deviceManager.devices.get(index)) } } } diff --git a/mea/ui/magic/SimpleStateEvaluatorDelegate.qml b/mea/ui/magic/SimpleStateEvaluatorDelegate.qml index 8497cf2a..6c641f20 100644 --- a/mea/ui/magic/SimpleStateEvaluatorDelegate.qml +++ b/mea/ui/magic/SimpleStateEvaluatorDelegate.qml @@ -2,17 +2,23 @@ import QtQuick 2.8 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.2 import Mea 1.0 +import "../components" SwipeDelegate { id: root Layout.fillWidth: true + clip: true property var stateEvaluator: null property bool showChilds: false readonly property var device: stateEvaluator ? Engine.deviceManager.devices.getDevice(stateEvaluator.stateDescriptor.deviceId) : null readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null - readonly property var stateType: deviceClass ? deviceClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) : null + readonly property var iface: stateEvaluator ? Interfaces.findByName(stateEvaluator.stateDescriptor.interfaceName) : null + readonly property var stateType: deviceClass ? deviceClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) + : iface ? iface.stateTypes.findByName(stateEvaluator.stateDescriptor.interfaceState) : null + + signal deleteClicked(); Rectangle { anchors.fill: parent @@ -22,49 +28,24 @@ SwipeDelegate { } contentItem: ColumnLayout { - Label { + RowLayout { Layout.fillWidth: true - property string operatorString: { - if (!root.stateEvaluator) { - return ""; - } - - switch (root.stateEvaluator.stateDescriptor.valueOperator) { - case StateDescriptor.ValueOperatorEquals: - return "="; - case StateDescriptor.ValueOperatorNotEquals: - return "!="; - case StateDescriptor.ValueOperatorGreater: - return ">"; - case StateDescriptor.ValueOperatorGreaterOrEqual: - return ">="; - case StateDescriptor.ValueOperatorLess: - return "<"; - case StateDescriptor.ValueOperatorLessOrEqual: - return "<="; - } - return "FIXME" + ColorIcon { + Layout.preferredHeight: childEvaluatorsRepeater.count > 0 ? app.iconSize * .6 : app.iconSize + Layout.preferredWidth: height + name: root.stateEvaluator.stateDescriptor.interfaceName.length === 0 ? "../images/state.svg" : "../images/state-interface.svg" + color: app.guhAccent } - text: { - if (!root.device) { - return qsTr("Press to edit condition") - } - return qsTr("%1: %2 %3 %4").arg(root.device.name).arg(root.stateType.displayName).arg(operatorString).arg(root.stateEvaluator.stateDescriptor.value) - } - } - Repeater { - model: root.showChilds ? root.stateEvaluator.childEvaluators : null - delegate: Label { + Label { Layout.fillWidth: true - property var stateEvaluator: root.stateEvaluator.childEvaluators.get(index) - property var stateDescriptor: stateEvaluator.stateDescriptor - readonly property var device: Engine.deviceManager.devices.getDevice(stateDescriptor.deviceId) - readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) - readonly property var stateType: deviceClass.stateTypes.getStateType(stateDescriptor.stateTypeId) - + font.pixelSize: childEvaluatorsRepeater.count > 0 ? app.smallFont : app.mediumFont property string operatorString: { - switch (stateDescriptor.valueOperator) { + if (!root.stateEvaluator) { + return ""; + } + + switch (root.stateEvaluator.stateDescriptor.valueOperator) { case StateDescriptor.ValueOperatorEquals: return "="; case StateDescriptor.ValueOperatorNotEquals: @@ -80,8 +61,90 @@ SwipeDelegate { } return "FIXME" } - text: qsTr("%1 %2: %3 %4 %5%6").arg(root.stateEvaluator.stateOperator === StateEvaluator.StateOperatorAnd ? "and" : "or").arg(device.name).arg(stateType.displayName).arg(operatorString).arg(stateDescriptor.value).arg(stateEvaluator.childEvaluators.count > 0 ? "..." : "") + + text: { + if (!root.stateType) { + return qsTr("Press to edit condition") + } + if (root.device) { + return qsTr("%1: %2 %3 %4").arg(root.device.name).arg(root.stateType.displayName).arg(operatorString).arg(root.stateEvaluator.stateDescriptor.value) + } else if (root.iface) { + return qsTr("%1: %2 %3 %4").arg(root.iface.displayName).arg(root.stateType.displayName).arg(operatorString).arg(root.stateEvaluator.stateDescriptor.value) + } + return "--"; + } + } + } + + Repeater { + id: childEvaluatorsRepeater + model: root.showChilds ? root.stateEvaluator.childEvaluators : null + delegate: RowLayout { + id: childEvaluatorDelegate + Layout.fillWidth: true + + property var stateEvaluator: root.stateEvaluator.childEvaluators.get(index) + property var stateDescriptor: stateEvaluator.stateDescriptor + readonly property var device: Engine.deviceManager.devices.getDevice(stateDescriptor.deviceId) + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + readonly property var iface: Interfaces.findByName(stateEvaluator.stateDescriptor.interfaceName) + readonly property var stateType: device ? deviceClass.stateTypes.getStateType(stateDescriptor.stateTypeId) + : iface ? iface.stateTypes.findByName(stateEvaluator.stateDescriptor.interfaceState) + : null + + ColorIcon { + Layout.preferredHeight: app.iconSize * .6 + Layout.preferredWidth: height + name: childEvaluatorDelegate.stateDescriptor.interfaceName.length === 0 ? "../images/state.svg" : "../images/state-interface.svg" + color: app.guhAccent + } + Label { + font.pixelSize: app.smallFont + Layout.fillWidth: true + + property string operatorString: { + switch (childEvaluatorDelegate.stateDescriptor.valueOperator) { + case StateDescriptor.ValueOperatorEquals: + return "="; + case StateDescriptor.ValueOperatorNotEquals: + return "!="; + case StateDescriptor.ValueOperatorGreater: + return ">"; + case StateDescriptor.ValueOperatorGreaterOrEqual: + return ">="; + case StateDescriptor.ValueOperatorLess: + return "<"; + case StateDescriptor.ValueOperatorLessOrEqual: + return "<="; + } + return "FIXME" + } + text: device ? ("%1 %2: %3 %4 %5%6").arg(root.stateEvaluator.stateOperator === StateEvaluator.StateOperatorAnd ? "and" : "or").arg(childEvaluatorDelegate.device.name).arg(childEvaluatorDelegate.stateType.displayName).arg(operatorString).arg(childEvaluatorDelegate.stateDescriptor.value).arg(childEvaluatorDelegate.stateEvaluator.childEvaluators.count > 0 ? "..." : "") + : iface ? ("%1 %2: %3 %4 %5%6").arg(root.stateEvaluator.stateOperator === StateEvaluator.StateOperatorAnd ? "and" : "or").arg(childEvaluatorDelegate.iface.displayName).arg(childEvaluatorDelegate.stateType.displayName).arg(operatorString).arg(childEvaluatorDelegate.stateDescriptor.value).arg(childEvaluatorDelegate.stateEvaluator.childEvaluators.count > 0 ? "..." : "") + : "???" + } } } } + + swipe.right: MouseArea { + height: parent.height + width: height + anchors.right: parent.right + Rectangle { + anchors.fill: parent + color: "red" + } + + ColorIcon { + anchors.fill: parent + anchors.margins: app.margins + name: "../images/delete.svg" + color: "white" + } + onClicked: { + swipe.close() + root.deleteClicked(); + } + } } diff --git a/mea/ui/magic/StateEvaluatorDelegate.qml b/mea/ui/magic/StateEvaluatorDelegate.qml index e7904382..08cca33d 100644 --- a/mea/ui/magic/StateEvaluatorDelegate.qml +++ b/mea/ui/magic/StateEvaluatorDelegate.qml @@ -2,8 +2,9 @@ import QtQuick 2.8 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.2 import Mea 1.0 +import "../components" -SwipeDelegate { +ItemDelegate { id: root property var stateEvaluator: null @@ -11,19 +12,54 @@ SwipeDelegate { readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null readonly property var stateType: deviceClass ? deviceClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) : null + property bool canDelete: true + signal deleteClicked() + + function editStateDescriptor(interfaceMode) { + if (interfaceMode === undefined) { + interfaceMode = false; + } + + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {selectInterface: interfaceMode, showStates: true}); + page.backPressed.connect(function() { + pageStack.pop() + }) + page.thingSelected.connect(function(device) { + root.stateEvaluator.stateDescriptor.interfaceName = ""; + root.stateEvaluator.stateDescriptor.deviceId = device.id; + selectStateDescriptorData() + }); + page.interfaceSelected.connect(function(interfaceName) { + root.stateEvaluator.stateDescriptor.deviceId = ""; + root.stateEvaluator.stateDescriptor.interfaceName = interfaceName; + selectStateDescriptorData(); + }); + } + function editInterfaceStateDescriptor() { + editStateDescriptor(true) + } + function selectStateDescriptorData() { + var statePage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorPage.qml"), {text: "Select state", stateDescriptor: root.stateEvaluator.stateDescriptor}) + statePage.backPressed.connect(function() { + pageStack.pop(); + }) + statePage.done.connect(function() { + pageStack.pop(); + pageStack.pop(); + pageStack.pop(); + }) + } + contentItem: ColumnLayout { SimpleStateEvaluatorDelegate { Layout.fillWidth: true stateEvaluator: root.stateEvaluator + swipe.enabled: root.canDelete onClicked: { - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml")); - page.backPressed.connect(function() {pageStack.pop()}) - page.thingSelected.connect(function(device) { - root.stateEvaluator.stateDescriptor.deviceId = device.id - var statePage = pageStack.push(Qt.resolvedUrl("SelectStateDescriptorPage.qml"), {text: "Select state", stateDescriptor: root.stateEvaluator.stateDescriptor}) - statePage.backPressed.connect(function() {pageStack.pop()}) - statePage.done.connect(function() {pageStack.pop(); pageStack.pop()}) - }) + var page = pageStack.push(stateQuestionPageComponent); + } + onDeleteClicked: { + root.deleteClicked() } } @@ -46,6 +82,9 @@ SwipeDelegate { onClicked: { pageStack.push(Qt.resolvedUrl("EditStateEvaluatorPage.qml"), {stateEvaluator: stateEvaluator}) } + onDeleteClicked: { + root.stateEvaluator.childEvaluators.remove(index) + } } } @@ -54,7 +93,48 @@ SwipeDelegate { text: qsTr("Add a condition") onClicked: { root.stateEvaluator.addChildEvaluator() - // root.editStateEvaluator() + } + } + } + + Component { + id: stateQuestionPageComponent + Page { + header: GuhHeader { + text: qsTr("Edit condition...") + + onBackPressed: pageStack.pop() + } + + ColumnLayout { + anchors.fill: parent + + Repeater { + model: ListModel { + ListElement { + iconName: "../images/state.svg" + text: qsTr("When one of my things is in a certain state") + method: "editStateDescriptor" + + } + ListElement { + iconName: "../images/state-interface.svg" + text: qsTr("When a thing of a given type enters a state") + method: "editInterfaceStateDescriptor" + } + } + delegate: MeaListItemDelegate { + Layout.fillWidth: true + iconName: model.iconName + text: model.text + progressive: true + iconSize: app.iconSize * 2 + + onClicked: { + root[model.method]() + } + } + } } } } diff --git a/mea/ui/magic/TimeEventDelegate.qml b/mea/ui/magic/TimeEventDelegate.qml index b5e70b9b..b7240c00 100644 --- a/mea/ui/magic/TimeEventDelegate.qml +++ b/mea/ui/magic/TimeEventDelegate.qml @@ -4,9 +4,11 @@ import QtQuick.Layouts 1.3 import Mea 1.0 import "../components" -SwipeDelegate { +MeaListItemDelegate{ id: root implicitHeight: app.delegateHeight + progressive: false + canDelete: true property var timeEventItem: null @@ -15,88 +17,7 @@ SwipeDelegate { signal removeTimeEventItem(); - contentItem: RowLayout { - spacing: app.margins - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: app.iconSize - name: "../images/alarm-clock.svg" - color: app.guhAccent - } - - ColumnLayout { - - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: qsTr("At %1").arg(root.isDateBased ? Qt.formatDateTime(root.timeEventItem.dateTime) : Qt.formatTime(root.timeEventItem.time)) - } - - Label { - Layout.fillWidth: true - text: qsTr("repeated %1").arg(repeatingString) - elide: Text.ElideRight - font.pixelSize: app.smallFont - - property string repeatingString: { - switch (root.timeEventItem.repeatingOption.repeatingMode) { - case RepeatingOption.RepeatingModeNone: - return qsTr("never"); - case RepeatingOption.RepeatingModeHourly: - return qsTr("hourly"); - case RepeatingOption.RepeatingModeDaily: - return qsTr("daily"); - case RepeatingOption.RepeatingModeWeekly: - var weekdays = [] - for (var i = 0; i < root.timeEventItem.repeatingOption.weekDays.length; i++) { - switch (root.timeEventItem.repeatingOption.weekDays[i]) { - case 1: - weekdays.push(qsTr("Mon")); - break; - case 2: - weekdays.push(qsTr("Tue")); - break; - case 3: - weekdays.push(qsTr("Wed")); - break; - case 4: - weekdays.push(qsTr("Thu")); - break; - case 5: - weekdays.push(qsTr("Fri")); - break; - case 6: - weekdays.push(qsTr("Sat")); - break; - case 7: - weekdays.push(qsTr("Sun")); - break; - } - } - - return qsTr("weekly on %1").arg(weekdays.join(', ')); - case RepeatingOption.RepeatingModeMonthly: - return qsTr("monthly on the %1").arg(root.timeEventItem.repeatingOption.monthDays.join(', ')); - case RepeatingOption.RepeatingModeYearly: - return qsTr("every year"); - } - } - } - } - } - - swipe.right: MouseArea { - height: root.height - width: height - anchors.right: parent.right - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/delete.svg" - color: "red" - } - onClicked: root.removeTimeEventItem() - } + onDeleteClicked: root.removeTimeEventItem() onClicked: { var page = pageStack.push(Qt.resolvedUrl("EditTimeEventItemPage.qml"), {timeEventItem: root.timeEventItem}) @@ -106,4 +27,52 @@ SwipeDelegate { print("timeeventItem.time is now", root.timeEventItem.time) }) } + + iconName: "../images/alarm-clock.svg" + text: qsTr("At %1").arg(root.isDateBased ? Qt.formatDateTime(root.timeEventItem.dateTime) : Qt.formatTime(root.timeEventItem.time)) + subText: qsTr("repeated %1").arg(repeatingString) + + property string repeatingString: { + switch (root.timeEventItem.repeatingOption.repeatingMode) { + case RepeatingOption.RepeatingModeNone: + return qsTr("never"); + case RepeatingOption.RepeatingModeHourly: + return qsTr("hourly"); + case RepeatingOption.RepeatingModeDaily: + return qsTr("daily"); + case RepeatingOption.RepeatingModeWeekly: + var weekdays = [] + for (var i = 0; i < root.timeEventItem.repeatingOption.weekDays.length; i++) { + switch (root.timeEventItem.repeatingOption.weekDays[i]) { + case 1: + weekdays.push(qsTr("Mon")); + break; + case 2: + weekdays.push(qsTr("Tue")); + break; + case 3: + weekdays.push(qsTr("Wed")); + break; + case 4: + weekdays.push(qsTr("Thu")); + break; + case 5: + weekdays.push(qsTr("Fri")); + break; + case 6: + weekdays.push(qsTr("Sat")); + break; + case 7: + weekdays.push(qsTr("Sun")); + break; + } + } + + return qsTr("weekly on %1").arg(weekdays.join(', ')); + case RepeatingOption.RepeatingModeMonthly: + return qsTr("monthly on the %1").arg(root.timeEventItem.repeatingOption.monthDays.join(', ')); + case RepeatingOption.RepeatingModeYearly: + return qsTr("every year"); + } + } } diff --git a/mea/ui/system/AboutNymeaPage.qml b/mea/ui/system/AboutNymeaPage.qml new file mode 100644 index 00000000..d819cf3e --- /dev/null +++ b/mea/ui/system/AboutNymeaPage.qml @@ -0,0 +1,39 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Mea 1.0 +import "../components" + +Page { + + id: root + header: GuhHeader { + text: qsTr("About %1").arg(app.systemName) + onBackPressed: pageStack.pop() + } + + ColumnLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Server UUID:") + subText: Engine.jsonRpcClient.serverUuid + progressive: false + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Server version:") + subText: Engine.jsonRpcClient.serverVersion + progressive: false + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Protocol version:") + subText: Engine.jsonRpcClient.jsonRpcVersion + progressive: false + } + } +} diff --git a/mea/ui/system/PluginsPage.qml b/mea/ui/system/PluginsPage.qml index aa7c7f84..3dcaf7e4 100644 --- a/mea/ui/system/PluginsPage.qml +++ b/mea/ui/system/PluginsPage.qml @@ -18,21 +18,11 @@ Page { model: Engine.deviceManager.plugins clip: true - delegate: ItemDelegate { + delegate: MeaListItemDelegate { width: parent.width - contentItem: RowLayout { - Label { - Layout.fillWidth: true - text: model.name - } - Image { - source: "../images/next.svg" - Layout.preferredHeight: parent.height - Layout.preferredWidth: height - } - } + iconName: "../images/plugin.svg" + text: model.name onClicked: pageStack.push(Qt.resolvedUrl("PluginParamsPage.qml"), {plugin: Engine.deviceManager.plugins.get(index)}) } } - }