diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index 7fddca5a..9bcd7c76 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -355,6 +355,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // check if this is a notification if (dataMap.contains("notification")) { +// qDebug() << "Incoming notification:" << jsonDoc.toJson(); QStringList notification = dataMap.value("notification").toString().split("."); QString nameSpace = notification.first(); JsonHandler *handler = m_notificationHandlers.value(nameSpace).first; diff --git a/libnymea-app-core/jsonrpc/jsontypes.cpp b/libnymea-app-core/jsonrpc/jsontypes.cpp index ef405dd3..26c46400 100644 --- a/libnymea-app-core/jsonrpc/jsontypes.cpp +++ b/libnymea-app-core/jsonrpc/jsontypes.cpp @@ -449,7 +449,7 @@ QVariantMap JsonTypes::packTimeEventItem(TimeEventItem *timeEventItem) ret.insert("time", timeEventItem->time().toString("hh:mm")); } if (!timeEventItem->dateTime().isNull()) { - ret.insert("dateTime", timeEventItem->dateTime().toSecsSinceEpoch()); + ret.insert("datetime", timeEventItem->dateTime().toSecsSinceEpoch()); } ret.insert("repeating", packRepeatingOption(timeEventItem->repeatingOption())); return ret; diff --git a/libnymea-app-core/rulemanager.cpp b/libnymea-app-core/rulemanager.cpp index 885232cd..3c3a495b 100644 --- a/libnymea-app-core/rulemanager.cpp +++ b/libnymea-app-core/rulemanager.cpp @@ -62,6 +62,7 @@ void RuleManager::addRule(const QVariantMap params) void RuleManager::addRule(Rule *rule) { QVariantMap params = JsonTypes::packRule(rule); + qDebug() << "packed rule:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); m_jsonClient->sendCommand("Rules.AddRule", params, this, "onAddRuleReply"); } diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.h b/libnymea-app-core/ruletemplates/ruleactiontemplate.h index 95e857c5..960c53a0 100644 --- a/libnymea-app-core/ruletemplates/ruleactiontemplate.h +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.h @@ -18,6 +18,7 @@ public: enum SelectionMode { SelectionModeAny, SelectionModeDevice, + SelectionModeDevices, SelectionModeInterface }; Q_ENUM(SelectionMode) diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index f49a7e82..7a3e5967 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -196,5 +196,6 @@ ui/devicepages/DoorbellDevicePage.qml ui/magic/NewMagicPage.qml ui/components/WebViewWrapper.qml + ui/magic/NewScenePage.qml diff --git a/nymea-app/ruletemplates/buttontemplates.json b/nymea-app/ruletemplates/buttontemplates.json index 16a147d7..ff36b868 100644 --- a/nymea-app/ruletemplates/buttontemplates.json +++ b/nymea-app/ruletemplates/buttontemplates.json @@ -1,7 +1,7 @@ { "templates": [ { - "description": "Turn on a light", + "description": "Turn on lights", "ruleNameTemplate": "%0 turns on %1", "eventDescriptorTemplates": [ { @@ -15,7 +15,7 @@ "interfaceName": "light", "interfaceAction": "power", "selectionId": 1, - "selectionMode": "SelectionModeDevice", + "selectionMode": "SelectionModeDevices", "params": [ { "name": "power", @@ -26,7 +26,7 @@ ] }, { - "description": "Turn off a light", + "description": "Turn off lights", "ruleNameTemplate": "%0 turns off %1", "eventDescriptorTemplates": [ { @@ -40,7 +40,7 @@ "interfaceName": "light", "interfaceAction": "power", "selectionId": 1, - "selectionMode": "SelectionModeDevice", + "selectionMode": "SelectionModeDevices", "params": [ { "name": "power", diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index f9dc09cf..d44d6ac6 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -275,8 +275,8 @@ Page { if (engine.deviceManager.devices.count === 0) { pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml")) } else { - var page = pageStack.push(Qt.resolvedUrl("MagicPage.qml")) - page.addRule() + var page = pageStack.push(Qt.resolvedUrl("magic/NewScenePage.qml")) +// page.addRule() } } } diff --git a/nymea-app/ui/magic/NewScenePage.qml b/nymea-app/ui/magic/NewScenePage.qml new file mode 100644 index 00000000..cdd0b77c --- /dev/null +++ b/nymea-app/ui/magic/NewScenePage.qml @@ -0,0 +1,33 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 +import "../components" + +Page { + id: root + + header: NymeaHeader { + text: qsTr("New scene") + onBackPressed: pageStack.pop() + } + + signal done() + + + ListView { + anchors.fill: parent + model: RuleTemplatesFilterModel { + ruleTemplates: RuleTemplates {} + filterByDevices: DevicesProxy { + engine: _engine + } + + } + + delegate: NymeaListItemDelegate { + text: model.description + } + } + +} diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index 02fdbf41..acf029c0 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -24,6 +24,7 @@ Page { QtObject { id: d property var selectedThings: ({}) + property var selectedThingNames: ({}) property var selectedInterfaces: ({}) function fillRuleFromTemplate(rule, ruleTemplate) { @@ -44,6 +45,7 @@ Page { if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0) { print("Root device is matching and not used. Using for selectionId", eventDescriptorTemplate.selectionId, ":", root.device.name) selectedThings[eventDescriptorTemplate.selectionId] = root.device.id; + selectedThingNames[eventDescriptorTemplate.selectionId] = root.device.name; createEventDescriptor(rule, ruleTemplate, root.device, eventDescriptorTemplate) return; } @@ -53,6 +55,7 @@ Page { var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]}); page.thingSelected.connect(function(device) { selectedThings[eventDescriptorTemplate.selectionId] = device.id; + selectedThingNames[eventDescriptorTemplate.selectionId] = device.name; createEventDescriptor(rule, ruleTemplate, device, eventDescriptorTemplate) return; }) @@ -98,7 +101,7 @@ Page { if (ruleActionTemplate.selectionId in selectedThings) { var device = engine.deviceManager.devices.getDevice(selectedThings[ruleActionTemplate.selectionId]); print("Already have a device for selectionId", ruleActionTemplate.selectionId, ":", device.name) - createRuleAction(rule, ruleTemplate, rule.actions, device, ruleActionTemplate) + createRuleAction(rule, ruleTemplate, rule.actions, [device], ruleActionTemplate) return; } @@ -106,18 +109,29 @@ Page { if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0) { print("Root device is matching and not used. Using for selectionId", ruleActionTemplate.selectionId, ":", root.device.name) selectedThings[ruleActionTemplate.selectionId] = root.device.id; - createRuleAction(rule, ruleTemplate, rule.actions, root.device, ruleActionTemplate) + selectedThingNames[ruleActionTemplate.selectionId] = root.device.name; + createRuleAction(rule, ruleTemplate, rule.actions, [root.device], ruleActionTemplate) return; } // Ok, we need to pick a thing -// print("Need to select a thing.") - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName]}); + var multipleSelection = ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeDevices; + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName], multipleSelection: multipleSelection}); page.thingSelected.connect(function(device) { selectedThings[ruleActionTemplate.selectionId] = device.id; - createRuleAction(rule, ruleTemplate, rule.actions, device, ruleActionTemplate) - return; + selectedThingNames[ruleActionTemplate.selectionId] = device.name; + createRuleAction(rule, ruleTemplate, rule.actions, [device], ruleActionTemplate) }) + page.thingsSelected.connect(function(devices) { + var names = [] + for (var i = 0; i < devices.length; i++) { + names.push(devices[i].name) + } + selectedThingNames[ruleActionTemplate.selectionId] = names.join(", "); + + createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate) + }); + page.backPressed.connect(function() {rule.destroy(); root.done();}) return; } @@ -144,35 +158,45 @@ Page { // Did we pick a thing for this index before? if (ruleExitActionTemplate.selectionId in selectedThings) { var device = engine.deviceManager.devices.getDevice(selectedThings[ruleExitActionTemplate.selectionId]); - createRuleAction(rule, ruleTemplate, rule.exitActions, device, ruleExitActionTemplate); + createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate); return; } // Did we already use the thing we opened this page from? if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0) { selectedThings[ruleExitActionTemplate.selectionId] = root.device.id; - createRuleAction(rule, ruleTemplate, rule.exitActions, root.device, ruleExitActionTemplate); + selectedThingNames[ruleExitActionTemplate.selectionId] = root.device.name; + createRuleAction(rule, ruleTemplate, rule.exitActions, [root.device], ruleExitActionTemplate); return; } // Ok, we need to pick a thing - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleExitActionTemplate.interfaceName]}); + var multipleSelection = ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeDevices; + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleExitActionTemplate.interfaceName], multipleSelection: multipleSelection}); page.thingSelected.connect(function(device) { selectedThings[ruleExitActionTemplate.selectionId] = device.id; - createRuleAction(rule, ruleTemplate, rule.exitActions, device, ruleExitActionTemplate); + selectedThingNames[ruleExitActionTemplate.selectionId] = device.name; + createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate); return; }) + page.thingsSelected.connect(function(devices) { + var names = [] + for (var i = 0; i < devices.length; i++) { + names.push(devices[i].name) + } + selectedThingNames[ruleExitActionTemplate.selectionId] = devices.join(", "); + createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate); + }); page.backPressed.connect(function() {rule.destroy(); root.done();}) return; } - // Now replace %i in title and action params with selectedThings[i].name + // Now replace %i in title and action params with selectedThingNames[id] rule.name = ruleTemplate.ruleNameTemplate; - for (var selectionId in selectedThings) { - print("Replacing", selectionId, "with", selectedThings[selectionId], selectedInterfaces[selectionId]) - var device = engine.deviceManager.devices.getDevice(selectedThings[selectionId]); - rule.name = rule.name.replace("%" + selectionId, device.name) + for (var selectionId in selectedThingNames) { + print("Replacing", selectionId, "with", selectedThingNames[selectionId], selectedInterfaces[selectionId]) + rule.name = rule.name.replace("%" + selectionId, selectedThingNames[selectionId]) for (var j = 0; j < rule.actions.count; j++) { var action = rule.actions.get(j); @@ -238,6 +262,7 @@ Page { stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.id; + selectedThingNames[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.name; fillRuleFromTemplate(rule, ruleTemplate); return true; } @@ -251,6 +276,7 @@ Page { stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.id; + selectedThingNames[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.name; fillRuleFromTemplate(rule, ruleTemplate) }) page.onAnySelected.connect(function() { @@ -314,7 +340,13 @@ Page { fillRuleFromTemplate(rule, ruleTemplate); } - function createRuleAction(rule, ruleTemplate, ruleActions, device, ruleActionTemplate) { + function createRuleAction(rule, ruleTemplate, ruleActions, devices, ruleActionTemplate) { + + var device = devices.shift(); + // device param -> devices + // take first, run code + // at end, if devices is empty, continue with fillRuleFromTemplate otherwise run again + var ruleAction = ruleActions.createNewRuleAction(); var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); @@ -369,14 +401,22 @@ Page { paramsPage.onCompleted.connect(function() { pageStack.pop(); ruleActions.addRuleAction(ruleAction); - fillRuleFromTemplate(rule, ruleTemplate); + if (devices.length === 0) { + fillRuleFromTemplate(rule, ruleTemplate); + } else { + createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate) + } }) return; } } ruleActions.addRuleAction(ruleAction); - fillRuleFromTemplate(rule, ruleTemplate); + if (devices.length === 0) { + fillRuleFromTemplate(rule, ruleTemplate); + } else { + createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate) + } } function deviceIsUsed(deviceId) { diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index ee45c3ba..abb75083 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -14,9 +14,11 @@ Page { property alias showStates: interfacesProxy.showStates property alias shownInterfaces: devicesProxy.shownInterfaces property bool allowSelectAny: false + property bool multipleSelection: false signal backPressed(); signal thingSelected(var device); + signal thingsSelected(var devices); signal interfaceSelected(string interfaceName); signal anySelected(); @@ -66,12 +68,24 @@ Page { } ThinDivider { visible: root.allowSelectAny } - GroupedListView { + id: listView Layout.fillWidth: true Layout.fillHeight: true model: root.selectInterface ? interfacesProxy : devicesProxy clip: true + property var checkBoxCache: ({}) + function toggleCheckBoxCache(deviceId) { + var newCache = listView.checkBoxCache; + if (!newCache.hasOwnProperty(deviceId) || !newCache[deviceId]) { + newCache[deviceId] = true + } else { + newCache[deviceId] = false + } + listView.checkBoxCache = newCache; + print("new checked state;", newCache[deviceId]) + } + delegate: NymeaListItemDelegate { width: parent.width text: root.selectInterface ? model.displayName : model.name @@ -79,10 +93,39 @@ Page { onClicked: { if (root.selectInterface) { root.interfaceSelected(interfacesProxy.get(index).name) - } else { + } else if (!root.multipleSelection) { root.thingSelected(devicesProxy.get(index)) + } else { + listView.toggleCheckBoxCache(model.id) } } + progressive: !root.multipleSelection + + additionalItem: root.multipleSelection ? entryCheckBox : null + CheckBox { + id: entryCheckBox + height: parent.height + visible: root.multipleSelection + checked: listView.checkBoxCache.hasOwnProperty(model.id) && listView.checkBoxCache[model.id] + onClicked: listView.toggleCheckBoxCache(model.id) + } + } + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("OK") + visible: root.multipleSelection + onClicked: { + var devices = [] + for (var i = 0; i < devicesProxy.count; i++) { + var device = devicesProxy.get(i); + if (listView.checkBoxCache.hasOwnProperty(device.id) && listView.checkBoxCache[device.id]) { + devices.push(device) + } + } + root.thingsSelected(devices) } } }