diff --git a/libnymea-app/thingdiscovery.cpp b/libnymea-app/thingdiscovery.cpp index 0affa5e8..6d58626e 100644 --- a/libnymea-app/thingdiscovery.cpp +++ b/libnymea-app/thingdiscovery.cpp @@ -116,7 +116,10 @@ QList ThingDiscovery::discoverThingsByInterface(const QString &interfaceNam for (int i = 0; i < m_engine->thingManager()->thingClasses()->rowCount(); i++) { ThingClass *thingClass = m_engine->thingManager()->thingClasses()->get(i); - if (!thingClass->interfaces().contains(interfaceName)) { + if (!thingClass->interfaces().contains(interfaceName) && !thingClass->providedInterfaces().contains(interfaceName)) { + continue; + } + if (!thingClass->createMethods().contains("CreateMethodDiscovery")) { continue; } pendingCommands.append(discoverThingsInternal(thingClass->id())); diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 9f6268d9..9a0b7acf 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -274,5 +274,6 @@ ui/components/SettingsTile.qml ui/components/NymeaTextField.qml ui/system/TunnelProxyServerConfigurationDialog.qml + ui/delegates/StateDelegate.qml diff --git a/nymea-app/ruletemplates/mediatemplates.json b/nymea-app/ruletemplates/mediatemplates.json index 96984dcd..dc0b2278 100644 --- a/nymea-app/ruletemplates/mediatemplates.json +++ b/nymea-app/ruletemplates/mediatemplates.json @@ -106,48 +106,6 @@ } ] }, - { - "description": "Automatic night mode", - "ruleNameTemplate": "Automatic night mode on %0", - "timeDescriptorTemplate": { - "calendarItemTemplates": [ - { - "startTime": "22:00", - "duration": 600, - "repeatingOption": { - "repeatingMode": "RepeatingModeDaily" - }, - "editable": true - } - ] - }, - "ruleActionTemplates": [ - { - "interfaceName": "mediacontroller", - "interfaceAction": "nightMode", - "selectionId": 0, - "params": [ - { - "name": "nightMode", - "value": true - } - ] - } - ], - "ruleExitActionTemplates": [ - { - "interfaceName": "mediacontroller", - "interfaceAction": "nightMode", - "selectionId": 0, - "params": [ - { - "name": "nightMode", - "value": false - } - ] - } - ] - }, { "description": "Play/pause music by button press", "ruleNameTemplate": "%1 toggles play/pause on %0", diff --git a/nymea-app/ui/components/NymeaSwipeDelegate.qml b/nymea-app/ui/components/NymeaSwipeDelegate.qml index afaf3130..1e80e73f 100644 --- a/nymea-app/ui/components/NymeaSwipeDelegate.qml +++ b/nymea-app/ui/components/NymeaSwipeDelegate.qml @@ -39,6 +39,7 @@ SwipeDelegate { implicitWidth: 200 implicitHeight: Style.smallDelegateHeight + property string subText property bool progressive: true property bool canDelete: false @@ -199,6 +200,15 @@ SwipeDelegate { } } + swipe.enabled: { + for (var i = 0; i < d.finalContextOptions.length; i++) { + if (d.finalContextOptions[i].visible) { + return true + } + } + return false + } + swipe.right: swipeComponent Component { diff --git a/nymea-app/ui/delegates/ParamDescriptorDelegate.qml b/nymea-app/ui/delegates/ParamDescriptorDelegate.qml index 374ed996..d39ca541 100644 --- a/nymea-app/ui/delegates/ParamDescriptorDelegate.qml +++ b/nymea-app/ui/delegates/ParamDescriptorDelegate.qml @@ -37,14 +37,20 @@ import "../components" ItemDelegate { id: root - property var paramType: null + property ParamType paramType: null + property StateType stateType: null property var value: null property int operatorType: ParamDescriptors.ValueOperatorEquals + readonly property string type: paramType ? paramType.type.toLowerCase() : stateType ? stateType.type.toLowerCase() : "" + readonly property var minValue: paramType ? paramType.minValue : stateType ? stateType.minValue : undefined + readonly property var maxValue: paramType ? paramType.maxValue : stateType ? stateType.maxValue : undefined + readonly property var allowedValues: paramType ? paramType.allowedValues : stateType ? stateType.allowedValues : undefined + readonly property int unit: paramType ? root.paramType.unit : root.stateType.unit contentItem: ColumnLayout { Label { Layout.fillWidth: true - text: paramType.displayName + text: root.paramType ? root.paramType.displayName : root.stateType.displayName } RowLayout { Layout.fillWidth: true @@ -64,7 +70,7 @@ ItemDelegate { } property bool isNumeric: { - switch (paramType.type.toLowerCase()) { + switch (root.type) { case "bool": case "string": case "qstring": @@ -75,7 +81,7 @@ ItemDelegate { case "double": return true; } - console.warn("ParamDescriptorDelegate: Unhandled data type:", paramType.type.toLowerCase()); + console.warn("ParamDescriptorDelegate: Unhandled data type:", root.type); return false; } @@ -114,44 +120,45 @@ ItemDelegate { Layout.fillWidth: true sourceComponent: { - print("Datatye is:", paramType.name, paramType.type, paramType.minValue, paramType.maxValue, paramType.allowedValues) - switch (paramType.type.toLowerCase()) { + print("Datatye is:", root.type, root.minValue, root.maxValue, root.allowedValues) + switch (root.type) { case "bool": return boolComponent; case "uint": case "int": case "double": - if (paramType.minValue !== undefined && paramType.maxValue !== undefined) { + if (root.minValue !== undefined && root.maxValue !== undefined) { return labelComponent; } return spinboxComponent; case "string": case "qstring": case "color": - if (paramType.allowedValues.length > 0) { + if (root.allowedValues.length > 0) { return comboBoxComponent } return textFieldComponent; } - console.warn("ParamDescriptorDelegate: Type delegate not implemented", paramType.type) + console.warn("ParamDescriptorDelegate: Type delegate not implemented", root.type) return null; } } Label { - text: Types.toUiUnit(paramType.unit) - visible: paramType.unit !== Types.UnitNone + text: Types.toUiUnit(root.unit) + visible: root.unit !== Types.UnitNone } } Loader { Layout.fillWidth: true sourceComponent: { - print("***********+ loading", paramType.type) - switch (paramType.type.toLowerCase()) { + print("***********+ loading", root.type) + switch (root.type) { + case "uint": case "int": case "double": - if (paramType.minValue !== undefined && paramType.maxValue !== undefined) { + if (root.minValue !== undefined && root.maxValue !== undefined) { return sliderComponent } @@ -166,11 +173,11 @@ ItemDelegate { id: labelComponent Label { text: { - switch (root.paramType.type.toLowerCase()) { + switch (root.type.toLowerCase()) { case "double": - return Math.round(Types.toUiValue(root.value, root.paramType.unit) * 10) / 10 + return Math.round(Types.toUiValue(root.value, root.unit) * 10) / 10 } - return Types.toUiValue(root.value, root.paramType.unit) + return Types.toUiValue(root.value, root.unit) } } } @@ -189,13 +196,13 @@ ItemDelegate { id: sliderComponent RowLayout { spacing: app.margins - Label { text: Types.toUiValue(root.paramType.minValue, root.paramType.unit) } + Label { text: Types.toUiValue(root.minValue, root.unit) } Slider { - from: paramType.minValue - to: paramType.maxValue + from: root.minValue + to: root.maxValue value: root.value stepSize: { - switch (root.paramType.type.toLowerCase()) { + switch (root.type.toLowerCase()) { case "double": return 0.1 } @@ -206,7 +213,7 @@ ItemDelegate { root.value = value; } } - Label { text: Types.toUiValue(root.paramType.maxValue, root.paramType.unit) } + Label { text: Types.toUiValue(root.maxValue, root.unit) } } } @@ -214,11 +221,11 @@ ItemDelegate { Component { id: spinboxComponent NymeaSpinBox { - from: paramType.minValue - to: paramType.maxValue + from: root.minValue + to: root.maxValue value: root.value != undefined ? root.value : 0 onValueModified: root.value = value - floatingPoint: root.paramType.type.toLowerCase() === "double" + floatingPoint: root.type === "double" } } @@ -238,9 +245,9 @@ ItemDelegate { Component { id: comboBoxComponent ComboBox { - model: paramType.allowedValues + model: root.allowedValues onCurrentIndexChanged: { - root.value = paramType.allowedValues[currentIndex] + root.value = root.allowedValues[currentIndex] } } } diff --git a/nymea-app/ui/delegates/StateDelegate.qml b/nymea-app/ui/delegates/StateDelegate.qml new file mode 100644 index 00000000..48cb3359 --- /dev/null +++ b/nymea-app/ui/delegates/StateDelegate.qml @@ -0,0 +1,362 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import Nymea 1.0 +import "../components" + +ItemDelegate { + id: root + + property StateType stateType: null + property alias value: d.value + property Param param: Param { + id: d + paramTypeId: stateType.id + value: stateType.defaultValue + } + property bool writable: true + property alias nameVisible: nameLabel.visible + property string placeholderText: "" + + topPadding: 0 + bottomPadding: 0 + contentItem: ColumnLayout { + id: contentItemColumn + RowLayout { + spacing: app.margins + property bool labelFillsWidth: loader.sourceComponent !== textFieldComponent + && loader.sourceComponent !== stringComponent + Label { + id: nameLabel + Layout.fillWidth: parent.labelFillsWidth + // Layout.minimumWidth: parent.width / 2 + text: root.stateType.displayName + elide: Text.ElideRight + } + Loader { + id: loader + Layout.fillWidth: !parent.labelFillsWidth + sourceComponent: { + print("Loading ParamDelegate"); + print("Writable:", root.writable, "type:", root.stateType.type, "min:", root.stateType.minValue, "max:", root.stateType.maxValue, "value:", root.param.value) + if (!root.writable) { + return stringComponent; + } + + switch (root.stateType.type.toLowerCase()) { + case "bool": + return boolComponent; + case "uint": + case "int": + if (root.stateType.name == "colorTemperature") { + return null; + } + case "double": + if (root.stateType.allowedValues.length > 0) { + return comboBoxComponent; + } else if (root.stateType.minValue !== undefined && root.stateType.maxValue !== undefined + && (root.stateType.maxValue - root.stateType.minValue <= 100)) { + return sliderComponent; + } else { + return spinnerComponent; + } + case "string": + case "qstring": + if (root.stateType.allowedValues.length > 0) { + return comboBoxComponent; + } + return textFieldComponent; + case "color": + return colorPreviewComponent; + } + console.warn("Param Delegate: Fallback to stringComponent", root.stateType.name, root.stateType.type) + return stringComponent; + } + } + } + Loader { + Layout.fillWidth: true + sourceComponent: { + if (root.stateType.name == "colorTemperature") { + return colorTemperaturePickerComponent; + } + + switch (root.stateType.type.toLowerCase()) { + case "color": + return colorPickerComponent + } + return null; + } + } + } + + Component { + id: stringComponent + Label { + text: { + switch (root.stateType.type.toLowerCase()) { + case "int": + return Math.round(root.param.value); + } + return root.param.value; + } + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + } + } + Component { + id: boolComponent + Item { + implicitHeight: theSwitch.implicitHeight + implicitWidth: theSwitch.implicitWidth + Switch { + id: theSwitch + anchors { top: parent.top; right: parent.right; bottom: parent.bottom } + width: Math.min(parent.width, implicitiWidth) + checked: root.param.value === true + Component.onCompleted: { + if (root.param.value === undefined) { + root.param.value = checked; + } + } + + onClicked: { + root.param.value = checked; + } + } + } + + } + Component { + id: sliderComponent + RowLayout { + spacing: app.margins + + Slider { + id: slider + Layout.fillWidth: true + from: root.stateType.minValue + to: root.stateType.maxValue + value: root.param.value + Component.onCompleted: { + if (root.param.value === undefined) { + if (root.stateType.defaultValue !== undefined) { + root.param.value = root.stateType.defaultValue + } else { + root.param.value = root.stateType.minValue + } + } + } + + stepSize: { + var ret = 1 + for (var i = 0; i < decimals; i++) { + ret /= 10; + } + return ret; + } + property int decimals: root.stateType.type.toLocaleLowerCase() === "double" ? 1 : 0 + + onMoved: { + var newValue + switch (root.stateType.type.toLowerCase()) { + case "int": + newValue = Math.round(value) + break; + default: + newValue = Math.round(value * 10) / 10 + } + root.param.value = newValue; + } + } + Label { + text: Types.toUiValue(root.param.value, root.stateType.unit).toFixed(slider.decimals) + Types.toUiUnit(root.stateType.unit) + } + } + + } + + Component { + id: spinnerComponent + RowLayout { + spacing: app.margins + + NymeaSpinBox { + id: spinbox + value: root.param.value ? root.param.value : 0 + from: root.stateType.minValue !== undefined + ? root.stateType.minValue + : root.stateType.type.toLowerCase() === "uint" + ? 0 + : -2000000000 + to: root.stateType.maxValue !== undefined + ? root.stateType.maxValue + : 2000000000 + editable: true + width: 150 + onValueModified: root.param.value = value + + floatingPoint: root.stateType.type.toLowerCase() == "double" + + Component.onCompleted: { + print("from:", from, "min", root.stateType.minValue) + if (root.value === undefined) { + root.value = value + } + } + } + Label { + text: Types.toUiUnit(root.stateType.unit) + visible: text.length > 0 + } + } + } + + Component { + id: textFieldComponent + TextField { + text: root.param.value !== undefined + ? root.param.value + : root.stateType.defaultValue + ? root.stateType.defaultValue + : "" + onEditingFinished: { + root.param.value = text + } + Component.onCompleted: { + if (root.param.value === undefined) { + root.param.value = text; + } + } + placeholderText: root.placeholderText + } + } + + Component { + id: comboBoxComponent + ComboBox { + id: control + Layout.fillWidth: true + model: root.stateType.allowedValues + displayText: currentText + ( root.stateType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") + currentIndex: root.stateType.allowedValues.indexOf(root.param.value !== undefined ? root.param.value : root.stateType.defaultValue) + delegate: ItemDelegate { + width: control.width + text: Types.toUiValue(modelData, root.stateType.unit) + ( root.stateType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") + highlighted: control.highlightedIndex === index + } + onActivated: { + root.param.value = root.stateType.allowedValues[index] + } + Component.onCompleted: { + if (root.value === undefined) { + root.value = model[0] + } + } + } + } + + Component { + id: colorPickerComponent + ColorPickerPre510 { + id: colorPicker + implicitHeight: 200 + // color: root.param.value + + Binding { + target: colorPicker + property: "color" + value: root.param.value + when: !colorPicker.pressed + } + + onColorChanged: { + root.param.value = color; + } + + touchDelegate: Rectangle { + height: 15 + width: height + radius: height / 2 + color: Material.accent + + + Rectangle { + color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent" + anchors.centerIn: parent + height: 30 + width: height + radius: width / 2 + Behavior on color { ColorAnimation { duration: 200 } } + } + } + } + } + + Component { + id: colorTemperaturePickerComponent + ColorPickerCt { + id: colorPickerCt + implicitHeight: 50 + minCt: root.stateType.minValue + maxCt: root.stateType.maxValue + ct: root.param.value !== undefined + ? root.param.value + : root.stateType.defaultValue + ? root.stateType.defaultValue + : root.stateType.minValue + + onCtChanged: { + root.param.value = ct + } + + + touchDelegate: Rectangle { + height: colorPickerCt.height + width: 5 + color: Style.accentColor + } + } + } + + Component { + id: colorPreviewComponent + Rectangle { + implicitHeight: app.mediumFont + implicitWidth: implicitHeight + color: root.param.value + radius: width / 4 + } + } +} diff --git a/nymea-app/ui/magic/EditRulePage.qml b/nymea-app/ui/magic/EditRulePage.qml index 51143101..929e6df0 100644 --- a/nymea-app/ui/magic/EditRulePage.qml +++ b/nymea-app/ui/magic/EditRulePage.qml @@ -298,6 +298,7 @@ Page { Flickable { anchors.fill: parent contentHeight: contentColumn.implicitHeight + app.margins + clip: true ColumnLayout { id: contentColumn @@ -554,7 +555,7 @@ Page { wrapMode: Text.WordWrap font.pixelSize: app.smallFont font.italic: true - text: qsTr("Examples:\n• While I'm at home...\n• When the temperature is below 0...\n• Between 9 am and 6 pm...") + text: qsTr("Examples:\n• While I'm at home...\n• While the TV is on...\n• Between 9 am and 6 pm...") visible: root.isEmpty } @@ -750,7 +751,7 @@ Page { minimumJsonRpcVersion: "1.0" } } - delegate: NymeaSwipeDelegate { + delegate: NymeaItemDelegate { Layout.fillWidth: true Layout.preferredHeight: Style.largeDelegateHeight iconName: model.iconName @@ -801,7 +802,7 @@ Page { minimumJsonRpcVersion: "1.0" } } - delegate: NymeaSwipeDelegate { + delegate: NymeaItemDelegate { Layout.fillWidth: true Layout.preferredHeight: Style.largeDelegateHeight iconName: model.iconName diff --git a/nymea-app/ui/magic/EventDescriptorDelegate.qml b/nymea-app/ui/magic/EventDescriptorDelegate.qml index ff48cc78..dd80b6bc 100644 --- a/nymea-app/ui/magic/EventDescriptorDelegate.qml +++ b/nymea-app/ui/magic/EventDescriptorDelegate.qml @@ -46,13 +46,16 @@ NymeaSwipeDelegate { readonly property Interface iface: eventDescriptor.interfaceName ? Interfaces.findByName(eventDescriptor.interfaceName) : null readonly property EventType eventType: thingClass ? thingClass.eventTypes.getEventType(eventDescriptor.eventTypeId) : iface ? iface.eventTypes.findByName(eventDescriptor.interfaceEvent) : null + readonly property StateType stateType: thingClass ? thingClass.stateTypes.getStateType(eventDescriptor.eventTypeId) + : iface ? iface.stateTypes.findByName(eventDescriptor.interfaceEvent) : null + readonly property var actualEventType: eventType || stateType signal removeEventDescriptor() onDeleteClicked: root.removeEventDescriptor() iconName: root.thing ? "../images/event.svg" : "../images/event-interface.svg" - text: qsTr("%1 - %2").arg(root.thing ? root.thing.name : root.iface.displayName).arg(root.eventType.displayName) + text: "%1 - %2".arg(root.thing ? root.thing.name : root.iface.displayName).arg(root.actualEventType.displayName) subText: { var ret = qsTr("anytime"); for (var i = 0; i < root.eventDescriptor.paramDescriptors.count; i++) { @@ -81,9 +84,10 @@ NymeaSwipeDelegate { operatorString = " ? "; } - var paramType = paramDescriptor.paramName - ? root.eventType.paramTypes.findByName(paramDescriptor.paramName) - : root.eventType.paramTypes.getParamType(paramDescriptor.paramTypeId) + print("**", root.eventType, root.stateType, paramDescriptor.paramName, paramDescriptor.paramTypeId) + var paramType = root.eventType ? + (paramDescriptor.paramName ? root.eventType.paramTypes.findByName(paramDescriptor.paramName) : root.eventType.paramTypes.getParamType(paramDescriptor.paramTypeId) ) + : root.stateType if (i === 0) { // TRANSLATORS: example: "only if temperature > 5" diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index 8f9aca7b..d2541a59 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -378,13 +378,29 @@ Page { function createEventDescriptor(rule, ruleTemplate, thing, eventDescriptorTemplate) { var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); eventDescriptor.thingId = thing.id; - eventDescriptor.eventTypeId = thing.thingClass.eventTypes.findByName(eventDescriptorTemplate.eventName).id + var eventType = thing.thingClass.eventTypes.findByName(eventDescriptorTemplate.eventName) + var stateType = thing.thingClass.stateTypes.findByName(eventDescriptorTemplate.eventName) var needsParams = false; + print("Creating event descriptor from template:", eventDescriptorTemplate.interfaceName, eventDescriptorTemplate.eventName, thing.name, eventType, stateType) + if (eventType) { + eventDescriptor.eventTypeId = eventType.id - var eventType = thing.thingClass.eventTypes.getEventType(eventDescriptor.eventTypeId); - for (var j = 0; j < eventType.paramTypes.count; j++) { - var paramType = eventType.paramTypes.get(j); - var paramDescriptorTemplate = eventDescriptorTemplate.paramDescriptors.getParamDescriptorByName(paramType.name) + for (var j = 0; j < eventType.paramTypes.count; j++) { + var paramType = eventType.paramTypes.get(j); + var paramDescriptorTemplate = eventDescriptorTemplate.paramDescriptors.getParamDescriptorByName(paramType.name) + // has the template a value for this? If so, set it, otherwise flag as needsParams + print("template:", paramType.id, eventDescriptorTemplate.paramDescriptors.count) + if (paramDescriptorTemplate && paramDescriptorTemplate.value !== undefined) { + print("filling in param descriptor:", paramDescriptorTemplate.value) + eventDescriptor.paramDescriptors.setParamDescriptorByName(paramDescriptorTemplate.paramName, paramDescriptorTemplate.value, paramDescriptorTemplate.operatorType); + } else { + needsParams = true; + } + } + } else if (stateType) { + eventDescriptor.eventTypeId = stateType.id + var paramType = stateType.id + var paramDescriptorTemplate = eventDescriptorTemplate.paramDescriptors.getParamDescriptorByName(stateType.name) // has the template a value for this? If so, set it, otherwise flag as needsParams print("template:", paramType.id, eventDescriptorTemplate.paramDescriptors.count) if (paramDescriptorTemplate && paramDescriptorTemplate.value !== undefined) { diff --git a/nymea-app/ui/magic/SelectEventDescriptorPage.qml b/nymea-app/ui/magic/SelectEventDescriptorPage.qml index 0fcd10b3..7ae56955 100644 --- a/nymea-app/ui/magic/SelectEventDescriptorPage.qml +++ b/nymea-app/ui/magic/SelectEventDescriptorPage.qml @@ -1,157 +1,187 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU General Public License as published by the Free Software -* Foundation, GNU version 3. This project is distributed in the hope that it -* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty -* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -* Public License for more details. -* -* You should have received a copy of the GNU General Public License along with -* this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -import QtQuick 2.4 -import QtQuick.Controls 2.1 -import "../components" -import Nymea 1.0 - -Page { - id: root - property alias text: header.text - - // an eventDescriptor object needs to be set and prefilled with either thingId or interfaceName - property var eventDescriptor: null - - readonly property Thing thing: eventDescriptor && eventDescriptor.thingId ? engine.thingManager.things.getThing(eventDescriptor.thingId) : null - - signal backPressed(); - signal done(); - - onEventDescriptorChanged: buildInterface() - Component.onCompleted: buildInterface() - - header: NymeaHeader { - id: header - onBackPressed: root.backPressed(); - - property bool interfacesMode: root.eventDescriptor.interfaceName !== "" - onInterfacesModeChanged: root.buildInterface() - - HeaderButton { - imageSource: header.interfacesMode ? "../images/view-expand.svg" : "../images/view-collapse.svg" - visible: root.eventDescriptor.interfaceName === "" - onClicked: header.interfacesMode = !header.interfacesMode - } - } - - ListModel { - id: generatedModel - ListElement { displayName: ""; eventTypeId: "" } - } - - function buildInterface() { - if (header.interfacesMode) { - if (root.thing) { - generatedModel.clear(); - for (var i = 0; i < Interfaces.count; i++) { - var iface = Interfaces.get(i); - if (root.thing.thingClass.interfaces.indexOf(iface.name) >= 0) { - for (var j = 0; j < iface.eventTypes.count; j++) { - var ifaceEt = iface.eventTypes.get(j); - var dcEt = root.thing.thingClass.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 { - console.warn("You need to set thing or interfaceName"); - } - } else { - if (root.thing) { - listView.model = root.thing.thingClass.eventTypes; - } - } - } - - ListView { - id: listView - anchors.fill: parent - - delegate: ItemDelegate { - width: parent.width - text: model.displayName - onClicked: { - if (header.interfacesMode) { - if (root.thing) { - root.eventDescriptor.eventTypeId = model.eventTypeId; - var eventType = root.thing.thingClass.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(); - } - } else if (root.eventDescriptor.interfaceName !== "") { - root.eventDescriptor.interfaceEvent = model.name; - if (listView.model.get(index).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(); - } - } else { - console.warn("Neither thingId not interfaceName set. Cannot continue..."); - } - } else { - if (root.thing) { - var eventType = root.thing.thingClass.eventTypes.getEventType(model.id); - root.eventDescriptor.eventTypeId = model.id; - 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(); - } - - print("have type", eventType.id) - } else { - console.warn("FIXME: not implemented yet"); - } - } - } - } - } -} +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.4 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import "../components" +import Nymea 1.0 + +Page { + id: root + property alias text: header.text + + // an eventDescriptor object needs to be set and prefilled with either thingId or interfaceName + property var eventDescriptor: null + + readonly property Thing thing: eventDescriptor && eventDescriptor.thingId ? engine.thingManager.things.getThing(eventDescriptor.thingId) : null + + signal backPressed(); + signal done(); + + onEventDescriptorChanged: buildInterface() + Component.onCompleted: buildInterface() + + header: NymeaHeader { + id: header + onBackPressed: root.backPressed(); + + property bool interfacesMode: root.eventDescriptor.interfaceName !== "" + onInterfacesModeChanged: root.buildInterface() + +// HeaderButton { +// imageSource: header.interfacesMode ? "../images/view-expand.svg" : "../images/view-collapse.svg" +// visible: root.eventDescriptor.interfaceName === "" +// onClicked: header.interfacesMode = !header.interfacesMode +// } + } + + ListModel { + id: generatedModel + ListElement { displayName: ""; eventTypeId: "" } + } + + function buildInterface() { + if (header.interfacesMode) { + if (root.thing) { + generatedModel.clear(); + for (var i = 0; i < Interfaces.count; i++) { + var iface = Interfaces.get(i); + if (root.thing.thingClass.interfaces.indexOf(iface.name) >= 0) { + for (var j = 0; j < iface.eventTypes.count; j++) { + var ifaceEt = iface.eventTypes.get(j); + var dcEt = root.thing.thingClass.eventTypes.findByName(ifaceEt.name) + if (!dcEt) { + dcEt = root.thing.thingClass.stateTypes.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 { + console.warn("You need to set thing or interfaceName"); + } + } else { + if (root.thing) { + generatedModel.clear(); + for (var i = 0; i < root.thing.thingClass.stateTypes.count; i++) { + var stateType = root.thing.thingClass.stateTypes.get(i) + generatedModel.append({displayName: stateType.displayName, id: stateType.id.toString(), type: "state"}) + } + for (var i = 0; i < root.thing.thingClass.eventTypes.count; i++) { + var eventType = root.thing.thingClass.eventTypes.get(i) + generatedModel.append({displayName: eventType.displayName, id: eventType.id.toString(), type: "event"}) + } + + listView.model = generatedModel; + } + } + } + + ListView { + id: listView + anchors.fill: parent + clip: true + + section.property: "type" + section.delegate: ListSectionHeader { + text: section === "state" ? qsTr("State change") : qsTr("Event") + } + + delegate: ItemDelegate { + width: parent.width + text: model.displayName + onClicked: { + if (header.interfacesMode) { + if (root.thing) { + root.eventDescriptor.eventTypeId = model.eventTypeId; + var eventType = root.thing.thingClass.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(); + } + } else if (root.eventDescriptor.interfaceName !== "") { + root.eventDescriptor.interfaceEvent = model.name; + if (listView.model.get(index).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(); + } + } else { + console.warn("Neither thingId not interfaceName set. Cannot continue..."); + } + } else { + if (root.thing) { + root.eventDescriptor.eventTypeId = model.id; + + if (model.type === "state") { + 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 if (model.type === "event") { + var eventType = root.thing.thingClass.eventTypes.getEventType(model.id); + 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(); + } + } + } else { + console.warn("FIXME: not implemented yet"); + } + } + } + } + } +} diff --git a/nymea-app/ui/magic/SelectEventDescriptorParamsPage.qml b/nymea-app/ui/magic/SelectEventDescriptorParamsPage.qml index c20132c5..f3eb25aa 100644 --- a/nymea-app/ui/magic/SelectEventDescriptorParamsPage.qml +++ b/nymea-app/ui/magic/SelectEventDescriptorParamsPage.qml @@ -42,7 +42,9 @@ Page { readonly property Thing thing: eventDescriptor && eventDescriptor.thingId ? engine.thingManager.things.getThing(eventDescriptor.thingId) : null readonly property var iface: eventDescriptor && eventDescriptor.interfaceName ? Interfaces.findByName(eventDescriptor.interfaceName) : null - readonly property var eventType: thing ? thing.thingClass.eventTypes.getEventType(eventDescriptor.eventTypeId) + readonly property EventType eventType: thing ? thing.thingClass.eventTypes.getEventType(eventDescriptor.eventTypeId) + : iface ? iface.eventTypes.findByName(eventDescriptor.interfaceEvent) : null + readonly property StateType stateType: thing ? thing.thingClass.stateTypes.getStateType(eventDescriptor.eventTypeId) : iface ? iface.eventTypes.findByName(eventDescriptor.interfaceEvent) : null signal backPressed(); @@ -57,16 +59,17 @@ Page { anchors.fill: parent Repeater { id: delegateRepeater - model: root.eventType.paramTypes + model: root.eventType ? root.eventType.paramTypes : 1 delegate: ColumnLayout { Layout.fillWidth: true property alias paramType: paramDescriptorDelegate.paramType + property alias stateType: paramDescriptorDelegate.stateType property alias value: paramDescriptorDelegate.value property alias considerParam: paramCheckBox.checked property alias operatorType: paramDescriptorDelegate.operatorType CheckBox { id: paramCheckBox - text: qsTr("Only consider event if") + text: root.eventType ? qsTr("Only consider event if") : qsTr("Only consider state change if") Layout.fillWidth: true Layout.leftMargin: app.margins Layout.rightMargin: app.margins @@ -76,8 +79,9 @@ Page { id: paramDescriptorDelegate enabled: paramCheckBox.checked Layout.fillWidth: true - paramType: root.eventType.paramTypes.get(index) - value: paramType.defaultValue + paramType: root.eventType ? root.eventType.paramTypes.get(index) : null + stateType: root.stateType + value: paramType ? paramType.defaultValue : stateType.defaultValue } } } @@ -95,10 +99,13 @@ Page { var paramDelegate = delegateRepeater.itemAt(i); if (paramDelegate.considerParam) { if (root.thing) { - root.eventDescriptor.paramDescriptors.setParamDescriptor(paramDelegate.paramType.id, paramDelegate.value, paramDelegate.operatorType) + var paramTypeId = paramDelegate.paramType ? paramDelegate.paramType.id : paramDelegate.stateType.id + print("setting param descriptor by id:", paramTypeId, paramDelegate.value) + root.eventDescriptor.paramDescriptors.setParamDescriptor(paramTypeId, 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) + var name = root.eventType ? root.eventType.paramTypes.get(i).name : root.stateType.name + print("setting param descriptors by name", name, paramDelegate.value) + root.eventDescriptor.paramDescriptors.setParamDescriptorByName(name, paramDelegate.value, paramDelegate.operatorType) } } } diff --git a/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml b/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml index 709c0792..74f7f491 100644 --- a/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml +++ b/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml @@ -117,15 +117,14 @@ Page { ThinDivider { Layout.columnSpan: parent.columns } - ParamDelegate { + StateDelegate { id: staticValueParamDelegate Layout.fillWidth: true hoverEnabled: false padding: 0 - paramType: root.thing.thingClass.eventTypes.getEventType(root.stateType.id).paramTypes.getParamType(root.stateType.id) + stateType: root.stateType enabled: staticValueRadioButton.checked nameVisible: false - value: root.stateType.defaultValue visible: staticValueRadioButton.checked placeholderText: qsTr("Insert value here") } diff --git a/nymea-app/ui/magic/SelectStateEventDescriptorParamPage.qml b/nymea-app/ui/magic/SelectStateEventDescriptorParamPage.qml new file mode 100644 index 00000000..656d1a76 --- /dev/null +++ b/nymea-app/ui/magic/SelectStateEventDescriptorParamPage.qml @@ -0,0 +1,101 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import "../components" +import "../delegates" +import Nymea 1.0 + +Page { + id: root + // Needs to be set and filled in with thingId and eventTypeId + property var eventDescriptor: null + + readonly property Thing thing: eventDescriptor && eventDescriptor.thingId ? engine.thingManager.things.getThing(eventDescriptor.thingId) : null + readonly property var iface: eventDescriptor && eventDescriptor.interfaceName ? Interfaces.findByName(eventDescriptor.interfaceName) : null + readonly property var stateType: thing ? thing.thingClass.stateTypes.getStateType(eventDescriptor.eventTypeId) + : iface ? iface.eventTypes.findByName(eventDescriptor.interfaceEvent) : null + + signal backPressed(); + signal completed(); + + header: NymeaHeader { + text: "Options" + onBackPressed: root.backPressed(); + } + + ColumnLayout { + anchors.fill: parent + ColumnLayout { + Layout.fillWidth: true + property alias paramType: paramDescriptorDelegate.paramType + property alias value: paramDescriptorDelegate.value + property alias considerParam: paramCheckBox.checked + property alias operatorType: paramDescriptorDelegate.operatorType + CheckBox { + id: paramCheckBox + text: qsTr("Only consider state change if") + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + } + + ParamDescriptorDelegate { + id: paramDescriptorDelegate + enabled: paramCheckBox.checked + Layout.fillWidth: true + stateType: root.stateType + value: stateType.defaultValue + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + Button { + text: "OK" + Layout.fillWidth: true + Layout.margins: app.margins + onClicked: { + root.eventDescriptor.paramDescriptors.clear(); + if (paramDelegate.considerParam) { + if (root.thing) { + root.eventDescriptor.paramDescriptors.setParamDescriptor(root.stateType.id, paramDescriptorDelegate.value, paramDescriptorDelegate.operatorType) + } else if (root.iface) { + root.eventDescriptor.paramDescriptors.setParamDescriptorByName(root.stateType.name, paramDescriptorDelegate.value, paramDescriptorDelegate.operatorType) + } + } + root.completed() + } + } + } +} diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index c8e1af6b..165b5870 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -119,7 +119,7 @@ Page { print("new checked state;", newCache[thingId]) } - delegate: NymeaSwipeDelegate { + delegate: NymeaItemDelegate { width: parent.width text: root.selectInterface ? model.displayName : model.name iconName: root.selectInterface ? app.interfaceToIcon(model.name) : app.interfacesToIcon(model.interfaces)