rework the generic device page
This commit is contained in:
parent
eaaf7300cd
commit
f1d29034b0
@ -239,7 +239,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms)
|
||||
value.convert(QVariant::Int);
|
||||
}
|
||||
device->setStateValue(stateTypeId, value);
|
||||
qDebug() << "Set device state value:" << device->stateValue(stateTypeId) << value;
|
||||
// qDebug() << "Set device state value:" << device->stateValue(stateTypeId) << value;
|
||||
}
|
||||
devices()->addDevice(device);
|
||||
}
|
||||
@ -297,7 +297,7 @@ void DeviceManager::editDeviceResponse(const QVariantMap ¶ms)
|
||||
void DeviceManager::executeActionResponse(const QVariantMap ¶ms)
|
||||
{
|
||||
qDebug() << "Execute Action response" << params;
|
||||
emit executeActionReply(params.value("params").toMap());
|
||||
emit executeActionReply(params);
|
||||
}
|
||||
|
||||
void DeviceManager::savePluginConfig(const QUuid &pluginId)
|
||||
@ -366,7 +366,7 @@ void DeviceManager::editDevice(const QUuid &deviceId, const QString &name)
|
||||
m_jsonClient->sendCommand("Devices.EditDevice", params, this, "editDeviceResponse");
|
||||
}
|
||||
|
||||
void DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms)
|
||||
int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms)
|
||||
{
|
||||
qDebug() << "JsonRpc: execute action " << deviceId.toString() << actionTypeId.toString() << params;
|
||||
QVariantMap p;
|
||||
@ -377,5 +377,5 @@ void DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionType
|
||||
}
|
||||
|
||||
qDebug() << "Params:" << p;
|
||||
m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
|
||||
return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ public:
|
||||
Q_INVOKABLE void confirmPairing(const QUuid &pairingTransactionId, const QString &secret = QString());
|
||||
Q_INVOKABLE void removeDevice(const QUuid &deviceId, RemovePolicy policy = RemovePolicyNone);
|
||||
Q_INVOKABLE void editDevice(const QUuid &deviceId, const QString &name);
|
||||
Q_INVOKABLE void executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms = QVariantList());
|
||||
Q_INVOKABLE int executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms = QVariantList());
|
||||
|
||||
private:
|
||||
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
|
||||
@ -98,6 +98,7 @@ signals:
|
||||
void editDeviceReply(const QVariantMap ¶ms);
|
||||
void executeActionReply(const QVariantMap ¶ms);
|
||||
void fetchingDataChanged();
|
||||
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms);
|
||||
|
||||
private:
|
||||
Vendors *m_vendors;
|
||||
|
||||
@ -91,7 +91,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
||||
version = txtRecord.second;
|
||||
}
|
||||
}
|
||||
// qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
|
||||
qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
|
||||
|
||||
|
||||
DiscoveryDevice* device = m_discoveryModel->find(uuid);
|
||||
|
||||
@ -56,15 +56,15 @@ void JsonRpcClient::registerNotificationHandler(JsonHandler *handler, const QStr
|
||||
m_notificationHandlers.insert(handler->nameSpace(), qMakePair<JsonHandler*, QString>(handler, method));
|
||||
}
|
||||
|
||||
void JsonRpcClient::sendCommand(const QString &method, const QVariantMap ¶ms, QObject *caller, const QString &callbackMethod)
|
||||
int JsonRpcClient::sendCommand(const QString &method, const QVariantMap ¶ms, QObject *caller, const QString &callbackMethod)
|
||||
{
|
||||
JsonRpcReply *reply = createReply(method, params, caller, callbackMethod);
|
||||
m_replies.insert(reply->commandId(), reply);
|
||||
sendRequest(reply->requestMap());
|
||||
|
||||
return reply->commandId();
|
||||
}
|
||||
|
||||
void JsonRpcClient::sendCommand(const QString &method, QObject *caller, const QString &callbackMethod)
|
||||
int JsonRpcClient::sendCommand(const QString &method, QObject *caller, const QString &callbackMethod)
|
||||
{
|
||||
return sendCommand(method, QVariantMap(), caller, callbackMethod);
|
||||
}
|
||||
|
||||
@ -60,8 +60,8 @@ public:
|
||||
|
||||
void registerNotificationHandler(JsonHandler *handler, const QString &method);
|
||||
|
||||
void sendCommand(const QString &method, const QVariantMap ¶ms, QObject *caller = nullptr, const QString &callbackMethod = QString());
|
||||
void sendCommand(const QString &method, QObject *caller = nullptr, const QString &callbackMethod = QString());
|
||||
int sendCommand(const QString &method, const QVariantMap ¶ms, QObject *caller = nullptr, const QString &callbackMethod = QString());
|
||||
int sendCommand(const QString &method, QObject *caller = nullptr, const QString &callbackMethod = QString());
|
||||
|
||||
void setConnection(NymeaConnection *connection);
|
||||
bool connected() const;
|
||||
|
||||
@ -55,6 +55,7 @@
|
||||
#include "ruletemplates/ruleactiontemplate.h"
|
||||
#include "ruletemplates/ruleactionparamtemplate.h"
|
||||
#include "connection/awsclient.h"
|
||||
#include "models/devicemodel.h"
|
||||
|
||||
#include <QtQml/qqml.h>
|
||||
|
||||
@ -114,6 +115,8 @@ void registerQmlTypes() {
|
||||
qmlRegisterType<DeviceClassesProxy>(uri, 1, 0, "DeviceClassesProxy");
|
||||
qmlRegisterType<DeviceDiscovery>(uri, 1, 0, "DeviceDiscovery");
|
||||
|
||||
qmlRegisterType<DeviceModel>(uri, 1, 0, "DeviceModel");
|
||||
|
||||
qmlRegisterUncreatableType<RuleManager>(uri, 1, 0, "RuleManager", "Get it from the Engine");
|
||||
qmlRegisterUncreatableType<Rules>(uri, 1, 0, "Rules", "Get it from RuleManager");
|
||||
qmlRegisterUncreatableType<Rule>(uri, 1, 0, "Rule", "Get it from Rules");
|
||||
|
||||
@ -83,7 +83,8 @@ SOURCES += \
|
||||
configuration/nymeaconfiguration.cpp \
|
||||
models/mqttpolicies.cpp \
|
||||
configuration/mqttpolicy.cpp \
|
||||
configuration/mqttpolicies.cpp
|
||||
configuration/mqttpolicies.cpp \
|
||||
models/devicemodel.cpp
|
||||
|
||||
HEADERS += \
|
||||
engine.h \
|
||||
@ -145,7 +146,8 @@ HEADERS += \
|
||||
configuration/serverconfigurations.h \
|
||||
configuration/nymeaconfiguration.h \
|
||||
configuration/mqttpolicy.h \
|
||||
configuration/mqttpolicies.h
|
||||
configuration/mqttpolicies.h \
|
||||
models/devicemodel.h
|
||||
|
||||
unix {
|
||||
target.path = /usr/lib
|
||||
|
||||
164
libnymea-app-core/models/devicemodel.cpp
Normal file
164
libnymea-app-core/models/devicemodel.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#include "devicemodel.h"
|
||||
|
||||
#include "types/statetype.h"
|
||||
|
||||
DeviceModel::DeviceModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int DeviceModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_list.count();
|
||||
}
|
||||
|
||||
QVariant DeviceModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == RoleId) {
|
||||
return m_list.at(index.row());
|
||||
}
|
||||
if (role == RoleType) {
|
||||
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||
if (stateType) {
|
||||
return TypeStateType;
|
||||
}
|
||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
if (actionType) {
|
||||
return TypeActionType;
|
||||
}
|
||||
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
||||
if (eventType) {
|
||||
return TypeEventType;
|
||||
}
|
||||
}
|
||||
if (role == RoleDisplayName) {
|
||||
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||
if (stateType) {
|
||||
return stateType->displayName();
|
||||
}
|
||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
if (actionType) {
|
||||
return actionType->displayName();
|
||||
}
|
||||
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
||||
if (eventType) {
|
||||
return eventType->displayName();
|
||||
}
|
||||
}
|
||||
if (role == RoleWritable) {
|
||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
return actionType != nullptr;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> DeviceModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles.insert(RoleId, "id");
|
||||
roles.insert(RoleType, "type");
|
||||
roles.insert(RoleDisplayName, "displayName");
|
||||
roles.insert(RoleWritable, "writable");
|
||||
return roles;
|
||||
}
|
||||
|
||||
QVariant DeviceModel::getData(int index, int role) const
|
||||
{
|
||||
if (index < 0 || index >= m_list.count()) {
|
||||
return QVariant();
|
||||
}
|
||||
return data(this->index(index), role);
|
||||
}
|
||||
|
||||
Device *DeviceModel::device() const
|
||||
{
|
||||
return m_device;
|
||||
}
|
||||
|
||||
void DeviceModel::setDevice(Device *device)
|
||||
{
|
||||
if (m_device != device) {
|
||||
m_device = device;
|
||||
emit deviceChanged();
|
||||
updateList();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceModel::showStates() const
|
||||
{
|
||||
return m_showStates;
|
||||
}
|
||||
|
||||
void DeviceModel::setShowStates(bool showStates)
|
||||
{
|
||||
if (m_showStates != showStates) {
|
||||
m_showStates = showStates;
|
||||
emit showStatesChanged();
|
||||
updateList();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceModel::showActions() const
|
||||
{
|
||||
return m_showActions;
|
||||
}
|
||||
|
||||
void DeviceModel::setShowActions(bool showActions)
|
||||
{
|
||||
if (m_showActions != showActions) {
|
||||
m_showActions = showActions;
|
||||
emit showActionsChanged();
|
||||
updateList();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceModel::showEvents() const
|
||||
{
|
||||
return m_showEvents;
|
||||
}
|
||||
|
||||
void DeviceModel::setShowEvents(bool showEvents)
|
||||
{
|
||||
if (m_showEvents != showEvents) {
|
||||
m_showEvents = showEvents;
|
||||
emit showEventsChanged();
|
||||
updateList();
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceModel::updateList()
|
||||
{
|
||||
if (!m_device) {
|
||||
beginResetModel();
|
||||
m_list.clear();
|
||||
endResetModel();
|
||||
emit countChanged();
|
||||
return;
|
||||
}
|
||||
beginResetModel();
|
||||
m_list.clear();
|
||||
if (m_showStates) {
|
||||
for (int i = 0; i < m_device->deviceClass()->stateTypes()->rowCount(); i++) {
|
||||
m_list.append(m_device->deviceClass()->stateTypes()->get(i)->id());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_showActions) {
|
||||
for (int i = 0; i < m_device->deviceClass()->actionTypes()->rowCount(); i++) {
|
||||
if (!m_list.contains(m_device->deviceClass()->actionTypes()->get(i)->id())) {
|
||||
m_list.append(m_device->deviceClass()->actionTypes()->get(i)->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_showEvents) {
|
||||
for (int i = 0; i < m_device->deviceClass()->eventTypes()->rowCount(); i++) {
|
||||
if (!m_list.contains(m_device->deviceClass()->eventTypes()->get(i)->id())) {
|
||||
m_list.append(m_device->deviceClass()->eventTypes()->get(i)->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
emit countChanged();
|
||||
}
|
||||
77
libnymea-app-core/models/devicemodel.h
Normal file
77
libnymea-app-core/models/devicemodel.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef DEVICEMODEL_H
|
||||
#define DEVICEMODEL_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "types/device.h"
|
||||
#include "types/deviceclass.h"
|
||||
|
||||
class DeviceModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
Q_PROPERTY(Device* device READ device WRITE setDevice NOTIFY deviceChanged)
|
||||
|
||||
Q_PROPERTY(bool showStates READ showStates WRITE setShowStates NOTIFY showStatesChanged)
|
||||
Q_PROPERTY(bool showActions READ showActions WRITE setShowActions NOTIFY showActionsChanged)
|
||||
Q_PROPERTY(bool showEvents READ showEvents WRITE setShowEvents NOTIFY showEventsChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
RoleId,
|
||||
RoleType,
|
||||
RoleDisplayName,
|
||||
RoleWritable
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
enum Type {
|
||||
TypeStateType,
|
||||
TypeActionType,
|
||||
TypeEventType
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
explicit DeviceModel(QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
Q_INVOKABLE QVariant getData(int index, int role) const;
|
||||
|
||||
Device* device() const;
|
||||
void setDevice(Device *device);
|
||||
|
||||
bool showStates() const;
|
||||
void setShowStates(bool showStates);
|
||||
|
||||
bool showActions() const;
|
||||
void setShowActions(bool showActions);
|
||||
|
||||
bool showEvents() const;
|
||||
void setShowEvents(bool showEvents);
|
||||
|
||||
signals:
|
||||
void deviceChanged();
|
||||
|
||||
void countChanged();
|
||||
|
||||
bool showStatesChanged();
|
||||
bool showActionsChanged();
|
||||
bool showEventsChanged();
|
||||
|
||||
private:
|
||||
void updateList();
|
||||
|
||||
private:
|
||||
Device *m_device = nullptr;
|
||||
|
||||
bool m_showStates = true;
|
||||
bool m_showActions = true;
|
||||
bool m_showEvents = true;
|
||||
|
||||
QList<QString> m_list;
|
||||
};
|
||||
|
||||
#endif // DEVICEMODEL_H
|
||||
@ -104,6 +104,11 @@ void LogsModelNg::setTypeIds(const QStringList &typeIds)
|
||||
if (m_typeIds != typeIds) {
|
||||
m_typeIds = typeIds;
|
||||
emit typeIdsChanged();
|
||||
beginResetModel();
|
||||
qDeleteAll(m_list);
|
||||
m_list.clear();
|
||||
endResetModel();
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ class State : public QObject
|
||||
Q_PROPERTY(QVariant value READ value NOTIFY valueChanged)
|
||||
|
||||
public:
|
||||
explicit State(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value, QObject *parent = 0);
|
||||
explicit State(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value, QObject *parent = nullptr);
|
||||
|
||||
QUuid deviceId() const;
|
||||
QUuid stateTypeId() const;
|
||||
|
||||
@ -36,7 +36,9 @@ QList<StateType *> StateTypes::stateTypes()
|
||||
|
||||
StateType *StateTypes::get(int index) const
|
||||
{
|
||||
qDebug() << "returning" << m_stateTypes.at(index);
|
||||
if (index < 0 || index >= m_stateTypes.count()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_stateTypes.at(index);
|
||||
}
|
||||
|
||||
|
||||
@ -60,7 +60,6 @@
|
||||
<file>ui/customviews/ExtendedVolumeController.qml</file>
|
||||
<file>ui/devicepages/MediaDevicePage.qml</file>
|
||||
<file>ui/devicepages/ButtonDevicePage.qml</file>
|
||||
<file>ui/devicepages/GenericDeviceStateDetailsPage.qml</file>
|
||||
<file>ui/devicepages/GenericDevicePage.qml</file>
|
||||
<file>ui/devicepages/WeatherDevicePagePre110.qml</file>
|
||||
<file>ui/devicepages/WeatherDevicePagePost110.qml</file>
|
||||
@ -137,5 +136,7 @@
|
||||
<file>ui/system/MqttBrokerSettingsPage.qml</file>
|
||||
<file>ui/system/ServerConfigurationDialog.qml</file>
|
||||
<file>ui/system/MqttPolicyPage.qml</file>
|
||||
<file>ui/devicepages/DeviceLogPage.qml</file>
|
||||
<file>ui/components/Led.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -123,7 +123,7 @@ Page {
|
||||
print("should setup", deviceClass.name, deviceClass.setupMethod, deviceClass.createMethods, deviceClass["discoveryParamTypes"].count)
|
||||
}
|
||||
|
||||
swipe.enabled: true// deviceClass.createMethods.indexOf("CreateMethodUser") !== -1
|
||||
swipe.enabled: deviceClass.createMethods.indexOf("CreateMethodUser") !== -1
|
||||
swipe.right: MouseArea {
|
||||
height: deviceClassDelegate.height
|
||||
width: height
|
||||
|
||||
@ -74,9 +74,10 @@ Item {
|
||||
}
|
||||
|
||||
Engine {
|
||||
id: engine
|
||||
id: engineObject
|
||||
}
|
||||
property alias _engine: engine
|
||||
readonly property Engine engine: engineObject
|
||||
readonly property Engine _engine: engineObject // In case a child cannot use "engine"
|
||||
property int connectionTabIndex: index
|
||||
onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url
|
||||
|
||||
|
||||
@ -4,13 +4,28 @@ Item {
|
||||
id: root
|
||||
|
||||
property color color: actionState
|
||||
property Component touchDelegate: null
|
||||
property bool pressed: mouseArea.pressed
|
||||
property bool hovered: mouseArea.containsMouse
|
||||
|
||||
property variant lights
|
||||
property bool active: true
|
||||
|
||||
property Component touchDelegate: Rectangle {
|
||||
height: 15
|
||||
width: height
|
||||
radius: height / 2
|
||||
color: app.accentColor
|
||||
|
||||
|
||||
Rectangle {
|
||||
color: root.hovered || root.pressed ? "#11000000" : "transparent"
|
||||
anchors.centerIn: parent
|
||||
height: 30
|
||||
width: height
|
||||
radius: width / 2
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
|
||||
function calculateXy(color) {
|
||||
if (!color.hasOwnProperty("r")) {
|
||||
// Qt 4.8's color doesn't have r,g,b properties
|
||||
@ -189,7 +204,7 @@ Item {
|
||||
x: item ? Math.max(0, Math.min(point.x - width * .5, parent.width - item.width)) : 0
|
||||
y: item ? Math.max(0, Math.min(point.y - height * .5, parent.height - item.height)) : 0
|
||||
sourceComponent: root.touchDelegate
|
||||
visible: mouseArea.draggedItem != touchDelegateLoader && root.active
|
||||
visible: mouseArea.draggedItem !== touchDelegateLoader && root.active
|
||||
// Behavior on x {
|
||||
// enabled: !mouseArea.pressed
|
||||
// NumberAnimation {}
|
||||
@ -199,6 +214,6 @@ Item {
|
||||
Loader {
|
||||
id: dndItem
|
||||
sourceComponent: root.touchDelegate
|
||||
visible: mouseArea.draggedItem != undefined && root.active
|
||||
visible: mouseArea.draggedItem !== undefined && root.active
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,14 @@ import QtQuick.Layouts 1.3
|
||||
MenuItem {
|
||||
id: root
|
||||
property alias iconSource: icon.name
|
||||
implicitWidth: 200
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: app.margins
|
||||
ColorIcon {
|
||||
id: icon
|
||||
height: parent.height
|
||||
width: height
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.preferredWidth: height
|
||||
}
|
||||
Label {
|
||||
id: label
|
||||
|
||||
18
nymea-app/ui/components/Led.qml
Normal file
18
nymea-app/ui/components/Led.qml
Normal file
@ -0,0 +1,18 @@
|
||||
import QtQuick 2.9
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitHeight: app.iconSize * .8
|
||||
implicitWidth: height
|
||||
|
||||
property bool on: false
|
||||
|
||||
Rectangle {
|
||||
height: Math.min(parent.height, parent.height)
|
||||
width: height
|
||||
radius: width / 2
|
||||
color: root.on ? "lightgreen" : "lightgray"
|
||||
border.width: 1
|
||||
border.color: app.foregroundColor
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,8 @@ Item {
|
||||
id: root
|
||||
implicitHeight: width * .6
|
||||
|
||||
property var device: null
|
||||
property var stateType: null
|
||||
property Device device: null
|
||||
property StateType stateType: null
|
||||
property int roundTo: 2
|
||||
readonly property var valueState: device.states.getState(stateType.id)
|
||||
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
|
||||
@ -55,12 +55,20 @@ Item {
|
||||
color: root.color
|
||||
}
|
||||
|
||||
Led {
|
||||
visible: root.stateType.type.toLowerCase() === "bool"
|
||||
on: root.valueState.value === true
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: 1.0 * Math.round(root.valueState.value * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + root.stateType.unitString
|
||||
text: root.stateType.type.toLowerCase() === "bool"
|
||||
? root.stateType.displayName
|
||||
: 1.0 * Math.round(root.valueState.value * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + root.stateType.unitString
|
||||
font.pixelSize: app.largeFont
|
||||
}
|
||||
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/zoom-out.svg"
|
||||
onClicked: {
|
||||
@ -96,11 +104,19 @@ Item {
|
||||
|
||||
ValueAxis {
|
||||
id: yAxis
|
||||
max: logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05)
|
||||
min: logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05)
|
||||
max: Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05))
|
||||
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
|
||||
labelsFont.pixelSize: app.smallFont
|
||||
labelFormat: {
|
||||
switch (root.stateType.type.toLowerCase()) {
|
||||
case "bool":
|
||||
return "x";
|
||||
default:
|
||||
return "%d";
|
||||
}
|
||||
}
|
||||
labelsColor: app.foregroundColor
|
||||
tickCount: chartView.height / 40
|
||||
tickCount: root.stateType.type.toLowerCase() === "bool" ? 2 : chartView.height / 40
|
||||
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
|
||||
gridLineColor: color
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import "../components"
|
||||
ItemDelegate {
|
||||
id: root
|
||||
|
||||
property ActionType actionType: null
|
||||
property var actionType: null
|
||||
property var actionState: null
|
||||
|
||||
signal executeAction(var params)
|
||||
@ -294,23 +294,6 @@ ItemDelegate {
|
||||
root.executeAction(params)
|
||||
}
|
||||
}
|
||||
|
||||
touchDelegate: Rectangle {
|
||||
height: 15
|
||||
width: height
|
||||
radius: height / 2
|
||||
color: Material.accent
|
||||
|
||||
|
||||
Rectangle {
|
||||
color: colorPicker.hovered || colorPicker.pressed ? "#11000000" : "transparent"
|
||||
anchors.centerIn: parent
|
||||
height: 30
|
||||
width: height
|
||||
radius: width / 2
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -143,14 +143,20 @@ ItemDelegate {
|
||||
Component {
|
||||
id: spinnerComponent
|
||||
SpinBox {
|
||||
value: root.param.value
|
||||
from: root.paramType.minValue
|
||||
to: root.paramType.maxValue
|
||||
value: root.param.value ? root.param.value : 0
|
||||
from: root.paramType.minValue ? root.paramType.minValue : -999999999
|
||||
to: root.paramType.maxValue ? root.paramType.maxValue : 999999999
|
||||
editable: true
|
||||
width: 150
|
||||
onValueModified: root.param.value = value
|
||||
textFromValue: function(value) {
|
||||
return value
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (root.value === undefined) {
|
||||
root.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,6 +179,11 @@ ItemDelegate {
|
||||
root.param.value = root.paramType.allowedValues[index]
|
||||
print("setting value to", root.param.value)
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (root.value === undefined) {
|
||||
root.value = model[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -78,9 +78,7 @@ DeviceListPageBase {
|
||||
Rectangle {
|
||||
anchors { left: parent.left; top: parent.top; bottom: parent.bottom }
|
||||
width: app.margins / 2
|
||||
color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ?
|
||||
"red"
|
||||
: itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000"
|
||||
color: itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000"
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
@ -105,22 +103,12 @@ DeviceListPageBase {
|
||||
// source: icon
|
||||
// }
|
||||
|
||||
Glow {
|
||||
anchors.fill: icon
|
||||
radius: 1
|
||||
samples: 17
|
||||
color: app.backgroundColor
|
||||
source: icon
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: icon
|
||||
anchors.fill: parent
|
||||
color: app.accentColor
|
||||
// anchors.margins: app.margins / 4
|
||||
// color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ?
|
||||
// "red"
|
||||
// : itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000"
|
||||
color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false
|
||||
? "red"
|
||||
: app.accentColor
|
||||
name: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ?
|
||||
"../images/dialog-warning-symbolic.svg"
|
||||
: itemDelegate.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg"
|
||||
|
||||
291
nymea-app/ui/devicepages/DeviceLogPage.qml
Normal file
291
nymea-app/ui/devicepages/DeviceLogPage.qml
Normal file
@ -0,0 +1,291 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
Page {
|
||||
id: root
|
||||
|
||||
property Device device: null
|
||||
property var filterTypeIds: []
|
||||
|
||||
header: GuhHeader {
|
||||
text: qsTr("History for %1").arg(root.device.name)
|
||||
onBackPressed: pageStack.pop()
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/filters.svg"
|
||||
color: logsModelNg.filterEnabled ? app.accentColor : keyColor
|
||||
onClicked: logsModelNg.filterEnabled = !logsModelNg.filterEnabled
|
||||
visible: root.filterTypeIds.length === 0
|
||||
}
|
||||
}
|
||||
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
typeIds: root.filterTypeIds.length > 0
|
||||
? root.filterTypeIds
|
||||
: filterEnabled
|
||||
? [filterDeviceModel.getData(filterComboBox.currentIndex, DeviceModel.RoleId)]
|
||||
: []
|
||||
live: true
|
||||
|
||||
property bool filterEnabled: false
|
||||
}
|
||||
|
||||
DeviceModel {
|
||||
id: filterDeviceModel
|
||||
device: root.device
|
||||
}
|
||||
|
||||
Pane {
|
||||
id: filterPane
|
||||
anchors { left: parent.left; top: parent.top; right: parent.right }
|
||||
Behavior on height { NumberAnimation { duration: 120; easing.type: Easing.InOutQuad } }
|
||||
|
||||
height: logsModelNg.filterEnabled ? implicitHeight + app.margins * 2 : 0
|
||||
Material.elevation: 1
|
||||
|
||||
leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0
|
||||
contentItem: Rectangle {
|
||||
color: app.primaryColor
|
||||
clip: true
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
spacing: app.margins
|
||||
Label {
|
||||
text: qsTr("Filter by")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: filterComboBox
|
||||
Layout.fillWidth: true
|
||||
textRole: "displayName"
|
||||
model: filterDeviceModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: graphLoader
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: filterPane.bottom
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
readonly property StateType stateType: root.device.deviceClass.stateTypes.getStateType(root.filterTypeIds[0])
|
||||
|
||||
readonly property bool canShowGraph: {
|
||||
if (stateType === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (stateType.unit === Types.UnitUnixTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (stateType.type) {
|
||||
case "Int":
|
||||
case "Double":
|
||||
return true;
|
||||
case "Bool":
|
||||
return engine.jsonRpcClient.ensureServerVersion("1.10")
|
||||
}
|
||||
print("not showing graph for", root.stateType.type)
|
||||
return false;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.filterTypeIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (!canShowGraph) {
|
||||
return;
|
||||
}
|
||||
|
||||
var source;
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
|
||||
} else {
|
||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
|
||||
}
|
||||
setSource(source, {device: root.device, stateType: stateType})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ListView {
|
||||
anchors { left: parent.left; top: graphLoader.bottom; right: parent.right; bottom: parent.bottom }
|
||||
clip: true
|
||||
model: logsModelNg
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: logsModelNg.busy
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: entryDelegate
|
||||
width: parent.width
|
||||
|
||||
property StateType stateType: model.source === LogEntry.LoggingSourceStates ? root.device.deviceClass.stateTypes.getStateType(model.typeId) : null
|
||||
property EventType eventType: model.source === LogEntry.LoggingSourceEvents || model.source === LogEntry.LoggingSourceStates ? root.device.deviceClass.eventTypes.getEventType(model.typeId) : null
|
||||
property ActionType actionType: model.source === LogEntry.LoggingSourceActions ? root.device.deviceClass.actionTypes.getActionType(model.typeId) : null
|
||||
|
||||
contentItem: RowLayout {
|
||||
ColorIcon {
|
||||
Layout.preferredWidth: app.iconSize
|
||||
Layout.preferredHeight: width
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: {
|
||||
switch (model.source) {
|
||||
case LogEntry.LoggingSourceStates:
|
||||
case LogEntry.LoggingSourceSystem:
|
||||
case LogEntry.LoggingSourceActions:
|
||||
case LogEntry.LoggingSourceEvents:
|
||||
return app.accentColor
|
||||
case LogEntry.LoggingSourceRules:
|
||||
if (model.loggingEventType === LogEntry.LoggingEventTypeActiveChange) {
|
||||
return model.value === true ? "green" : keyColor
|
||||
}
|
||||
return app.accentColor
|
||||
}
|
||||
}
|
||||
name: {
|
||||
switch (model.source) {
|
||||
case LogEntry.LoggingSourceStates:
|
||||
return "../images/state.svg"
|
||||
case LogEntry.LoggingSourceSystem:
|
||||
return "../images/system-shutdown.svg"
|
||||
case LogEntry.LoggingSourceActions:
|
||||
return "../images/action.svg"
|
||||
case LogEntry.LoggingSourceEvents:
|
||||
return "../images/event.svg"
|
||||
case LogEntry.LoggingSourceRules:
|
||||
return "../images/magic.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label {
|
||||
text: {
|
||||
switch (model.source) {
|
||||
case LogEntry.LoggingSourceStates:
|
||||
// return entryDelegate.stateType.displayName
|
||||
case LogEntry.LoggingSourceEvents:
|
||||
return entryDelegate.eventType.displayName
|
||||
case LogEntry.LoggingSourceActions:
|
||||
return entryDelegate.actionType.displayName
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
text: Qt.formatDateTime(model.timestamp,"dd.MM.yy hh:mm:ss")
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: app.smallFont
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Loader {
|
||||
id: valueLoader
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: {
|
||||
switch (model.source) {
|
||||
case LogEntry.LoggingSourceStates:
|
||||
switch (entryDelegate.stateType.type.toLowerCase()) {
|
||||
case "bool":
|
||||
return boolComponent;
|
||||
case "color":
|
||||
return colorComponent
|
||||
default:
|
||||
if (entryDelegate.stateType.unit == Types.UnitUnixTime) {
|
||||
return dateTimeComponent
|
||||
}
|
||||
|
||||
return labelComponent
|
||||
|
||||
}
|
||||
case LogEntry.LoggingSourceActions:
|
||||
|
||||
break;
|
||||
case LogEntry.LoggingSourceEvents:
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return labelComponent
|
||||
}
|
||||
Binding { target: valueLoader.item; property: "value"; value: model.value }
|
||||
Binding {
|
||||
target: entryDelegate.stateType && valueLoader.item.hasOwnProperty("unitString") ? valueLoader.item : null;
|
||||
property: "unitString"
|
||||
value: entryDelegate.stateType ? entryDelegate.stateType.unitString : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: labelComponent
|
||||
Label {
|
||||
property var value
|
||||
property string unitString
|
||||
text: value + " " + unitString
|
||||
font.pixelSize: app.smallFont
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: dateTimeComponent
|
||||
Label {
|
||||
property var value
|
||||
text: Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: boolComponent
|
||||
Item {
|
||||
id: boolLed
|
||||
property var value
|
||||
Led {
|
||||
implicitHeight: app.smallFont
|
||||
on: boolLed.value === "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: colorComponent
|
||||
Item {
|
||||
property var value
|
||||
implicitHeight: app.smallFont
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: height * 2
|
||||
color: parent.value
|
||||
// radius: width / 2
|
||||
border.color: app.foregroundColor
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,8 +6,11 @@ import "../components"
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property var device: null
|
||||
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
|
||||
property Device device: null
|
||||
readonly property DeviceClass deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
|
||||
|
||||
property bool showLogsButton: true
|
||||
property bool showDetailsButton: true
|
||||
|
||||
default property alias data: contentItem.data
|
||||
|
||||
@ -35,7 +38,13 @@ Page {
|
||||
Component.onCompleted: {
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Magic"), iconSource: "../images/magic.svg", functionName: "openDeviceMagicPage"}))
|
||||
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing details"), iconSource: "../images/info.svg", functionName: "openDeviceInfoPage"}))
|
||||
if (root.showDetailsButton) {
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing details"), iconSource: "../images/configure.svg", functionName: "openGenericDevicePage"}))
|
||||
}
|
||||
if (root.showLogsButton) {
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Thing logs"), iconSource: "../images/logs.svg", functionName: "openDeviceLogPage"}))
|
||||
}
|
||||
|
||||
if (engine.jsonRpcClient.ensureServerVersion(1.6)) {
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
|
||||
{
|
||||
@ -48,8 +57,8 @@ Page {
|
||||
function openDeviceMagicPage() {
|
||||
pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
|
||||
}
|
||||
function openDeviceInfoPage() {
|
||||
pageStack.push(Qt.resolvedUrl("GenericDeviceStateDetailsPage.qml"), {device: root.device})
|
||||
function openGenericDevicePage() {
|
||||
pageStack.push(Qt.resolvedUrl("GenericDevicePage.qml"), {device: root.device})
|
||||
}
|
||||
function toggleFavorite() {
|
||||
if (favoritesProxy.count === 0) {
|
||||
@ -58,6 +67,9 @@ Page {
|
||||
engine.tagsManager.untagDevice(root.device.id, "favorites")
|
||||
}
|
||||
}
|
||||
function openDeviceLogPage() {
|
||||
pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device });
|
||||
}
|
||||
|
||||
Component {
|
||||
id: menuEntryComponent
|
||||
|
||||
@ -1,134 +1,487 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../delegates"
|
||||
|
||||
DevicePageBase {
|
||||
id: root
|
||||
showDetailsButton: false
|
||||
|
||||
Flickable {
|
||||
function executeAction(actionTypeId, params) {
|
||||
return engine.deviceManager.executeAction(root.device.id, actionTypeId, params)
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: flickable
|
||||
anchors.fill: parent
|
||||
contentHeight: contentColumn.height
|
||||
contentWidth: parent.width
|
||||
|
||||
clip: true
|
||||
|
||||
property var d: root.device
|
||||
property var dc: root.deviceClass
|
||||
section.property: "type"
|
||||
section.delegate: ListSectionHeader {
|
||||
text: {
|
||||
switch (parseInt(section)) {
|
||||
case DeviceModel.TypeStateType:
|
||||
return qsTr("States")
|
||||
case DeviceModel.TypeActionType:
|
||||
return qsTr("Actions")
|
||||
case DeviceModel.TypeEventType:
|
||||
return qsTr("Events")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
model: DeviceModel {
|
||||
device: root.device
|
||||
}
|
||||
delegate: SwipeDelegate {
|
||||
id: delegate
|
||||
width: parent.width
|
||||
// spacing: app.margins
|
||||
|
||||
Repeater {
|
||||
id: interfaceViewsRepeater
|
||||
property bool unhandledInterface: false
|
||||
readonly property StateType stateType: model.type === DeviceModel.TypeStateType ? root.deviceClass.stateTypes.getStateType(model.id) : null
|
||||
readonly property ActionType actionType: model.writable ? root.deviceClass.actionTypes.getActionType(model.id) : null
|
||||
readonly property EventType eventType: model.type === DeviceModel.TypeEventType ? root.deviceClass.eventTypes.getEventType(model.id) : null
|
||||
|
||||
// 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":
|
||||
case "moisturesensor":
|
||||
case "lightsensor":
|
||||
case "conductivitysensor":
|
||||
case "pressuresensor":
|
||||
case "noisesensor":
|
||||
case "co2sensor":
|
||||
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("WARNING: Unhandled interface", modelData)
|
||||
interfaceViewsRepeater.unhandledInterface = true
|
||||
Layout.fillWidth: true
|
||||
topPadding: model.type === DeviceModel.TypeActionType ? app.margins / 2 : 0
|
||||
bottomPadding: 0
|
||||
contentItem: Loader {
|
||||
id: inlineLoader
|
||||
sourceComponent: {
|
||||
switch (model.type) {
|
||||
case DeviceModel.TypeStateType:
|
||||
return stateComponent;
|
||||
case DeviceModel.TypeActionType:
|
||||
return actionComponent;
|
||||
case DeviceModel.TypeEventType:
|
||||
return eventComponent;
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: inlineLoader.item
|
||||
when: model.type === DeviceModel.TypeStateType
|
||||
property: "stateType"
|
||||
value: delegate.stateType
|
||||
}
|
||||
Binding {
|
||||
target: inlineLoader.item
|
||||
when: model.type === DeviceModel.TypeActionType
|
||||
property: "actionType"
|
||||
value: delegate.actionType
|
||||
}
|
||||
Binding {
|
||||
target: inlineLoader.item
|
||||
when: model.type === DeviceModel.TypeEventType
|
||||
property: "eventType"
|
||||
value: delegate.eventType
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device, filterTypeIds: [model.id]})
|
||||
|
||||
swipe.right: RowLayout {
|
||||
height: delegate.height
|
||||
anchors.right: parent.right
|
||||
MouseArea {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
ColorIcon {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
name: "../images/logs.svg"
|
||||
}
|
||||
onClicked: {
|
||||
pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device, filterTypeIds: [model.id]})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stateComponent
|
||||
|
||||
RowLayout {
|
||||
id: stateDelegate
|
||||
property StateType stateType: null
|
||||
readonly property State deviceState: stateType ? root.device.states.getState(stateType.id) : null
|
||||
readonly property bool writable: root.deviceClass.actionTypes.getActionType(stateType.id) !== null
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: stateDelegate.stateType.displayName
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Loader {
|
||||
id: stateDelegateLoader
|
||||
sourceComponent: {
|
||||
switch (stateType.type.toLowerCase()) {
|
||||
case "string":
|
||||
if (stateDelegate.writable) {
|
||||
if (stateDelegate.stateType.allowedValues !== undefined) {
|
||||
return comboBoxComponent
|
||||
}
|
||||
return textFieldComponent;
|
||||
} else {
|
||||
return labelComponent;
|
||||
}
|
||||
case "stringlist":
|
||||
return listComponent;
|
||||
case "bool":
|
||||
if (stateDelegate.writable) {
|
||||
return switchComponent;
|
||||
} else {
|
||||
return ledComponent;
|
||||
}
|
||||
case "int":
|
||||
case "double":
|
||||
if (stateDelegate.stateType.unit === Types.UnitUnixTime) {
|
||||
return dateTimeComponent;
|
||||
}
|
||||
|
||||
return Qt.resolvedUrl("../customviews/" + src);
|
||||
if (stateDelegate.writable) {
|
||||
return spinBoxComponent;
|
||||
}
|
||||
return numberLabelComponent;
|
||||
case "color":
|
||||
return colorComponent;
|
||||
default:
|
||||
print("Unhandled state type", stateType.displayName, stateType.type)
|
||||
}
|
||||
Binding {
|
||||
target: stateViewLoader.item ? stateViewLoader.item : null
|
||||
property: "device"
|
||||
value: device
|
||||
|
||||
print("GenericDevicePage: unhandled entry", stateDelegate.stateType.displayName)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: stateDelegate.stateType.unit !== Types.UnitUnixTime && stateDelegate.stateType.unitString.length > 0
|
||||
text: stateDelegate.stateType.unitString
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: stateDelegateLoader.item
|
||||
property: "value"
|
||||
value: root.device.states.getState(stateDelegate.stateType.id).value
|
||||
}
|
||||
Binding {
|
||||
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("possibleValues") ? stateDelegateLoader.item : null
|
||||
property: "possibleValues"
|
||||
value: stateDelegate.stateType.allowedValues
|
||||
}
|
||||
Binding {
|
||||
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("from") ? stateDelegateLoader.item : null
|
||||
property: "from"
|
||||
value: stateDelegate.stateType.minValue !== undefined ? stateDelegate.stateType.minValue : -999999999999
|
||||
}
|
||||
Binding {
|
||||
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("to") ? stateDelegateLoader.item : null
|
||||
property: "to"
|
||||
value: stateDelegate.stateType.maxValue !== undefined ? stateDelegate.stateType.maxValue : 999999999999
|
||||
}
|
||||
Binding {
|
||||
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("actionTypeId") ? stateDelegateLoader.item : null
|
||||
property: "actionTypeId"
|
||||
value: stateDelegate.stateType.id
|
||||
}
|
||||
Connections {
|
||||
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("changed") ? stateDelegateLoader.item : null
|
||||
onChanged: {
|
||||
var params = []
|
||||
var param1 = {}
|
||||
param1["paramTypeId"] = stateDelegate.stateType.id
|
||||
param1["value"] = value;
|
||||
params.push(param1)
|
||||
root.executeAction(stateDelegate.stateType.id, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: actionComponent
|
||||
|
||||
RowLayout {
|
||||
id: actionDelegate
|
||||
|
||||
property ActionType actionType: null
|
||||
property int pendingActionId: -1
|
||||
property bool lastSuccess: false
|
||||
|
||||
Connections {
|
||||
target: engine.deviceManager
|
||||
onExecuteActionReply: {
|
||||
if (params["id"] === actionDelegate.pendingActionId) {
|
||||
print("blubb!")
|
||||
pendingTimer.start();
|
||||
actionDelegate.lastSuccess = params["params"]["deviceError"] === "DeviceErrorNoError"
|
||||
actionDelegate.pendingActionId = -1
|
||||
}
|
||||
Binding {
|
||||
target: stateViewLoader.item ? stateViewLoader.item : null
|
||||
property: "deviceClass"
|
||||
value: deviceClass
|
||||
}
|
||||
}
|
||||
Timer { id: pendingTimer; interval: 1000; repeat: false; running: false }
|
||||
|
||||
Button {
|
||||
text: actionType.displayName
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
||||
onClicked: {
|
||||
if (actionDelegate.actionType.paramTypes.count === 0) {
|
||||
actionDelegate.pendingActionId = root.executeAction(actionDelegate.actionType.id, [])
|
||||
} else {
|
||||
var dialog = paramsDialogComponent.createObject(root, { actionType: actionDelegate.actionType })
|
||||
dialog.open()
|
||||
}
|
||||
Binding {
|
||||
target: stateViewLoader.item ? stateViewLoader.item : null
|
||||
property: "interfaceName"
|
||||
value: modelData
|
||||
}
|
||||
|
||||
Component {
|
||||
id: paramsDialogComponent
|
||||
Dialog {
|
||||
id: paramsDialog
|
||||
modal: true
|
||||
width: parent.width - app.margins * 2
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
padding: 0
|
||||
|
||||
property ActionType actionType: null
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Repeater {
|
||||
id: paramsRepeater
|
||||
model: paramsDialog.actionType.paramTypes
|
||||
delegate: ParamDelegate {
|
||||
Layout.fillWidth: true
|
||||
paramType: paramsDialog.actionType.paramTypes.get(index)
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.margins: app.margins
|
||||
spacing: app.margins
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
Layout.fillWidth: true
|
||||
onClicked: paramsDialog.close()
|
||||
}
|
||||
Button {
|
||||
text: qsTr("OK")
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
var params = []
|
||||
for (var i = 0; i < paramsRepeater.count; i++) {
|
||||
var param = {}
|
||||
param["paramTypeId"] = paramsRepeater.itemAt(i).paramType.id
|
||||
param["value"] = paramsRepeater.itemAt(i).value
|
||||
params.push(param)
|
||||
}
|
||||
actionDelegate.pendingActionId = root.executeAction(paramsDialog.actionType.id, params);
|
||||
paramsDialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: preferredSize
|
||||
Layout.preferredWidth: preferredSize
|
||||
property int preferredSize: actionDelegate.pendingActionId !== -1 || pendingTimer.running ? app.iconSize : 0
|
||||
Behavior on preferredSize { NumberAnimation { duration: 100 } }
|
||||
|
||||
Repeater {
|
||||
model: interfaceViewsRepeater.count == 0 || interfaceViewsRepeater.unhandledInterface ? deviceClass.actionTypes : null
|
||||
delegate: ItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: delegateLoader.height
|
||||
BusyIndicator {
|
||||
anchors.fill: parent
|
||||
visible: actionDelegate.pendingActionId !== -1
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: delegateLoader
|
||||
width: parent.width
|
||||
property var actionType: deviceClass.actionTypes.get(index)
|
||||
property var actionValue: device.hasState(actionType.id) ? device.states.getState(actionType.id).value : null
|
||||
source: Qt.resolvedUrl("../delegates/ActionDelegate.qml")
|
||||
ColorIcon {
|
||||
anchors.fill: parent
|
||||
visible: actionDelegate.pendingActionId === -1
|
||||
name: actionDelegate.lastSuccess ? "../images/tick.svg" : "../images/close.svg"
|
||||
color: actionDelegate.lastSuccess ? "green" : "red"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: delegateLoader.item ? delegateLoader.item : null
|
||||
property: "actionType"
|
||||
value: delegateLoader.actionType
|
||||
}
|
||||
Binding {
|
||||
target: delegateLoader.item ? delegateLoader.item : null
|
||||
property: "actionState"
|
||||
value: delegateLoader.actionValue
|
||||
Component {
|
||||
id: eventComponent
|
||||
RowLayout {
|
||||
id: eventComponentItem
|
||||
property EventType eventType: null
|
||||
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: eventComponentItem.eventType.displayName
|
||||
}
|
||||
Rectangle {
|
||||
id: flashlight
|
||||
Layout.preferredHeight: app.iconSize * .8
|
||||
Layout.preferredWidth: height
|
||||
color: "lightgray"
|
||||
radius: width / 2
|
||||
border.color: app.foregroundColor
|
||||
border.width: 1
|
||||
|
||||
SequentialAnimation on color {
|
||||
id: flashlightAnimation
|
||||
running: false
|
||||
ColorAnimation { to: "lightgreen"; duration: 100 }
|
||||
ColorAnimation { to: "lightgray"; duration: 500 }
|
||||
}
|
||||
}
|
||||
LogsModelNg {
|
||||
engine: _engine
|
||||
live: true
|
||||
deviceId: root.device.id
|
||||
typeIds: eventComponentItem.eventType.id
|
||||
onCountChanged: {
|
||||
flashlightAnimation.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: ledComponent
|
||||
Led {
|
||||
property bool value
|
||||
on: value === true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: labelComponent
|
||||
Label {
|
||||
property var value
|
||||
text: value
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: numberLabelComponent
|
||||
Label {
|
||||
property var value
|
||||
text: Math.round(value * 100) / 100
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: textFieldComponent
|
||||
TextField {
|
||||
property var value
|
||||
text: value
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: listComponent
|
||||
Label {
|
||||
property var value
|
||||
text: value.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: checkBoxComponent
|
||||
CheckBox {
|
||||
property var value
|
||||
checked: value === true
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: switchComponent
|
||||
Switch {
|
||||
property var value
|
||||
signal changed(var value)
|
||||
checked: value === true
|
||||
onClicked: {
|
||||
changed(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: spinBoxComponent
|
||||
SpinBox {
|
||||
width: 150
|
||||
signal changed(var value)
|
||||
stepSize: (to - from) / 10
|
||||
editable: true
|
||||
onValueModified: {
|
||||
changed(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: comboBoxComponent
|
||||
ComboBox {
|
||||
property var value
|
||||
property var possibleValues
|
||||
|
||||
signal changed(var value)
|
||||
model: possibleValues
|
||||
onActivated: changed(model[index])
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: colorComponent
|
||||
Item {
|
||||
id: colorComponentItem
|
||||
implicitWidth: app.iconSize * 2
|
||||
implicitHeight: app.iconSize
|
||||
property var value
|
||||
signal changed(var value)
|
||||
|
||||
Pane {
|
||||
anchors.fill: parent
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
Material.elevation: 1
|
||||
contentItem: Rectangle {
|
||||
color: colorComponentItem.value
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
var pos = colorComponentItem.mapToItem(root, 0, colorComponentItem.height)
|
||||
print("opening", colorComponentItem.value)
|
||||
var colorPicker = colorPickerComponent.createObject(root, {preferredY: pos.y, colorValue: colorComponentItem.value })
|
||||
colorPicker.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property int commandId: 0
|
||||
Connections {
|
||||
target: delegateLoader.item ? delegateLoader.item : null
|
||||
onExecuteAction: {
|
||||
engine.deviceManager.executeAction(root.device.id, model.id, params)
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: engine.jsonRpcClient
|
||||
onResponseReceived: {
|
||||
if (commandId == delegateLoader.commandId) {
|
||||
print("response:", response["error"])
|
||||
}
|
||||
Component {
|
||||
id: colorPickerComponent
|
||||
Dialog {
|
||||
id: colorPickerDialog
|
||||
modal: true
|
||||
x: (parent.width - width) / 2
|
||||
y: Math.min(preferredY, parent.height - height)
|
||||
width: parent.width - app.margins * 2
|
||||
height: 200
|
||||
padding: app.margins
|
||||
property var colorValue
|
||||
property int preferredY: 0
|
||||
contentItem: ColorPicker {
|
||||
color: colorPickerDialog.colorValue
|
||||
property var lastSentTime: new Date()
|
||||
onColorChanged: {
|
||||
var currentTime = new Date();
|
||||
if (pressed && currentTime - lastSentTime > 200) {
|
||||
colorComponentItem.changed(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,4 +489,12 @@ DevicePageBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: dateTimeComponent
|
||||
Label {
|
||||
property var value
|
||||
text: Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,169 +0,0 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
|
||||
Page {
|
||||
id: root
|
||||
|
||||
property var device
|
||||
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId)
|
||||
property bool readOnly: true
|
||||
|
||||
header: GuhHeader {
|
||||
text: "Details for " + root.device.name
|
||||
onBackPressed: pageStack.pop()
|
||||
}
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
contentHeight: statesColumn.height + app.margins*2
|
||||
|
||||
ColumnLayout {
|
||||
id: statesColumn
|
||||
anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins }
|
||||
spacing: app.margins
|
||||
|
||||
Repeater {
|
||||
model: deviceClass.stateTypes
|
||||
delegate: RowLayout {
|
||||
width: parent.width
|
||||
height: app.largeFont
|
||||
|
||||
property var stateType: deviceClass.stateTypes.get(index)
|
||||
|
||||
Label {
|
||||
id: stateLabel
|
||||
Layout.preferredWidth: parent.width / 2
|
||||
text: displayName
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: placeHolder
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: {
|
||||
var writable = deviceClass.actionTypes.getActionType(id) !== null;
|
||||
if (root.readOnly || !writable) {
|
||||
return labelComponent;
|
||||
}
|
||||
|
||||
switch (stateType.type) {
|
||||
case "Bool":
|
||||
return boolComponent;
|
||||
case "Int":
|
||||
case "Double":
|
||||
if (stateType.minValue !== undefined && stateType.maxValue !== undefined) {
|
||||
return sliderComponent;
|
||||
}
|
||||
return textFieldComponent;
|
||||
case "String":
|
||||
return textFieldComponent;
|
||||
case "Color":
|
||||
return colorPreviewComponent;
|
||||
}
|
||||
console.warn("DeviceStateDetailsPage: Type delegate not implemented", stateType.type)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/info.svg"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -app.margins / 2
|
||||
onClicked: {
|
||||
pageStack.push(Qt.resolvedUrl("StateLogPage.qml"),
|
||||
{device: root.device, stateType: stateType})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: placeHolder.item
|
||||
when: placeHolder.item
|
||||
property: "value"
|
||||
value: device.states.getState(id).value
|
||||
}
|
||||
// Binding {
|
||||
// target: placeHolder.item
|
||||
// when: placeHolder.item
|
||||
// property: "enabled"
|
||||
// value: deviceClass.actionTypes.getActionType(id) !== null
|
||||
// }
|
||||
Binding {
|
||||
target: placeHolder.item
|
||||
when: placeHolder.item
|
||||
property: "stateTypeId"
|
||||
value: id
|
||||
}
|
||||
|
||||
// Label {
|
||||
// id: valueLable
|
||||
// Layout.fillWidth: true
|
||||
// text: device.states.getState(id).value + " " + deviceClass.stateTypes.getStateType(id).unitString
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: labelComponent
|
||||
Label {
|
||||
property var value: ""
|
||||
property var stateTypeId: null
|
||||
property var unitString: deviceClass.stateTypes.getStateType(stateTypeId).unitString
|
||||
text: unitString === "datetime" ? Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate) : value + " " + unitString
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: textFieldComponent
|
||||
TextField {
|
||||
property var value: ""
|
||||
property var stateTypeId: null
|
||||
text: value
|
||||
onEditingFinished: {
|
||||
executeAction(stateTypeId, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: boolComponent
|
||||
Switch {
|
||||
property var value: false
|
||||
property var stateTypeId: null
|
||||
checked: value
|
||||
onClicked: executeAction(stateTypeId, checked)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: colorPreviewComponent
|
||||
Rectangle {
|
||||
property var value: "blue"
|
||||
property var stateTypeId: null
|
||||
color: value
|
||||
implicitHeight: app.mediumFont
|
||||
implicitWidth: height
|
||||
radius: height / 4
|
||||
}
|
||||
}
|
||||
|
||||
function executeAction(stateTypeId, value) {
|
||||
var paramList = []
|
||||
var muteParam = {}
|
||||
muteParam["paramTypeId"] = stateTypeId;
|
||||
muteParam["value"] = value;
|
||||
paramList.push(muteParam)
|
||||
engine.deviceManager.executeAction(root.device.id, stateTypeId, paramList)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user