Update ParamDelegates and many small improvements

This commit is contained in:
Simon Stürz 2026-02-24 15:42:49 +01:00
parent 1431e7001e
commit 28ce85b561
14 changed files with 154 additions and 102 deletions

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of libnymea-app. * This file is part of libnymea-app.
* *
@ -26,10 +26,9 @@
#include <QDebug> #include <QDebug>
ThingClass::ThingClass(QObject *parent) : ThingClass::ThingClass(QObject *parent)
QObject(parent) : QObject(parent)
{ {}
}
QUuid ThingClass::id() const QUuid ThingClass::id() const
{ {
@ -330,3 +329,8 @@ bool ThingClass::hasActionType(const QUuid &actionTypeId)
} }
return false; return false;
} }
bool ThingClass::isAutoCreated() const
{
return m_createMethods.contains("CreateMethodAuto");
}

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of libnymea-app. * This file is part of libnymea-app.
* *
@ -25,15 +25,15 @@
#ifndef THINGCLASS_H #ifndef THINGCLASS_H
#define THINGCLASS_H #define THINGCLASS_H
#include <QObject>
#include <QUuid>
#include <QList> #include <QList>
#include <QObject>
#include <QString> #include <QString>
#include <QUuid>
#include "actiontypes.h"
#include "eventtypes.h"
#include "paramtypes.h" #include "paramtypes.h"
#include "statetypes.h" #include "statetypes.h"
#include "eventtypes.h"
#include "actiontypes.h"
class ThingClass : public QObject class ThingClass : public QObject
{ {
@ -135,6 +135,8 @@ public:
Q_INVOKABLE bool hasActionType(const QUuid &actionTypeId); Q_INVOKABLE bool hasActionType(const QUuid &actionTypeId);
Q_INVOKABLE bool isAutoCreated() const;
signals: signals:
void paramTypesChanged(); void paramTypesChanged();
void settingsTypesChanged(); void settingsTypesChanged();

View File

@ -126,7 +126,7 @@ Item {
ValueAxis { ValueAxis {
id: yAxis id: yAxis
max: { max: {
if (root.stateType && root.stateType.type.toLowerCase() == "bool") { if (root.stateType && root.stateType.type.toLowerCase() === "bool") {
return 1; return 1;
} else { } else {
Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05)) Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05))
@ -137,7 +137,7 @@ Item {
// onMaxChanged: applyNiceNumbers(); // onMaxChanged: applyNiceNumbers();
labelsFont: Style.extraSmallFont labelsFont: Style.extraSmallFont
labelFormat: { labelFormat: {
if (root.stateType && root.stateType.type.toLowerCase() == "bool") { if (root.stateType && root.stateType.type.toLowerCase() === "bool") {
return "x"; return "x";
} else { } else {
return "%d"; return "%d";

View File

@ -147,7 +147,7 @@ Item {
// y: chartView.plotArea.y // y: chartView.plotArea.y
// height: chartView.plotArea.height // height: chartView.plotArea.height
// width: chartView.plotArea.x - x // width: chartView.plotArea.x - x
// visible: root.stateType.type.toLowerCase() != "bool" && logsModel.minValue != logsModel.maxValue // visible: root.stateType.type.toLowerCase() !== "bool" && logsModel.minValue != logsModel.maxValue
// property double range: Math.abs(valueAxis.max - valueAxis.min) // property double range: Math.abs(valueAxis.max - valueAxis.min)
// property double stepSize: range / (valueAxis.tickCount - 1) // property double stepSize: range / (valueAxis.tickCount - 1)
// property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0 // property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0
@ -256,7 +256,7 @@ Item {
print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values)) print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values))
d.ensureValue(zeroSeries, entry.timestamp) d.ensureValue(zeroSeries, entry.timestamp)
if (stateType.type.toLowerCase() == "bool") { if (stateType.type.toLowerCase() === "bool") {
var value = entry.values[stateType.name] var value = entry.values[stateType.name]
if (value == null) { if (value == null) {
value = false; value = false;
@ -290,7 +290,7 @@ Item {
} }
} }
if (stateType.type.toLowerCase() == "bool") { if (stateType.type.toLowerCase() === "bool") {
var last = series.at(series.count-1); var last = series.at(series.count-1);
if (last.x < d.endTime) { if (last.x < d.endTime) {
@ -304,7 +304,7 @@ Item {
} }
onEntriesRemoved: (index, count) => { onEntriesRemoved: (index, count) => {
print("removing:", index, count, series.count) print("removing:", index, count, series.count)
if (stateType.type.toLowerCase() == "bool") { if (stateType.type.toLowerCase() === "bool") {
series.removePoints(index * 2, count * 2) series.removePoints(index * 2, count * 2)
if (series.count == 1) { if (series.count == 1) {
series.removePoints(0, 1); series.removePoints(0, 1);

View File

@ -57,7 +57,7 @@ Item {
if (root.interfaceName == "closablesensor") { if (root.interfaceName == "closablesensor") {
return true return true
} }
return sensorStateType && sensorStateType.type.toLowerCase() == "bool" && sensorState.value === true return sensorStateType && sensorStateType.type.toLowerCase() === "bool" && sensorState.value === true
} }
iconSource: { iconSource: {
if (root.interfaceName == "closablesensor") { if (root.interfaceName == "closablesensor") {
@ -100,7 +100,7 @@ Item {
} }
sourceComponent: { sourceComponent: {
if (stateType.type.toLowerCase() == "bool") { if (stateType.type.toLowerCase() === "bool") {
return boolComponent; return boolComponent;
} }

View File

@ -56,7 +56,7 @@ Item {
property date now: new Date() property date now: new Date()
readonly property int range: selectionTabs.currentValue.range readonly property int range: selectionTabs.currentValue.range
readonly property int sampleRate: root.stateType == null || root.stateType.type.toLowerCase() == "bool" ? NewLogsModel.SampleRateAny : selectionTabs.currentValue.sampleRate readonly property int sampleRate: root.stateType == null || root.stateType.type.toLowerCase() === "bool" ? NewLogsModel.SampleRateAny : selectionTabs.currentValue.sampleRate
readonly property int visibleValues: range / sampleRate readonly property int visibleValues: range / sampleRate
@ -114,7 +114,7 @@ Item {
// print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values)) // print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values))
zeroSeries.ensureValue(entry.timestamp) zeroSeries.ensureValue(entry.timestamp)
if (root.stateType.type.toLowerCase() == "bool") { if (root.stateType.type.toLowerCase() === "bool") {
var value = entry.values[root.stateType.name] var value = entry.values[root.stateType.name]
if (value == null) { if (value == null) {
value = false; value = false;
@ -148,7 +148,7 @@ Item {
} }
} }
if (root.stateType.type.toLowerCase() == "bool") { if (root.stateType.type.toLowerCase() === "bool") {
var last = valueSeries.at(valueSeries.count-1); var last = valueSeries.at(valueSeries.count-1);
if (last.x < d.endTime) { if (last.x < d.endTime) {
valueSeries.append(d.endTime, last.y) valueSeries.append(d.endTime, last.y)
@ -160,7 +160,7 @@ Item {
} }
onEntriesRemoved: (index, count) => { onEntriesRemoved: (index, count) => {
print("removing:", index, count, valueSeries.count) print("removing:", index, count, valueSeries.count)
if (root.stateType.type.toLowerCase() == "bool") { if (root.stateType.type.toLowerCase() === "bool") {
valueSeries.removePoints(index * 2, count * 2) valueSeries.removePoints(index * 2, count * 2)
if (valueSeries.count == 1) { if (valueSeries.count == 1) {
valueSeries.removePoints(0, 1); valueSeries.removePoints(0, 1);
@ -419,7 +419,7 @@ Item {
y: chartView.y + chartView.plotArea.y y: chartView.y + chartView.plotArea.y
height: chartView.plotArea.height height: chartView.plotArea.height
width: Math.max(0, chartContainer.yAxisLabelAreaWidth - Style.smallMargins) width: Math.max(0, chartContainer.yAxisLabelAreaWidth - Style.smallMargins)
visible: root.stateType && root.stateType.type.toLowerCase() != "bool" && logsModel.minValue != logsModel.maxValue visible: root.stateType && root.stateType.type.toLowerCase() !== "bool" && logsModel.minValue != logsModel.maxValue
property double range: Math.abs(valueAxis.max - valueAxis.min) property double range: Math.abs(valueAxis.max - valueAxis.min)
property double stepSize: range / (valueAxis.tickCount - 1) property double stepSize: range / (valueAxis.tickCount - 1)
property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0 property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0
@ -589,7 +589,7 @@ Item {
elide: Text.ElideRight elide: Text.ElideRight
text: toolTip.value === null text: toolTip.value === null
? qsTr("No data") ? qsTr("No data")
: root.stateType.type.toLowerCase() == "bool" : root.stateType.type.toLowerCase() === "bool"
? root.stateType.displayName + ": " + (toolTip.value ? qsTr("Yes") : qsTr("No")) ? root.stateType.displayName + ": " + (toolTip.value ? qsTr("Yes") : qsTr("No"))
: Types.toUiValue(toolTip.value, root.stateType.unit).toFixed(root.roundTo) + Types.toUiUnit(root.stateType.unit) : Types.toUiValue(toolTip.value, root.stateType.unit).toFixed(root.roundTo) + Types.toUiUnit(root.stateType.unit)
font: Style.extraSmallFont font: Style.extraSmallFont

View File

@ -37,6 +37,7 @@ ItemDelegate {
property alias value: d.value property alias value: d.value
property Param param: Param { property Param param: Param {
id: d id: d
paramTypeId: paramType.id paramTypeId: paramType.id
value: paramType.defaultValue value: paramType.defaultValue
} }
@ -48,9 +49,11 @@ ItemDelegate {
bottomPadding: 0 bottomPadding: 0
contentItem: ColumnLayout { contentItem: ColumnLayout {
id: contentItemColumn id: contentItemColumn
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Style.margins anchors.leftMargin: Style.margins
anchors.rightMargin: Style.margins anchors.rightMargin: Style.margins
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.margins spacing: Style.margins
@ -70,8 +73,6 @@ ItemDelegate {
Layout.fillWidth: true//!parent.labelFillsWidth Layout.fillWidth: true//!parent.labelFillsWidth
Layout.maximumWidth: root.nameVisible ? contentItemColumn.width / 2 : contentItemColumn.width Layout.maximumWidth: root.nameVisible ? contentItemColumn.width / 2 : contentItemColumn.width
sourceComponent: { sourceComponent: {
print("Loading ParamDelegate");
print("Writable:", root.writable, "type:", root.paramType.type, "min:", root.paramType.minValue, "max:", root.paramType.maxValue, "value:", root.param.value)
if (!root.writable) { if (!root.writable) {
return stringComponent; return stringComponent;
} }
@ -123,12 +124,11 @@ ItemDelegate {
return null; return null;
} }
} }
} }
Component { Component {
id: stringComponent id: stringComponent
Label { Label {
text: { text: {
switch (root.paramType.type.toLowerCase()) { switch (root.paramType.type.toLowerCase()) {
@ -141,15 +141,17 @@ ItemDelegate {
elide: Text.ElideRight elide: Text.ElideRight
} }
} }
Component { Component {
id: boolComponent id: boolComponent
Item { Item {
implicitHeight: theSwitch.implicitHeight implicitHeight: theSwitch.implicitHeight
implicitWidth: theSwitch.implicitWidth implicitWidth: theSwitch.implicitWidth
Switch { Switch {
id: theSwitch id: theSwitch
anchors { top: parent.top; right: parent.right; bottom: parent.bottom } anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
width: Math.min(parent.width, implicitiWidth) width: Math.min(parent.width, implicitWidth)
checked: root.param.value === true checked: root.param.value === true
Component.onCompleted: { Component.onCompleted: {
if (root.param.value === undefined) { if (root.param.value === undefined) {
@ -162,10 +164,11 @@ ItemDelegate {
} }
} }
} }
} }
Component { Component {
id: sliderComponent id: sliderComponent
RowLayout { RowLayout {
spacing: Style.margins spacing: Style.margins
@ -206,6 +209,7 @@ ItemDelegate {
root.param.value = newValue; root.param.value = newValue;
} }
} }
Label { Label {
text: Types.toUiValue(root.param.value, root.paramType.unit).toFixed(slider.decimals) + Types.toUiUnit(root.paramType.unit) text: Types.toUiValue(root.param.value, root.paramType.unit).toFixed(slider.decimals) + Types.toUiUnit(root.paramType.unit)
} }
@ -231,18 +235,19 @@ ItemDelegate {
: 2000000000 : 2000000000
editable: true editable: true
width: 150 width: 150
onValueModified: root.param.value = value floatingPoint: root.paramType.type.toLowerCase() === "double"
floatingPoint: root.paramType.type.toLowerCase() == "double" onValueModified: (value) => { root.param.value = value }
Component.onCompleted: { Component.onCompleted: {
print("from:", from, "min", root.paramType.minValue) print("from:", from, "min", root.paramType.minValue)
print("to:", to, "max", root.paramType.maxValue) print("to:", to, "max", root.paramType.maxValue)
if (root.value === undefined) { if (root.param.value === undefined) {
root.value = value root.param.value = value
} }
} }
} }
Label { Label {
text: Types.toUiUnit(root.paramType.unit) text: Types.toUiUnit(root.paramType.unit)
visible: text.length > 0 visible: text.length > 0
@ -255,40 +260,48 @@ ItemDelegate {
TextField { TextField {
text: root.param.value !== undefined text: root.param.value !== undefined
? root.param.value ? root.param.value
: root.paramType.defaultValue : root.paramType.defaultValue !== undefined
? root.paramType.defaultValue ? root.paramType.defaultValue
: "" : ""
placeholderText: root.placeholderText
onEditingFinished: { onEditingFinished: {
root.param.value = text root.param.value = text
} }
Component.onCompleted: { Component.onCompleted: {
if (root.param.value === undefined) { if (root.param.value === undefined) {
root.param.value = text; root.param.value = text;
} }
} }
placeholderText: root.placeholderText
} }
} }
Component { Component {
id: comboBoxComponent id: comboBoxComponent
ComboBox { ComboBox {
id: control id: control
Layout.fillWidth: true Layout.fillWidth: true
model: root.paramType.allowedValues model: root.paramType.allowedValues
displayText: currentText + ( root.paramType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "") displayText: {
if (currentIndex < 0 || currentIndex >= root.paramType.allowedValues.length) {
return "";
}
return Types.toUiValue(root.paramType.allowedValues[currentIndex], root.paramType.unit)
+ (root.paramType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "");
}
currentIndex: root.paramType.allowedValues.indexOf(root.param.value !== undefined ? root.param.value : root.paramType.defaultValue) currentIndex: root.paramType.allowedValues.indexOf(root.param.value !== undefined ? root.param.value : root.paramType.defaultValue)
delegate: ItemDelegate { delegate: ItemDelegate {
width: control.width width: control.width
text: Types.toUiValue(modelData, root.paramType.unit) + ( root.paramType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "") text: Types.toUiValue(modelData, root.paramType.unit) + ( root.paramType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "")
highlighted: control.highlightedIndex === index highlighted: control.highlightedIndex === index
} }
onActivated: (index) => { onActivated: (index) => { root.param.value = root.paramType.allowedValues[index] }
root.param.value = root.paramType.allowedValues[index]
}
Component.onCompleted: { Component.onCompleted: {
if (root.value === undefined) { if (root.param.value === undefined) {
root.value = model[0] root.param.value = model[0]
} }
} }
} }
@ -296,6 +309,7 @@ ItemDelegate {
Component { Component {
id: colorPickerComponent id: colorPickerComponent
ColorPickerPre510 { ColorPickerPre510 {
id: colorPicker id: colorPicker
implicitHeight: 200 implicitHeight: 200
@ -333,6 +347,7 @@ ItemDelegate {
Component { Component {
id: colorTemperaturePickerComponent id: colorTemperaturePickerComponent
ColorPickerCt { ColorPickerCt {
id: colorPickerCt id: colorPickerCt
implicitHeight: 50 implicitHeight: 50
@ -340,7 +355,7 @@ ItemDelegate {
maxCt: root.paramType.maxValue maxCt: root.paramType.maxValue
ct: root.param.value !== undefined ct: root.param.value !== undefined
? root.param.value ? root.param.value
: root.paramType.defaultValue : root.paramType.defaultValue !== undefined
? root.paramType.defaultValue ? root.paramType.defaultValue
: root.paramType.minValue : root.paramType.minValue
@ -359,6 +374,7 @@ ItemDelegate {
Component { Component {
id: colorPreviewComponent id: colorPreviewComponent
Rectangle { Rectangle {
implicitHeight: app.mediumFont implicitHeight: app.mediumFont
implicitWidth: implicitHeight implicitWidth: implicitHeight

View File

@ -63,7 +63,7 @@ ItemDelegate {
id: loader id: loader
Layout.fillWidth: !parent.labelFillsWidth Layout.fillWidth: !parent.labelFillsWidth
sourceComponent: { sourceComponent: {
print("Loading ParamDelegate"); print("Loading StateDelegate");
print("Writable:", root.writable, "type:", root.stateType.type, "min:", root.stateType.minValue, "max:", root.stateType.maxValue, "value:", root.param.value) print("Writable:", root.writable, "type:", root.stateType.type, "min:", root.stateType.minValue, "max:", root.stateType.maxValue, "value:", root.param.value)
if (!root.writable) { if (!root.writable) {
return stringComponent; return stringComponent;
@ -222,7 +222,7 @@ ItemDelegate {
width: 150 width: 150
onValueModified: root.param.value = value onValueModified: root.param.value = value
floatingPoint: root.stateType.type.toLowerCase() == "double" floatingPoint: root.stateType.type.toLowerCase() === "double"
Component.onCompleted: { Component.onCompleted: {
print("from:", from, "min", root.stateType.minValue) print("from:", from, "min", root.stateType.minValue)
@ -264,11 +264,11 @@ ItemDelegate {
id: control id: control
Layout.fillWidth: true Layout.fillWidth: true
model: root.stateType.allowedValues model: root.stateType.allowedValues
displayText: currentText + ( root.stateType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") 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) currentIndex: root.stateType.allowedValues.indexOf(root.param.value !== undefined ? root.param.value : root.stateType.defaultValue)
delegate: ItemDelegate { delegate: ItemDelegate {
width: control.width width: control.width
text: Types.toUiValue(modelData, root.stateType.unit) + ( root.stateType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") text: Types.toUiValue(modelData, root.stateType.unit) + ( root.stateType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "")
highlighted: control.highlightedIndex === index highlighted: control.highlightedIndex === index
} }
onActivated: (index) => { onActivated: (index) => {

View File

@ -36,7 +36,7 @@ SpinBox {
stepSize: Math.min(10, (to - from) / 10) stepSize: Math.min(10, (to - from) / 10)
property var unit: Types.UnitNone property var unit: Types.UnitNone
editable: true editable: true
onValueModified: { onValueModified: (value) => {
changed(value) changed(value)
} }
textFromValue: function(value) { textFromValue: function(value) {

View File

@ -172,17 +172,9 @@ Page {
var maxValue = stateDelegate.stateType.maxValue !== undefined var maxValue = stateDelegate.stateType.maxValue !== undefined
? stateDelegate.stateType.maxValue ? stateDelegate.stateType.maxValue
: 2000000000; : 2000000000;
print("pushing delegate for", stateDelegate.stateType.name, "from:", minValue, "to:", maxValue, "possible:", stateDelegate.stateType.possibleValuesDisplayNames) console.log("pushing delegate for", stateDelegate.stateType.name, "from:", minValue, "to:", maxValue, "possible:", stateDelegate.stateType.possibleValuesDisplayNames)
stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp, stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp, {
{ value: root.thing.states.getState(stateType.id).value
value: root.thing.states.getState(stateType.id).value,
possibleValues: stateDelegate.stateType.possibleValues,
possibleValuesDisplayNames: stateDelegate.stateType.possibleValuesDisplayNames,
from: minValue,
to: maxValue,
unit: stateDelegate.stateType.unit,
writable: false,
stateType: stateDelegate.stateType
}) })
} }
@ -231,6 +223,16 @@ Page {
property: "unit" property: "unit"
value: stateDelegate.stateType.unit value: stateDelegate.stateType.unit
} }
Binding {
target: stateDelegateLoader.item.hasOwnProperty("writable") ? stateDelegateLoader.item : null
property: "writable"
value: false
}
Binding {
target: stateDelegateLoader.item.hasOwnProperty("stateType") ? stateDelegateLoader.item : null
property: "stateType"
value: stateDelegate.stateType
}
} }
} }

View File

@ -61,7 +61,7 @@ NymeaSwipeDelegate {
operatorString = " = "; operatorString = " = ";
break; break;
case ParamDescriptor.ValueOperatorNotEquals: case ParamDescriptor.ValueOperatorNotEquals:
operatorString = " != "; operatorString = " !== ";
break; break;
case ParamDescriptor.ValueOperatorGreater: case ParamDescriptor.ValueOperatorGreater:
operatorString = " > "; operatorString = " > ";

View File

@ -84,7 +84,7 @@ DashboardDelegateBase {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
visible: root.stateType && root.stateType.type.toLowerCase() != "bool" visible: root.stateType && root.stateType.type.toLowerCase() !== "bool"
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font: Style.largeFont font: Style.largeFont
elide: Text.ElideRight elide: Text.ElideRight

View File

@ -59,20 +59,27 @@ SettingsPageBase {
width: implicitWidth + app.margins width: implicitWidth + app.margins
x: parent.width - width x: parent.width - width
Component.onCompleted: { function addMenuEntry(properties) {
deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Rename"), iconSource: "qrc:/icons/edit.svg", functionName: "renameThing"})) var item = menuEntryComponent.createObject(null, properties)
// FIXME: This isn't entirely correct... we should have a way to know if a particular thing is in fact autocreated if (!item) {
// This check might be wrong for thingClasses with multiple create methods... console.warn("Failed to create menu entry", properties && properties.text)
if (!root.thing.isChild || root.thing.thingClass.createMethods.indexOf("CreateMethodAuto") < 0) { return
deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Delete"), iconSource: "qrc:/icons/delete.svg", functionName: "deleteThing"}))
}
// FIXME: This isn't entirely correct... we should have a way to know if a particular thing is in fact autocreated
// This check might be wrong for thingClasses with multiple create methods...
if (!root.thing.isChild || root.thingClass.createMethods.indexOf("CreateMethodAuto") < 0) {
deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Reconfigure"), iconSource: "qrc:/icons/configure.svg", functionName: "reconfigureThing"}))
} }
deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Details"), iconSource: "qrc:/icons/info.svg", functionName: "thingDetails"})) deviceMenu.addItem(item)
}
Component.onCompleted: {
addMenuEntry({text: qsTr("Rename"), iconSource: "qrc:/icons/edit.svg", functionName: "renameThing"})
// FIXME: This isn't entirely correct... we should have a way to know if a particular thing is in fact autocreated
// This check might be wrong for thingClasses with multiple create methods...
if (!root.thing.isChild && !root.thing.thingClass.isAutoCreated()) {
addMenuEntry({text: qsTr("Delete"), iconSource: "qrc:/icons/delete.svg", functionName: "deleteThing"})
addMenuEntry({text: qsTr("Reconfigure"), iconSource: "qrc:/icons/configure.svg", functionName: "reconfigureThing"})
}
addMenuEntry({text: qsTr("Details"), iconSource: "qrc:/icons/info.svg", functionName: "thingDetails"})
} }
function renameThing() { function renameThing() {
@ -106,8 +113,8 @@ SettingsPageBase {
Connections { Connections {
target: engine.thingManager target: engine.thingManager
onRemoveThingReply: { function onRemoveThingReply(commandId, thingError, ruleIds) {
if (d.pendingCommand != commandId) { if (d.pendingCommand !== commandId) {
return; return;
} }
@ -140,6 +147,7 @@ SettingsPageBase {
prominentSubText: false prominentSubText: false
progressive: false progressive: false
} }
NymeaItemDelegate { NymeaItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: root.thing.thingClass.displayName text: root.thing.thingClass.displayName
@ -191,6 +199,7 @@ SettingsPageBase {
StateTypesProxy { StateTypesProxy {
id: ioModel id: ioModel
stateTypes: root.thing.thingClass.stateTypes stateTypes: root.thing.thingClass.stateTypes
digitalInputs: true digitalInputs: true
digitalOutputs: true digitalOutputs: true
@ -200,13 +209,14 @@ SettingsPageBase {
Repeater { Repeater {
model: ioModel model: ioModel
delegate: NymeaSwipeDelegate { delegate: NymeaSwipeDelegate {
Layout.fillWidth: true Layout.fillWidth: true
iconName: "qrc:/icons/io-connections.svg" iconName: "qrc:/icons/io-connections.svg"
text: model.displayName text: model.displayName
subText: { subText: {
if (ioStateType.ioType == Types.IOTypeDigitalInput || ioStateType.ioType == Types.IOTypeAnalogInput) { if (ioStateType.ioType === Types.IOTypeDigitalInput || ioStateType.ioType === Types.IOTypeAnalogInput) {
if (inputConnectionWatcher.ioConnection) { if (inputConnectionWatcher.ioConnection) {
return "%1: %2".arg(inputConnectionWatcher.outputThing.name).arg(inputConnectionWatcher.outputStateType.displayName) return "%1: %2".arg(inputConnectionWatcher.outputThing.name).arg(inputConnectionWatcher.outputStateType.displayName)
} }
@ -223,14 +233,17 @@ SettingsPageBase {
IOInputConnectionWatcher { IOInputConnectionWatcher {
id: inputConnectionWatcher id: inputConnectionWatcher
ioConnections: engine.thingManager.ioConnections ioConnections: engine.thingManager.ioConnections
inputThingId: root.thing.id inputThingId: root.thing.id
inputStateTypeId: ioStateType.id inputStateTypeId: ioStateType.id
property Thing outputThing: ioConnection ? engine.thingManager.things.getThing(ioConnection.outputThingId) : null property Thing outputThing: ioConnection ? engine.thingManager.things.getThing(ioConnection.outputThingId) : null
property StateType outputStateType: ioConnection ? outputThing.thingClass.stateTypes.getStateType(ioConnection.outputStateTypeId) : null property StateType outputStateType: ioConnection ? outputThing.thingClass.stateTypes.getStateType(ioConnection.outputStateTypeId) : null
} }
IOOutputConnectionWatcher { IOOutputConnectionWatcher {
id: outputConnectionWatcher id: outputConnectionWatcher
ioConnections: engine.thingManager.ioConnections ioConnections: engine.thingManager.ioConnections
outputThingId: root.thing.id outputThingId: root.thing.id
outputStateTypeId: ioStateType.id outputStateTypeId: ioStateType.id
@ -253,6 +266,7 @@ SettingsPageBase {
Repeater { Repeater {
id: settingsRepeater id: settingsRepeater
model: root.thing.settings model: root.thing.settings
delegate: ParamDelegate { delegate: ParamDelegate {
Layout.fillWidth: true Layout.fillWidth: true
@ -360,7 +374,7 @@ SettingsPageBase {
property IOInputConnectionWatcher inputWatcher: null property IOInputConnectionWatcher inputWatcher: null
property IOOutputConnectionWatcher outputWatcher: null property IOOutputConnectionWatcher outputWatcher: null
readonly property bool isInput: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput readonly property bool isInput: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
@ -377,16 +391,16 @@ SettingsPageBase {
model: ThingsProxy { model: ThingsProxy {
id: connectableIODevices id: connectableIODevices
engine: _engine engine: _engine
showDigitalInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalOutput showDigitalInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalOutput
showDigitalOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput showDigitalOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput
showAnalogInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogOutput showAnalogInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogOutput
showAnalogOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput showAnalogOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput
} }
textRole: "name" textRole: "name"
Layout.fillWidth: true Layout.fillWidth: true
Component.onCompleted: { Component.onCompleted: {
for (var i = 0; i < connectableIODevices.count; i++) { for (var i = 0; i < connectableIODevices.count; i++) {
if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) {
if (connectableIODevices.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputThingId) { if (connectableIODevices.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputThingId) {
ioThingComboBox.currentIndex = i; ioThingComboBox.currentIndex = i;
break; break;
@ -407,10 +421,10 @@ SettingsPageBase {
model: StateTypesProxy { model: StateTypesProxy {
id: connectableStateTypes id: connectableStateTypes
stateTypes: connectableIODevices.get(ioThingComboBox.currentIndex).thingClass.stateTypes stateTypes: connectableIODevices.get(ioThingComboBox.currentIndex).thingClass.stateTypes
digitalInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalOutput digitalInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalOutput
digitalOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput digitalOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput
analogInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogOutput analogInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogOutput
analogOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput analogOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput
} }
textRole: "displayName" textRole: "displayName"
Layout.fillWidth: true Layout.fillWidth: true
@ -418,7 +432,7 @@ SettingsPageBase {
// print("loading for:", ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) // print("loading for:", ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId)
for (var i = 0; i < connectableStateTypes.count; i++) { for (var i = 0; i < connectableStateTypes.count; i++) {
print("checking:", connectableStateTypes.get(i).id) print("checking:", connectableStateTypes.get(i).id)
if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) {
if (connectableStateTypes.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) { if (connectableStateTypes.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) {
ioStateComboBox.currentIndex = i; ioStateComboBox.currentIndex = i;
break; break;
@ -442,6 +456,7 @@ SettingsPageBase {
CheckBox { CheckBox {
id: invertCheckBox id: invertCheckBox
checked: ioConnectionDialog.isInput ? ioConnectionDialog.inputWatcher.ioConnection.inverted : ioConnectionDialog.outputWatcher.ioConnection.inverted checked: ioConnectionDialog.isInput ? ioConnectionDialog.inputWatcher.ioConnection.inverted : ioConnectionDialog.outputWatcher.ioConnection.inverted
} }
} }
@ -453,7 +468,7 @@ SettingsPageBase {
columns: width > (cancelButton.implicitWidth + disconnectButton.implicitWidth + connectButton.implicitWidth) columns: width > (cancelButton.implicitWidth + disconnectButton.implicitWidth + connectButton.implicitWidth)
? 4 : 1 ? 4 : 1
layoutDirection: columns == 1 ? Qt.RightToLeft : Qt.LeftToRight layoutDirection: columns === 1 ? Qt.RightToLeft : Qt.LeftToRight
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
@ -473,8 +488,8 @@ SettingsPageBase {
Layout.fillWidth: buttonGrid.columns === 1 Layout.fillWidth: buttonGrid.columns === 1
onClicked: { onClicked: {
if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput
|| ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) {
engine.thingManager.disconnectIO(ioConnectionDialog.inputWatcher.ioConnection.id); engine.thingManager.disconnectIO(ioConnectionDialog.inputWatcher.ioConnection.id);
} else { } else {
engine.thingManager.disconnectIO(ioConnectionDialog.outputWatcher.ioConnection.id); engine.thingManager.disconnectIO(ioConnectionDialog.outputWatcher.ioConnection.id);
@ -495,8 +510,8 @@ SettingsPageBase {
var inputStateTypeId; var inputStateTypeId;
var outputThingId; var outputThingId;
var outputStateTypeId; var outputStateTypeId;
if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput
|| ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) {
inputThingId = root.thing.id; inputThingId = root.thing.id;
inputStateTypeId = ioConnectionDialog.ioStateType.id; inputStateTypeId = ioConnectionDialog.ioStateType.id;
outputThingId = connectableIODevices.get(ioThingComboBox.currentIndex).id; outputThingId = connectableIODevices.get(ioThingComboBox.currentIndex).id;

View File

@ -156,13 +156,12 @@ Page {
Connections { Connections {
target: engine.thingManager target: engine.thingManager
onPairThingReply: { function onPairThingReply(commandId, thingError, pairingTransactionId, setupMethod, displayMessage, oAuthUrl) {
busyOverlay.shown = false busyOverlay.shown = false
if (thingError !== Thing.ThingErrorNoError) { if (thingError !== Thing.ThingErrorNoError) {
busyOverlay.shown = false; busyOverlay.shown = false;
internalPageStack.push(resultsPage, {thingError: thingError, message: displayMessage}); internalPageStack.push(resultsPage, {thingError: thingError, message: displayMessage});
return; return;
} }
d.pairingTransactionId = pairingTransactionId; d.pairingTransactionId = pairingTransactionId;
@ -179,17 +178,21 @@ Page {
break; break;
default: default:
print("Setup method reply not handled:", setupMethod); print("Setup method reply not handled:", setupMethod);
break;
} }
} }
onConfirmPairingReply: {
function onConfirmPairingReply(commandId, thingError, thingId, displayMessage) {
busyOverlay.shown = false busyOverlay.shown = false
internalPageStack.push(resultsPage, {thingError: thingError, thingId: thingId, message: displayMessage}) internalPageStack.push(resultsPage, {thingError: thingError, thingId: thingId, message: displayMessage})
} }
onAddThingReply: {
function onAddThingReply(commandId, thingError, thingId, displayMessage) {
busyOverlay.shown = false; busyOverlay.shown = false;
internalPageStack.push(resultsPage, {thingError: thingError, thingId: thingId, message: displayMessage}) internalPageStack.push(resultsPage, {thingError: thingError, thingId: thingId, message: displayMessage})
} }
onReconfigureThingReply: {
function onReconfigureThingReply(commandId, thingError, displayMessage) {
busyOverlay.shown = false; busyOverlay.shown = false;
internalPageStack.push(resultsPage, {thingError: thingError, thingId: root.thing.id, message: displayMessage}) internalPageStack.push(resultsPage, {thingError: thingError, thingId: root.thing.id, message: displayMessage})
} }
@ -372,7 +375,7 @@ Page {
Component.onCompleted: { Component.onCompleted: {
if (root.thingClass.id.toString().match(/\{?f0dd4c03-0aca-42cc-8f34-9902457b05de\}?/)) { if (root.thingClass.id.toString().match(/\{?f0dd4c03-0aca-42cc-8f34-9902457b05de\}?/)) {
console.warn("checking Notification permission!") console.warn("checking Notification permission!")
if (PlatformPermissions.notificationsPermission != PlatformPermissions.PermissionStatusGranted) { if (PlatformPermissions.notificationsPermission !== PlatformPermissions.PermissionStatusGranted) {
console.warn("Notification permission missing!") console.warn("Notification permission missing!")
PlatformPermissions.requestPermission(PlatformPermissions.PermissionNotifications) PlatformPermissions.requestPermission(PlatformPermissions.PermissionNotifications)
} }
@ -460,6 +463,11 @@ Page {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.margins
}
TextField { TextField {
id: usernameTextField id: usernameTextField
Layout.fillWidth: true Layout.fillWidth: true
@ -468,6 +476,11 @@ Page {
visible: pairingPage.setupMethod === "SetupMethodUserAndPassword" visible: pairingPage.setupMethod === "SetupMethodUserAndPassword"
} }
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.margins
}
PasswordTextField { PasswordTextField {
id: pinTextField id: pinTextField
Layout.fillWidth: true Layout.fillWidth: true
@ -476,7 +489,6 @@ Page {
signup: false signup: false
} }
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: app.margins Layout.margins: app.margins
@ -549,8 +561,9 @@ Page {
WebView { WebView {
id: oAuthWebView id: oAuthWebView
anchors.fill: parent
url: oAuthPage.oAuthUrl url: oAuthPage.oAuthUrl
anchors.fill: parent
function finishProcess(url) { function finishProcess(url) {
print("Confirm pairing") print("Confirm pairing")
@ -559,7 +572,7 @@ Page {
oAuthWebView.visible = false oAuthWebView.visible = false
} }
onUrlChanged: { onUrlChanged: (url) => {
print("OAUTH URL changed", url) print("OAUTH URL changed", url)
if (url.toString().indexOf("https://127.0.0.1") == 0) { if (url.toString().indexOf("https://127.0.0.1") == 0) {
print("Redirect URL detected!") print("Redirect URL detected!")