diff --git a/libnymea-app/rulemanager.cpp b/libnymea-app/rulemanager.cpp index 31d903d1..0a232a42 100644 --- a/libnymea-app/rulemanager.cpp +++ b/libnymea-app/rulemanager.cpp @@ -280,7 +280,7 @@ void RuleManager::parseEventDescriptors(const QVariantList &eventDescriptorList, StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluatorMap) { - // qDebug() << "Parsing state evaluator. Child count:" << stateEvaluatorMap.value("childEvaluators").toList().count(); + qDebug() << "Parsing state evaluator:" << qUtf8Printable(QJsonDocument::fromVariant(stateEvaluatorMap).toJson()); if (!stateEvaluatorMap.contains("stateDescriptor")) { return nullptr; } @@ -295,6 +295,11 @@ StateEvaluator *RuleManager::parseStateEvaluator(const QVariantMap &stateEvaluat } else { sd = new StateDescriptor(sdMap.value("interface").toString(), sdMap.value("interfaceState").toString(), op, sdMap.value("value"), stateEvaluator); } + if (sdMap.contains("valueThingId") && sdMap.contains("valueStateTypeId")) { + sd->setValueThingId(sdMap.value("valueThingId").toUuid()); + sd->setValueStateTypeId(sdMap.value("valueStateTypeId").toUuid()); + } + qDebug() << "Parsing stuff" << sd->valueThingId() << sd->valueStateTypeId(); stateEvaluator->setStateDescriptor(sd); foreach (const QVariant &childEvaluatorVariant, stateEvaluatorMap.value("childEvaluators").toList()) { @@ -572,7 +577,12 @@ QVariantMap RuleManager::packStateEvaluator(StateEvaluator *stateEvaluator) } QMetaEnum valueOperatorEnum = QMetaEnum::fromType(); stateDescriptor.insert("operator", valueOperatorEnum.valueToKeys(stateEvaluator->stateDescriptor()->valueOperator())); - stateDescriptor.insert("value", stateEvaluator->stateDescriptor()->value()); + if (!stateEvaluator->stateDescriptor()->value().isNull()) { + stateDescriptor.insert("value", stateEvaluator->stateDescriptor()->value()); + } else { + stateDescriptor.insert("valueThingId", stateEvaluator->stateDescriptor()->valueThingId()); + stateDescriptor.insert("valueStateTypeId", stateEvaluator->stateDescriptor()->valueStateTypeId()); + } ret.insert("stateDescriptor", stateDescriptor); QVariantList childEvaluators; for (int i = 0; i < stateEvaluator->childEvaluators()->rowCount(); i++) { diff --git a/libnymea-app/types/rule.cpp b/libnymea-app/types/rule.cpp index e55529ab..5aed0a82 100644 --- a/libnymea-app/types/rule.cpp +++ b/libnymea-app/types/rule.cpp @@ -330,7 +330,7 @@ QDebug printStateEvaluator(QDebug &dbg, StateEvaluator *stateEvaluator, int inde dbg << ">="; break; } - dbg << stateEvaluator->stateDescriptor()->value() << endl; + dbg << stateEvaluator->stateDescriptor()->value() << '/' << stateEvaluator->stateDescriptor()->valueThingId() << stateEvaluator->stateDescriptor()->valueStateTypeId() << endl; } if (stateEvaluator->childEvaluators()->rowCount() > 0) { for (int i = 0; i < indentLevel; i++) { dbg << " "; } diff --git a/libnymea-app/types/statedescriptor.cpp b/libnymea-app/types/statedescriptor.cpp index e02d0bd6..5f346024 100644 --- a/libnymea-app/types/statedescriptor.cpp +++ b/libnymea-app/types/statedescriptor.cpp @@ -135,11 +135,39 @@ void StateDescriptor::setValue(const QVariant &value) } } +QUuid StateDescriptor::valueThingId() const +{ + return m_valueThingId; +} + +void StateDescriptor::setValueThingId(const QUuid &valueThingId) +{ + if (m_valueThingId != valueThingId) { + m_valueThingId = valueThingId; + emit valueThingIdChanged(); + } +} + +QUuid StateDescriptor::valueStateTypeId() const +{ + return m_valueStateTypeId; +} + +void StateDescriptor::setValueStateTypeId(const QUuid &valueStateTypeId) +{ + if (m_valueStateTypeId != valueStateTypeId) { + m_valueStateTypeId = valueStateTypeId; + emit valueStateTypeIdChanged(); + } +} + StateDescriptor *StateDescriptor::clone() const { StateDescriptor *ret = new StateDescriptor(thingId(), stateTypeId(), valueOperator(), value()); ret->setInterfaceName(interfaceName()); ret->setInterfaceState(interfaceState()); + ret->setValueThingId(valueThingId()); + ret->setValueStateTypeId(valueStateTypeId()); return ret; } @@ -147,11 +175,13 @@ StateDescriptor *StateDescriptor::clone() const #define COMPARE_PTR(a, b) if (!a->operator==(b)) { qDebug() << a << "!=" << b; return false; } bool StateDescriptor::operator==(StateDescriptor *other) const { - COMPARE(m_thingId, other->thingId()); - COMPARE(m_stateTypeId, other->stateTypeId()); - COMPARE(m_interfaceName, other->interfaceName()); - COMPARE(m_interfaceState, other->interfaceState()); - COMPARE(m_operator, other->valueOperator()); - COMPARE(m_value, other->value()); + COMPARE(m_thingId, other->thingId()) + COMPARE(m_stateTypeId, other->stateTypeId()) + COMPARE(m_interfaceName, other->interfaceName()) + COMPARE(m_interfaceState, other->interfaceState()) + COMPARE(m_operator, other->valueOperator()) + COMPARE(m_value, other->value()) + COMPARE(m_valueThingId, other->valueThingId()) + COMPARE(m_valueStateTypeId, other->valueStateTypeId()) return true; } diff --git a/libnymea-app/types/statedescriptor.h b/libnymea-app/types/statedescriptor.h index 322814ea..3c4b8656 100644 --- a/libnymea-app/types/statedescriptor.h +++ b/libnymea-app/types/statedescriptor.h @@ -44,6 +44,8 @@ class StateDescriptor : public QObject Q_PROPERTY(QString interfaceState READ interfaceState WRITE setInterfaceState NOTIFY interfaceStateChanged) Q_PROPERTY(ValueOperator valueOperator READ valueOperator WRITE setValueOperator NOTIFY valueOperatorChanged) Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(QUuid valueThingId READ valueThingId WRITE setValueThingId NOTIFY valueThingIdChanged) + Q_PROPERTY(QUuid valueStateTypeId READ valueStateTypeId WRITE setValueStateTypeId NOTIFY valueStateTypeIdChanged) public: enum ValueOperator { @@ -78,6 +80,12 @@ public: QVariant value() const; void setValue(const QVariant &value); + QUuid valueThingId() const; + void setValueThingId(const QUuid &valueThingId); + + QUuid valueStateTypeId() const; + void setValueStateTypeId(const QUuid &valueStateTypeId); + StateDescriptor* clone() const; bool operator==(StateDescriptor *other) const; @@ -88,6 +96,8 @@ signals: void interfaceStateChanged(); void valueOperatorChanged(); void valueChanged(); + void valueThingIdChanged(); + void valueStateTypeIdChanged(); private: QUuid m_thingId; @@ -96,6 +106,8 @@ private: QString m_interfaceState; ValueOperator m_operator = ValueOperatorEquals; QVariant m_value; + QUuid m_valueThingId; + QUuid m_valueStateTypeId; }; #endif // STATEDESCRIPTOR_H diff --git a/libnymea-app/types/stateevaluator.cpp b/libnymea-app/types/stateevaluator.cpp index 29d72c18..55dfead6 100644 --- a/libnymea-app/types/stateevaluator.cpp +++ b/libnymea-app/types/stateevaluator.cpp @@ -96,12 +96,8 @@ StateEvaluator *StateEvaluator::clone() const { StateEvaluator *ret = new StateEvaluator(); ret->m_operator = this->m_operator; - ret->m_stateDescriptor->setThingId(this->m_stateDescriptor->thingId()); - ret->m_stateDescriptor->setStateTypeId(this->m_stateDescriptor->stateTypeId()); - ret->m_stateDescriptor->setInterfaceName(this->m_stateDescriptor->interfaceName()); - ret->m_stateDescriptor->setInterfaceState(this->m_stateDescriptor->interfaceState()); - ret->m_stateDescriptor->setValueOperator(this->m_stateDescriptor->valueOperator()); - ret->m_stateDescriptor->setValue(this->m_stateDescriptor->value()); + delete ret->m_stateDescriptor; // Delete existing empty one and replace it with a clone of the + ret->m_stateDescriptor = this->m_stateDescriptor->clone(); for (int i = 0; i < this->m_childEvaluators->rowCount(); i++) { ret->m_childEvaluators->addStateEvaluator(this->m_childEvaluators->get(i)->clone()); } diff --git a/nymea-app/ui/delegates/ParamDelegate.qml b/nymea-app/ui/delegates/ParamDelegate.qml index 2d71dee8..0718818b 100644 --- a/nymea-app/ui/delegates/ParamDelegate.qml +++ b/nymea-app/ui/delegates/ParamDelegate.qml @@ -137,18 +137,26 @@ ItemDelegate { } Component { id: boolComponent - Switch { - checked: root.param.value === true - Component.onCompleted: { - if (root.param.value === undefined) { + 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; } } - - onClicked: { - root.param.value = checked; - } } + } Component { id: sliderComponent diff --git a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml index 7b90a295..a288be38 100644 --- a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml +++ b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml @@ -122,6 +122,8 @@ Page { ParamDelegate { id: paramDelegate Layout.fillWidth: true + hoverEnabled: false + padding: 0 paramType: root.actionType.paramTypes.get(index) enabled: staticParamRadioButton.checked nameVisible: false diff --git a/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml b/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml index 87df3e32..6a608ca5 100644 --- a/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml +++ b/nymea-app/ui/magic/SelectStateDescriptorParamsPage.qml @@ -38,38 +38,151 @@ import Nymea 1.0 Page { id: root // Needs to be set and filled in with thingId and eventTypeId - property var stateDescriptor: null + property StateDescriptor stateDescriptor: null readonly property Thing thing: stateDescriptor && stateDescriptor.thingId ? engine.thingManager.things.getThing(stateDescriptor.thingId) : null - readonly property var iface: stateDescriptor && stateDescriptor.interfaceName ? Interfaces.findByName(stateDescriptor.interfaceName) : null - readonly property var stateType: thing ? thing.thingClass.stateTypes.getStateType(stateDescriptor.stateTypeId) + readonly property Interface iface: stateDescriptor && stateDescriptor.interfaceName ? Interfaces.findByName(stateDescriptor.interfaceName) : null + readonly property StateType stateType: thing ? thing.thingClass.stateTypes.getStateType(stateDescriptor.stateTypeId) : iface ? iface.stateTypes.findByName(stateDescriptor.interfaceState) : null signal backPressed(); signal completed(); header: NymeaHeader { - text: qsTr("Options") + text: qsTr("Condition") onBackPressed: root.backPressed(); } - ColumnLayout { - anchors.fill: parent - ParamDescriptorDelegate { - id: paramDelegate - Layout.fillWidth: true - paramType: root.stateType - value: paramType.defaultValue + QtObject { + id: d + property var value: root.stateType.defaultValue + } + + GroupBox { + anchors { + left: parent.left + top: parent.top + right: parent.right + margins: Style.margins } - Button { - text: qsTr("OK") - Layout.fillWidth: true - Layout.margins: app.margins - onClicked: { - root.stateDescriptor.valueOperator = paramDelegate.operatorType - root.stateDescriptor.value = paramDelegate.value - root.completed() + + GridLayout { + anchors.fill: parent + columns: width > 600 ? 2 : 1 + + Label { + Layout.fillWidth: true + text: "%1, %2".arg(root.thing.name).arg(root.stateType.displayName) } + + ComboBox { + id: operatorComboBox + Layout.fillWidth: true + property bool isNumeric: { + switch (root.stateType.type.toLowerCase()) { + case "bool": + case "string": + case "qstring": + case "color": + return false; + case "int": + case "double": + return true; + } + console.warn("ParamDescriptorDelegate: Unhandled data type:", root.stateType.type.toLowerCase()); + return false; + } + + model: isNumeric ? + [qsTr("is equal to"), qsTr("is not equal to"), qsTr("is smaller than"), qsTr("is greater than"), qsTr("is smaller or equal than"), qsTr("is greater or equal than")] + : [qsTr("is"), qsTr("is not")]; + } + + GroupBox { + Layout.columnSpan: parent.columns + Layout.fillWidth: true + + GridLayout { + anchors.fill: parent + columns: root.width > 600 ? 2 : 1 + RadioButton { + id: staticValueRadioButton + Layout.fillWidth: true + checked: true + text: qsTr("a static value:") + font.pixelSize: app.smallFont + } + RadioButton { + id: stateValueRadioButton + Layout.fillWidth: true + text: qsTr("another thing's state:") + font.pixelSize: app.smallFont + visible: engine.jsonRpcClient.ensureServerVersion("5.3") + } + + ThinDivider { Layout.columnSpan: parent.columns } + + ParamDelegate { + Layout.fillWidth: true + hoverEnabled: false + padding: 0 + paramType: root.thing.thingClass.eventTypes.getEventType(root.stateType.id).paramTypes.getParamType(root.stateType.id) + enabled: staticValueRadioButton.checked + nameVisible: false + value: d.value + visible: staticValueRadioButton.checked + placeholderText: qsTr("Insert value here") + } + + NymeaItemDelegate { + id: statePickerDelegate + Layout.fillWidth: true + text: thingId === null || stateTypeId === null + ? qsTr("Select a state") + : thing.name + " - " + thing.thingClass.stateTypes.getStateType(stateTypeId).displayName + visible: stateValueRadioButton.checked + + property var thingId: null + property var stateTypeId: null + + readonly property Thing thing: engine.thingManager.things.getThing(thingId) + + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {showStates: true, showEvents: false, showActions: false }); + page.thingSelected.connect(function(thing) { + print("Thing selected", thing.name); + statePickerDelegate.thingId = thing.id + var selectStatePage = pageStack.replace(Qt.resolvedUrl("SelectStatePage.qml"), {thing: thing}) + selectStatePage.stateSelected.connect(function(stateTypeId) { + print("State selected", stateTypeId) + pageStack.pop(); + statePickerDelegate.stateTypeId = stateTypeId; + }) + }) + page.backPressed.connect(function() { + pageStack.pop(); + }) + } + } + } + } + + Button { + text: qsTr("OK") + Layout.fillWidth: true + Layout.margins: app.margins + onClicked: { + root.stateDescriptor.valueOperator = operatorComboBox.currentIndex + if (staticValueRadioButton.checked) { + root.stateDescriptor.value = d.value + } else { + root.stateDescriptor.valueThingId = statePickerDelegate.thingId + root.stateDescriptor.valueStateTypeId = statePickerDelegate.stateTypeId + } + root.completed() + } + } + } } } diff --git a/nymea-app/ui/magic/SelectStatePage.qml b/nymea-app/ui/magic/SelectStatePage.qml index a815ce40..bb6c1c39 100644 --- a/nymea-app/ui/magic/SelectStatePage.qml +++ b/nymea-app/ui/magic/SelectStatePage.qml @@ -49,7 +49,7 @@ Page { ListView { anchors.fill: parent - model: thing.thingClass.stateTypes + model: root.thing.thingClass.stateTypes delegate: NymeaSwipeDelegate { width: parent.width diff --git a/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml b/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml index fcfbbf9f..87747a13 100644 --- a/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml +++ b/nymea-app/ui/magic/SimpleStateEvaluatorDelegate.qml @@ -39,12 +39,12 @@ SwipeDelegate { Layout.fillWidth: true clip: true - property var stateEvaluator: null + property StateEvaluator stateEvaluator: null property bool showChilds: false readonly property Thing thing: stateEvaluator ? engine.thingManager.things.getThing(stateEvaluator.stateDescriptor.thingId) : null - readonly property var iface: stateEvaluator ? Interfaces.findByName(stateEvaluator.stateDescriptor.interfaceName) : null - readonly property var stateType: thing ? thing.thingClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) + readonly property Interface iface: stateEvaluator ? Interfaces.findByName(stateEvaluator.stateDescriptor.interfaceName) : null + readonly property StateType stateType: thing ? thing.thingClass.stateTypes.getStateType(stateEvaluator.stateDescriptor.stateTypeId) : iface ? iface.stateTypes.findByName(stateEvaluator.stateDescriptor.interfaceState) : null signal deleteClicked(); @@ -96,17 +96,26 @@ SwipeDelegate { if (!root.stateType) { return qsTr("Press to edit condition") } - var valueText = root.stateEvaluator.stateDescriptor.value; - switch (root.stateType.type.toLowerCase()) { - case "bool": - valueText = root.stateEvaluator.stateDescriptor.value === true ? qsTr("True") : qsTr("False") - break; + + var valueText; + if (root.stateEvaluator.stateDescriptor.value !== undefined) { + valueText = root.stateEvaluator.stateDescriptor.value; + switch (root.stateType.type.toLowerCase()) { + case "bool": + valueText = root.stateEvaluator.stateDescriptor.value === true ? qsTr("True") : qsTr("False") + break; + } + } else { + print("value thing id:", root.stateEvaluator.stateDescriptor.valueThingId) + var valueThing = engine.thingManager.things.getThing(root.stateEvaluator.stateDescriptor.valueThingId) + valueText = valueThing.name + valueText += ", " + valueThing.thingClass.stateTypes.getStateType(root.stateEvaluator.stateDescriptor.valueStateTypeId).displayName } if (root.thing) { - return qsTr("%1: %2 %3 %4").arg(root.thing.name).arg(root.stateType.displayName).arg(operatorString).arg(valueText) + return "%1: %2 %3 %4".arg(root.thing.name).arg(root.stateType.displayName).arg(operatorString).arg(valueText) } else if (root.iface) { - return qsTr("%1: %2 %3 %4").arg(root.iface.displayName).arg(root.stateType.displayName).arg(operatorString).arg(valueText) + return "%1, %2 %3 %4".arg(root.iface.displayName).arg(root.stateType.displayName).arg(operatorString).arg(valueText) } return "--"; }