From 509ce837e45c49559ea2626209ac847b92dead4f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 6 Nov 2017 21:55:35 +0100 Subject: [PATCH] loads of more work --- guh-control.pro | 2 +- guh-control/deviceclasses.cpp | 22 +- guh-control/deviceclasses.h | 11 +- guh-control/devicediscovery.cpp | 5 +- guh-control/devicemanager.cpp | 44 ++- guh-control/devicemanager.h | 9 +- guh-control/guhconnection.cpp | 4 + guh-control/jsonrpc/jsonrpcclient.cpp | 15 +- guh-control/jsonrpc/jsonrpcclient.h | 1 + guh-control/jsonrpc/jsontypes.cpp | 6 +- guh-control/main.cpp | 6 + guh-control/models/logsmodel.cpp | 134 ++++++++ guh-control/models/logsmodel.h | 67 ++++ guh-control/models/valuelogsproxymodel.cpp | 136 ++++++++ guh-control/models/valuelogsproxymodel.h | 53 +++ guh-control/resources.qrc | 77 ++++- guh-control/rulemanager.cpp | 2 +- guh-control/ui/DevicesPage.qml | 171 +++------- guh-control/ui/MagicPage.qml | 2 +- guh-control/ui/MainPage.qml | 78 +++-- guh-control/ui/NewDeviceWizard.qml | 100 ++++-- guh-control/ui/SettingsPage.qml | 34 +- guh-control/ui/SystemInfoPage.qml | 48 +++ guh-control/ui/components/ColorPickerCt.qml | 74 ++++ guh-control/ui/components/Graph.qml | 318 ++++++++++++++++++ guh-control/ui/components/GuhHeader.qml | 11 +- guh-control/ui/components/IconMenuItem.qml | 22 ++ guh-control/ui/components/ThinDivider.qml | 8 + guh-control/ui/components/ThrottledSlider.qml | 40 +++ guh-control/ui/customviews/CustomViewBase.qml | 1 + .../customviews/ExtendedVolumeController.qml | 17 +- .../ui/customviews/MediaControllerView.qml | 80 ++++- .../ui/customviews/NotificationsView.qml | 37 ++ guh-control/ui/customviews/SensorView.qml | 90 ++++- guh-control/ui/customviews/WeatherView.qml | 84 ++++- .../devicelistpages/GenericDeviceListPage.qml | 42 ++- .../devicelistpages/LightsDeviceListPage.qml | 7 +- .../ui/devicepages/ButtonDevicePage.qml | 26 +- .../ui/devicepages/ColorLightDevicePage.qml | 144 ++++++++ guh-control/ui/devicepages/DevicePageBase.qml | 51 +++ .../ui/devicepages/GenericDevicePage.qml | 91 +++-- .../GenericDeviceStateDetailsPage.qml | 2 +- .../ui/devicepages/SensorDevicePage.qml | 44 +++ .../ui/devicepages/WeatherDevicePage.qml | 30 ++ .../images/audio-speakers-muted-symbolic.svg | 20 ++ .../ui/images/audio-speakers-symbolic.svg | 20 ++ guh-control/ui/images/battery/battery-000.svg | 18 + guh-control/ui/images/battery/battery-010.svg | 19 ++ guh-control/ui/images/battery/battery-020.svg | 19 ++ guh-control/ui/images/battery/battery-030.svg | 19 ++ guh-control/ui/images/battery/battery-040.svg | 19 ++ guh-control/ui/images/battery/battery-050.svg | 19 ++ guh-control/ui/images/battery/battery-060.svg | 19 ++ guh-control/ui/images/battery/battery-070.svg | 19 ++ guh-control/ui/images/battery/battery-080.svg | 19 ++ guh-control/ui/images/battery/battery-090.svg | 19 ++ guh-control/ui/images/battery/battery-100.svg | 19 ++ .../ui/images/dialog-warning-symbolic.svg | 176 ++++++++++ .../ui/images/media-playback-pause.svg | 168 +++++++++ .../ui/images/media-playback-start.svg | 163 +++++++++ guh-control/ui/images/media-playback-stop.svg | 163 +++++++++ guh-control/ui/images/media-seek-backward.svg | 174 ++++++++++ guh-control/ui/images/media-seek-forward.svg | 173 ++++++++++ ...t-symbolic.svg => media-skip-backward.svg} | 62 ++-- guh-control/ui/images/media-skip-forward.svg | 175 ++++++++++ ...alert-symbolic.svg => navigation-menu.svg} | 38 ++- .../ui/images/network-wired-symbolic.svg | 160 +++++++++ guh-control/ui/images/notification.svg | 180 ++++++++++ guh-control/ui/images/sensors.svg | 227 +++++++++++++ ...her-flurries-symbolic.svg => settings.svg} | 55 ++- guh-control/ui/images/share.svg | 174 ++++++++++ guh-control/ui/images/stock_link.svg | 169 ++++++++++ guh-control/ui/images/temperature.svg | 185 ++++++++++ ...ather-cloud-symbolic.svg => torch-off.svg} | 32 +- ...-symbolic.svg => weather-app-symbolic.svg} | 0 .../ui/images/weathericons/humidity.svg | 160 +++++++++ ...sun-symbolic.svg => weather-clear-day.svg} | 0 ...t-symbolic.svg => weather-clear-night.svg} | 0 ...-night-symbolic.svg => weather-clouds.svg} | 0 ...ymbolic.svg => weather-few-clouds-day.svg} | 76 +++-- ...bolic.svg => weather-few-clouds-night.svg} | 0 ...ather-fog-symbolic.svg => weather-fog.svg} | 0 ...ed-symbolic.svg => weather-light-rain.svg} | 0 ...cast-symbolic.svg => weather-overcast.svg} | 0 ...n-symbolic.svg => weather-shower-rain.svg} | 0 ...her-snow-symbolic.svg => weather-snow.svg} | 0 ...-symbolic.svg => weather-thunderstorm.svg} | 0 ...r-chance-of-wind-symbolic.svg => wind.svg} | 0 guh-control/ui/magic/NewRulePage.qml | 2 +- guh-control/ui/magic/SelectActionPage.qml | 169 +++++++--- guh-control/ui/main.qml | 98 +++++- .../ui/paramdelegates/BoolParamDelegate.qml | 20 ++ .../ui/paramdelegates/IntParamDelegate.qml | 25 ++ .../ui/paramdelegates/ParamDelegate.qml | 46 +++ .../ui/paramdelegates/ParamDelegateBase.qml | 9 + .../ui/paramdelegates/StringParamDelegate.qml | 24 ++ libguh-common/libguh-common.pro | 6 +- libguh-common/types/device.cpp | 2 + libguh-common/types/deviceclass.cpp | 10 + libguh-common/types/deviceclass.h | 5 + libguh-common/types/logentry.cpp | 52 +++ libguh-common/types/logentry.h | 32 ++ libguh-common/types/state.cpp | 3 + libguh-common/types/statetype.cpp | 10 + libguh-common/types/statetype.h | 5 + libguh-common/types/statetypes.cpp | 30 +- libguh-common/types/statetypes.h | 15 +- libguh-common/types/vendor.cpp | 9 + libguh-common/types/vendor.h | 5 + libguh-common/types/vendors.cpp | 12 +- libguh-common/types/vendors.h | 7 +- 111 files changed, 5267 insertions(+), 533 deletions(-) create mode 100644 guh-control/models/logsmodel.cpp create mode 100644 guh-control/models/logsmodel.h create mode 100644 guh-control/models/valuelogsproxymodel.cpp create mode 100644 guh-control/models/valuelogsproxymodel.h create mode 100644 guh-control/ui/SystemInfoPage.qml create mode 100644 guh-control/ui/components/ColorPickerCt.qml create mode 100644 guh-control/ui/components/Graph.qml create mode 100644 guh-control/ui/components/IconMenuItem.qml create mode 100644 guh-control/ui/components/ThinDivider.qml create mode 100644 guh-control/ui/components/ThrottledSlider.qml create mode 100644 guh-control/ui/customviews/NotificationsView.qml create mode 100644 guh-control/ui/devicepages/ColorLightDevicePage.qml create mode 100644 guh-control/ui/devicepages/DevicePageBase.qml create mode 100644 guh-control/ui/devicepages/SensorDevicePage.qml create mode 100644 guh-control/ui/devicepages/WeatherDevicePage.qml create mode 100644 guh-control/ui/images/audio-speakers-muted-symbolic.svg create mode 100644 guh-control/ui/images/audio-speakers-symbolic.svg create mode 100644 guh-control/ui/images/battery/battery-000.svg create mode 100644 guh-control/ui/images/battery/battery-010.svg create mode 100644 guh-control/ui/images/battery/battery-020.svg create mode 100644 guh-control/ui/images/battery/battery-030.svg create mode 100644 guh-control/ui/images/battery/battery-040.svg create mode 100644 guh-control/ui/images/battery/battery-050.svg create mode 100644 guh-control/ui/images/battery/battery-060.svg create mode 100644 guh-control/ui/images/battery/battery-070.svg create mode 100644 guh-control/ui/images/battery/battery-080.svg create mode 100644 guh-control/ui/images/battery/battery-090.svg create mode 100644 guh-control/ui/images/battery/battery-100.svg create mode 100644 guh-control/ui/images/dialog-warning-symbolic.svg create mode 100644 guh-control/ui/images/media-playback-pause.svg create mode 100644 guh-control/ui/images/media-playback-start.svg create mode 100644 guh-control/ui/images/media-playback-stop.svg create mode 100644 guh-control/ui/images/media-seek-backward.svg create mode 100644 guh-control/ui/images/media-seek-forward.svg rename guh-control/ui/images/{weathericons/weather-sleet-symbolic.svg => media-skip-backward.svg} (54%) create mode 100644 guh-control/ui/images/media-skip-forward.svg rename guh-control/ui/images/{weathericons/weather-severe-alert-symbolic.svg => navigation-menu.svg} (62%) create mode 100644 guh-control/ui/images/network-wired-symbolic.svg create mode 100644 guh-control/ui/images/notification.svg create mode 100644 guh-control/ui/images/sensors.svg rename guh-control/ui/images/{weathericons/weather-flurries-symbolic.svg => settings.svg} (61%) create mode 100644 guh-control/ui/images/share.svg create mode 100644 guh-control/ui/images/stock_link.svg create mode 100644 guh-control/ui/images/temperature.svg rename guh-control/ui/images/{weathericons/weather-cloud-symbolic.svg => torch-off.svg} (72%) rename guh-control/ui/images/{weathericons/weather-few-clouds-symbolic.svg => weather-app-symbolic.svg} (100%) create mode 100644 guh-control/ui/images/weathericons/humidity.svg rename guh-control/ui/images/weathericons/{weather-sun-symbolic.svg => weather-clear-day.svg} (100%) rename guh-control/ui/images/weathericons/{weather-clear-night-symbolic.svg => weather-clear-night.svg} (100%) rename guh-control/ui/images/weathericons/{weather-cloud-night-symbolic.svg => weather-clouds.svg} (100%) rename guh-control/ui/images/weathericons/{weather-hazy-symbolic.svg => weather-few-clouds-day.svg} (65%) rename guh-control/ui/images/weathericons/{weather-few-clouds-night-symbolic.svg => weather-few-clouds-night.svg} (100%) rename guh-control/ui/images/weathericons/{weather-fog-symbolic.svg => weather-fog.svg} (100%) rename guh-control/ui/images/weathericons/{weather-scattered-symbolic.svg => weather-light-rain.svg} (100%) rename guh-control/ui/images/weathericons/{weather-overcast-symbolic.svg => weather-overcast.svg} (100%) rename guh-control/ui/images/weathericons/{weather-rain-symbolic.svg => weather-shower-rain.svg} (100%) rename guh-control/ui/images/weathericons/{weather-snow-symbolic.svg => weather-snow.svg} (100%) rename guh-control/ui/images/weathericons/{weather-thunder-symbolic.svg => weather-thunderstorm.svg} (100%) rename guh-control/ui/images/weathericons/{weather-chance-of-wind-symbolic.svg => wind.svg} (100%) create mode 100644 guh-control/ui/paramdelegates/BoolParamDelegate.qml create mode 100644 guh-control/ui/paramdelegates/IntParamDelegate.qml create mode 100644 guh-control/ui/paramdelegates/ParamDelegate.qml create mode 100644 guh-control/ui/paramdelegates/ParamDelegateBase.qml create mode 100644 guh-control/ui/paramdelegates/StringParamDelegate.qml create mode 100644 libguh-common/types/logentry.cpp create mode 100644 libguh-common/types/logentry.h diff --git a/guh-control.pro b/guh-control.pro index be9bcf3f..f0371105 100644 --- a/guh-control.pro +++ b/guh-control.pro @@ -1,6 +1,6 @@ TEMPLATE=subdirs -SUBDIRS += libguh-common guh-control +SUBDIRS = libguh-common guh-control libguh-common.subdir = libguh-common guh-control.subdir = guh-control diff --git a/guh-control/deviceclasses.cpp b/guh-control/deviceclasses.cpp index e63f4db7..e73c74f1 100644 --- a/guh-control/deviceclasses.cpp +++ b/guh-control/deviceclasses.cpp @@ -46,13 +46,16 @@ QVariant DeviceClasses::data(const QModelIndex &index, int role) const return QVariant(); DeviceClass *deviceClass = m_deviceClasses.at(index.row()); - if (role == NameRole) { - return deviceClass->name(); - } else if (role == IdRole) { + switch (role) { + case RoleId: return deviceClass->id().toString(); - } else if (role == PluginIdRole) { + case RoleName: + return deviceClass->name(); + case RoleDisplayName: + return deviceClass->displayName(); + case RolePluginId: return deviceClass->pluginId().toString(); - } else if (role == VendorIdRole) { + case RoleVendorId: return deviceClass->vendorId().toString(); } return QVariant(); @@ -98,9 +101,10 @@ void DeviceClasses::clearModel() QHash DeviceClasses::roleNames() const { QHash roles; - roles[NameRole] = "name"; - roles[IdRole] = "id"; - roles[PluginIdRole] = "pluginId"; - roles[VendorIdRole] = "vendorId"; + roles[RoleId] = "id"; + roles[RoleName] = "name"; + roles[RoleDisplayName] = "displayName"; + roles[RolePluginId] = "pluginId"; + roles[RoleVendorId] = "vendorId"; return roles; } diff --git a/guh-control/deviceclasses.h b/guh-control/deviceclasses.h index 9cc6ede0..8bf70369 100644 --- a/guh-control/deviceclasses.h +++ b/guh-control/deviceclasses.h @@ -31,11 +31,12 @@ class DeviceClasses : public QAbstractListModel { Q_OBJECT public: - enum DeviceClassRole { - NameRole = Qt::DisplayRole, - IdRole, - PluginIdRole, - VendorIdRole + enum Role { + RoleId, + RoleName, + RoleDisplayName, + RolePluginId, + RoleVendorId }; explicit DeviceClasses(QObject *parent = 0); diff --git a/guh-control/devicediscovery.cpp b/guh-control/devicediscovery.cpp index 977adab1..a5aae9be 100644 --- a/guh-control/devicediscovery.cpp +++ b/guh-control/devicediscovery.cpp @@ -43,7 +43,7 @@ void DeviceDiscovery::discoverDevices(const QUuid &deviceClassId, const QVariant if (!discoveryParams.isEmpty()) { params.insert("discoveryParams", discoveryParams); } - Engine::instance()->jsonRpcClient()->sendCommand("Devices.DiscoverDevices", params, this, "discoverDevicesResponse"); + Engine::instance()->jsonRpcClient()->sendCommand("Devices.GetDiscoveredDevices", params, this, "discoverDevicesResponse"); m_busy = true; emit busyChanged(); emit countChanged(); @@ -60,8 +60,9 @@ void DeviceDiscovery::discoverDevicesResponse(const QVariantMap ¶ms) emit busyChanged(); qDebug() << "response received" << params; - QVariantList descriptors = params.value("deviceDescriptors").toList(); + QVariantList descriptors = params.value("params").toMap().value("deviceDescriptors").toList(); foreach (const QVariant &descriptor, descriptors) { + qDebug() << "descriptor" << descriptor; if (!contains(descriptor.toMap().value("id").toUuid())) { beginInsertRows(QModelIndex(), m_foundDevices.count(), m_foundDevices.count()); m_foundDevices.append(DeviceDescriptor(descriptor.toMap().value("id").toUuid(), diff --git a/guh-control/devicemanager.cpp b/guh-control/devicemanager.cpp index ab467f3d..cc38238b 100644 --- a/guh-control/devicemanager.cpp +++ b/guh-control/devicemanager.cpp @@ -71,11 +71,12 @@ DeviceClasses *DeviceManager::deviceClasses() const return m_deviceClasses; } -void DeviceManager::addDevice(const QUuid &deviceClassId, const QVariantList &deviceParams) +void DeviceManager::addDevice(const QUuid &deviceClassId, const QString &name, const QVariantList &deviceParams) { qDebug() << "add device " << deviceClassId.toString(); QVariantMap params; params.insert("deviceClassId", deviceClassId.toString()); + params.insert("name", name); params.insert("deviceParams", deviceParams); m_jsonClient->sendCommand("Devices.AddConfiguredDevice", params, this, "addDeviceResponse"); } @@ -97,12 +98,13 @@ void DeviceManager::notificationReceived(const QVariantMap &data) void DeviceManager::getVendorsResponse(const QVariantMap ¶ms) { - qDebug() << "Got GetSupportedVendors response"; + qDebug() << "Got GetSupportedVendors response" << params; if (params.value("params").toMap().keys().contains("vendors")) { QVariantList vendorList = params.value("params").toMap().value("vendors").toList(); foreach (QVariant vendorVariant, vendorList) { Vendor *vendor = JsonTypes::unpackVendor(vendorVariant.toMap(), Engine::instance()->deviceManager()->vendors()); m_vendors->addVendor(vendor); + qDebug() << "Added Vendor:" << vendor->name(); } } @@ -134,7 +136,7 @@ void DeviceManager::getPluginsResponse(const QVariantMap ¶ms) m_plugins->addPlugin(plugin); } } - m_jsonClient->sendCommand("Devices.GetVendors", this, "getVendorsResponse"); + m_jsonClient->sendCommand("Devices.GetSupportedVendors", this, "getVendorsResponse"); } void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms) @@ -144,29 +146,45 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms) foreach (QVariant deviceVariant, deviceList) { Device *device = JsonTypes::unpackDevice(deviceVariant.toMap(), Engine::instance()->deviceManager()->devices()); if (!device) continue; - Engine::instance()->deviceManager()->devices()->addDevice(device); - //qDebug() << QJsonDocument::fromVariant(deviceVariant).toJson(); +// qDebug() << QJsonDocument::fromVariant(deviceVariant).toJson(); + DeviceClass *dc = m_deviceClasses->getDeviceClass(device->deviceClassId()); + if (!dc) { + qWarning() << "Can't find a deviceClass for this device" << device->name(); + continue; + } // set initial state values QVariantList stateVariantList = deviceVariant.toMap().value("states").toList(); foreach (const QVariant &stateMap, stateVariantList) { QUuid stateTypeId = stateMap.toMap().value("stateTypeId").toUuid(); + StateType *st = dc->stateTypes()->getStateType(stateTypeId); + if (!st) { + qWarning() << "Can't find a statetype for this state"; + continue; + } QVariant value = stateMap.toMap().value("value"); + if (st->type() == "Bool") { + value.convert(QVariant::Bool); + } device->setStateValue(stateTypeId, value); } + Engine::instance()->deviceManager()->devices()->addDevice(device); } } } void DeviceManager::addDeviceResponse(const QVariantMap ¶ms) { - if (params.value("params").toMap().keys().contains("device")) { + if (params.value("params").toMap().value("deviceError").toString() != "DeviceErrorNoError") { + qWarning() << "Failed to add the device:" << params.value("params").toMap().value("deviceError").toString(); + } else if (params.value("params").toMap().keys().contains("device")) { QVariantMap deviceVariant = params.value("params").toMap().value("device").toMap(); Device *device = JsonTypes::unpackDevice(deviceVariant, m_devices); qDebug() << "Device added" << device->id().toString(); m_devices->addDevice(device); } + emit addDeviceReply(params.value("params").toMap()); } void DeviceManager::removeDeviceResponse(const QVariantMap ¶ms) @@ -178,6 +196,16 @@ void DeviceManager::removeDeviceResponse(const QVariantMap ¶ms) device->deleteLater(); } +void DeviceManager::pairDeviceResponse(const QVariantMap ¶ms) +{ + emit pairDeviceReply(params.value("params").toMap()); +} + +void DeviceManager::confirmPairintResponse(const QVariantMap ¶ms) +{ + emit confirmPairingReply(params.value("params").toMap()); +} + void DeviceManager::addDiscoveredDevice(const QUuid &deviceClassId, const QUuid &deviceDescriptorId, const QString &name) { qDebug() << "JsonRpc: add discovered device " << deviceClassId.toString(); @@ -195,7 +223,7 @@ void DeviceManager::pairDevice(const QUuid &deviceClassId, const QUuid &deviceDe params.insert("name", "name"); params.insert("deviceClassId", deviceClassId.toString()); params.insert("deviceDescriptorId", deviceDescriptorId.toString()); - m_jsonClient->sendCommand("Devices.PairDevice", params); + m_jsonClient->sendCommand("Devices.PairDevice", params, this, "pairDeviceResponse"); } void DeviceManager::confirmPairing(const QUuid &pairingTransactionId, const QString &secret) @@ -204,7 +232,7 @@ void DeviceManager::confirmPairing(const QUuid &pairingTransactionId, const QStr QVariantMap params; params.insert("pairingTransactionId", pairingTransactionId.toString()); params.insert("secret", secret); - m_jsonClient->sendCommand("Devices.ConfirmPairing", params); + m_jsonClient->sendCommand("Devices.ConfirmPairing", params, this, "confirmPairingResponse"); } void DeviceManager::removeDevice(const QUuid &deviceId) diff --git a/guh-control/devicemanager.h b/guh-control/devicemanager.h index b277895d..081d1d0f 100644 --- a/guh-control/devicemanager.h +++ b/guh-control/devicemanager.h @@ -51,7 +51,7 @@ public: Devices *devices() const; DeviceClasses *deviceClasses() const; - Q_INVOKABLE void addDevice(const QUuid &deviceClassId, const QVariantList &deviceParams); + Q_INVOKABLE void addDevice(const QUuid &deviceClassId, const QString &name, const QVariantList &deviceParams); Q_INVOKABLE void addDiscoveredDevice(const QUuid &deviceClassId, const QUuid &deviceDescriptorId, const QString &name); Q_INVOKABLE void pairDevice(const QUuid &deviceClassId, const QUuid &deviceDescriptorId); Q_INVOKABLE void confirmPairing(const QUuid &pairingTransactionId, const QString &secret = QString()); @@ -66,8 +66,13 @@ private: Q_INVOKABLE void getConfiguredDevicesResponse(const QVariantMap ¶ms); Q_INVOKABLE void addDeviceResponse(const QVariantMap ¶ms); Q_INVOKABLE void removeDeviceResponse(const QVariantMap ¶ms); + Q_INVOKABLE void pairDeviceResponse(const QVariantMap ¶ms); + Q_INVOKABLE void confirmPairintResponse(const QVariantMap ¶ms); - +signals: + void pairDeviceReply(const QVariantMap ¶ms); + void confirmPairingReply(const QVariantMap ¶ms); + void addDeviceReply(const QVariantMap ¶ms); private: Vendors *m_vendors; diff --git a/guh-control/guhconnection.cpp b/guh-control/guhconnection.cpp index c889d343..c162ed22 100644 --- a/guh-control/guhconnection.cpp +++ b/guh-control/guhconnection.cpp @@ -36,6 +36,10 @@ void GuhConnection::connect(const QString &url) void GuhConnection::disconnect() { + if (!m_currentInterface || !m_currentInterface->isConnected()) { + qWarning() << "not connected, cannot disconnect"; + return; + } m_currentInterface->disconnect(); } diff --git a/guh-control/jsonrpc/jsonrpcclient.cpp b/guh-control/jsonrpc/jsonrpcclient.cpp index f35803bf..55c029fa 100644 --- a/guh-control/jsonrpc/jsonrpcclient.cpp +++ b/guh-control/jsonrpc/jsonrpcclient.cpp @@ -28,6 +28,7 @@ #include #include #include +#include JsonRpcClient::JsonRpcClient(GuhConnection *connection, QObject *parent) : JsonHandler(parent), @@ -202,6 +203,11 @@ void JsonRpcClient::dataReceived(const QByteArray &data) m_authenticationRequired = dataMap.value("authenticationRequired").toBool(); m_serverUuid = dataMap.value("uuid").toString(); + QString protoVersionString = dataMap.value("protocol version").toString(); + if (!protoVersionString.contains('.')) { + protoVersionString.prepend("0."); + } + if (m_initialSetupRequired) { emit initialSetupRequiredChanged(); } else if (m_authenticationRequired) { @@ -218,13 +224,20 @@ void JsonRpcClient::dataReceived(const QByteArray &data) m_connected = true; emit connectedChanged(true); + + QVersionNumber minimumRequiredVersion = QVersionNumber(1, 0); + QVersionNumber protocolVersion = QVersionNumber::fromString(protoVersionString); + if (protocolVersion < minimumRequiredVersion) { + m_connection->disconnect(); + emit invalidProtocolVersion(protocolVersion.toString(), minimumRequiredVersion.toString()); + } } // check if this is a reply to a request int commandId = dataMap.value("id").toInt(); JsonRpcReply *reply = m_replies.take(commandId); if (reply) { - qDebug() << QString("JsonRpc: got response for %1.%2").arg(reply->nameSpace(), reply->method()) << reply->callback() << reply->callback(); +// qDebug() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback(); if (reply->caller() != nullptr && !reply->callback().isEmpty()) { QMetaObject::invokeMethod(reply->caller(), reply->callback().toLatin1().data(), Q_ARG(QVariantMap, dataMap)); diff --git a/guh-control/jsonrpc/jsonrpcclient.h b/guh-control/jsonrpc/jsonrpcclient.h index 172348e4..ece32fc3 100644 --- a/guh-control/jsonrpc/jsonrpcclient.h +++ b/guh-control/jsonrpc/jsonrpcclient.h @@ -66,6 +66,7 @@ signals: void authenticationRequiredChanged(); void connectedChanged(bool connected); void tokenChanged(); + void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion); void responseReceived(const int &commandId, const QVariantMap &response); diff --git a/guh-control/jsonrpc/jsontypes.cpp b/guh-control/jsonrpc/jsontypes.cpp index 1f0249b6..d4be34c0 100644 --- a/guh-control/jsonrpc/jsontypes.cpp +++ b/guh-control/jsonrpc/jsontypes.cpp @@ -34,7 +34,9 @@ JsonTypes::JsonTypes(QObject *parent) : Vendor *JsonTypes::unpackVendor(const QVariantMap &vendorMap, QObject *parent) { - return new Vendor(vendorMap.value("id").toUuid(), vendorMap.value("name").toString(), parent); + Vendor *v = new Vendor(vendorMap.value("id").toUuid(), vendorMap.value("name").toString(), parent); + v->setDisplayName(vendorMap.value("displayName").toString()); + return v; } Plugin *JsonTypes::unpackPlugin(const QVariantMap &pluginMap, QObject *parent) @@ -54,6 +56,7 @@ DeviceClass *JsonTypes::unpackDeviceClass(const QVariantMap &deviceClassMap, QOb { DeviceClass *deviceClass = new DeviceClass(parent); deviceClass->setName(deviceClassMap.value("name").toString()); + deviceClass->setDisplayName(deviceClassMap.value("displayName").toString()); deviceClass->setId(deviceClassMap.value("id").toUuid()); deviceClass->setVendorId(deviceClassMap.value("vendorId").toUuid()); QVariantList createMethodsList = deviceClassMap.value("createMethods").toList(); @@ -133,6 +136,7 @@ StateType *JsonTypes::unpackStateType(const QVariantMap &stateTypeMap, QObject * StateType *stateType = new StateType(parent); stateType->setId(stateTypeMap.value("id").toUuid()); stateType->setName(stateTypeMap.value("name").toString()); + stateType->setDisplayName(stateTypeMap.value("displayName").toString()); stateType->setIndex(stateTypeMap.value("index").toInt()); stateType->setDefaultValue(stateTypeMap.value("defaultValue")); stateType->setType(stateTypeMap.value("type").toString()); diff --git a/guh-control/main.cpp b/guh-control/main.cpp index 702bc783..d45df001 100644 --- a/guh-control/main.cpp +++ b/guh-control/main.cpp @@ -40,6 +40,8 @@ #include "types/ruleactionparams.h" #include "types/ruleactionparam.h" #include "types/rule.h" +#include "models/logsmodel.h" +#include "models/valuelogsproxymodel.h" int main(int argc, char *argv[]) { @@ -104,6 +106,10 @@ int main(int argc, char *argv[]) qmlRegisterType(uri, 1, 0, "UpnpDiscovery"); qmlRegisterType(uri, 1, 0, "ZeroconfDiscovery"); + qmlRegisterType(uri, 1, 0, "LogsModel"); + qmlRegisterType(uri, 1, 0, "ValueLogsProxyModel"); + qmlRegisterUncreatableType(uri, 1, 0, "LogEntry", "Get them from LogsModel"); + Engine::instance(); QQmlApplicationEngine engine; diff --git a/guh-control/models/logsmodel.cpp b/guh-control/models/logsmodel.cpp new file mode 100644 index 00000000..8e8ca485 --- /dev/null +++ b/guh-control/models/logsmodel.cpp @@ -0,0 +1,134 @@ +#include "logsmodel.h" + +#include "engine.h" + +LogsModel::LogsModel(QObject *parent) : QAbstractListModel(parent) +{ + +} + +bool LogsModel::busy() const +{ + return m_busy; +} + +int LogsModel::rowCount(const QModelIndex &parent) const +{ + return m_list.count(); +} + +QVariant LogsModel::data(const QModelIndex &index, int role) const +{ + return QVariant(); +} + +QHash LogsModel::roleNames() const +{ + QHash roles; + roles.insert(RoleValue, "value"); + return roles; +} + +QString LogsModel::deviceId() const +{ + return m_deviceId; +} + +void LogsModel::setDeviceId(const QString &deviceId) +{ + if (m_deviceId != deviceId) { + m_deviceId = deviceId; + emit deviceIdChanged(); + } +} + +QString LogsModel::typeId() const +{ + return m_typeId; +} + +void LogsModel::setTypeId(const QString &typeId) +{ + if (m_typeId != typeId) { + m_typeId = typeId; + emit typeIdChanged(); + } +} + +QDateTime LogsModel::startTime() const +{ + return m_startTime; +} + +void LogsModel::setStartTime(const QDateTime &startTime) +{ + if (m_startTime != startTime) { + m_startTime = startTime; + emit startTimeChanged(); + } +} + +QDateTime LogsModel::endTime() const +{ + return m_endTime; +} + +void LogsModel::setEndTime(const QDateTime &endTime) +{ + if (m_endTime != endTime) { + m_endTime = endTime; + emit endTimeChanged(); + } +} + +LogEntry *LogsModel::get(int index) const +{ + if (index >= 0 && index < m_list.count()) { + return m_list.at(index); + } + return nullptr; +} + +void LogsModel::update() +{ + m_busy = true; + emit busyChanged(); + + QVariantMap params; + if (!m_deviceId.isEmpty()) { + QVariantList deviceIds; + deviceIds.append(m_deviceId); + params.insert("deviceIds", deviceIds); + } + if (!m_typeId.isEmpty()) { + QVariantList typeIds; + typeIds.append(m_typeId); + params.insert("typeIds", typeIds); + } + QVariantList timeFilters; + QVariantMap timeFilter; + timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch()); + timeFilter.insert("endDate", m_endTime.toSecsSinceEpoch()); + timeFilters.append(timeFilter); + params.insert("timeFilters", timeFilters); + Engine::instance()->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); +} + +void LogsModel::logsReply(const QVariantMap &data) +{ + m_busy = false; + emit busyChanged(); + beginResetModel(); + qDeleteAll(m_list); + m_list.clear(); + + QList logEntries = data.value("params").toMap().value("logEntries").toList(); + foreach (const QVariant &logEntryVariant, logEntries) { + LogEntry *entry = new LogEntry(QDateTime::fromMSecsSinceEpoch(logEntryVariant.toMap().value("timestamp").toLongLong()), logEntryVariant.toMap().value("value"), this); + m_list.append(entry); + qDebug() << "Added log entry" << entry->dayString() << QDateTime::fromMSecsSinceEpoch(logEntryVariant.toMap().value("timestamp").toLongLong()).date().dayOfWeek(); + } + + endResetModel(); + emit countChanged(); +} diff --git a/guh-control/models/logsmodel.h b/guh-control/models/logsmodel.h new file mode 100644 index 00000000..e67bccea --- /dev/null +++ b/guh-control/models/logsmodel.h @@ -0,0 +1,67 @@ +#ifndef LOGSMODEL_H +#define LOGSMODEL_H + +#include + +#include "types/logentry.h" + +class LogsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) + Q_PROPERTY(QString typeId READ typeId WRITE setTypeId NOTIFY typeIdChanged) + Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) + Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) + +public: + enum Roles { + RoleValue + }; + explicit LogsModel(QObject *parent = nullptr); + + bool busy() const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + QString deviceId() const; + void setDeviceId(const QString &deviceId); + + QString typeId() const; + void setTypeId(const QString &typeId); + + QDateTime startTime() const; + void setStartTime(const QDateTime &startTime); + + QDateTime endTime() const; + void setEndTime(const QDateTime &endTime); + + Q_INVOKABLE LogEntry* get(int index) const; + +signals: + void busyChanged(); + void countChanged(); + void deviceIdChanged(); + void typeIdChanged(); + void startTimeChanged(); + void endTimeChanged(); + +public slots: + virtual void update(); + +private slots: + virtual void logsReply(const QVariantMap &data); + +protected: + QList m_list; + QString m_deviceId; + QString m_typeId; + QDateTime m_startTime = QDateTime::currentDateTime().addDays(-1); + QDateTime m_endTime = QDateTime::currentDateTime(); + bool m_busy = false; + +}; + +#endif // LOGSMODEL_H diff --git a/guh-control/models/valuelogsproxymodel.cpp b/guh-control/models/valuelogsproxymodel.cpp new file mode 100644 index 00000000..bd7d0463 --- /dev/null +++ b/guh-control/models/valuelogsproxymodel.cpp @@ -0,0 +1,136 @@ +#include "valuelogsproxymodel.h" + +#include + +ValueLogsProxyModel::ValueLogsProxyModel(QObject *parent) : LogsModel(parent) +{ + +} + +void ValueLogsProxyModel::update() +{ + // modify starttime to add a day earlier so we have more chances to have meaningful data right from the start + m_startTime = m_startTime.addDays(-1); + LogsModel::update(); + m_startTime = m_startTime.addDays(1); +} + +ValueLogsProxyModel::Average ValueLogsProxyModel::average() const +{ + return m_average; +} + +void ValueLogsProxyModel::setAverage(ValueLogsProxyModel::Average average) +{ + if (m_average != average) { + m_average = average; + emit averageChanged(); + } +} + +QVariant ValueLogsProxyModel::minimumValue() const +{ + return m_minimumValue; +} + +QVariant ValueLogsProxyModel::maximumValue() const +{ + return m_maximumValue; +} + +void ValueLogsProxyModel::logsReply(const QVariantMap &data) +{ + m_busy = false; + emit busyChanged(); + + qDebug() << "logs reply"; + + beginResetModel(); + + m_minimumValue = QVariant(); + m_maximumValue = QVariant(); + + int stepSize = 1; + switch (m_average) { + case AverageMonth: + stepSize *= 30; + case AverageDay: + stepSize *= 8; + case AverageDayTime: + stepSize *= 3; + case AverageHourly: + stepSize *= 4; + case AverageQuarterHour: + stepSize *= 15; + case AverageMinute: + stepSize *= 60; + } + int totalSlots = startTime().secsTo(endTime()) / stepSize; + qDebug() << "slots" << totalSlots; + + QHash > entries; + + QList logEntries = data.value("params").toMap().value("logEntries").toList(); + + QVariant startValue; + for (int i = 0; i < logEntries.count(); i++) { + QVariantMap entryMap = logEntries.at(i).toMap(); + QDateTime entryTimestamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); + int slot = startTime().secsTo(entryTimestamp) / stepSize; + if (slot < 0) { + // We're before the actual starttime (see update()). store the most recent value + startValue = entryMap.value("value"); +// qDebug() << "have new startvalue" << startValue << entryTimestamp; + continue; + } + QList tmp = entries[slot]; + tmp.append(entryMap.value("value")); + entries[slot] = tmp; +// qDebug() << "adding value to slot" << slot << entryMap.value("value") << QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); + } + if (!startValue.isNull() && entries[0].isEmpty()) { + QList tmp; + tmp.append(startValue); + entries[0] = tmp; + } +// qDebug() << "slotsize:" << stepSize << entries.keys(); + + qDeleteAll(m_list); + m_list.clear(); + for (int i = 0; i <= totalSlots; i++) { + QVariant avg = 0; + int counter = 0; + foreach (const QVariant &value, entries[i]) { + avg = avg.toDouble() + value.toDouble(); + counter++; + } + if (counter > 0) { + avg = avg.toDouble() / counter; + } else if (entries[i-1].count() > 0) { + avg = entries[i-1].last(); + } else if (m_list.count() > 0){ + avg = m_list.last()->value(); + } else { + continue; + } + LogEntry *entry = new LogEntry(startTime().addSecs(stepSize * i).addSecs(stepSize * .5), avg, this); +// qDebug() << "filling slot" << i << "average:" << avg << entry->timestamp(); + m_list.append(entry); + + if (m_minimumValue.isNull() || entry->value() < m_minimumValue) { + m_minimumValue = qRound(entry->value().toDouble()); + } + if (m_maximumValue.isNull() || entry->value() > m_maximumValue) { + m_maximumValue = qRound(entry->value().toDouble()); + } + + } + + endResetModel(); + + emit minimumValueChanged(); + emit maximumValueChanged(); + emit countChanged(); + qDebug() << "min" << minimumValue() << "max" << maximumValue(); + +} diff --git a/guh-control/models/valuelogsproxymodel.h b/guh-control/models/valuelogsproxymodel.h new file mode 100644 index 00000000..29494940 --- /dev/null +++ b/guh-control/models/valuelogsproxymodel.h @@ -0,0 +1,53 @@ +#ifndef VALUELOGSPROXYMODEL_H +#define VALUELOGSPROXYMODEL_H + +#include + +#include "logsmodel.h" + +class ValueLogsProxyModel : public LogsModel +{ + Q_OBJECT + Q_PROPERTY(Average average READ average WRITE setAverage NOTIFY averageChanged) + + Q_PROPERTY(QVariant minimumValue READ minimumValue NOTIFY minimumValueChanged) + Q_PROPERTY(QVariant maximumValue READ maximumValue NOTIFY maximumValueChanged) + +public: + enum Average { + AverageMonth, + AverageDay, + AverageDayTime, + AverageHourly, + AverageQuarterHour, + AverageMinute + }; + Q_ENUM(Average) + + explicit ValueLogsProxyModel(QObject *parent = nullptr); + + void update() override; + + Average average() const; + void setAverage(Average average); + + QVariant minimumValue() const; + QVariant maximumValue() const; + +signals: + void averageChanged(); + + void minimumValueChanged(); + void maximumValueChanged(); + +protected: + void logsReply(const QVariantMap &data) override; + +private: + Average m_average = AverageHourly; + + QVariant m_minimumValue; + QVariant m_maximumValue; +}; + +#endif // VALUELOGSPROXYMODEL_H diff --git a/guh-control/resources.qrc b/guh-control/resources.qrc index a6ba8baa..a6ea305e 100644 --- a/guh-control/resources.qrc +++ b/guh-control/resources.qrc @@ -14,31 +14,14 @@ ui/images/back.svg ui/images/close.svg ui/MainPage.qml - ui/images/weathericons/weather-clear-night-symbolic.svg - ui/images/weathericons/weather-few-clouds-night-symbolic.svg - ui/images/weathericons/weather-few-clouds-symbolic.svg - ui/images/weathericons/weather-flurries-symbolic.svg - ui/images/weathericons/weather-fog-symbolic.svg - ui/images/weathericons/weather-hazy-symbolic.svg - ui/images/weathericons/weather-overcast-symbolic.svg - ui/images/weathericons/weather-severe-alert-symbolic.svg - ui/images/weathericons/weather-sleet-symbolic.svg - ui/images/weathericons/weather-snow-symbolic.svg - ui/images/weathericons/weather-chance-of-wind-symbolic.svg ui/actiondelegates/ActionDelegateFallback.qml ui/images/info.svg - ui/images/weathericons/weather-rain-symbolic.svg ui/actiondelegates/ActionDelegateColor.qml ui/components/ColorPicker.qml - ui/images/weathericons/weather-scattered-symbolic.svg ui/actiondelegates/ActionDelegateNoParams.qml - ui/images/weathericons/weather-cloud-night-symbolic.svg - ui/images/weathericons/weather-cloud-symbolic.svg ui/actiondelegates/ActionDelegateStringFromStringList.qml - ui/images/weathericons/weather-sun-symbolic.svg ui/customviews/CustomViewBase.qml ui/customviews/WeatherView.qml - ui/images/weathericons/weather-thunder-symbolic.svg ui/customviews/MediaControllerView.qml ui/LoginPage.qml ui/customviews/SensorView.qml @@ -58,5 +41,65 @@ ui/magic/SelectActionPage.qml ui/devicepages/GenericDeviceStateDetailsPage.qml ui/images/delete.svg + ui/images/audio-speakers-muted-symbolic.svg + ui/images/audio-speakers-symbolic.svg + ui/images/media-playback-pause.svg + ui/images/media-playback-start.svg + ui/images/media-playback-stop.svg + ui/images/media-seek-backward.svg + ui/images/media-seek-forward.svg + ui/images/media-skip-backward.svg + ui/images/media-skip-forward.svg + ui/paramdelegates/BoolParamDelegate.qml + ui/components/ThinDivider.qml + ui/images/weather-app-symbolic.svg + ui/images/weathericons/weather-clear-day.svg + ui/images/weathericons/weather-clear-night.svg + ui/images/weathericons/weather-clouds.svg + ui/images/weathericons/weather-few-clouds-day.svg + ui/images/weathericons/weather-few-clouds-night.svg + ui/images/weathericons/weather-fog.svg + ui/images/weathericons/weather-light-rain.svg + ui/images/weathericons/weather-overcast.svg + ui/images/weathericons/weather-shower-rain.svg + ui/images/weathericons/weather-snow.svg + ui/images/weathericons/weather-thunderstorm.svg + ui/images/weathericons/humidity.svg + ui/images/weathericons/wind.svg + ui/devicepages/WeatherDevicePage.qml + ui/devicepages/ColorLightDevicePage.qml + ui/images/torch-off.svg + ui/components/ThrottledSlider.qml + ui/components/ColorPickerCt.qml + ui/images/navigation-menu.svg + ui/components/IconMenuItem.qml + ui/images/settings.svg + ui/images/stock_link.svg + ui/images/share.svg + ui/SystemInfoPage.qml + ui/devicepages/SensorDevicePage.qml + ui/components/Graph.qml + ui/images/sensors.svg + ui/images/temperature.svg + ui/images/network-wired-symbolic.svg + ui/paramdelegates/StringParamDelegate.qml + ui/paramdelegates/ParamDelegate.qml + ui/paramdelegates/ParamDelegateBase.qml + ui/images/notification.svg + ui/paramdelegates/IntParamDelegate.qml + ui/customviews/NotificationsView.qml + ui/devicepages/DevicePageBase.qml + ui/images/battery/battery-000.svg + ui/images/battery/battery-010.svg + ui/images/battery/battery-020.svg + ui/images/battery/battery-030.svg + ui/images/battery/battery-040.svg + ui/images/battery/battery-050.svg + ui/images/battery/battery-060.svg + ui/images/battery/battery-070.svg + ui/images/battery/battery-080.svg + ui/images/battery/battery-090.svg + ui/images/battery/battery-100.svg + ui/images/dialog-warning-symbolic.svg diff --git a/guh-control/rulemanager.cpp b/guh-control/rulemanager.cpp index 7400ece6..adc027fc 100644 --- a/guh-control/rulemanager.cpp +++ b/guh-control/rulemanager.cpp @@ -99,7 +99,7 @@ void RuleManager::getRuleDetailsReply(const QVariantMap ¶ms) qDebug() << "Got rule details for a rule we don't know"; return; } - qDebug() << "got rule details for rule" << ruleMap; +// qDebug() << "got rule details for rule" << ruleMap; parseEventDescriptors(ruleMap.value("eventDescriptors").toList(), rule); parseRuleActions(ruleMap.value("actions").toList(), rule); } diff --git a/guh-control/ui/DevicesPage.qml b/guh-control/ui/DevicesPage.qml index fbc5d12c..ee78d1b4 100644 --- a/guh-control/ui/DevicesPage.qml +++ b/guh-control/ui/DevicesPage.qml @@ -1,142 +1,61 @@ -import QtQuick 2.5 +import QtQuick 2.8 import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.1 import QtQuick.Controls.Material 2.1 import Guh 1.0 import "components" -Page { - id: root +GridView { + id: interfacesGridView - header: GuhHeader { - text: "My things" - backButtonVisible: false + property alias shownInterfaces: interfacesModel.shownInterfaces - HeaderButton { - imageSource: "images/add.svg" - onClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) - } + model: InterfacesModel { + id: interfacesModel + devices: Engine.deviceManager.devices } - SwipeView { - anchors.fill: parent - clip: true - - Item { - GridView { - id: interfacesGridView - anchors.fill: parent - anchors.margins: app.margins - - model: InterfacesModel { - id: interfacesModel - devices: Engine.deviceManager.devices - shownInterfaces: ["light", "weather", "sensor", "media", "button"] - } - - cellWidth: { - if (count < 6) { - return (interfacesGridView.width - spacing) / 2 - } - return (interfacesGridView.width - spacing * 2) / 3 - } - cellHeight: cellWidth - delegate: Item { - width: interfacesGridView.cellWidth - height: interfacesGridView.cellHeight - Pane { - anchors.fill: parent - anchors.margins: app.margins + property int tilesPerRow: Math.ceil(Math.sqrt(interfacesGridView.count)) + cellWidth: width / tilesPerRow + cellHeight: height / tilesPerRow + delegate: Item { + width: interfacesGridView.cellWidth + height: interfacesGridView.cellHeight + Pane { + anchors.fill: parent + anchors.margins: app.margins // color: "#22000000" - Material.elevation: 1 - Column { - anchors.centerIn: parent - spacing: app.margins - ColorIcon { - height: app.iconSize * 2 - width: height - color: app.guhAccent - anchors.horizontalCenter: parent.horizontalCenter - name: interfaceToIcon(model.name) - } - - Label { - text: interfaceToString(model.name).toUpperCase() - anchors.horizontalCenter: parent.horizontalCenter - } - - } - - MouseArea { - anchors.fill: parent - onClicked: { - var page; - switch (model.name) { - case "light": - page = "LightsDeviceListPage.qml" - break; - default: - page = "GenericDeviceListPage.qml" - } - - pageStack.push(Qt.resolvedUrl("devicelistpages/" + page), {filterInterface: model.name}) - } - } - } - } - } - } - - Item { - GridView { - id: gridView - anchors.fill: parent - anchors.margins: app.margins - - model: DevicesBasicTagsModel { - id: devicesBasicTagsModel - devices: Engine.deviceManager.devices - hideSystemTags: true + Material.elevation: 1 + Column { + anchors.centerIn: parent + spacing: app.margins + ColorIcon { + height: app.iconSize * 2 + width: height + color: app.guhAccent + anchors.horizontalCenter: parent.horizontalCenter + name: interfaceToIcon(model.name) } - cellWidth: { - if (count < 6) { - return (gridView.width - spacing) / 2 - } - return (gridView.width - spacing * 2) / 3 - } - cellHeight: cellWidth - delegate: Item { - width: gridView.cellWidth - height: gridView.cellHeight - Pane { - anchors.fill: parent - anchors.margins: app.margins -// color: "#22000000" - Material.elevation: 2 - Label { - anchors.centerIn: parent - text: model.tagLabel - - } - MouseArea { - anchors.fill: parent - onClicked: { - pageStack.push(Qt.resolvedUrl("devicelistpages/GenericDeviceListPage.qml"), {filterTag: model.tag}) - } - } - } - } - } - } - ListView { - model: Engine.deviceManager.devices - delegate: ItemDelegate { - width: parent.width Label { - anchors { fill: parent; leftMargin: app.margins; rightMargin: app.margins } - text: model.name - verticalAlignment: Text.AlignVCenter + text: interfaceToString(model.name).toUpperCase() + anchors.horizontalCenter: parent.horizontalCenter + } + + } + + MouseArea { + anchors.fill: parent + onClicked: { + var page; + switch (model.name) { + case "light": + page = "LightsDeviceListPage.qml" + break; + default: + page = "GenericDeviceListPage.qml" + } + + pageStack.push(Qt.resolvedUrl("devicelistpages/" + page), {filterInterface: model.name}) } } } diff --git a/guh-control/ui/MagicPage.qml b/guh-control/ui/MagicPage.qml index a73ce661..7144ddb8 100644 --- a/guh-control/ui/MagicPage.qml +++ b/guh-control/ui/MagicPage.qml @@ -1,5 +1,5 @@ import QtQuick 2.8 -import QtQuick.Controls 2.2 +import QtQuick.Controls 2.1 import "components" import Guh 1.0 diff --git a/guh-control/ui/MainPage.qml b/guh-control/ui/MainPage.qml index 7eb0174f..51555955 100644 --- a/guh-control/ui/MainPage.qml +++ b/guh-control/ui/MainPage.qml @@ -1,40 +1,74 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 -import QtQuick.Controls.Material 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.2 +import Guh 1.0 +import "components" Page { id: root - footer: TabBar { - Material.elevation: 2 - TabButton { - text: "Things" - onClicked: mainSwipeView.currentIndex = 0 + + header: GuhHeader { + text: "My things" + backButtonVisible: false + menuButtonVisible: true + onMenuPressed: mainMenu.open() + } + + Menu { + id: mainMenu + width: implicitWidth + app.margins + IconMenuItem { + iconSource: "../images/share.svg" + text: "Configure things" } - TabButton { - text: "Magic" - onClicked: mainSwipeView.currentIndex = 1 + IconMenuItem { + iconSource: "../images/add.svg" + text: "Add a new thing..." + onTriggered: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) } - TabButton { - text: "Settings" - onClicked: mainSwipeView.currentIndex = 2 + IconMenuItem { + iconSource: "../images/settings.svg" + text: "App settings" + onTriggered: pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) + } + IconMenuItem { + iconSource: "../images/info.svg" + text: "System information" + onTriggered: pageStack.push(Qt.resolvedUrl("SystemInfoPage.qml")) } } - SwipeView { - id: mainSwipeView + ColumnLayout { anchors.fill: parent - interactive: false + anchors.margins: app.margins - DevicesPage { + SwipeView { + id: swipeView + Layout.fillWidth: true + Layout.fillHeight: true + DevicesPage { + width: parent.view.width + height: parent.view.height + shownInterfaces: ["light", "weather", "sensor", "media"] + } + + DevicesPage { + width: parent.view.width + height: parent.view.height + shownInterfaces: ["gateway", "button", "notifications"] + } } - MagicPage { - - } - - SettingsPage { - + PageIndicator { + Layout.alignment: Qt.AlignHCenter + count: swipeView.count + currentIndex: swipeView.currentIndex } } + + + + } diff --git a/guh-control/ui/NewDeviceWizard.qml b/guh-control/ui/NewDeviceWizard.qml index ca713a48..6581e92b 100644 --- a/guh-control/ui/NewDeviceWizard.qml +++ b/guh-control/ui/NewDeviceWizard.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 import Guh 1.0 import "components" +import "paramdelegates" Page { id: root @@ -38,27 +39,28 @@ Page { } Connections { - target: Engine.jsonRpcClient - onResponseReceived: { - print("response received", response) - if (commandId == d.addRequestId) { - print("result", response["deviceError"]) - d.addResult = (response["deviceError"] === "DeviceErrorNoError") - internalPageStack.push(resultsPage) - } else if (commandId == d.pairRequestId) { - switch (response["setupMethod"]) { - case "SetupMethodPushButton": - d.pairingTransactionId = response["pairingTransactionId"]; - print("response", response["displayMessage"], d.pairingTransactionId) - internalPageStack.push(pairingPage, {text: response["displayMessage"]}) - break; - default: - print("Setup method", response["setupMethod"], "not handled"); - - } + target: Engine.deviceManager + onPairDeviceReply: { + switch (params["setupMethod"]) { + case "SetupMethodPushButton": + d.pairingTransactionId = params["pairingTransactionId"]; + print("response", params["displayMessage"], d.pairingTransactionId) + internalPageStack.push(pairingPage, {text: params["displayMessage"]}) + break; + default: + print("Setup method", params["setupMethod"], "not handled"); } } + onConfirmPairingReply: { + print("result", params["deviceError"]) + d.addResult = (params["deviceError"] === "DeviceErrorNoError") + internalPageStack.push(resultsPage) + } + onAddDeviceReply: { + d.addResult = (params["deviceError"] === "DeviceErrorNoError") + internalPageStack.push(resultsPage) + } } StackView { @@ -77,7 +79,7 @@ Page { anchors.fill: parent anchors.margins: app.margins Label { - text: model.name + text: model.displayName Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter @@ -111,7 +113,7 @@ Page { anchors.fill: parent anchors.margins: app.margins Label { - text: model.name + text: model.displayName Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter @@ -128,6 +130,8 @@ Page { discovery.discoverDevices(deviceClass.id) internalPageStack.push(discoveryPage) } + } else if (deviceClass.createMethods.indexOf("CreateMethodUser") !== -1) { + internalPageStack.push(paramsPage) } print("should setup", deviceClass.name, deviceClass.setupMethod, deviceClass.createMethods, deviceClass["discoveryParamTypes"].count) @@ -281,23 +285,40 @@ Page { id: paramsView ColumnLayout { anchors.fill: parent - anchors.margins: 10 - spacing: 10 - Label { - text: "Name the thing:" - Layout.fillWidth: true - } - TextField { - id: nameTextField - text: d.deviceName ? d.deviceName : "" + spacing: app.margins + ColumnLayout { + Layout.margins: app.margins Layout.fillWidth: true + Label { + text: "Name the thing:" + Layout.fillWidth: true + } + TextField { + id: nameTextField + text: d.deviceName ? d.deviceName : "" + Layout.fillWidth: true + } } - Item { + ThinDivider {} + Flickable { Layout.fillWidth: true Layout.fillHeight: true + + Column { + width: parent.width + Repeater { + id: paramRepeater + model: d.deviceClass.paramTypes + delegate: ParamDelegate { + width: parent.width + paramType: d.deviceClass.paramTypes.get(index) + } + } + } } + Button { Layout.fillWidth: true text: "OK" @@ -305,12 +326,25 @@ Page { print("setupMethod", d.deviceClass.setupMethod) switch (d.deviceClass.setupMethod) { case 0: - d.addRequestId = Engine.jsonRpcClient.addDiscoveredDevice(d.deviceClass.id, d.deviceDescriptorId, nameTextField.text); + if (d.deviceDescriptorId) { + Engine.deviceManager.addDiscoveredDevice(d.deviceClass.id, d.deviceDescriptorId, nameTextField.text); + } else { + var params = [] + for (var i = 0; i < paramRepeater.count; i++) { + var param = {} + param.paramTypeId = paramRepeater.itemAt(i).paramType.id + param.value = paramRepeater.itemAt(i).value + params.push(param) + } + + Engine.deviceManager.addDevice(d.deviceClass.id, nameTextField.text, params); + } + break; case 1: case 2: case 3: - d.pairRequestId = Engine.jsonRpcClient.pairDevice(d.deviceClass.id, d.deviceDescriptorId); + Engine.deviceManager.pairDevice(d.deviceClass.id, d.deviceDescriptorId); break; } @@ -339,7 +373,7 @@ Page { Layout.fillWidth: true text: "OK" onClicked: { - d.addRequestId = Engine.jsonRpcClient.confirmPairing(d.pairingTransactionId, d.deviceDescriptorId); + Engine.deviceManager.confirmPairing(d.pairingTransactionId, d.deviceDescriptorId); } } } diff --git a/guh-control/ui/SettingsPage.qml b/guh-control/ui/SettingsPage.qml index c8e623de..c16f2b3c 100644 --- a/guh-control/ui/SettingsPage.qml +++ b/guh-control/ui/SettingsPage.qml @@ -3,32 +3,44 @@ import QtQuick.Controls 2.1 import QtQuick.Controls.Material 2.1 import QtQuick.Layouts 1.1 import Guh 1.0 +import "components" Page { id: root + header: GuhHeader { + text: "Settings" + backButtonVisible: true + onBackPressed: pageStack.pop() + } ColumnLayout { anchors { left: parent.left; right: parent.right; top: parent.top; margins: app.margins } Label { Layout.fillWidth: true - text: "Connected to:" - color: Material.accent + text: "Application".toUpperCase() + color: app.guhAccent } - RowLayout { Layout.fillWidth: true - Label { Layout.fillWidth: true - text: Engine.connection.url + text: "Full screen" } - Button { - text: "Disconnect" - onClicked: { - settings.lastConnectedHost = ""; - Engine.connection.disconnect(); - } + CheckBox { + checked: settings.fullscreen + onClicked: settings.fullscreen = checked + } + } + RowLayout { + Layout.fillWidth: true + Label { + Layout.fillWidth: true + text: "Return to home on idle" + } + CheckBox { + checked: settings.returnToHome + onClicked: settings.returnToHome = checked } } } diff --git a/guh-control/ui/SystemInfoPage.qml b/guh-control/ui/SystemInfoPage.qml new file mode 100644 index 00000000..6246a94c --- /dev/null +++ b/guh-control/ui/SystemInfoPage.qml @@ -0,0 +1,48 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "components" +import Guh 1.0 + +Page { + id: root + header: GuhHeader { + text: "System information" + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: app.margins + + Label { + Layout.fillWidth: true + text: "Connected to:" + color: Material.accent + } + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + text: Engine.connection.url + } + Button { + text: "Disconnect" + onClicked: { + settings.lastConnectedHost = ""; + Engine.connection.disconnect(); + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + } +} diff --git a/guh-control/ui/components/ColorPickerCt.qml b/guh-control/ui/components/ColorPickerCt.qml new file mode 100644 index 00000000..c48faceb --- /dev/null +++ b/guh-control/ui/components/ColorPickerCt.qml @@ -0,0 +1,74 @@ +import QtQuick 2.3 + +Item { + id: root + + property int ct: minCt + property int minCt: 153 + property int maxCt: 500 + property Component touchDelegate + property bool active: true + property bool pressed: mouseArea.pressed + + Rectangle { + height: parent.width + width: parent.height + anchors.centerIn: parent + rotation: -90 + + gradient: Gradient { + GradientStop { position: 0.0; color: "#efffff" } + GradientStop { position: 0.5; color: "#ffffea" } + GradientStop { position: 1.0; color: "#ffd649" } + } + } + + Loader { + id: touchDelegateLoader + // 0 : width = minCt : maxCt + // x = (width * (ct - minCt) / (maxCt-minCt)) + property int position: (root.width * (root.ct - root.minCt) / (root.maxCt - root.minCt)); + x: item ? Math.max(0, Math.min(position - width * .5, parent.width - item.width)) : 0 + sourceComponent: root.touchDelegate + visible: !mouseArea.pressed && root.active + Behavior on x { + enabled: !mouseArea.pressed + NumberAnimation {} + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + preventStealing: true + + drag.minimumX: 0 + drag.maximumX: width - dndItem.width + drag.minimumY: 0 + drag.maximumY: height - dndItem.height + + onPressed: { + dndItem.x = Math.min(width - dndItem.width, Math.max(0, mouseX - dndItem.width / 2)) + dndItem.y = 0; + mouseArea.drag.target = dndItem; + } + + onPositionChanged: { + root.ct = Math.min(root.maxCt, Math.max(root.minCt, (mouseX * (root.maxCt - root.minCt) / width) + root.minCt)) + print("ct is now", root.ct) + } + + onReleased: { + root.ct = Math.min(root.maxCt, Math.max(root.minCt, (mouseX * (root.maxCt - root.minCt) / width) + root.minCt)) + mouseArea.drag.target = undefined; + } + + } + + Loader { + id: dndItem + sourceComponent: root.touchDelegate + visible: mouseArea.pressed && root.active + } + +} diff --git a/guh-control/ui/components/Graph.qml b/guh-control/ui/components/Graph.qml new file mode 100644 index 00000000..fd41e3a8 --- /dev/null +++ b/guh-control/ui/components/Graph.qml @@ -0,0 +1,318 @@ +import QtQuick 2.4 +import QtQuick.Controls 2.1 +import Guh 1.0 + +Item { + id: root + + property var model: null + + property color color: "grey" + + Connections { + target: model + onCountChanged: canvas.requestPaint() + } + onModelChanged: canvas.requestPaint() + + Label { + anchors.centerIn: parent + width: parent.width - 2 * app.margins + wrapMode: Text.WordWrap + text: "Sorry, there isn't enough data to display a graph here yet!" + visible: !root.model.busy && root.model.count <= 2 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: app.largeFont + } + + BusyIndicator { + anchors.centerIn: parent + visible: model.busy + } + + Canvas { + id: canvas + visible: root.model.count > 2 + + anchors.fill: parent + + property int minTemp: { + var lower = Math.floor(root.model.minimumValue - 2); + var upper = Math.ceil(root.model.maximumValue + 2); + print("upper", upper, "lower", lower) + if (!lower || !upper) { + return 0 + } + + while ((upper - lower) % 10 != 0) { + lower -= 1; + if ((upper - lower) % 10 != 0) { + upper += 1; + } + } + return lower; + } + + property int maxTemp: { + var lower = Math.floor(root.model.minimumValue - 2); + var upper = Math.ceil(root.model.maximumValue + 2); + if (!lower || !upper) { + return 0 + } + while ((upper - lower) % 10 != 0) { + lower -= 1; + if ((upper - lower) % 10 != 0) { + upper += 1; + } + } + return upper; + } + + property int topMargins: app.margins + property int bottomMargins: app.margins * 4 + property int leftMargins: app.margins * 3 + property int rightMargins: app.margins + + property color gridColor: "#d0d0d0" + + property int contentWidth: canvas.width - leftMargins - rightMargins + property int contentHeight: canvas.height - topMargins - bottomMargins + + property int totalSections: Math.round((maxTemp - minTemp) / 10) * 10 + property int sections: { + var tmp = totalSections; + while (tmp >= 10) { + tmp /= 2; + } + return tmp; + } + // Pixel per section + property real pps: contentHeight / sections; + + onPaint: { + + print("painting canvas", totalSections, sections) + var minTemp + + var ctx = canvas.getContext('2d'); + ctx.save(); + + ctx.reset() + + ctx.translate(leftMargins, topMargins) + + ctx.globalAlpha = 1//canvas.alpha; + //ctx.fillStyle = canvas.fillStyle; + ctx.font = "" + app.smallFont + "px Ubuntu"; + + paintGrid(ctx) + enumerate(ctx) +// paintWifis(ctx, canvas.selectedIndex) + paintGraph(ctx) + ctx.restore(); + + } + + function paintGrid(ctx) { + ctx.strokeStyle = canvas.gridColor; + ctx.fillStyle = "black" + ctx.lineWidth = 1; + + ctx.beginPath(); + ctx.rect(0, 0, contentWidth, contentHeight) + ctx.stroke(); + ctx.closePath(); + + // Horizontal lines + var tempInterval = (maxTemp - minTemp) / sections; + + for (var i = 0; i <= sections; i++) { + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.strokeStyle = canvas.gridColor + ctx.moveTo(0, i * pps); + ctx.lineTo(contentWidth, i * pps) + ctx.stroke(); + ctx.closePath(); + + ctx.beginPath(); + var label = maxTemp - (tempInterval * i).toFixed(0) + var textSize = ctx.measureText(label) + ctx.strokeStyle = "black" + ctx.fillStyle = "black" + ctx.lineWidth = 0; + ctx.text(label, -textSize.width - app.margins, i * pps + 5) +// ctx.stroke(); + ctx.fill() + ctx.closePath() + } + + ctx.beginPath(); + ctx.strokeStyle = "black" + ctx.fillStyle = "black" + ctx.lineWidth = 0; + var label = "°C" + var textSize = ctx.measureText(label) + ctx.text(label, -textSize.width - 1, -1 * pps + 5) +// ctx.stroke(); + ctx.fill() + ctx.closePath() + + } + + function enumerate(ctx) { + // enumate x axis + ctx.beginPath(); + ctx.globalAlpha = 1; + ctx.strokeStyle = "black" + ctx.fillStyle = "black" + ctx.lineWidth = 0; + // enumerate Y axis + + var lastTextX = -1; + for (var i = 0; i < model.count - 1; i++) { + var x = contentWidth / (model.count - 1) * i; + if (x < lastTextX) continue; + + var label = model.get(i).dayString + var textSize = ctx.measureText(label) + ctx.text(label.slice(0,2).concat("."), x, contentHeight + app.smallFont + app.margins / 2) + + switch (model.average) { + case ValueLogsProxyModel.AverageQuarterHour: + case ValueLogsProxyModel.AverageHourly: + case ValueLogsProxyModel.AverageDayTime: + label = model.get(i).timeString + break; + default: + label = model.get(i).dateString + } + + textSize = ctx.measureText(label) + ctx.text(label, x, contentHeight + app.smallFont * 2 + app.margins) + lastTextX = x + textSize.width; + } + +// ctx.stroke(); + ctx.fill() + ctx.closePath(); + } + + function paintGraph(ctx) { + if (model.count <= 1) { + return; + } + + var tempInterval = (maxTemp - minTemp) / sections; + + ctx.beginPath(); + ctx.globalAlpha = 1; + ctx.lineWidth = 2; + var graphStroke = root.color; + var grapFill = Qt.rgba(root.color.r, root.color.g, root.color.b, .4); + + ctx.strokeStyle = graphStroke; + ctx.fillStyle = grapFill; + + var points = new Array(); + for (var i = 0; i < model.count; i++) { + var value = model.get(i).value; + var point = new Object(); +// print("painting value", value) + point.x = (i == 0) ? 0 : (contentWidth / (model.count - 2) * i); + point.y = contentHeight - (value - minTemp) / tempInterval * pps; + points.push(point); + } + + paintBezier(ctx, points); + ctx.stroke(); + ctx.closePath(); + + ctx.beginPath(); + paintBezier(ctx, points) + ctx.lineTo(contentWidth, contentHeight); + ctx.lineTo(0, contentHeight); + ctx.fill(); + ctx.closePath(); + + + ctx.beginPath(); + ctx.globalAlpha = 1; + ctx.lineWidth = 2; + ctx.strokeStyle = "green" + ctx.fillStyle = "green" + + points = new Array(); + for (var i = 0; i < model.count; i++) { + var dayMaxTemp = model.get(i).maxTemp; + var point = new Object(); + point.x = (i == 0) ? 0 : (contentWidth / (model.count - 1) * i); + point.y = - (dayMaxTemp - maxTemp) / tempInterval * pps; + points.push(point); + } + + paintBezier(ctx, points); + + ctx.stroke(); + ctx.closePath(); + } + + function paintBezier(ctx, points) { + + if (points.length == 2) { + ctx.moveTo(points[0].x, points[0].y) + ctx.lineTo(points[1].x, points[1].y) + } else { + var n = points.length - 1; + points[0].rhsx = points[0].x + 2 * points[1].x; + points[0].rhsy = points[0].y + 2 * points[1].y; + for (var i = 1; i < n - 1; i++) { + points[i].rhsx = 4 * points[i].x + 2 * points[i+1].x; + points[i].rhsy = 4 * points[i].y + 2 * points[i+1].y; + } + points[n - 1].rhsx = (8 * points[n - 1].x + points[n].x) / 2; + points[n - 1].rhsy = (8 * points[n - 1].y + points[n].y) / 2; + + var b = 2.0; + n = points.length - 1; + points[0].firstcontrolx = points[0].rhsx / b; + points[0].firstcontroly = points[0].rhsy / b; + + for (var i = 1; i < n; i++) { + points[i].tmp = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - points[i].tmp; + points[i].firstcontrolx = (points[i].rhsx - points[i - 1].firstcontrolx) / b; + points[i].firstcontroly = (points[i].rhsy - points[i - 1].firstcontroly) / b; + } + for (var i = 1; i < n; i++) { + points[n - i - 1].firstcontrolx -= points[n - i].tmp * points[n - i].firstcontrolx; + points[n - i - 1].firstcontroly -= points[n - i].tmp * points[n - i].firstcontroly; + } + + n = points.length - 1; + for (var i = 0; i < n; i++) { + points[i].secondcontrolx = 2 * points[i + 1].x - points[i + 1].firstcontrolx; + points[i].secondcontroly = 2 * points[i + 1].y - points[i + 1].firstcontroly; + } + points[n - 1].secondcontrolx = (points[n].x + points[n - 1].firstcontrolx) / 2; + points[n - 1].secondcontroly = (points[n].x + points[n - 1].firstcontroly) / 2; + + ctx.moveTo(points[0].x, points[0].y); + for (var i = 0; i < n - 1; i++) { +// ctx.lineTo(points[i].firstcontrolx, points[i].firstcontroly) +// ctx.lineTo(points[i].secondcontrolx, points[i].secondcontroly) +// ctx.lineTo(points[i+1].x, points[i+1].y) + + ctx.bezierCurveTo(points[i].firstcontrolx, points[i].firstcontroly, + points[i].secondcontrolx, points[i].secondcontroly, + points[i + 1].x, points[i + 1].y) + } + } + } + + function hourToX(hour) { + var entries = root.day.count; + return canvas.contentWidth / entries * hour + } + } +} diff --git a/guh-control/ui/components/GuhHeader.qml b/guh-control/ui/components/GuhHeader.qml index 107c3d7b..6016a55d 100644 --- a/guh-control/ui/components/GuhHeader.qml +++ b/guh-control/ui/components/GuhHeader.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 -import QtQuick.Controls.Material 2.2 +import QtQuick.Controls.Material 2.1 ToolBar { id: root @@ -9,9 +9,11 @@ ToolBar { property string text property alias backButtonVisible: backButton.visible + property alias menuButtonVisible: menuButton.visible default property alias data: layout.data signal backPressed(); + signal menuPressed(); Rectangle { anchors.fill: parent @@ -22,6 +24,13 @@ ToolBar { id: layout anchors { fill: parent; leftMargin: app.margins; rightMargin: app.margins } + HeaderButton { + id: menuButton + imageSource: "../images/navigation-menu.svg" + visible: false + onClicked: root.menuPressed(); + } + HeaderButton { id: backButton imageSource: "../images/back.svg" diff --git a/guh-control/ui/components/IconMenuItem.qml b/guh-control/ui/components/IconMenuItem.qml new file mode 100644 index 00000000..3545b9e0 --- /dev/null +++ b/guh-control/ui/components/IconMenuItem.qml @@ -0,0 +1,22 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +MenuItem { + id: root + property alias iconSource: icon.name + + contentItem: RowLayout { + spacing: app.margins + ColorIcon { + id: icon + height: parent.height + width: height + } + Label { + text: root.text + Layout.fillWidth: true + font.pixelSize: app.mediumFont + } + } +} diff --git a/guh-control/ui/components/ThinDivider.qml b/guh-control/ui/components/ThinDivider.qml new file mode 100644 index 00000000..23d01722 --- /dev/null +++ b/guh-control/ui/components/ThinDivider.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.2 + +Rectangle { + height: 1 + Layout.fillWidth: true + color: app.guhAccent +} diff --git a/guh-control/ui/components/ThrottledSlider.qml b/guh-control/ui/components/ThrottledSlider.qml new file mode 100644 index 00000000..13b728d9 --- /dev/null +++ b/guh-control/ui/components/ThrottledSlider.qml @@ -0,0 +1,40 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 + +Item { + id: root + implicitHeight: slider.implicitHeight + + property real value: 0 + property alias from: slider.from + property alias to: slider.to + signal moved(real value); + + Slider { + id: slider + anchors.left: parent.left; anchors.right: parent.right + from: 0 + to: 100 + property var lastSentTime: new Date() + onValueChanged: { + var currentTime = new Date(); + if (pressed && currentTime - lastSentTime > 200) { + root.moved(slider.value) + lastSentTime = currentTime + } + } + onPressedChanged: { + if (!pressed) { + root.moved(slider.value) + } + } + } + + Binding { + target: slider + property: "value" + value: root.value + when: !slider.pressed + } +} + diff --git a/guh-control/ui/customviews/CustomViewBase.qml b/guh-control/ui/customviews/CustomViewBase.qml index aee1b6ab..adcd8805 100644 --- a/guh-control/ui/customviews/CustomViewBase.qml +++ b/guh-control/ui/customviews/CustomViewBase.qml @@ -4,5 +4,6 @@ Item { property var device: null property var deviceClass: null + property string interfaceName } diff --git a/guh-control/ui/customviews/ExtendedVolumeController.qml b/guh-control/ui/customviews/ExtendedVolumeController.qml index 6c3d36a9..9e959ead 100644 --- a/guh-control/ui/customviews/ExtendedVolumeController.qml +++ b/guh-control/ui/customviews/ExtendedVolumeController.qml @@ -2,6 +2,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 import Guh 1.0 +import "../components" CustomViewBase { id: root @@ -10,10 +11,20 @@ CustomViewBase { RowLayout { id: row anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins } - Button { + + AbstractButton { + width: app.iconSize * 2 + height: width + property var muteState: root.device.states.getState(deviceClass.stateTypes.findByName("mute").id) - property bool isMuted: muteState.value - text: isMuted ? "unmute" : "mute" + property bool isMuted: muteState.value === true + + ColorIcon { + anchors.fill: parent + name: "../images/audio-speakers-muted-symbolic.svg" + color: parent.isMuted ? app.guhAccent : keyColor + } + onClicked: { var paramList = [] var muteParam = {} diff --git a/guh-control/ui/customviews/MediaControllerView.qml b/guh-control/ui/customviews/MediaControllerView.qml index 4f7ead41..44ad3267 100644 --- a/guh-control/ui/customviews/MediaControllerView.qml +++ b/guh-control/ui/customviews/MediaControllerView.qml @@ -2,6 +2,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 import Guh 1.0 +import "../components" CustomViewBase { id: root @@ -21,51 +22,100 @@ CustomViewBase { RowLayout { Layout.fillWidth: true - Button { - text: "|<" + + Item { Layout.fillWidth: true; height: 1 } + + property int iconSize: Math.min(root.width / 6, app.iconSize * 2) + + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-skip-backward.svg" + } onClicked: { executeAction("skipBack") } } - Button { - text: "<<" + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-seek-backward.svg" + } onClicked: { executeAction("rewind") } } - Button { - text: "X"+ playbackState.value + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-playback-stop.svg" + } onClicked: { executeAction("stop") } } - Button { - text: ">" - visible: playbackState.value == "PAUSED" + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-playback-start.svg" + } + visible: playbackState.value == "PAUSED" || playbackState.value == "STOPPED" onClicked: { executeAction("play") } } - Button { - text: "||" + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-playback-pause.svg" + } visible: playbackState.value == "PLAYING" onClicked: { executeAction("pause") } } - Button { - text: ">>" + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-seek-forward.svg" + } onClicked: { executeAction("fastForward") } } - Button { - text: ">|" + AbstractButton { + Layout.fillWidth: true + height: Math.min(app.iconSize * 2) + ColorIcon { + height: parent.height + width: height + name: "../images/media-skip-forward.svg" + } onClicked: { executeAction("skipNext") } } + Item { Layout.fillWidth: true; height: 1 } + } } } diff --git a/guh-control/ui/customviews/NotificationsView.qml b/guh-control/ui/customviews/NotificationsView.qml new file mode 100644 index 00000000..4967168f --- /dev/null +++ b/guh-control/ui/customviews/NotificationsView.qml @@ -0,0 +1,37 @@ +import QtQuick 2.5 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import "../components" +import Guh 1.0 + +CustomViewBase { + id: root + height: grid.implicitHeight + app.margins * 2 + + ColumnLayout { + id: grid + anchors { left: parent.left; top: parent.top; right: parent.right } + Label { + Layout.fillWidth: true + text: "Send a notification now:" + } + TextArea { + id: textArea + Layout.fillWidth: true + } + Button { + Layout.fillWidth: true + text: "Send" + onClicked: { + + var params = [] + var param1 = {} + var paramTypeId = root.deviceClass.actionTypes.findByName("notify").paramTypes.getParamType("title").id + param1.paramTypeId = paramTypeId + param1.value = textArea.text + params.push(param1) + Engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("notify").id, params) + } + } + } +} diff --git a/guh-control/ui/customviews/SensorView.qml b/guh-control/ui/customviews/SensorView.qml index 6b6f157f..4edf0aaf 100644 --- a/guh-control/ui/customviews/SensorView.qml +++ b/guh-control/ui/customviews/SensorView.qml @@ -1,13 +1,95 @@ import QtQuick 2.5 -import QtQuick.Controls 2.2 +import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 +import "../components" +import Guh 1.0 CustomViewBase { - height: grid.implicitHeight + app.margins * 2 + id: root + implicitHeight: grid.implicitHeight + app.margins * 2 + + property string interfaceName + + readonly property var stateType: deviceClass.stateTypes.findByName(interfaceName.replace("sensor", "")) + readonly property var deviceState: device.states.getState(stateType.id) + + ValueLogsProxyModel { + id: logsModel + deviceId: root.device.id + typeId: stateType.id + average: zoomTabBar.currentItem.avg + startTime: zoomTabBar.currentItem.startTime + Component.onCompleted: updateTimer.start(); + onAverageChanged: updateTimer.start() + onStartTimeChanged: updateTimer.start(); + } + + Timer { + id: updateTimer + interval: 0 + repeat: false + onTriggered: { + print("updating:", logsModel.startTime) + logsModel.update() + } + } ColumnLayout { - Label { - text: "bla" + id: grid + anchors { left: parent.left; top: parent.top; right: parent.right } + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins + ColorIcon { + name: app.interfaceToIcon(root.interfaceName) + height: app.iconSize + width: height + color: app.interfaceToColor(root.interfaceName) + } + Label { + text: deviceState.value + " " + stateType.unitString + font.pixelSize: app.largeFont + } + + TabBar { + id: zoomTabBar + Layout.fillWidth: true + TabButton { + text: "6 h" + property int avg: ValueLogsProxyModel.AverageQuarterHour + property date startTime: { + var date = new Date(); + date.setHours(new Date().getHours() - 6) + return date; + } + } + TabButton { + text: "24 h" + property int avg: ValueLogsProxyModel.AverageHourly + property date startTime: { + var date = new Date(); + date.setHours(new Date().getHours() - 24); + return date; + } + } + TabButton { + text: "7 d" + property int avg: ValueLogsProxyModel.AverageDayTime + property date startTime: { + var date = new Date(); + date.setDate(new Date().getDate() - 7); + return date; + } + } + } + } + Graph { + Layout.fillWidth: true + Layout.preferredHeight: 200 + model: logsModel + color: app.interfaceToColor(root.interfaceName) } } } diff --git a/guh-control/ui/customviews/WeatherView.qml b/guh-control/ui/customviews/WeatherView.qml index aba4b59a..c9dda3b4 100644 --- a/guh-control/ui/customviews/WeatherView.qml +++ b/guh-control/ui/customviews/WeatherView.qml @@ -1,60 +1,108 @@ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 +import "../components" CustomViewBase { id: root height: grid.implicitHeight + app.margins * 2 - readonly property var weatherIconStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("weatherIcon") : null - readonly property var weatherIconState: weatherIconStateType && device.states ? device.states.getState(weatherIconStateType.id) : null + readonly property var weatherConditionStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("weatherCondition") : null + readonly property var weatherConditionState: weatherConditionStateType && device.states ? device.states.getState(weatherConditionStateType.id) : null + + readonly property var weatherDescriptionStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("weatherDescription") : null + readonly property var weatherDescriptionState: weatherDescriptionStateType && device.states ? device.states.getState(weatherDescriptionStateType.id) : null readonly property var temperatureStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("temperature") : null readonly property var temperatureState: temperatureStateType && device.states ? device.states.getState(temperatureStateType.id) : null - readonly property var minTemperatureStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("temperature minimum") : null - readonly property var minTemperatureState: minTemperatureStateType && device.states ? device.states.getState(minTemperatureStateType.id) : null + readonly property var humidityStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("humidity") : null + readonly property var humidityState: humidityStateType && device.states ? device.states.getState(humidityStateType.id) : null - readonly property var maxTemperatureStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("temperature maximum") : null - readonly property var maxTemperatureState: maxTemperatureStateType && device.states ? device.states.getState(maxTemperatureStateType.id) : null + readonly property var windDirectionStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("windDirection") : null + readonly property var windDirectionState: windDirectionStateType && device.states ? device.states.getState(windDirectionStateType.id) : null + + readonly property var windSpeedStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("windSpeed") : null + readonly property var windSpeedState: windSpeedStateType && device.states ? device.states.getState(windSpeedStateType.id) : null ColumnLayout { id: grid anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins } + spacing: app.margins RowLayout { Layout.fillWidth: true Label { - text: (temperatureState ? temperatureState.value : "N/A") + " °" + text: (temperatureState ? Math.round(temperatureState.value * 10) / 10 : "N/A") + " °" Layout.fillWidth: true - Layout.preferredWidth: parent.width + Layout.preferredWidth: (parent.width - mainImage.width) / 2 font.pixelSize: app.largeFont horizontalAlignment: Text.AlignHCenter } Image { + id: mainImage // h : w = ss.h : ss.w Layout.preferredWidth: height * implicitWidth / implicitHeight Layout.preferredHeight: app.largeFont * 4 - source: weatherIconState ? "../images/weathericons/weather-" + weatherIconState.value + "-symbolic.svg" : "" + source: weatherConditionState ? "../images/weathericons/weather-" + weatherConditionState.value + ".svg" : "" sourceSize.height: height } ColumnLayout { Layout.fillWidth: true - Layout.preferredWidth: parent.width - Label { - Layout.fillWidth: true - text: maxTemperatureState ? (maxTemperatureState.value + "°") : "" - horizontalAlignment: Text.AlignHCenter + Layout.preferredWidth: (parent.width - mainImage.width) / 2 + RowLayout { + ColorIcon { + name: "../images/weathericons/humidity.svg" + width: app.iconSize + height: width + } + + Label { + text: (humidityState ? humidityState.value : "N/A") + " %" + } } - Label { - Layout.fillWidth: true - text: minTemperatureState ? (minTemperatureState.value + "°") : "" - horizontalAlignment: Text.AlignHCenter + RowLayout { + ColorIcon { + name: "../images/weathericons/wind.svg" + width: app.iconSize + height: width + } + + Label { + text: windSpeedState && windDirectionState ? "%1 km/h %2".arg(windSpeedState.value).arg(angleToOrientation(windDirectionState.value)) : "N/A" + function angleToOrientation(windAngle) { + if (windAngle < 23) { + return qsTr("N"); + } else if (windAngle < 68) { + return qsTr("NE"); + } else if (windAngle < 113) { + return qsTr("E"); + } else if (windAngle < 156) { + return qsTr("SE"); + } else if (windAngle < 203) { + return qsTr("S"); + } else if (windAngle < 248) { + return qsTr("SW"); + } else if (windAngle < 293) { + return qsTr("W"); + } else if (windAngle < 338) { + return qsTr("NW"); + } + + return qsTr("N"); + } + } } } } + Label { + Layout.fillWidth: true + text: weatherDescriptionState.value + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + } } } diff --git a/guh-control/ui/devicelistpages/GenericDeviceListPage.qml b/guh-control/ui/devicelistpages/GenericDeviceListPage.qml index bfdf7857..994d9e64 100644 --- a/guh-control/ui/devicelistpages/GenericDeviceListPage.qml +++ b/guh-control/ui/devicelistpages/GenericDeviceListPage.qml @@ -9,6 +9,12 @@ Page { property alias filterTag: devicesProxy.filterTag property alias filterInterface: devicesProxy.filterInterface + Component.onCompleted: { + if (devicesProxy.count == 1) { + enterPage(0, true) + } + } + header: GuhHeader { text: { if (subPage.filterTag != DeviceClass.BasicTagNone) { @@ -22,6 +28,29 @@ Page { onBackPressed: pageStack.pop() } + function enterPage(index, replace) { + var device = devicesProxy.get(index); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + print("clicked", deviceClass.interfaces) + var page; +/* if (deviceClass.interfaces.indexOf("media") >= 0) { + page = "MediaDevicePage.qml"; + } else */if (deviceClass.interfaces.indexOf("button") >= 0) { + page = "ButtonDevicePage.qml"; + } else if (deviceClass.interfaces.indexOf("weather") >= 0) { + page = "WeatherDevicePage.qml"; +// } else if (deviceClass.interfaces.indexOf("sensor") >= 0) { +// page = "SensorDevicePage.qml"; + } else { + page = "GenericDevicePage.qml"; + } + if (replace) { + pageStack.replace(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) + } else { + pageStack.push(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) + } + } + ListView { anchors.fill: parent model: DevicesProxy { @@ -37,18 +66,7 @@ Page { } onClicked: { - var device = devicesProxy.get(index); - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - print("clicked", deviceClass.interfaces) - var page; - if (deviceClass.interfaces.indexOf("media") >= 0) { - page = "MediaDevicePage.qml"; - } else if (deviceClass.interfaces.indexOf("button") >= 0) { - page = "ButtonDevicePage.qml"; - } else { - page = "GenericDevicePage.qml"; - } - pageStack.push(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) + enterPage(index, false) } } } diff --git a/guh-control/ui/devicelistpages/LightsDeviceListPage.qml b/guh-control/ui/devicelistpages/LightsDeviceListPage.qml index fc41cba8..0a00aa0c 100644 --- a/guh-control/ui/devicelistpages/LightsDeviceListPage.qml +++ b/guh-control/ui/devicelistpages/LightsDeviceListPage.qml @@ -41,6 +41,7 @@ Page { ListView { Layout.fillHeight: true Layout.fillWidth: true + clip: true model: DevicesProxy { id: devicesProxy devices: Engine.deviceManager.devices @@ -98,12 +99,12 @@ Page { } } - onClicked: { - pageStack.push(Qt.resolvedUrl("../devicepages/GenericDevicePage.qml"), {device: devicesProxy.get(index)}) + if (deviceClass.interfaces.indexOf("colorlight") >= 0) { + pageStack.push(Qt.resolvedUrl("../devicepages/ColorLightDevicePage.qml"), {device: devicesProxy.get(index)}) + } } } - } } } diff --git a/guh-control/ui/devicepages/ButtonDevicePage.qml b/guh-control/ui/devicepages/ButtonDevicePage.qml index ef9b8120..40ab87e2 100644 --- a/guh-control/ui/devicepages/ButtonDevicePage.qml +++ b/guh-control/ui/devicepages/ButtonDevicePage.qml @@ -4,11 +4,8 @@ import QtQuick.Layouts 1.1 import Guh 1.0 import "../components" -Page { +DevicePageBase { id: root - property var device: null - readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) - header: GuhHeader { text: device.name @@ -46,9 +43,18 @@ Page { property var ruleActionType: ruleAction ? ruleActionDeviceClass.actionTypes.getActionType(ruleAction.actionTypeId) : null property var ruleActionDevice: ruleAction ? Engine.deviceManager.devices.getDevice(ruleAction.deviceId) : null property var ruleActionDeviceClass: ruleActionDevice ? Engine.deviceManager.deviceClasses.getDeviceClass(ruleActionDevice.deviceClassId) : null - property var ruleActionParams: ruleAction ? ruleAction.ruleActionParams : null + property var ruleActionParams: ruleAction && ruleAction ? ruleAction.ruleActionParams : null property var ruleActionParam: ruleActionParams.count == 1 ? ruleActionParams.get(0) : null - text: ruleActions.count > 1 ? "Multiple actions" : qsTr("%1: Set %2 to %3").arg(ruleActionDevice.name).arg(ruleActionType.name).arg(ruleActionParam.value) + text: { + if (ruleActions && ruleActions.count > 1) { + return "Multiple actions"; + } else if (ruleActionParam) { + return qsTr("%1: Set %2 to %3").arg(ruleActionDevice.name).arg(ruleActionType.name).arg(ruleActionParam.value) + } else { + return qsTr("%1: Call %2").arg(ruleActionDevice.name).arg(ruleActionType.name) + } + } + swipe.right: MouseArea { anchors.right: parent.right height: parent.height @@ -92,13 +98,7 @@ Page { event["eventTypeId"] = eventDeviceClass.eventTypes.findByName("pressed").id; events.push(event); rule["eventDescriptors"] = events; - var actions = []; - var action = {}; - action["actionTypeId"] = page.actionType.id; - action["deviceId"] = page.device.id; - action["ruleActionParams"] = page.params; - actions.push(action); - rule["actions"] = actions; + rule["actions"] = page.actions; Engine.ruleManager.addRule(rule); pageStack.pop(root) }) diff --git a/guh-control/ui/devicepages/ColorLightDevicePage.qml b/guh-control/ui/devicepages/ColorLightDevicePage.qml new file mode 100644 index 00000000..264fa321 --- /dev/null +++ b/guh-control/ui/devicepages/ColorLightDevicePage.qml @@ -0,0 +1,144 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Guh 1.0 +import "../components" +import "../actiondelegates" + +Page { + id: root + property var device: null + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + + + header: GuhHeader { + text: device.name + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/info.svg" + onClicked: pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device}) + } + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + id: powerRow + Layout.fillWidth: true + Layout.margins: app.margins + spacing: app.margins + + property var powerStateType: root.deviceClass.stateTypes.findByName("power") + property var powerState: root.device.states.getState(powerStateType.id) + + property var brightnessStateType: root.deviceClass.stateTypes.findByName("brightness") + property var brightnessState: root.device.states.getState(brightnessStateType.id) + + AbstractButton { + width: app.iconSize * 2 + height: width + ColorIcon { + anchors.fill: parent + name: "../images/torch-off.svg" + color: powerRow.powerState.value === true ? keyColor : app.guhAccent + } + onClicked: { + var actionType = root.deviceClass.actionTypes.findByName("power"); + var params = [] + var param = {} + param["paramTypeId"] = actionType.paramTypes.get(0).id; + param["value"] = false; + params.push(param) + Engine.deviceManager.executeAction(root.device.id, powerRow.powerStateType.id, params); + } + } + + ThrottledSlider { + id: brightnessSlider + Layout.fillWidth: true + value: powerRow.brightnessState.value + onMoved: { + var actionType = root.deviceClass.actionTypes.findByName("brightness"); + var params = [] + var param = {} + param["paramTypeId"] = actionType.paramTypes.get(0).id; + param["value"] = value; + params.push(param) + Engine.deviceManager.executeAction(root.device.id, powerRow.brightnessStateType.id, params); + } + } + + AbstractButton { + width: app.iconSize * 2 + height: width + ColorIcon { + anchors.fill: parent + name: "../images/torch-on.svg" + color: powerRow.powerState.value === true ? app.guhAccent : keyColor + } + onClicked: { + var actionType = root.deviceClass.actionTypes.findByName("power"); + var params = [] + var param = {} + param["paramTypeId"] = actionType.paramTypes.get(0).id; + param["value"] = true; + params.push(param) + Engine.deviceManager.executeAction(root.device.id, powerRow.powerStateType.id, params); + } + } + } + + + ColorPickerCt { + id: pickerCt + Layout.fillWidth: true + Layout.margins: app.margins + property var actionType: root.deviceClass.actionTypes.findByName("colorTemperature") + property var ctState: root.device.states.getState(actionType.id) + ct: ctState.value + + height: 80 + + touchDelegate: Rectangle { + height: pickerCt.height + width: 5 + color: "black" + } + + property var lastSentTime: new Date() + onCtChanged: { + var currentTime = new Date(); + if (pressed && currentTime - lastSentTime > 200) { + setColorTemp(ct) + lastSentTime = currentTime + } + } + + function setColorTemp(ct) { + var actionType = root.deviceClass.actionTypes.findByName("colorTemperature"); + var params = [] + var param = {} + param["paramTypeId"] = actionType.paramTypes.get(0).id; + param["value"] = ct; + params.push(param) + Engine.deviceManager.executeAction(root.device.id, actionType.id, params); + } + + } + + ActionDelegateColor { + Layout.fillWidth: true + Layout.fillHeight: true + actionType: root.deviceClass.actionTypes.findByName("color") + actionState: root.device.states.getState(actionType.id).value + + onExecuteAction: { + Engine.deviceManager.executeAction(root.device.id, actionType.id, params) + } + } + + } + +} diff --git a/guh-control/ui/devicepages/DevicePageBase.qml b/guh-control/ui/devicepages/DevicePageBase.qml new file mode 100644 index 00000000..c1e4083b --- /dev/null +++ b/guh-control/ui/devicepages/DevicePageBase.qml @@ -0,0 +1,51 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Guh 1.0 +import "../components" + +Page { + id: root + property var device: null + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + + default property alias data: contentItem.data + + Pane { + id: infoPane + visible: batteryState !== null || (connectedState !== null && connectedState.value === false) + height: visible ? implicitHeight : 0 + anchors { left: parent.left; top: parent.top; right: parent.right } + property var batteryState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryLevel").id) : null +// property var connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null + property var connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null + + RowLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + Item { + Layout.fillWidth: true + height: app.iconSize + } + + ColorIcon { + height: app.iconSize + width: height + visible: infoPane.connectedState !== null && infoPane.connectedState.value === false + color: "red" + name: "../images/dialog-warning-symbolic.svg" + } + + ColorIcon { + height: app.iconSize + width: height * 1.23 + name: infoPane.batteryState !== null ? "../images/battery/battery-" + ("00" + (Math.floor(infoPane.batteryState.value / 10) * 10)).slice(-3) + ".svg" : "" + } + } + } + + Item { + id: contentItem + anchors.fill: parent + anchors.topMargin: infoPane.height + } +} diff --git a/guh-control/ui/devicepages/GenericDevicePage.qml b/guh-control/ui/devicepages/GenericDevicePage.qml index c8752de4..06729678 100644 --- a/guh-control/ui/devicepages/GenericDevicePage.qml +++ b/guh-control/ui/devicepages/GenericDevicePage.qml @@ -4,11 +4,8 @@ import QtQuick.Layouts 1.1 import Guh 1.0 import "../components" -Page { +DevicePageBase { id: root - property var device: null - readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) - header: GuhHeader { text: device.name @@ -36,39 +33,71 @@ Page { width: parent.width // spacing: app.margins - Loader { - id: stateViewLoader - Layout.fillWidth: true - source: { - var src = ""; - print("**** devicetags", deviceClass.basicTags) - if (deviceClass.interfaces.indexOf("weather") >= 0) { - src = "WeatherView.qml"; - } - if (deviceClass.interfaces.indexOf("mediacontroller") >= 0) { - src = "MediaControllerView.qml" - } - if (deviceClass.interfaces.indexOf("sensor") >= 0) { - src = "SensorView.qml" - } + Repeater { + id: interfaceViewsRepeater + property bool unhandledInterface: false - return Qt.resolvedUrl("../customviews/" + src); - } - Binding { - target: stateViewLoader.item ? stateViewLoader.item : null - property: "device" - value: device - } - Binding { - target: stateViewLoader.item ? stateViewLoader.item : null - property: "deviceClass" - value: deviceClass + model: deviceClass.interfaces + delegate: Loader { + id: stateViewLoader + Layout.fillWidth: true + source: { + var src = ""; + var options = [] + switch (modelData) { + case "weather": + src = "WeatherView.qml"; + break; + case "mediacontroller": + src = "MediaControllerView.qml" + break; + case "extendedvolumecontroller": + src = "ExtendedVolumeController.qml" + break; + case "temperaturesensor": + case "humiditysensor": + src = "SensorView.qml" + options.interfaceName = modelData; + break; + case "notifications": + src = "NotificationsView.qml" + break; + case "battery": + case "connectable": + // Handled by our base class + break; + case "sensor": + case "media": + // Ignore interfaces without any states/actions + break; + default: + print("unhandled interface", modelData) + interfaceViewsRepeater.unhandledInterface = true + } + + return Qt.resolvedUrl("../customviews/" + src); + } + Binding { + target: stateViewLoader.item ? stateViewLoader.item : null + property: "device" + value: device + } + Binding { + target: stateViewLoader.item ? stateViewLoader.item : null + property: "deviceClass" + value: deviceClass + } + Binding { + target: stateViewLoader.item ? stateViewLoader.item : null + property: "interfaceName" + value: modelData + } } } Repeater { - model: deviceClass.actionTypes + model: interfaceViewsRepeater.count == 0 || interfaceViewsRepeater.unhandledInterface ? deviceClass.actionTypes : null delegate: ItemDelegate { Layout.fillWidth: true Layout.preferredHeight: delegateLoader.height diff --git a/guh-control/ui/devicepages/GenericDeviceStateDetailsPage.qml b/guh-control/ui/devicepages/GenericDeviceStateDetailsPage.qml index 06dfb6c2..894928f2 100644 --- a/guh-control/ui/devicepages/GenericDeviceStateDetailsPage.qml +++ b/guh-control/ui/devicepages/GenericDeviceStateDetailsPage.qml @@ -27,7 +27,7 @@ Page { Label { id: stateLabel Layout.preferredWidth: parent.width / 2 - text: name + text: displayName } Label { diff --git a/guh-control/ui/devicepages/SensorDevicePage.qml b/guh-control/ui/devicepages/SensorDevicePage.qml new file mode 100644 index 00000000..7d102f01 --- /dev/null +++ b/guh-control/ui/devicepages/SensorDevicePage.qml @@ -0,0 +1,44 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Guh 1.0 +import "../components" +import "../customviews" + +Page { + id: root + property var device: null + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + + + header: GuhHeader { + text: device.name + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/info.svg" + onClicked: pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device}) + } + } + + ListView { + anchors { fill: parent } + spacing: app.margins + model: ListModel { + Component.onCompleted: { + var supportedInterfaces = ["temperaturesensor", "humiditysensor"] + for (var i = 0; i < supportedInterfaces.length; i++) { + if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { + append({name: supportedInterfaces[i]}); + } + } + } + } + delegate: SensorView { + width: parent.width + interfaceName: modelData + device: root.device + deviceClass: root.deviceClass + } + } +} diff --git a/guh-control/ui/devicepages/WeatherDevicePage.qml b/guh-control/ui/devicepages/WeatherDevicePage.qml new file mode 100644 index 00000000..549b7f2a --- /dev/null +++ b/guh-control/ui/devicepages/WeatherDevicePage.qml @@ -0,0 +1,30 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Guh 1.0 +import "../components" +import "../customviews" + +Page { + id: root + property var device: null + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + + + header: GuhHeader { + text: device.name + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: "../images/info.svg" + onClicked: pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device}) + } + } + + WeatherView { + anchors.fill: parent + device: root.device + deviceClass: root.deviceClass + } + +} diff --git a/guh-control/ui/images/audio-speakers-muted-symbolic.svg b/guh-control/ui/images/audio-speakers-muted-symbolic.svg new file mode 100644 index 00000000..e747e7a4 --- /dev/null +++ b/guh-control/ui/images/audio-speakers-muted-symbolic.svg @@ -0,0 +1,20 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/guh-control/ui/images/audio-speakers-symbolic.svg b/guh-control/ui/images/audio-speakers-symbolic.svg new file mode 100644 index 00000000..c72bb31d --- /dev/null +++ b/guh-control/ui/images/audio-speakers-symbolic.svg @@ -0,0 +1,20 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-000.svg b/guh-control/ui/images/battery/battery-000.svg new file mode 100644 index 00000000..1a9f77b4 --- /dev/null +++ b/guh-control/ui/images/battery/battery-000.svg @@ -0,0 +1,18 @@ + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-010.svg b/guh-control/ui/images/battery/battery-010.svg new file mode 100644 index 00000000..42319b25 --- /dev/null +++ b/guh-control/ui/images/battery/battery-010.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-020.svg b/guh-control/ui/images/battery/battery-020.svg new file mode 100644 index 00000000..56633b3c --- /dev/null +++ b/guh-control/ui/images/battery/battery-020.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-030.svg b/guh-control/ui/images/battery/battery-030.svg new file mode 100644 index 00000000..4da0a0d1 --- /dev/null +++ b/guh-control/ui/images/battery/battery-030.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-040.svg b/guh-control/ui/images/battery/battery-040.svg new file mode 100644 index 00000000..fb19a8dc --- /dev/null +++ b/guh-control/ui/images/battery/battery-040.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-050.svg b/guh-control/ui/images/battery/battery-050.svg new file mode 100644 index 00000000..5fd9bbdb --- /dev/null +++ b/guh-control/ui/images/battery/battery-050.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-060.svg b/guh-control/ui/images/battery/battery-060.svg new file mode 100644 index 00000000..2faa41ad --- /dev/null +++ b/guh-control/ui/images/battery/battery-060.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-070.svg b/guh-control/ui/images/battery/battery-070.svg new file mode 100644 index 00000000..f7f63ff4 --- /dev/null +++ b/guh-control/ui/images/battery/battery-070.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-080.svg b/guh-control/ui/images/battery/battery-080.svg new file mode 100644 index 00000000..2ab0bb0a --- /dev/null +++ b/guh-control/ui/images/battery/battery-080.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-090.svg b/guh-control/ui/images/battery/battery-090.svg new file mode 100644 index 00000000..a931579f --- /dev/null +++ b/guh-control/ui/images/battery/battery-090.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/battery/battery-100.svg b/guh-control/ui/images/battery/battery-100.svg new file mode 100644 index 00000000..c1a68074 --- /dev/null +++ b/guh-control/ui/images/battery/battery-100.svg @@ -0,0 +1,19 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/guh-control/ui/images/dialog-warning-symbolic.svg b/guh-control/ui/images/dialog-warning-symbolic.svg new file mode 100644 index 00000000..857fd101 --- /dev/null +++ b/guh-control/ui/images/dialog-warning-symbolic.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/media-playback-pause.svg b/guh-control/ui/images/media-playback-pause.svg new file mode 100644 index 00000000..acb5f7c3 --- /dev/null +++ b/guh-control/ui/images/media-playback-pause.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/media-playback-start.svg b/guh-control/ui/images/media-playback-start.svg new file mode 100644 index 00000000..4b9865cb --- /dev/null +++ b/guh-control/ui/images/media-playback-start.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/media-playback-stop.svg b/guh-control/ui/images/media-playback-stop.svg new file mode 100644 index 00000000..48c83da9 --- /dev/null +++ b/guh-control/ui/images/media-playback-stop.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/media-seek-backward.svg b/guh-control/ui/images/media-seek-backward.svg new file mode 100644 index 00000000..8494c251 --- /dev/null +++ b/guh-control/ui/images/media-seek-backward.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/media-seek-forward.svg b/guh-control/ui/images/media-seek-forward.svg new file mode 100644 index 00000000..5bd3cb7e --- /dev/null +++ b/guh-control/ui/images/media-seek-forward.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/weathericons/weather-sleet-symbolic.svg b/guh-control/ui/images/media-skip-backward.svg similarity index 54% rename from guh-control/ui/images/weathericons/weather-sleet-symbolic.svg rename to guh-control/ui/images/media-skip-backward.svg index f21e3226..d2d00a81 100644 --- a/guh-control/ui/images/weathericons/weather-sleet-symbolic.svg +++ b/guh-control/ui/images/media-skip-backward.svg @@ -15,7 +15,7 @@ version="1.1" inkscape:version="0.91+devel r" viewBox="0 0 96 96.000001" - sodipodi:docname="weather-sleet-symbolic.svg"> + sodipodi:docname="media-skip-backward.svg"> + inkscape:guide-bbox="true"> + + - @@ -150,31 +153,20 @@ transform="scale(-1,1)" /> + d="m 388.43206,425.65086 c -0.12331,0.0552 -0.008,0.0131 -0.0996,0.0586 a 4.0244714,4.0244712 0 0 0 -0.21883,0.10547 c -0.002,10e-4 0.01,-0.007 0.008,-0.006 l 0.31067,-0.15821 z" + id="path2194-5" + inkscape:connector-curvature="0" /> + - - + id="path4187" /> diff --git a/guh-control/ui/images/media-skip-forward.svg b/guh-control/ui/images/media-skip-forward.svg new file mode 100644 index 00000000..13db2af8 --- /dev/null +++ b/guh-control/ui/images/media-skip-forward.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/weathericons/weather-severe-alert-symbolic.svg b/guh-control/ui/images/navigation-menu.svg similarity index 62% rename from guh-control/ui/images/weathericons/weather-severe-alert-symbolic.svg rename to guh-control/ui/images/navigation-menu.svg index 7865dab0..3e3f60ea 100644 --- a/guh-control/ui/images/weathericons/weather-severe-alert-symbolic.svg +++ b/guh-control/ui/images/navigation-menu.svg @@ -15,7 +15,7 @@ version="1.1" inkscape:version="0.91+devel r" viewBox="0 0 96 96.000001" - sodipodi:docname="weather-severe-alert-symbolic.svg"> + sodipodi:docname="navigation-menu.svg"> + image/svg+xml - + @@ -145,15 +149,23 @@ y="345.36221" transform="scale(-1,1)" /> + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;marker:none;enable-background:accumulate" + d="m 416.99416,429.36224 -4.0016,0 0,-72.00002 4.0016,0 z" + id="path4182" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;marker:none;enable-background:accumulate" + d="m 391.98426,429.36224 -4.00159,0 0,-72.00002 4.00159,0 z" + id="path4180" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + diff --git a/guh-control/ui/images/network-wired-symbolic.svg b/guh-control/ui/images/network-wired-symbolic.svg new file mode 100644 index 00000000..1897dcdb --- /dev/null +++ b/guh-control/ui/images/network-wired-symbolic.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/notification.svg b/guh-control/ui/images/notification.svg new file mode 100644 index 00000000..f3fae095 --- /dev/null +++ b/guh-control/ui/images/notification.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/sensors.svg b/guh-control/ui/images/sensors.svg new file mode 100644 index 00000000..3ce4c9de --- /dev/null +++ b/guh-control/ui/images/sensors.svg @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/weathericons/weather-flurries-symbolic.svg b/guh-control/ui/images/settings.svg similarity index 61% rename from guh-control/ui/images/weathericons/weather-flurries-symbolic.svg rename to guh-control/ui/images/settings.svg index 4e87186c..0b0ef3e6 100644 --- a/guh-control/ui/images/weathericons/weather-flurries-symbolic.svg +++ b/guh-control/ui/images/settings.svg @@ -15,7 +15,7 @@ version="1.1" inkscape:version="0.91+devel r" viewBox="0 0 96 96.000001" - sodipodi:docname="weather-flurries-symbolic.svg"> + sodipodi:docname="settings.svg"> + + - @@ -149,28 +153,19 @@ y="345.36221" transform="scale(-1,1)" /> - - + diff --git a/guh-control/ui/images/share.svg b/guh-control/ui/images/share.svg new file mode 100644 index 00000000..e6494ec5 --- /dev/null +++ b/guh-control/ui/images/share.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/stock_link.svg b/guh-control/ui/images/stock_link.svg new file mode 100644 index 00000000..a72dfc6b --- /dev/null +++ b/guh-control/ui/images/stock_link.svg @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/temperature.svg b/guh-control/ui/images/temperature.svg new file mode 100644 index 00000000..7762310f --- /dev/null +++ b/guh-control/ui/images/temperature.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/weathericons/weather-cloud-symbolic.svg b/guh-control/ui/images/torch-off.svg similarity index 72% rename from guh-control/ui/images/weathericons/weather-cloud-symbolic.svg rename to guh-control/ui/images/torch-off.svg index b8342eeb..239533b3 100644 --- a/guh-control/ui/images/weathericons/weather-cloud-symbolic.svg +++ b/guh-control/ui/images/torch-off.svg @@ -15,7 +15,7 @@ version="1.1" inkscape:version="0.91+devel r" viewBox="0 0 96 96.000001" - sodipodi:docname="weather-clouds-symbolic.svg"> + sodipodi:docname="torch-off.svg"> + - @@ -148,11 +148,19 @@ x="-438.00244" y="345.36221" transform="scale(-1,1)" /> + + id="path4721" /> diff --git a/guh-control/ui/images/weathericons/weather-few-clouds-symbolic.svg b/guh-control/ui/images/weather-app-symbolic.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-few-clouds-symbolic.svg rename to guh-control/ui/images/weather-app-symbolic.svg diff --git a/guh-control/ui/images/weathericons/humidity.svg b/guh-control/ui/images/weathericons/humidity.svg new file mode 100644 index 00000000..ff11c53d --- /dev/null +++ b/guh-control/ui/images/weathericons/humidity.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/guh-control/ui/images/weathericons/weather-sun-symbolic.svg b/guh-control/ui/images/weathericons/weather-clear-day.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-sun-symbolic.svg rename to guh-control/ui/images/weathericons/weather-clear-day.svg diff --git a/guh-control/ui/images/weathericons/weather-clear-night-symbolic.svg b/guh-control/ui/images/weathericons/weather-clear-night.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-clear-night-symbolic.svg rename to guh-control/ui/images/weathericons/weather-clear-night.svg diff --git a/guh-control/ui/images/weathericons/weather-cloud-night-symbolic.svg b/guh-control/ui/images/weathericons/weather-clouds.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-cloud-night-symbolic.svg rename to guh-control/ui/images/weathericons/weather-clouds.svg diff --git a/guh-control/ui/images/weathericons/weather-hazy-symbolic.svg b/guh-control/ui/images/weathericons/weather-few-clouds-day.svg similarity index 65% rename from guh-control/ui/images/weathericons/weather-hazy-symbolic.svg rename to guh-control/ui/images/weathericons/weather-few-clouds-day.svg index 5947472e..a8ca54dc 100644 --- a/guh-control/ui/images/weathericons/weather-hazy-symbolic.svg +++ b/guh-control/ui/images/weathericons/weather-few-clouds-day.svg @@ -15,7 +15,7 @@ version="1.1" inkscape:version="0.91+devel r" viewBox="0 0 96 96.000001" - sodipodi:docname="weather-hazy-symbolic.svg"> + sodipodi:docname="weather-few-clouds-symbolic.svg"> - + + + + - + id="rect4210" + width="4.0011816" + height="10.00099" + x="532.4411" + y="-187.76564" + transform="matrix(0.86611103,0.49985167,0.50014835,-0.86593974,0,0)" + inkscape:transform-center-y="18.499977" /> - + inkscape:transform-center-y="32.042932" + transform="matrix(0.50014835,0.86593974,0.86611103,-0.49985167,0,0)" + y="98.950249" + x="533.62909" + height="10.002968" + width="4.0003905" + id="rect4212" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + inkscape:transform-center-x="18.500021" /> diff --git a/guh-control/ui/images/weathericons/weather-few-clouds-night-symbolic.svg b/guh-control/ui/images/weathericons/weather-few-clouds-night.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-few-clouds-night-symbolic.svg rename to guh-control/ui/images/weathericons/weather-few-clouds-night.svg diff --git a/guh-control/ui/images/weathericons/weather-fog-symbolic.svg b/guh-control/ui/images/weathericons/weather-fog.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-fog-symbolic.svg rename to guh-control/ui/images/weathericons/weather-fog.svg diff --git a/guh-control/ui/images/weathericons/weather-scattered-symbolic.svg b/guh-control/ui/images/weathericons/weather-light-rain.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-scattered-symbolic.svg rename to guh-control/ui/images/weathericons/weather-light-rain.svg diff --git a/guh-control/ui/images/weathericons/weather-overcast-symbolic.svg b/guh-control/ui/images/weathericons/weather-overcast.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-overcast-symbolic.svg rename to guh-control/ui/images/weathericons/weather-overcast.svg diff --git a/guh-control/ui/images/weathericons/weather-rain-symbolic.svg b/guh-control/ui/images/weathericons/weather-shower-rain.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-rain-symbolic.svg rename to guh-control/ui/images/weathericons/weather-shower-rain.svg diff --git a/guh-control/ui/images/weathericons/weather-snow-symbolic.svg b/guh-control/ui/images/weathericons/weather-snow.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-snow-symbolic.svg rename to guh-control/ui/images/weathericons/weather-snow.svg diff --git a/guh-control/ui/images/weathericons/weather-thunder-symbolic.svg b/guh-control/ui/images/weathericons/weather-thunderstorm.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-thunder-symbolic.svg rename to guh-control/ui/images/weathericons/weather-thunderstorm.svg diff --git a/guh-control/ui/images/weathericons/weather-chance-of-wind-symbolic.svg b/guh-control/ui/images/weathericons/wind.svg similarity index 100% rename from guh-control/ui/images/weathericons/weather-chance-of-wind-symbolic.svg rename to guh-control/ui/images/weathericons/wind.svg diff --git a/guh-control/ui/magic/NewRulePage.qml b/guh-control/ui/magic/NewRulePage.qml index fc855805..da473cce 100644 --- a/guh-control/ui/magic/NewRulePage.qml +++ b/guh-control/ui/magic/NewRulePage.qml @@ -1,5 +1,5 @@ import QtQuick 2.8 -import QtQuick.Controls 2.2 +import QtQuick.Controls 2.1 import "../components" Page { diff --git a/guh-control/ui/magic/SelectActionPage.qml b/guh-control/ui/magic/SelectActionPage.qml index bbe7ef20..865c0c62 100644 --- a/guh-control/ui/magic/SelectActionPage.qml +++ b/guh-control/ui/magic/SelectActionPage.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts 1.1 import Guh 1.0 import "../components" import "../actiondelegates" +import "../paramdelegates" Page { id: root @@ -12,9 +13,7 @@ Page { property string text // output - property var device: null - property var actionType: null - property var params: [] + property var actions: [] signal complete(); header: GuhHeader { @@ -23,24 +22,41 @@ Page { } ColumnLayout { - anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins } - spacing: app.margins + anchors.fill: parent Label { - text: root.text Layout.fillWidth: true + Layout.margins: app.margins + text: root.text + font.pixelSize: app.largeFont } - Button { - text: "control a certain device" + ListView { Layout.fillWidth: true - onClicked: { - pageStack.push(selectDeviceComponent) + Layout.fillHeight: true + model: ListModel { + ListElement { name: "switchLights"; text: "Switch lights..." } + ListElement { name: "controlMedia"; text: "Control media playback..." } + ListElement { name: "manualAction"; text: "Manually configure an action..." } + } + delegate: ItemDelegate { + width: parent.width + text: model.text + onClicked: { + switch (model.name) { + case "switchLights": + pageStack.push(switchLightsCompoent) + break; + case "controlMedia": + break; + case "muteMedia": + break; + case "manualAction": + pageStack.push(selectDeviceComponent) + break; + } + } } - } - Button { - text: "control a group of devices" - Layout.fillWidth: true } } @@ -68,9 +84,8 @@ Page { verticalAlignment: Text.AlignVCenter } onClicked: { - root.device = Engine.deviceManager.devices.get(index) - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(model.deviceClassId) - pageStack.push(selectDeviceActionComponent, {deviceClass: deviceClass}) + var device = Engine.deviceManager.devices.get(index) + pageStack.push(selectDeviceActionComponent, {device: device}) } } } @@ -82,7 +97,8 @@ Page { id: selectDeviceActionComponent Page { id: page - property var deviceClass + property var device + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) header: GuhHeader { text: "Select action" @@ -106,14 +122,17 @@ Page { } onClicked: { - root.actionType = page.deviceClass.actionTypes.get(index) + var actionType = page.deviceClass.actionTypes.get(index) if (page.deviceClass.actionTypes.get(index).paramTypes.count == 0) { // We're all set. + var action = {} + action["deviceId"] = page.device.id + action["actionTypeId"] = actionType.id + root.actions.push(action) root.complete(); } else { // need to fill in params - var actionType = page.deviceClass.actionTypes.get(index) - pageStack.push(selectDeviceActionParamComponent, {actionType: actionType}) + pageStack.push(selectDeviceActionParamComponent, {device: page.device, actionType: actionType}) } } } @@ -126,6 +145,7 @@ Page { id: selectDeviceActionParamComponent Page { id: page + property var device property var actionType header: GuhHeader { text: "params" @@ -137,22 +157,10 @@ Page { Repeater { id: delegateRepeater model: page.actionType.paramTypes - ItemDelegate { - id: paramDelegate - Layout.fillWidth: true - property var paramType: page.actionType.paramTypes.get(index) - property var value: paramType.defaultValue - RowLayout { - anchors.fill: parent - Label { - Layout.fillWidth: true - text: paramDelegate.paramType.name - } - Switch { - checked: paramDelegate.value - onClicked: paramDelegate.value = checked - } - } + delegate: ParamDelegate { + paramType: page.actionType.paramTypes.get(index) + value: paramType.defaultValue + } } Item { @@ -164,17 +172,102 @@ Page { Layout.fillWidth: true Layout.margins: app.margins onClicked: { + var params = []; for (var i = 0; i < delegateRepeater.count; i++) { var paramDelegate = delegateRepeater.itemAt(i); var param = {} param["paramTypeId"] = paramDelegate.paramType.id param["value"] = paramDelegate.value - root.params.push(param) + params.push(param) } + var action = {}; + action["deviceId"] = page.device.id + action["actionTypeId"] = page.actionType.id + action["ruleActionParams"] = params + root.actions.push(action) root.complete() } } } } } + + Component { + id: switchLightsCompoent + Page { + header: GuhHeader { + text: "Switch lights" + onBackPressed: pageStack.pop() + } + + ColumnLayout { + anchors.fill: parent + + SwitchDelegate { + id: switchDelegate + Layout.fillWidth: true + text: "Set selected lights power to" + position: 0 + } + ThinDivider {} + + Flickable { + Layout.fillHeight: true + Layout.fillWidth: true + interactive: contentHeight > height + clip: true + + Column { + width: parent.width + + Repeater { + id: lightsRepeater + + model: DevicesProxy { + id: lightsModel + devices: Engine.deviceManager.devices + filterInterface: "light" + } + delegate: CheckDelegate { + width: parent.width + text: model.name + } + } + } + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: "OK" + onClicked: { + for (var i = 0; i < lightsRepeater.count; i++) { + if (lightsRepeater.itemAt(i).checkState === Qt.Unchecked) { + continue; + } + var device = lightsModel.get(i); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + + var action = {} + action["deviceId"] = device.id + + var actionType = deviceClass.actionTypes.findByName("power") + action["actionTypeId"] = actionType.id + + var params = []; + var paramType = actionType.paramTypes.getParamType("power"); + var param = {} + param["paramTypeId"] = paramType.id + param["value"] = switchDelegate.position === 1 ? true : false; + params.push(param) + + action["ruleActionParams"] = params + root.actions.push(action) + } + root.complete(); + } + } + } + } + } } diff --git a/guh-control/ui/main.qml b/guh-control/ui/main.qml index 20a0836c..ed872f4b 100644 --- a/guh-control/ui/main.qml +++ b/guh-control/ui/main.qml @@ -1,6 +1,7 @@ -import QtQuick 2.5 +import QtQuick 2.8 import QtQuick.Controls 2.1 import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.2 import Qt.labs.settings 1.0 import Guh 1.0 @@ -9,15 +10,18 @@ ApplicationWindow { visible: true width: 270 * 1.5 height: 480 * 1.5 + visibility: settings.fullscreen ? ApplicationWindow.FullScreen : ApplicationWindow.Windowed + property color guhAccent: "#ff57baae" // Material.primary: "#ff57baae" Material.primary: "white" Material.accent: guhAccent - property int margins: 10 + property int margins: 14 property int bigMargins: 20 - property int smallFont: 10 + property int smallFont: 14 + property int mediumFont: 16 property int largeFont: 20 property int iconSize: 30 property int delegateHeight: 60 @@ -25,6 +29,8 @@ ApplicationWindow { Settings { id: settings property string lastConnectedHost: "" + property bool fullscreen: false + property bool returnToHome: false } Component.onCompleted: { @@ -49,6 +55,14 @@ ApplicationWindow { print("setup required changed") init(); } + + onInvalidProtocolVersion: { + var popup = invalidVersionComponent.createObject(app.contentItem); + popup.actualVersion = actualVersion; + popup.minimumVersion = minimumVersion + popup.open() + settings.lastConnectedHost = "" + } } function init() { @@ -76,6 +90,17 @@ ApplicationWindow { id: discovery } + Connections { + target: Qt.application + enabled: Engine.jsonRpcClient.connected && settings.returnToHome + onStateChanged: { + print("App active state changed:", state) + if (state !== Qt.ApplicationActive) { + init(); + } + } + } + function interfaceToString(name) { switch(name) { case "light": @@ -83,11 +108,15 @@ ApplicationWindow { case "weather": return "Weather" case "sensor": - return "Sensor" + return "Sensors" case "media": return "Media" case "button": return "Switches" + case "gateway": + return "Gateways" + case "notifications": + return "Notifications" } } @@ -95,14 +124,75 @@ ApplicationWindow { switch (name) { case "light": return Qt.resolvedUrl("images/torch-on.svg") + case "sensor": + return Qt.resolvedUrl("images/sensors.svg") case "media": return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg") case "button": return Qt.resolvedUrl("images/system-shutdown.svg") + case "weather": + return Qt.resolvedUrl("images/weather-app-symbolic.svg") + case "temperaturesensor": + return Qt.resolvedUrl("images/temperature.svg") + case "humiditysensor": + return Qt.resolvedUrl("images/weathericons/humidity.svg") + case "gateway": + return Qt.resolvedUrl("images/network-wired-symbolic.svg") + case "notifications": + return Qt.resolvedUrl("images/notification.svg") } } + function interfaceToColor(name) { + switch (name) { + case "temperaturesensor": + return "red"; + case "humiditysensor": + return "deepskyblue"; + } + return "grey"; + } + // ZeroconfDiscovery { // id: discovery // } + + Component { + id: invalidVersionComponent + Popup { + id: popup + + property string actualVersion: "0.0" + property string minimumVersion: "1.0" + + width: app.width * .8 + height: col.childrenRect.height + app.margins * 2 + x: (app.width - width) / 2 + y: (app.height - height) / 2 + visible: false + ColumnLayout { + id: col + anchors { left: parent.left; right: parent.right } + spacing: app.margins + Label { + text: "Connection error" + Layout.fillWidth: true + font.pixelSize: app.largeFont + } + Label { + text: qsTr("Sorry, the version of the guh box you are trying to connect to is too old. This app requires at least version %1 but the guh box only supports %2").arg(popup.minimumVersion).arg(popup.actualVersion) + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + Button { + Layout.fillWidth: true + text: "OK" + onClicked: { + Engine.connection.disconnect(); + popup.close() + } + } + } + } + } } diff --git a/guh-control/ui/paramdelegates/BoolParamDelegate.qml b/guh-control/ui/paramdelegates/BoolParamDelegate.qml new file mode 100644 index 00000000..e11b0dab --- /dev/null +++ b/guh-control/ui/paramdelegates/BoolParamDelegate.qml @@ -0,0 +1,20 @@ +import QtQuick 2.8 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 + +RowLayout { + id: root + + property alias text: label.text + property alias value: theSwitch.checked + + Label { + id: label + Layout.fillWidth: true + } + Switch { + id: theSwitch + checked: root.value === true + onClicked: root.value = checked + } +} diff --git a/guh-control/ui/paramdelegates/IntParamDelegate.qml b/guh-control/ui/paramdelegates/IntParamDelegate.qml new file mode 100644 index 00000000..cf4cb908 --- /dev/null +++ b/guh-control/ui/paramdelegates/IntParamDelegate.qml @@ -0,0 +1,25 @@ +import QtQuick 2.8 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 + +ParamDelegateBase { + id: root + + contentItem: RowLayout { + width: parent.width + + Label { + id: label + text: root.paramType.name + } + TextField { + id: textField + Layout.fillWidth: true + text: root.value ? root.value : root.paramType.defaultValue + onTextChanged: { + root.value = text; + } + + } + } +} diff --git a/guh-control/ui/paramdelegates/ParamDelegate.qml b/guh-control/ui/paramdelegates/ParamDelegate.qml new file mode 100644 index 00000000..c142aa39 --- /dev/null +++ b/guh-control/ui/paramdelegates/ParamDelegate.qml @@ -0,0 +1,46 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.1 + +Loader { + id: loader + + property var paramType: null + property var value: null + source: { + var comp; + switch (loader.paramType.type) { + case "bool": + comp = "Bool"; + break; + case "String": + comp = "String"; + break; + case "Int": + comp = "Int"; + break; + default: + print("unhandled param type:", paramType.type) + } + return Qt.resolvedUrl(comp + "ParamDelegate.qml") + } + + onStatusChanged: { + if (status == Loader.Ready) { + loader.item.value = root.value + } + } + + Binding { + target: loader.item + when: loader.item + property: "paramType" + value: loader.paramType + } + + Binding { + target: loader + when: loader.item + property: "value" + value: loader.item.value + } +} diff --git a/guh-control/ui/paramdelegates/ParamDelegateBase.qml b/guh-control/ui/paramdelegates/ParamDelegateBase.qml new file mode 100644 index 00000000..e3242a71 --- /dev/null +++ b/guh-control/ui/paramdelegates/ParamDelegateBase.qml @@ -0,0 +1,9 @@ +import QtQuick 2.4 +import QtQuick.Controls 2.1 + +ItemDelegate { + + property var paramType: null + property var value: null + +} diff --git a/guh-control/ui/paramdelegates/StringParamDelegate.qml b/guh-control/ui/paramdelegates/StringParamDelegate.qml new file mode 100644 index 00000000..43b745b0 --- /dev/null +++ b/guh-control/ui/paramdelegates/StringParamDelegate.qml @@ -0,0 +1,24 @@ +import QtQuick 2.8 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 + +ParamDelegateBase { + id: root + + contentItem: RowLayout { + width: parent.width + + Label { + id: label + text: root.paramType.name + } + TextField { + id: textField + Layout.fillWidth: true + text: root.value ? root.value : root.paramType.defaultValue + onTextChanged: { + root.value = text; + } + } + } +} diff --git a/libguh-common/libguh-common.pro b/libguh-common/libguh-common.pro index c7cc2283..60dbf1cd 100644 --- a/libguh-common/libguh-common.pro +++ b/libguh-common/libguh-common.pro @@ -36,7 +36,8 @@ HEADERS += types/types.h \ types/ruleaction.h \ types/ruleactions.h \ types/ruleactionparams.h \ - types/ruleactionparam.h + types/ruleactionparam.h \ + types/logentry.h SOURCES += types/vendor.cpp \ types/vendors.cpp \ @@ -64,7 +65,8 @@ SOURCES += types/vendor.cpp \ types/ruleaction.cpp \ types/ruleactions.cpp \ types/ruleactionparams.cpp \ - types/ruleactionparam.cpp + types/ruleactionparam.cpp \ + types/logentry.cpp # install header file with relative subdirectory for(header, HEADERS) { diff --git a/libguh-common/types/device.cpp b/libguh-common/types/device.cpp index 664f72fe..abab10fa 100644 --- a/libguh-common/types/device.cpp +++ b/libguh-common/types/device.cpp @@ -22,6 +22,8 @@ #include "device.h" +#include + Device::Device(QObject *parent) : QObject(parent), m_statesProxy(new StatesProxy(this)) diff --git a/libguh-common/types/deviceclass.cpp b/libguh-common/types/deviceclass.cpp index 06826704..d1251a17 100644 --- a/libguh-common/types/deviceclass.cpp +++ b/libguh-common/types/deviceclass.cpp @@ -70,6 +70,16 @@ void DeviceClass::setName(const QString &name) m_name = name; } +QString DeviceClass::displayName() const +{ + return m_displayName; +} + +void DeviceClass::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} + QStringList DeviceClass::createMethods() const { return m_createMethods; diff --git a/libguh-common/types/deviceclass.h b/libguh-common/types/deviceclass.h index 1462f90c..c9a0a43a 100644 --- a/libguh-common/types/deviceclass.h +++ b/libguh-common/types/deviceclass.h @@ -38,6 +38,7 @@ class DeviceClass : public QObject Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(QUuid id READ id CONSTANT) Q_PROPERTY(QUuid vendorId READ vendorId CONSTANT) Q_PROPERTY(QStringList createMethods READ createMethods CONSTANT) @@ -90,6 +91,9 @@ public: QString name() const; void setName(const QString &name); + QString displayName() const; + void setDisplayName(const QString &displayName); + QUuid id() const; void setId(const QUuid &id); @@ -136,6 +140,7 @@ private: QUuid m_vendorId; QUuid m_pluginId; QString m_name; + QString m_displayName; QStringList m_createMethods; SetupMethod m_setupMethod; QList m_basicTags; diff --git a/libguh-common/types/logentry.cpp b/libguh-common/types/logentry.cpp new file mode 100644 index 00000000..b486a464 --- /dev/null +++ b/libguh-common/types/logentry.cpp @@ -0,0 +1,52 @@ +#include "logentry.h" + +#include + +LogEntry::LogEntry(const QDateTime ×tamp, const QVariant &value, QObject *parent): + QObject(parent), + m_value(value), + m_timeStamp(timestamp) +{ + +} + +QVariant LogEntry::value() const +{ + return m_value; +} + +QDateTime LogEntry::timestamp() const +{ + return m_timeStamp; +} + +QString LogEntry::timeString() const +{ + return m_timeStamp.time().toString("hh:mm"); +} + +QString LogEntry::dayString() const +{ + switch (m_timeStamp.date().dayOfWeek()) { + case 1: + return "Mo"; + case 2: + return "Tu"; + case 3: + return "We"; + case 4: + return "Th"; + case 5: + return "Fr"; + case 6: + return "Sa"; + case 7: + return "Su"; + } + return "--"; +} + +QString LogEntry::dateString() const +{ + return m_timeStamp.date().toString("dd.MM."); +} diff --git a/libguh-common/types/logentry.h b/libguh-common/types/logentry.h new file mode 100644 index 00000000..4d2f807c --- /dev/null +++ b/libguh-common/types/logentry.h @@ -0,0 +1,32 @@ +#ifndef LOGENTRY_H +#define LOGENTRY_H + +#include +#include +#include + +class LogEntry : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariant value READ value CONSTANT) + + Q_PROPERTY(QString timeString READ timeString CONSTANT) + Q_PROPERTY(QString dayString READ dayString CONSTANT) + Q_PROPERTY(QString dateString READ dateString CONSTANT) + +public: + explicit LogEntry(const QDateTime ×tamp, const QVariant &value, QObject *parent = nullptr); + + QVariant value() const; + QDateTime timestamp() const; + + QString timeString() const; + QString dayString() const; + QString dateString() const; + +private: + QVariant m_value; + QDateTime m_timeStamp; +}; + +#endif // LOGENTRY_H diff --git a/libguh-common/types/state.cpp b/libguh-common/types/state.cpp index 71052bcf..f088ebc0 100644 --- a/libguh-common/types/state.cpp +++ b/libguh-common/types/state.cpp @@ -22,6 +22,8 @@ #include "state.h" +#include + State::State(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value, QObject *parent) : QObject(parent), m_deviceId(deviceId), @@ -42,6 +44,7 @@ QUuid State::stateTypeId() const QVariant State::value() const { + qDebug() << "returning value:" << m_value; return m_value; } diff --git a/libguh-common/types/statetype.cpp b/libguh-common/types/statetype.cpp index e3b38047..da33f16a 100644 --- a/libguh-common/types/statetype.cpp +++ b/libguh-common/types/statetype.cpp @@ -47,6 +47,16 @@ void StateType::setName(const QString &name) m_name = name; } +QString StateType::displayName() const +{ + return m_displayName; +} + +void StateType::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} + QString StateType::type() const { return m_type; diff --git a/libguh-common/types/statetype.h b/libguh-common/types/statetype.h index 3958fd05..abaf2178 100644 --- a/libguh-common/types/statetype.h +++ b/libguh-common/types/statetype.h @@ -34,6 +34,7 @@ class StateType : public QObject Q_OBJECT Q_PROPERTY(QUuid id READ id CONSTANT) Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(QString type READ type CONSTANT) Q_PROPERTY(int index READ index CONSTANT) Q_PROPERTY(QVariant defaultValue READ defaultValue CONSTANT) @@ -49,6 +50,9 @@ public: QString name() const; void setName(const QString &name); + QString displayName() const; + void setDisplayName(const QString &displayName); + QString type() const; void setType(const QString &type); @@ -67,6 +71,7 @@ public: private: QUuid m_id; QString m_name; + QString m_displayName; QString m_type; int m_index; QVariant m_defaultValue; diff --git a/libguh-common/types/statetypes.cpp b/libguh-common/types/statetypes.cpp index e7cd1ff9..3de0ad5b 100644 --- a/libguh-common/types/statetypes.cpp +++ b/libguh-common/types/statetypes.cpp @@ -66,17 +66,20 @@ QVariant StateTypes::data(const QModelIndex &index, int role) const return QVariant(); StateType *stateType = m_stateTypes.at(index.row()); - if (role == NameRole) { - return stateType->name(); - } else if (role == IdRole) { + switch (role) { + case RoleId: return stateType->id().toString(); - } else if (role == TypeRole) { + case RoleName: + return stateType->name(); + case RoleDisplayName: + return stateType->displayName(); + case RoleType: return stateType->type(); - } else if (role == DefaultValueRole) { + case RoleDefaultValue: return stateType->defaultValue(); - } else if (role == UnitStringRole) { + case RoleUnitString: return stateType->unitString(); - } else if (role == UnitRole) { + case RoleUnit: return stateType->unit(); } return QVariant(); @@ -112,12 +115,13 @@ void StateTypes::clearModel() QHash StateTypes::roleNames() const { QHash roles; - roles[NameRole] = "name"; - roles[IdRole] = "id"; - roles[TypeRole] = "type"; - roles[DefaultValueRole] = "defaultValue"; - roles[UnitStringRole] = "unitString"; - roles[UnitRole] = "unit"; + roles[RoleId] = "id"; + roles[RoleName] = "name"; + roles[RoleDisplayName] = "displayName"; + roles[RoleType] = "type"; + roles[RoleDefaultValue] = "defaultValue"; + roles[RoleUnitString] = "unitString"; + roles[RoleUnit] = "unit"; return roles; } diff --git a/libguh-common/types/statetypes.h b/libguh-common/types/statetypes.h index 45915bae..6120d864 100644 --- a/libguh-common/types/statetypes.h +++ b/libguh-common/types/statetypes.h @@ -33,13 +33,14 @@ class StateTypes : public QAbstractListModel Q_OBJECT public: - enum StateTypeRole { - NameRole = Qt::DisplayRole, - IdRole, - TypeRole, - DefaultValueRole, - UnitRole, - UnitStringRole + enum Role { + RoleId, + RoleName, + RoleDisplayName, + RoleType, + RoleDefaultValue, + RoleUnit, + RoleUnitString }; StateTypes(QObject *parent = 0); diff --git a/libguh-common/types/vendor.cpp b/libguh-common/types/vendor.cpp index 5584dd67..378aa782 100644 --- a/libguh-common/types/vendor.cpp +++ b/libguh-common/types/vendor.cpp @@ -48,3 +48,12 @@ void Vendor::setName(const QString &name) { m_name = name; } + +QString Vendor::displayName() const +{ + return m_displayName; +} +void Vendor::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} diff --git a/libguh-common/types/vendor.h b/libguh-common/types/vendor.h index c8e02645..4730ab69 100644 --- a/libguh-common/types/vendor.h +++ b/libguh-common/types/vendor.h @@ -31,6 +31,7 @@ class Vendor : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString displayName READ displayName) Q_PROPERTY(QUuid id READ id CONSTANT) public: @@ -42,9 +43,13 @@ public: QString name() const; void setName(const QString &name); + QString displayName() const; + void setDisplayName(const QString &displayName); + private: QUuid m_id; QString m_name; + QString m_displayName; }; #endif // VENDOR_H diff --git a/libguh-common/types/vendors.cpp b/libguh-common/types/vendors.cpp index babee157..0eb291dd 100644 --- a/libguh-common/types/vendors.cpp +++ b/libguh-common/types/vendors.cpp @@ -61,9 +61,12 @@ QVariant Vendors::data(const QModelIndex &index, int role) const return QVariant(); Vendor *vendor = m_vendors.at(index.row()); - if (role == NameRole) { + switch (role) { + case RoleName: return vendor->name(); - } else if (role == IdRole) { + case RoleDisplayName: + return vendor->displayName(); + case RoleId: return vendor->id().toString(); } return QVariant(); @@ -89,7 +92,8 @@ void Vendors::clearModel() QHash Vendors::roleNames() const { QHash roles; - roles[NameRole] = "name"; - roles[IdRole] = "id"; + roles[RoleId] = "id"; + roles[RoleName] = "name"; + roles[RoleDisplayName] = "displayName"; return roles; } diff --git a/libguh-common/types/vendors.h b/libguh-common/types/vendors.h index 3613b069..13d5fadc 100644 --- a/libguh-common/types/vendors.h +++ b/libguh-common/types/vendors.h @@ -31,9 +31,10 @@ class Vendors : public QAbstractListModel { Q_OBJECT public: - enum VendorRole { - NameRole = Qt::DisplayRole, - IdRole + enum Role { + RoleId, + RoleName, + RoleDisplayName }; explicit Vendors(QObject *parent = 0);