Allow multiple device selection for rule templates

This commit is contained in:
Michael Zanetti 2019-10-18 17:23:10 +02:00
parent 69f2f61df5
commit 629290767f
10 changed files with 147 additions and 27 deletions

View File

@ -355,6 +355,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
// check if this is a notification // check if this is a notification
if (dataMap.contains("notification")) { if (dataMap.contains("notification")) {
// qDebug() << "Incoming notification:" << jsonDoc.toJson();
QStringList notification = dataMap.value("notification").toString().split("."); QStringList notification = dataMap.value("notification").toString().split(".");
QString nameSpace = notification.first(); QString nameSpace = notification.first();
JsonHandler *handler = m_notificationHandlers.value(nameSpace).first; JsonHandler *handler = m_notificationHandlers.value(nameSpace).first;

View File

@ -449,7 +449,7 @@ QVariantMap JsonTypes::packTimeEventItem(TimeEventItem *timeEventItem)
ret.insert("time", timeEventItem->time().toString("hh:mm")); ret.insert("time", timeEventItem->time().toString("hh:mm"));
} }
if (!timeEventItem->dateTime().isNull()) { if (!timeEventItem->dateTime().isNull()) {
ret.insert("dateTime", timeEventItem->dateTime().toSecsSinceEpoch()); ret.insert("datetime", timeEventItem->dateTime().toSecsSinceEpoch());
} }
ret.insert("repeating", packRepeatingOption(timeEventItem->repeatingOption())); ret.insert("repeating", packRepeatingOption(timeEventItem->repeatingOption()));
return ret; return ret;

View File

@ -62,6 +62,7 @@ void RuleManager::addRule(const QVariantMap params)
void RuleManager::addRule(Rule *rule) void RuleManager::addRule(Rule *rule)
{ {
QVariantMap params = JsonTypes::packRule(rule); QVariantMap params = JsonTypes::packRule(rule);
qDebug() << "packed rule:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented));
m_jsonClient->sendCommand("Rules.AddRule", params, this, "onAddRuleReply"); m_jsonClient->sendCommand("Rules.AddRule", params, this, "onAddRuleReply");
} }

View File

@ -18,6 +18,7 @@ public:
enum SelectionMode { enum SelectionMode {
SelectionModeAny, SelectionModeAny,
SelectionModeDevice, SelectionModeDevice,
SelectionModeDevices,
SelectionModeInterface SelectionModeInterface
}; };
Q_ENUM(SelectionMode) Q_ENUM(SelectionMode)

View File

