diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index 073488bd..2e59dc56 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -21,6 +21,8 @@ #include "devicemanager.h" #include "engine.h" #include "jsonrpc/jsontypes.h" +#include "types/browseritems.h" +#include "types/browseritem.h" #include DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) : @@ -425,3 +427,62 @@ int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeI return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse"); } + +BrowserItems *DeviceManager::browseDevice(const QUuid &deviceId, const QString &nodeId) +{ + QVariantMap params; + params.insert("deviceId", deviceId.toString()); + params.insert("nodeId", nodeId); + int id = m_jsonClient->sendCommand("Devices.BrowseDevice", params, this, "browseDeviceResponse"); + + // Intentionally not parented. The caller takes ownership and needs to destroy when not needed any more. + BrowserItems *itemModel = new BrowserItems(); + itemModel->setBusy(true); + QPointer itemModelPtr(itemModel); + m_browsingRequests.insert(id, itemModelPtr); + + return itemModel; +} + +void DeviceManager::browseDeviceResponse(const QVariantMap ¶ms) +{ + qDebug() << "Browsing response:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + int id = params.value("id").toInt(); + if (!m_browsingRequests.contains(id)) { + qWarning() << "Received a browsing reply for an id we don't know."; + return; + } + + QPointer itemModel = m_browsingRequests.take(id); + if (!itemModel) { + qDebug() << "BrowserItems model seems to have disappeared. Discarding browsing result."; + return; + } + + foreach (const QVariant &itemVariant, params.value("params").toMap().value("items").toList()) { + QVariantMap itemMap = itemVariant.toMap(); + BrowserItem *item = new BrowserItem(itemMap.value("id").toString(), this); + item->setDisplayName(itemMap.value("displayName").toString()); + item->setDescription(itemMap.value("description").toString()); + item->setThumbnail(itemMap.value("thumbnail").toString()); + item->setExecutable(itemMap.value("executable").toBool()); + item->setBrowsable(itemMap.value("browsable").toBool()); + itemModel->addBrowserItem(item); + } + + itemModel->setBusy(false); +} + +void DeviceManager::executeBrowserItem(const QUuid &deviceId, const QString &nodeId) +{ + QVariantMap params; + params.insert("deviceId", deviceId); + params.insert("nodeId", nodeId); + m_jsonClient->sendCommand("Devices.ExecuteBrowserItem", params, this, "executeBrowserItemResponse"); +} + +void DeviceManager::executeBrowserItemResponse(const QVariantMap ¶ms) +{ + qDebug() << "Execute Browser Item finished" << params; +} + diff --git a/libnymea-app-core/devicemanager.h b/libnymea-app-core/devicemanager.h index 6257a770..5ae9ff77 100644 --- a/libnymea-app-core/devicemanager.h +++ b/libnymea-app-core/devicemanager.h @@ -30,6 +30,7 @@ #include "types/plugins.h" #include "jsonrpc/jsonhandler.h" #include "jsonrpc/jsonrpcclient.h" +class BrowserItems; class DeviceManager : public JsonHandler { @@ -73,6 +74,8 @@ public: Q_INVOKABLE void reconfigureDevice(const QUuid &deviceId, const QVariantList &deviceParams); Q_INVOKABLE void reconfigureDiscoveredDevice(const QUuid &deviceId, const QUuid &deviceDescriptorId); Q_INVOKABLE int executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms = QVariantList()); + Q_INVOKABLE BrowserItems* browseDevice(const QUuid &deviceId, const QString &nodeId = QString()); + Q_INVOKABLE void executeBrowserItem(const QUuid &deviceId, const QString &nodeId); private: Q_INVOKABLE void notificationReceived(const QVariantMap &data); @@ -89,6 +92,8 @@ private: Q_INVOKABLE void editDeviceResponse(const QVariantMap ¶ms); Q_INVOKABLE void executeActionResponse(const QVariantMap ¶ms); Q_INVOKABLE void reconfigureDeviceResponse(const QVariantMap ¶ms); + Q_INVOKABLE void browseDeviceResponse(const QVariantMap ¶ms); + Q_INVOKABLE void executeBrowserItemResponse(const QVariantMap ¶ms); public slots: void savePluginConfig(const QUuid &pluginId); @@ -117,6 +122,8 @@ private: JsonRpcClient *m_jsonClient = nullptr; + QHash > m_browsingRequests; + }; #endif // DEVICEMANAGER_H diff --git a/libnymea-app-core/jsonrpc/jsontypes.cpp b/libnymea-app-core/jsonrpc/jsontypes.cpp index 9c56b2dc..79dd7218 100644 --- a/libnymea-app-core/jsonrpc/jsontypes.cpp +++ b/libnymea-app-core/jsonrpc/jsontypes.cpp @@ -75,6 +75,7 @@ DeviceClass *JsonTypes::unpackDeviceClass(const QVariantMap &deviceClassMap, QOb deviceClass->setDisplayName(deviceClassMap.value("displayName").toString()); deviceClass->setId(deviceClassMap.value("id").toUuid()); deviceClass->setVendorId(deviceClassMap.value("vendorId").toUuid()); + deviceClass->setBrowsable(deviceClassMap.value("browsable").toBool()); QVariantList createMethodsList = deviceClassMap.value("createMethods").toList(); QStringList createMethods; foreach (QVariant method, createMethodsList) { diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index 7e2d2a46..75f76421 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -31,6 +31,8 @@ #include "types/statedescriptor.h" #include "types/stateevaluator.h" #include "types/stateevaluators.h" +#include "types/browseritems.h" +#include "types/browseritem.h" #include "models/logsmodel.h" #include "models/logsmodelng.h" #include "models/valuelogsproxymodel.h" @@ -109,6 +111,9 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "State", "Can't create this in QML. Get it from the States."); qmlRegisterUncreatableType(uri, 1, 0, "States", "Can't create this in QML. Get it from the Device."); + qmlRegisterUncreatableType(uri, 1, 0, "BrowserItems", "Can't create this in QML. Get it from DeviceManager."); + qmlRegisterUncreatableType(uri, 1, 0, "BrowserItem", "Can't create this in QML. Get it from BroweserItems."); + qmlRegisterUncreatableType(uri, 1, 0, "Vendor", "Can't create this in QML. Get it from the Vendors."); qmlRegisterUncreatableType(uri, 1, 0, "Vendors", "Can't create this in QML. Get it from the DeviceManager."); qmlRegisterType(uri, 1, 0, "VendorsProxy"); diff --git a/libnymea-common/libnymea-common.pro b/libnymea-common/libnymea-common.pro index 7b90c204..8e9fbaea 100644 --- a/libnymea-common/libnymea-common.pro +++ b/libnymea-common/libnymea-common.pro @@ -8,6 +8,8 @@ QT -= gui QT += network HEADERS += \ + types/browseritem.h \ + types/browseritems.h \ types/networkdevice.h \ types/networkdevices.h \ types/package.h \ @@ -62,6 +64,8 @@ HEADERS += \ types/wirelessaccesspoints.h \ SOURCES += \ + types/browseritem.cpp \ + types/browseritems.cpp \ types/networkdevice.cpp \ types/networkdevices.cpp \ types/package.cpp \ diff --git a/libnymea-common/types/browseritem.cpp b/libnymea-common/types/browseritem.cpp new file mode 100644 index 00000000..98250990 --- /dev/null +++ b/libnymea-common/types/browseritem.cpp @@ -0,0 +1,63 @@ +#include "browseritem.h" + +BrowserItem::BrowserItem(const QString &id, QObject *parent): + QObject(parent), + m_id(id) +{ + +} + +QString BrowserItem::id() const +{ + return m_id; +} + +QString BrowserItem::displayName() const +{ + return m_displayName; +} + +void BrowserItem::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} + +QString BrowserItem::description() const +{ + return m_description; +} + +void BrowserItem::setDescription(const QString &description) +{ + m_description = description; +} + +QString BrowserItem::thumbnail() const +{ + return m_thumbnail; +} + +void BrowserItem::setThumbnail(const QString &thumbnail) +{ + m_thumbnail = thumbnail; +} + +bool BrowserItem::executable() const +{ + return m_executable; +} + +void BrowserItem::setExecutable(bool executable) +{ + m_executable = executable; +} + +bool BrowserItem::browsable() const +{ + return m_browsable; +} + +void BrowserItem::setBrowsable(bool browsable) +{ + m_browsable = browsable; +} diff --git a/libnymea-common/types/browseritem.h b/libnymea-common/types/browseritem.h new file mode 100644 index 00000000..14bd51cd --- /dev/null +++ b/libnymea-common/types/browseritem.h @@ -0,0 +1,46 @@ +#ifndef BROWSERITEM_H +#define BROWSERITEM_H + +#include +#include + +class BrowserItem : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid id READ id CONSTANT) + Q_PROPERTY(QString displayName READ displayName CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QString thumbnail READ thumbnail CONSTANT) + Q_PROPERTY(bool executable READ executable CONSTANT) + Q_PROPERTY(bool browsable READ browsable CONSTANT) + +public: + explicit BrowserItem(const QString &id, QObject *parent = nullptr); + + QString id() const; + + QString displayName() const; + void setDisplayName(const QString &displayName); + + QString description() const; + void setDescription(const QString &description); + + QString thumbnail() const; + void setThumbnail(const QString &thumbnail); + + bool executable() const; + void setExecutable(bool executable); + + bool browsable() const; + void setBrowsable(bool browsable); + +private: + QString m_id; + QString m_displayName; + QString m_description; + QString m_thumbnail; + bool m_executable = false; + bool m_browsable = false; +}; + +#endif // BROWSERITEM_H diff --git a/libnymea-common/types/browseritems.cpp b/libnymea-common/types/browseritems.cpp new file mode 100644 index 00000000..bfcae8d4 --- /dev/null +++ b/libnymea-common/types/browseritems.cpp @@ -0,0 +1,73 @@ +#include "browseritems.h" +#include "browseritem.h" + +#include + +BrowserItems::BrowserItems(QObject *parent): QAbstractListModel(parent) +{ + +} + +BrowserItems::~BrowserItems() +{ + qDebug() << "Deleting BrowserItems"; +} + +bool BrowserItems::busy() const +{ + return m_busy; +} + +int BrowserItems::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_list.count(); +} + +QVariant BrowserItems::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleId: + return m_list.at(index.row())->id(); + case RoleDisplayName: + return m_list.at(index.row())->displayName(); + case RoleDescription: + return m_list.at(index.row())->description(); + case RoleThumbnail: + return m_list.at(index.row())->thumbnail(); + case RoleExecutable: + return m_list.at(index.row())->executable(); + case RoleBrowsable: + return m_list.at(index.row())->browsable(); + } + return QVariant(); +} + +QHash BrowserItems::roleNames() const +{ + QHash roles; + roles.insert(RoleId, "id"); + roles.insert(RoleDisplayName, "displayName"); + roles.insert(RoleDescription, "description"); + roles.insert(RoleThumbnail, "thumbnail"); + roles.insert(RoleExecutable, "executable"); + roles.insert(RoleBrowsable, "browsable"); + return roles; +} + +void BrowserItems::addBrowserItem(BrowserItem *browserItem) +{ + browserItem->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(browserItem); + endInsertRows(); + emit countChanged(); +} + +void BrowserItems::setBusy(bool busy) +{ + if (m_busy != busy) { + m_busy = busy; + emit busyChanged(); + } +} diff --git a/libnymea-common/types/browseritems.h b/libnymea-common/types/browseritems.h new file mode 100644 index 00000000..07257942 --- /dev/null +++ b/libnymea-common/types/browseritems.h @@ -0,0 +1,50 @@ +#ifndef BROWSERITEMS_H +#define BROWSERITEMS_H + +#include + +class BrowserItem; + +class BrowserItems: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) +public: + enum Roles { + RoleId, + RoleDisplayName, + RoleDescription, + RoleThumbnail, + RoleBrowsable, + RoleExecutable + }; + Q_ENUM(Roles) + + explicit BrowserItems(QObject *parent = nullptr); + virtual ~BrowserItems() override; + + bool busy() const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + virtual QHash roleNames() const override; + + virtual void addBrowserItem(BrowserItem *browserItem); + void setBusy(bool busy); + +// Q_INVOKABLE virtual BrowserItem* get(int index) const; +// Q_INVOKABLE virtual BrowserItem* getBrowserItem(const QString &itemId); + +// void clear(); + +signals: + void countChanged(); + void busyChanged(); + +protected: + bool m_busy = false; + QList m_list; +}; + +#endif // BROWSERITEMS_H diff --git a/libnymea-common/types/deviceclass.cpp b/libnymea-common/types/deviceclass.cpp index 029d9047..a9fcc81e 100644 --- a/libnymea-common/types/deviceclass.cpp +++ b/libnymea-common/types/deviceclass.cpp @@ -167,6 +167,16 @@ QString DeviceClass::baseInterface() const } +bool DeviceClass::browsable() const +{ + return m_browsable; +} + +void DeviceClass::setBrowsable(bool browsable) +{ + m_browsable = browsable; +} + ParamTypes *DeviceClass::paramTypes() const { return m_paramTypes; diff --git a/libnymea-common/types/deviceclass.h b/libnymea-common/types/deviceclass.h index 21ccb3f7..a86ae8b4 100644 --- a/libnymea-common/types/deviceclass.h +++ b/libnymea-common/types/deviceclass.h @@ -46,6 +46,7 @@ class DeviceClass : public QObject Q_PROPERTY(SetupMethod setupMethod READ setupMethod CONSTANT) Q_PROPERTY(QStringList interfaces READ interfaces CONSTANT) Q_PROPERTY(QString baseInterface READ baseInterface CONSTANT) + Q_PROPERTY(bool browsable READ browsable CONSTANT) Q_PROPERTY(ParamTypes *paramTypes READ paramTypes NOTIFY paramTypesChanged) Q_PROPERTY(ParamTypes *settingsTypes READ settingsTypes NOTIFY settingsTypesChanged) Q_PROPERTY(ParamTypes *discoveryParamTypes READ discoveryParamTypes NOTIFY discoveryParamTypesChanged) @@ -91,6 +92,9 @@ public: QString baseInterface() const; + bool browsable() const; + void setBrowsable(bool browsable); + ParamTypes *paramTypes() const; void setParamTypes(ParamTypes *paramTypes); @@ -120,6 +124,7 @@ private: QStringList m_createMethods; SetupMethod m_setupMethod; QStringList m_interfaces; + bool m_browsable = false; ParamTypes *m_paramTypes = nullptr; ParamTypes *m_settingsTypes = nullptr; diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 2ce38fb7..37db3d92 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -181,5 +181,6 @@ ui/images/lock-closed.svg ui/images/lock-open.svg ui/images/system-update.svg + ui/images/folder-symbolic.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index b70bac89..1b9790da 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -184,5 +184,6 @@ ui/components/UpdateRunningOverlay.qml ui/connection/SetupWizard.qml ui/system/NetworkSettingsPage.qml + ui/devicepages/DeviceBrowserPage.qml diff --git a/nymea-app/ui/devicepages/DeviceBrowserPage.qml b/nymea-app/ui/devicepages/DeviceBrowserPage.qml new file mode 100644 index 00000000..c53a6366 --- /dev/null +++ b/nymea-app/ui/devicepages/DeviceBrowserPage.qml @@ -0,0 +1,55 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + header: GuhHeader { + text: qsTr("Browse %1").arg(root.device.name) + onBackPressed: pageStack.pop() + } + property Device device: null + property string nodeId: "" + + Component.onCompleted: { + d.model = engine.deviceManager.browseDevice(root.device.id, root.nodeId); + } + + QtObject { + id: d + property BrowserItems model: null + } + + ListView { + id: listView + anchors.fill: parent + model: d.model + + delegate: MeaListItemDelegate { + width: parent.width + text: model.displayName + progressive: model.browsable + subText: model.description + prominentSubText: false + iconName: model.thumbnail + + onClicked: { + print("clicked:", model.id) + if (model.executable) { + engine.deviceManager.executeBrowserItem(root.device.id, model.id) + } else if (model.browsable) { + pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {device: root.device, nodeId: model.id}) + } + } + } + + BusyIndicator { + anchors.centerIn: parent + running: listView.model.busy + visible: running + } + } + +} diff --git a/nymea-app/ui/devicepages/DevicePageBase.qml b/nymea-app/ui/devicepages/DevicePageBase.qml index aec021f7..40621a1c 100644 --- a/nymea-app/ui/devicepages/DevicePageBase.qml +++ b/nymea-app/ui/devicepages/DevicePageBase.qml @@ -18,6 +18,14 @@ Page { text: device.name onBackPressed: pageStack.pop() + HeaderButton { + imageSource: "../images/folder-symbolic.svg" + visible: root.deviceClass.browsable + onClicked: { + pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {device: root.device}) + } + } + HeaderButton { imageSource: "../images/navigation-menu.svg" onClicked: thingMenu.open(); diff --git a/nymea-app/ui/images/folder-symbolic.svg b/nymea-app/ui/images/folder-symbolic.svg new file mode 100644 index 00000000..50289d3e --- /dev/null +++ b/nymea-app/ui/images/folder-symbolic.svg @@ -0,0 +1,17 @@ + + + + + + + image/svg+xml + + + + + + + + + +