From f735c5edfbbffe598197ca82e7b3f711508e66b4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 17 Jul 2019 22:49:04 +0200 Subject: [PATCH] Browser Items in rules --- libnymea-app-core/jsonrpc/jsontypes.cpp | 3 + libnymea-app-core/rulemanager.cpp | 3 + libnymea-common/types/ruleaction.cpp | 15 +++++ libnymea-common/types/ruleaction.h | 6 ++ nymea-app/resources.qrc | 2 + nymea-app/ui/MagicPage.qml | 4 +- .../ui/components/NymeaListItemDelegate.qml | 17 ++++++ .../ui/delegates/BrowserItemDelegate.qml | 40 +++++++++++++ .../ui/devicepages/DeviceBrowserPage.qml | 24 ++------ nymea-app/ui/devicepages/MediaDevicePage.qml | 24 ++------ nymea-app/ui/magic/EditRulePage.qml | 2 +- nymea-app/ui/magic/RuleActionDelegate.qml | 5 +- .../ui/magic/SelectBrowserItemActionPage.qml | 48 ++++++++++++++++ nymea-app/ui/magic/SelectRuleActionPage.qml | 56 ++++++++++++++----- nymea-app/ui/mainviews/ScenesView.qml | 2 +- 15 files changed, 195 insertions(+), 56 deletions(-) create mode 100644 nymea-app/ui/delegates/BrowserItemDelegate.qml create mode 100644 nymea-app/ui/magic/SelectBrowserItemActionPage.qml diff --git a/libnymea-app-core/jsonrpc/jsontypes.cpp b/libnymea-app-core/jsonrpc/jsontypes.cpp index 5635506d..2c4374a3 100644 --- a/libnymea-app-core/jsonrpc/jsontypes.cpp +++ b/libnymea-app-core/jsonrpc/jsontypes.cpp @@ -318,6 +318,9 @@ QVariantList JsonTypes::packRuleActions(RuleActions *ruleActions) if (!ra->actionTypeId().isNull() && !ra->deviceId().isNull()) { ruleAction.insert("deviceId", ra->deviceId()); ruleAction.insert("actionTypeId", ra->actionTypeId()); + } else if (!ra->deviceId().isNull() && !ra->browserItemId().isEmpty()) { + ruleAction.insert("deviceId", ra->deviceId()); + ruleAction.insert("browserItemId", ra->browserItemId()); } else { ruleAction.insert("interface", ra->interfaceName()); ruleAction.insert("interfaceAction", ra->interfaceAction()); diff --git a/libnymea-app-core/rulemanager.cpp b/libnymea-app-core/rulemanager.cpp index 8ea580ca..885232cd 100644 --- a/libnymea-app-core/rulemanager.cpp +++ b/libnymea-app-core/rulemanager.cpp @@ -286,6 +286,9 @@ RuleAction *RuleManager::parseRuleAction(const QVariantMap &ruleAction) if (ruleAction.contains("deviceId") && ruleAction.contains("actionTypeId")) { ret->setDeviceId(ruleAction.value("deviceId").toUuid()); ret->setActionTypeId(ruleAction.value("actionTypeId").toUuid()); + } else if (ruleAction.contains("deviceId") && ruleAction.contains("browserItemId")) { + ret->setDeviceId(ruleAction.value("deviceId").toUuid()); + ret->setBrowserItemId(ruleAction.value("browserItemId").toString()); } else { ret->setInterfaceName(ruleAction.value("interface").toString()); ret->setInterfaceAction(ruleAction.value("interfaceAction").toString()); diff --git a/libnymea-common/types/ruleaction.cpp b/libnymea-common/types/ruleaction.cpp index acb2b997..c5ca84f0 100644 --- a/libnymea-common/types/ruleaction.cpp +++ b/libnymea-common/types/ruleaction.cpp @@ -62,6 +62,19 @@ void RuleAction::setInterfaceAction(const QString &interfaceAction) } } +QString RuleAction::browserItemId() const +{ + return m_browserItemId; +} + +void RuleAction::setBrowserItemId(const QString &browserItemId) +{ + if (m_browserItemId != browserItemId) { + m_browserItemId = browserItemId; + emit browserItemIdChanged(); + } +} + RuleActionParams *RuleAction::ruleActionParams() const { return m_ruleActionParams; @@ -72,6 +85,7 @@ RuleAction *RuleAction::clone() const RuleAction *ret = new RuleAction(); ret->setDeviceId(deviceId()); ret->setActionTypeId(actionTypeId()); + ret->setBrowserItemId(browserItemId()); ret->setInterfaceName(interfaceName()); ret->setInterfaceAction(interfaceAction()); for (int i = 0; i < ruleActionParams()->rowCount(); i++) { @@ -88,6 +102,7 @@ bool RuleAction::operator==(RuleAction *other) const COMPARE(m_actionTypeId, other->actionTypeId()); COMPARE(m_interfaceName, other->interfaceName()); COMPARE(m_interfaceAction, other->interfaceAction()); + COMPARE(m_browserItemId, other->browserItemId()); COMPARE_PTR(m_ruleActionParams, other->ruleActionParams()); return true; } diff --git a/libnymea-common/types/ruleaction.h b/libnymea-common/types/ruleaction.h index 9b31c5ec..821424d0 100644 --- a/libnymea-common/types/ruleaction.h +++ b/libnymea-common/types/ruleaction.h @@ -13,6 +13,7 @@ class RuleAction : public QObject Q_PROPERTY(QUuid actionTypeId READ actionTypeId WRITE setActionTypeId NOTIFY actionTypeIdChanged) Q_PROPERTY(QString interfaceName READ interfaceName WRITE setInterfaceName NOTIFY interfaceNameChanged) Q_PROPERTY(QString interfaceAction READ interfaceAction WRITE setInterfaceAction NOTIFY interfaceActionChanged) + Q_PROPERTY(QString browserItemId READ browserItemId WRITE setBrowserItemId NOTIFY browserItemIdChanged) Q_PROPERTY(RuleActionParams* ruleActionParams READ ruleActionParams CONSTANT) public: @@ -30,6 +31,9 @@ public: QString interfaceAction() const; void setInterfaceAction(const QString &interfaceAction); + QString browserItemId() const; + void setBrowserItemId(const QString &browserItemId); + RuleActionParams* ruleActionParams() const; RuleAction *clone() const; @@ -40,12 +44,14 @@ signals: void actionTypeIdChanged(); void interfaceNameChanged(); void interfaceActionChanged(); + bool browserItemIdChanged(); private: QUuid m_deviceId; QUuid m_actionTypeId; QString m_interfaceName; QString m_interfaceAction; + QString m_browserItemId; RuleActionParams *m_ruleActionParams; }; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 2dfbea2f..7e21f360 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -189,5 +189,7 @@ ui/components/MediaControls.qml ui/components/MediaArtworkImage.qml ui/components/BrowserContextMenu.qml + ui/magic/SelectBrowserItemActionPage.qml + ui/delegates/BrowserItemDelegate.qml diff --git a/nymea-app/ui/MagicPage.qml b/nymea-app/ui/MagicPage.qml index 74703ded..049b44aa 100644 --- a/nymea-app/ui/MagicPage.qml +++ b/nymea-app/ui/MagicPage.qml @@ -77,8 +77,8 @@ Page { delegate: NymeaListItemDelegate { id: ruleDelegate width: parent.width - iconName: "../images/" + (model.executable ? (iconTag ? iconTag.value : "slideshow") : "magic") + ".svg" - iconColor: model.executable ? (colorTag ? colorTag.value : app.accentColor) : !model.enabled ? "red" : (model.active ? app.accentColor : "grey") + iconName: "../images/" + (model.executable ? (iconTag.value.length > 0 ? iconTag.value : "slideshow") : "magic") + ".svg" + iconColor: model.executable ? (colorTag.value.length > 0 ? colorTag.value : app.accentColor) : !model.enabled ? "red" : (model.active ? app.accentColor : "grey") text: model.name canDelete: true diff --git a/nymea-app/ui/components/NymeaListItemDelegate.qml b/nymea-app/ui/components/NymeaListItemDelegate.qml index 69cfdd04..ea67ae96 100644 --- a/nymea-app/ui/components/NymeaListItemDelegate.qml +++ b/nymea-app/ui/components/NymeaListItemDelegate.qml @@ -22,15 +22,18 @@ SwipeDelegate { property alias secondaryIconName: secondaryIcon.name property alias secondaryIconColor: secondaryIcon.color property alias secondaryIconKeyColor: secondaryIcon.keyColor + property alias secondaryIconClickable: secondaryIconMouseArea.enabled property alias tertiaryIconName: tertiaryIcon.name property alias tertiaryIconColor: tertiaryIcon.color property alias tertiaryIconKeyColor: tertiaryIcon.keyColor + property alias tertiaryIconClickable: tertiaryIconMouseArea.enabled property alias additionalItem: additionalItemContainer.children property alias busy: busyIndicator.running signal deleteClicked() + signal secondaryIconClicked() contentItem: RowLayout { id: innerLayout @@ -95,6 +98,13 @@ SwipeDelegate { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height visible: name.length > 0 + MouseArea { + id: secondaryIconMouseArea + enabled: false + anchors.fill: parent + anchors.margins: -app.margins + onClicked: root.secondaryIconClicked(); + } } ColorIcon { @@ -102,6 +112,13 @@ SwipeDelegate { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height visible: name.length > 0 + MouseArea { + id: tertiaryIconMouseArea + enabled: false + anchors.fill: parent + anchors.margins: -app.margins + onClicked: root.tertiaryIconClicked(); + } } ColorIcon { diff --git a/nymea-app/ui/delegates/BrowserItemDelegate.qml b/nymea-app/ui/delegates/BrowserItemDelegate.qml new file mode 100644 index 00000000..c245da99 --- /dev/null +++ b/nymea-app/ui/delegates/BrowserItemDelegate.qml @@ -0,0 +1,40 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +NymeaListItemDelegate { + id: root + width: parent.width + text: model.displayName + progressive: model.browsable + subText: model.description + prominentSubText: false + iconName: model.thumbnail + fallbackIcon: "../images/browser/" + model.icon + ".svg" + enabled: model.browsable || model.executable + secondaryIconName: model.actionTypeIds.length > 0 ? "../images/navigation-menu.svg" : "" + secondaryIconClickable: true + + property Device device: null + + onPressAndHold: openContextMenu() + onSecondaryIconClicked: openContextMenu() + + signal contextMenuActionTriggered(var actionTypeId, var params) + + function openContextMenu() { + if (model.actionTypeIds.length === 0) { + return; + } + + var actionDialogComponent = Qt.createComponent(Qt.resolvedUrl("../components/BrowserContextMenu.qml")); + var popup = actionDialogComponent.createObject(root, {device: root.device, title: model.displayName, itemId: model.id, actionTypeIds: model.actionTypeIds}); + popup.activated.connect(function(actionTypeId, params) { + root.contextMenuActionTriggered(actionTypeId, params) + }) + popup.open() + } +} diff --git a/nymea-app/ui/devicepages/DeviceBrowserPage.qml b/nymea-app/ui/devicepages/DeviceBrowserPage.qml index 05d23ab5..b20dca21 100644 --- a/nymea-app/ui/devicepages/DeviceBrowserPage.qml +++ b/nymea-app/ui/devicepages/DeviceBrowserPage.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" +import "../delegates" Page { id: root @@ -59,16 +60,10 @@ Page { model: d.model ScrollBar.vertical: ScrollBar {} - delegate: NymeaListItemDelegate { - width: parent.width - text: model.displayName - progressive: model.browsable - subText: model.description - prominentSubText: false - iconName: model.thumbnail - fallbackIcon: "../images/browser/" + model.icon + ".svg" - enabled: !model.disabled + delegate: BrowserItemDelegate { + id: delegate busy: d.pendingItemId === model.id + device: root.device onClicked: { print("clicked:", model.id) @@ -79,15 +74,8 @@ Page { } } - - onPressAndHold: { - print("show actions:", model.actionTypeIds) - var actionDialogComponent = Qt.createComponent(Qt.resolvedUrl("../components/BrowserContextMenu.qml")); - var popup = actionDialogComponent.createObject(this, {device: root.device, title: model.displayName, itemId: model.id, actionTypeIds: model.actionTypeIds}); - popup.activated.connect(function(actionTypeId, params) { - root.executeBrowserItemAction(model.id, actionTypeId, params) - }) - popup.open() + onContextMenuActionTriggered: { + root.executeBrowserItemAction(model.id, actionTypeId, params) } } diff --git a/nymea-app/ui/devicepages/MediaDevicePage.qml b/nymea-app/ui/devicepages/MediaDevicePage.qml index f4ef6ef5..a7720940 100644 --- a/nymea-app/ui/devicepages/MediaDevicePage.qml +++ b/nymea-app/ui/devicepages/MediaDevicePage.qml @@ -6,6 +6,7 @@ import QtGraphicalEffects 1.0 import Nymea 1.0 import "../components" import "../customviews" +import "../delegates" DevicePageBase { id: root @@ -161,17 +162,10 @@ DevicePageBase { browserItems = engine.deviceManager.browseDevice(root.device.id, nodeId); } - delegate: NymeaListItemDelegate { - width: parent.width - text: model.displayName - progressive: model.browsable - subText: model.description - prominentSubText: false - iconName: model.thumbnail + delegate: BrowserItemDelegate { fallbackIcon: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg" - enabled: model.browsable || model.executable busy: d.pendingItemId === model.id - secondaryIconName: model.actionTypeIds.length > 0 ? "../images/navigation-menu.svg" : "" + device: root.device onClicked: { print("clicked:", model.id) @@ -181,15 +175,9 @@ DevicePageBase { internalPageStack.push(internalBrowserPage, {device: root.device, nodeId: model.id}) } } - onPressAndHold: { - print("show actions:", model.actionTypeIds) - var actionDialogComponent = Qt.createComponent(Qt.resolvedUrl("../components/BrowserContextMenu.qml")); - var popup = actionDialogComponent.createObject(root, {device: root.device, title: model.displayName, itemId: model.id, actionTypeIds: model.actionTypeIds}); - popup.activated.connect(function(actionTypeId, params) { - print("params:", JSON.stringify(params)) - root.executeBrowserItemAction(model.id, actionTypeId, params) - }) - popup.open() + + onContextMenuActionTriggered: { + root.executeBrowserItemAction(model.id, actionTypeId, params) } } diff --git a/nymea-app/ui/magic/EditRulePage.qml b/nymea-app/ui/magic/EditRulePage.qml index 9a257fbc..1a148987 100644 --- a/nymea-app/ui/magic/EditRulePage.qml +++ b/nymea-app/ui/magic/EditRulePage.qml @@ -317,7 +317,7 @@ Page { Behavior on opacity { NumberAnimation {duration: 200; easing.type: Easing.InOutQuad } } Label { Layout.fillWidth: true - text: qsTr("This is a scene" + root.ruleColor, root.ruleIcon) + text: qsTr("This is a scene") } CheckBox { diff --git a/nymea-app/ui/magic/RuleActionDelegate.qml b/nymea-app/ui/magic/RuleActionDelegate.qml index e45bec6c..f662942c 100644 --- a/nymea-app/ui/magic/RuleActionDelegate.qml +++ b/nymea-app/ui/magic/RuleActionDelegate.qml @@ -17,13 +17,14 @@ NymeaListItemDelegate { property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null property var actionType: deviceClass ? deviceClass.actionTypes.getActionType(ruleAction.actionTypeId) : iface ? iface.actionTypes.findByName(ruleAction.interfaceAction) : null + property var browserItemId: ruleAction.browserItemId signal removeRuleAction() onDeleteClicked: root.removeRuleAction() - iconName: root.device ? "../images/action.svg" : "../images/action-interface.svg" - text: qsTr("%1 - %2").arg(root.device ? root.device.name : root.iface.displayName).arg(root.actionType.displayName) + iconName: root.device ? (root.browserItemId ? "../images/browser/BrowserIconFolder.svg" : "../images/action.svg") : "../images/action-interface.svg" + text: qsTr("%1 - %2").arg(root.device ? root.device.name : root.iface.displayName).arg(root.actionType ? root.actionType.displayName : qsTr("Launch an item")) subText: { var ret = []; for (var i = 0; i < root.ruleAction.ruleActionParams.count; i++) { diff --git a/nymea-app/ui/magic/SelectBrowserItemActionPage.qml b/nymea-app/ui/magic/SelectBrowserItemActionPage.qml new file mode 100644 index 00000000..4efb0ef3 --- /dev/null +++ b/nymea-app/ui/magic/SelectBrowserItemActionPage.qml @@ -0,0 +1,48 @@ +import QtQuick 2.4 +import QtQuick.Controls 2.1 +import Nymea 1.0 +import "../components" +import "../delegates" + +Page { + id: root + + property Device device: null + property string itemId: "" + + signal selected(string itemId) + + header: NymeaHeader { + onBackPressed: pageStack.pop() + text: qsTr("Select item") + } + + Component.onCompleted: { + listView.model = engine.deviceManager.browseDevice(root.device.id, root.itemId) + } + + ListView { + id: listView + anchors.fill: parent + ScrollBar.vertical: ScrollBar {} + + delegate: BrowserItemDelegate { + width: parent.width + device: root.device + secondaryIconName: "" // We don't support BrowserItemActions in rules yet + + onClicked: { + if (model.browsable) { + var page = pageStack.push(Qt.resolvedUrl("SelectBrowserItemActionPage.qml"), {device: root.device, itemId: model.id}); + page.selected.connect(function() { + pageStack.pop(); + root.selected(model.id); + }) + } else if (model.executable) { + pageStack.pop(); + root.selected(model.id); + } + } + } + } +} diff --git a/nymea-app/ui/magic/SelectRuleActionPage.qml b/nymea-app/ui/magic/SelectRuleActionPage.qml index 6062b28a..ec61f756 100644 --- a/nymea-app/ui/magic/SelectRuleActionPage.qml +++ b/nymea-app/ui/magic/SelectRuleActionPage.qml @@ -8,13 +8,13 @@ Page { property alias text: header.text // a ruleAction object needs to be set and prefilled with either deviceId or interfaceName - property var ruleAction: null + property RuleAction ruleAction: null // optionally, a rule which will be used when determining params for the actions - property var rule: null + property Rule rule: null - readonly property var device: ruleAction && ruleAction.deviceId ? engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null - readonly property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + readonly property Device device: ruleAction && ruleAction.deviceId ? engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null + readonly property DeviceClass deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null signal backPressed(); signal done(); @@ -65,7 +65,20 @@ Page { } } else { if (root.device) { - listView.model = deviceClass.actionTypes + generatedModel.clear(); + for (var i = 0; i < deviceClass.actionTypes.count; i++) { + var actionType = deviceClass.actionTypes.get(i); + generatedModel.append({displayName: actionType.displayName, actionTypeId: actionType.id}) + } + + // Append an item for browse mode + if (root.deviceClass.browsable) { + generatedModel.append({displayName: qsTr("Open an item on this thing..."), actionTypeId: "browse"}) + } + + listView.model = generatedModel; + +// listView.model = deviceClass.actionTypes } } } @@ -73,10 +86,16 @@ Page { ListView { id: listView anchors.fill: parent + ScrollBar.vertical: ScrollBar {} - delegate: ItemDelegate { + delegate: NymeaListItemDelegate { + id: delegate text: model.displayName width: parent.width + iconName: model.actionTypeId === "browse" ? "../images/browser/BrowserIconFolder.svg" : "../images/action.svg" + property ActionType actionType: root.deviceClass.actionTypes.getActionType(model.actionTypeId) + progressive: model.actionTypeId === "browse" || actionType.paramTypes.count > 0 + onClicked: { if (header.interfacesMode) { if (root.device) { @@ -109,18 +128,27 @@ Page { } } else { if (root.device) { - var actionType = root.deviceClass.actionTypes.getActionType(model.id); - console.log("ActionType", actionType.id, "selected. Has", actionType.paramTypes.count, "params"); - root.ruleAction.actionTypeId = actionType.id; - if (actionType.paramTypes.count > 0) { - var paramsPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionParamsPage.qml"), {ruleAction: root.ruleAction, rule: root.rule}) - paramsPage.onBackPressed.connect(function() { pageStack.pop(); }); - paramsPage.onCompleted.connect(function() { + if (model.actionTypeId === "browse") { + var page = pageStack.push(Qt.resolvedUrl("SelectBrowserItemActionPage.qml"), {device: root.device}); + page.selected.connect(function(itemId) { + root.ruleAction.browserItemId = itemId; pageStack.pop(); root.done(); }) } else { - root.done(); + var actionType = root.deviceClass.actionTypes.getActionType(model.actiontypeId); + console.log("ActionType", actionType.id, "selected. Has", actionType.paramTypes.count, "params"); + root.ruleAction.actionTypeId = actionType.id; + if (actionType.paramTypes.count > 0) { + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionParamsPage.qml"), {ruleAction: root.ruleAction, rule: root.rule}) + paramsPage.onBackPressed.connect(function() { pageStack.pop(); }); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + root.done(); + }) + } else { + root.done(); + } } } } diff --git a/nymea-app/ui/mainviews/ScenesView.qml b/nymea-app/ui/mainviews/ScenesView.qml index c481adb0..8bac0639 100644 --- a/nymea-app/ui/mainviews/ScenesView.qml +++ b/nymea-app/ui/mainviews/ScenesView.qml @@ -31,7 +31,7 @@ Item { height: interfacesGridView.cellHeight iconName: iconTag ? "../images/" + iconTag.value + ".svg" : "../images/slideshow.svg"; fallbackIconName: "../images/slideshow.svg" - iconColor: colorTag ? colorTag.value : app.accentColor; + iconColor: colorTag.value.length > 0 ? colorTag.value : app.accentColor; text: model.name.toUpperCase() property var colorTag: engine.tagsManager.tags.findRuleTag(model.id, "color")