@ -196,5 +196,6 @@
<file>ui/devicepages/DoorbellDevicePage.qml</file> <file>ui/devicepages/DoorbellDevicePage.qml</file>
<file>ui/magic/NewMagicPage.qml</file> <file>ui/magic/NewMagicPage.qml</file>
<file>ui/components/WebViewWrapper.qml</file> <file>ui/components/WebViewWrapper.qml</file>
<file>ui/magic/NewScenePage.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -1,7 +1,7 @@
{ {
"templates": [ "templates": [
{ {
"description": "Turn on a light", "description": "Turn on lights",
"ruleNameTemplate": "%0 turns on %1", "ruleNameTemplate": "%0 turns on %1",
"eventDescriptorTemplates": [ "eventDescriptorTemplates": [
{ {
@ -15,7 +15,7 @@
"interfaceName": "light", "interfaceName": "light",
"interfaceAction": "power", "interfaceAction": "power",
"selectionId": 1, "selectionId": 1,
"selectionMode": "SelectionModeDevice", "selectionMode": "SelectionModeDevices",
"params": [ "params": [
{ {
"name": "power", "name": "power",
@ -26,7 +26,7 @@
] ]
}, },
{ {
"description": "Turn off a light", "description": "Turn off lights",
"ruleNameTemplate": "%0 turns off %1", "ruleNameTemplate": "%0 turns off %1",
"eventDescriptorTemplates": [ "eventDescriptorTemplates": [
{ {
@ -40,7 +40,7 @@
"interfaceName": "light", "interfaceName": "light",
"interfaceAction": "power", "interfaceAction": "power",
"selectionId": 1, "selectionId": 1,
"selectionMode": "SelectionModeDevice", "selectionMode": "SelectionModeDevices",
"params": [ "params": [
{ {
"name": "power", "name": "power",

View File

@ -275,8 +275,8 @@ Page {
if (engine.deviceManager.devices.count === 0) { if (engine.deviceManager.devices.count === 0) {
pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml")) pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml"))
} else { } else {
var page = pageStack.push(Qt.resolvedUrl("MagicPage.qml")) var page = pageStack.push(Qt.resolvedUrl("magic/NewScenePage.qml"))
page.addRule() // page.addRule()
} }
} }
} }

View File

@ -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
}
}
}

View File

@ -24,6 +24,7 @@ Page {
QtObject { QtObject {
id: d id: d
property var selectedThings: ({}) property var selectedThings: ({})
property var selectedThingNames: ({})
property var selectedInterfaces: ({}) property var selectedInterfaces: ({})
function fillRuleFromTemplate(rule, ruleTemplate) { function fillRuleFromTemplate(rule, ruleTemplate) {
@ -44,6 +45,7 @@ Page {
if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0) { 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) print("Root device is matching and not used. Using for selectionId", eventDescriptorTemplate.selectionId, ":", root.device.name)
selectedThings[eventDescriptorTemplate.selectionId] = root.device.id; selectedThings[eventDescriptorTemplate.selectionId] = root.device.id;
selectedThingNames[eventDescriptorTemplate.selectionId] = root.device.name;
createEventDescriptor(rule, ruleTemplate, root.device, eventDescriptorTemplate) createEventDescriptor(rule, ruleTemplate, root.device, eventDescriptorTemplate)
return; return;
} }
@ -53,6 +55,7 @@ Page {
var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]}); var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]});
page.thingSelected.connect(function(device) { page.thingSelected.connect(function(device) {
selectedThings[eventDescriptorTemplate.selectionId] = device.id; selectedThings[eventDescriptorTemplate.selectionId] = device.id;
selectedThingNames[eventDescriptorTemplate.selectionId] = device.name;
createEventDescriptor(rule, ruleTemplate, device, eventDescriptorTemplate) createEventDescriptor(rule, ruleTemplate, device, eventDescriptorTemplate)
return; return;
}) })
@ -98,7 +101,7 @@ Page {
if (ruleActionTemplate.selectionId in selectedThings) { if (ruleActionTemplate.selectionId in selectedThings) {
var device = engine.deviceManager.devices.getDevice(selectedThings[ruleActionTemplate.selectionId]); var device = engine.deviceManager.devices.getDevice(selectedThings[ruleActionTemplate.selectionId]);
print("Already have a device for selectionId", ruleActionTemplate.selectionId, ":", device.name) 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; return;
} }
@ -106,18 +109,29 @@ Page {
if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0) { 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) print("Root device is matching and not used. Using for selectionId", ruleActionTemplate.selectionId, ":", root.device.name)
selectedThings[ruleActionTemplate.selectionId] = root.device.id; 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; return;
} }
// Ok, we need to pick a thing // Ok, we need to pick a thing
// print("Need to select a thing.") var multipleSelection = ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeDevices;
var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName]}); var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName], multipleSelection: multipleSelection});
page.thingSelected.connect(function(device) { page.thingSelected.connect(function(device) {
selectedThings[ruleActionTemplate.selectionId] = device.id; selectedThings[ruleActionTemplate.selectionId] = device.id;
createRuleAction(rule, ruleTemplate, rule.actions, device, ruleActionTemplate) selectedThingNames[ruleActionTemplate.selectionId] = device.name;
return; 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();}) page.backPressed.connect(function() {rule.destroy(); root.done();})
return; return;
} }
@ -144,35 +158,45 @@ Page {
// Did we pick a thing for this index before? // Did we pick a thing for this index before?
if (ruleExitActionTemplate.selectionId in selectedThings) { if (ruleExitActionTemplate.selectionId in selectedThings) {
var device = engine.deviceManager.devices.getDevice(selectedThings[ruleExitActionTemplate.selectionId]); var device = engine.deviceManager.devices.getDevice(selectedThings[ruleExitActionTemplate.selectionId]);
createRuleAction(rule, ruleTemplate, rule.exitActions, device, ruleExitActionTemplate); createRuleAction(rule, ruleTemplate, rule.exitActions, [device], ruleExitActionTemplate);
return; return;
} }
// Did we already use the thing we opened this page from? // 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) { if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0) {
selectedThings[ruleExitActionTemplate.selectionId] = root.device.id; 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; return;
} }
// Ok, we need to pick a thing // 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) { page.thingSelected.connect(function(device) {
selectedThings[ruleExitActionTemplate.selectionId] = device.id; 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; 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();}) page.backPressed.connect(function() {rule.destroy(); root.done();})
return; 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; rule.name = ruleTemplate.ruleNameTemplate;
for (var selectionId in selectedThings) { for (var selectionId in selectedThingNames) {
print("Replacing", selectionId, "with", selectedThings[selectionId], selectedInterfaces[selectionId]) print("Replacing", selectionId, "with", selectedThingNames[selectionId], selectedInterfaces[selectionId])
var device = engine.deviceManager.devices.getDevice(selectedThings[selectionId]); rule.name = rule.name.replace("%" + selectionId, selectedThingNames[selectionId])
rule.name = rule.name.replace("%" + selectionId, device.name)
for (var j = 0; j < rule.actions.count; j++) { for (var j = 0; j < rule.actions.count; j++) {
var action = rule.actions.get(j); var action = rule.actions.get(j);
@ -238,6 +262,7 @@ Page {
stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator;
stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value;
selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.id; selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.id;
selectedThingNames[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = root.device.name;
fillRuleFromTemplate(rule, ruleTemplate); fillRuleFromTemplate(rule, ruleTemplate);
return true; return true;
} }
@ -251,6 +276,7 @@ Page {
stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator;
stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value;
selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.id; selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.id;
selectedThingNames[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] = device.name;
fillRuleFromTemplate(rule, ruleTemplate) fillRuleFromTemplate(rule, ruleTemplate)
}) })
page.onAnySelected.connect(function() { page.onAnySelected.connect(function() {
@ -314,7 +340,13 @@ Page {
fillRuleFromTemplate(rule, ruleTemplate); 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 ruleAction = ruleActions.createNewRuleAction();
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
@ -369,14 +401,22 @@ Page {
paramsPage.onCompleted.connect(function() { paramsPage.onCompleted.connect(function() {
pageStack.pop(); pageStack.pop();
ruleActions.addRuleAction(ruleAction); ruleActions.addRuleAction(ruleAction);
fillRuleFromTemplate(rule, ruleTemplate); if (devices.length === 0) {
fillRuleFromTemplate(rule, ruleTemplate);
} else {
createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate)
}
}) })
return; return;
} }
} }
ruleActions.addRuleAction(ruleAction); ruleActions.addRuleAction(ruleAction);
fillRuleFromTemplate(rule, ruleTemplate); if (devices.length === 0) {
fillRuleFromTemplate(rule, ruleTemplate);
} else {
createRuleAction(rule, ruleTemplate, rule.actions, devices, ruleActionTemplate)
}
} }
function deviceIsUsed(deviceId) { function deviceIsUsed(deviceId) {

View File

@ -14,9 +14,11 @@ Page {
property alias showStates: interfacesProxy.showStates property alias showStates: interfacesProxy.showStates
property alias shownInterfaces: devicesProxy.shownInterfaces property alias shownInterfaces: devicesProxy.shownInterfaces
property bool allowSelectAny: false property bool allowSelectAny: false
property bool multipleSelection: false
signal backPressed(); signal backPressed();
signal thingSelected(var device); signal thingSelected(var device);
signal thingsSelected(var devices);
signal interfaceSelected(string interfaceName); signal interfaceSelected(string interfaceName);
signal anySelected(); signal anySelected();
@ -66,12 +68,24 @@ Page {
} }
ThinDivider { visible: root.allowSelectAny } ThinDivider { visible: root.allowSelectAny }
GroupedListView { GroupedListView {
id: listView
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
model: root.selectInterface ? interfacesProxy : devicesProxy model: root.selectInterface ? interfacesProxy : devicesProxy
clip: true 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 { delegate: NymeaListItemDelegate {
width: parent.width width: parent.width
text: root.selectInterface ? model.displayName : model.name text: root.selectInterface ? model.displayName : model.name
@ -79,10 +93,39 @@ Page {
onClicked: { onClicked: {
if (root.selectInterface) { if (root.selectInterface) {
root.interfaceSelected(interfacesProxy.get(index).name) root.interfaceSelected(interfacesProxy.get(index).name)
} else { } else if (!root.multipleSelection) {
root.thingSelected(devicesProxy.get(index)) 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)
} }
} }
} }