From 1b51a6e4f46985a209c0b23f1e714b306cf19167 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 13 Jun 2018 01:00:31 +0200 Subject: [PATCH] improve statelog view a bit --- libmea-core/libmea-core.h | 2 + libmea-core/libmea-core.pro | 4 +- libmea-core/models/logsmodelng.cpp | 297 ++++++++++++++++++ libmea-core/models/logsmodelng.h | 82 +++++ libnymea-common/types/statetypes.cpp | 2 +- mea/ui/components/Graph.qml | 86 ++--- mea/ui/customviews/GenericTypeLogView.qml | 29 +- mea/ui/devicepages/ButtonDevicePage.qml | 9 +- .../GenericDeviceStateDetailsPage.qml | 2 + mea/ui/devicepages/InputTriggerDevicePage.qml | 7 +- mea/ui/devicepages/StateLogPage.qml | 157 ++++++++- 11 files changed, 606 insertions(+), 71 deletions(-) create mode 100644 libmea-core/models/logsmodelng.cpp create mode 100644 libmea-core/models/logsmodelng.h diff --git a/libmea-core/libmea-core.h b/libmea-core/libmea-core.h index fea4f30a..96efb4fe 100644 --- a/libmea-core/libmea-core.h +++ b/libmea-core/libmea-core.h @@ -25,6 +25,7 @@ #include "types/stateevaluator.h" #include "types/stateevaluators.h" #include "models/logsmodel.h" +#include "models/logsmodelng.h" #include "models/valuelogsproxymodel.h" #include "models/eventdescriptorparamsfiltermodel.h" #include "basicconfiguration.h" @@ -122,6 +123,7 @@ void registerQmlTypes() { qmlRegisterType(uri, 1, 0, "EventDescriptorParamsFilterModel"); qmlRegisterType(uri, 1, 0, "LogsModel"); + qmlRegisterType(uri, 1, 0, "LogsModelNg"); qmlRegisterType(uri, 1, 0, "ValueLogsProxyModel"); qmlRegisterUncreatableType(uri, 1, 0, "LogEntry", "Get them from LogsModel"); diff --git a/libmea-core/libmea-core.pro b/libmea-core/libmea-core.pro index 2250ad89..9677b28b 100644 --- a/libmea-core/libmea-core.pro +++ b/libmea-core/libmea-core.pro @@ -56,6 +56,7 @@ SOURCES += \ wifisetup/wirelessaccesspoints.cpp \ wifisetup/wirelesssetupmanager.cpp \ wifisetup/networkmanagercontroler.cpp \ + models/logsmodelng.cpp HEADERS += \ engine.h \ @@ -97,7 +98,8 @@ HEADERS += \ wifisetup/wirelessaccesspoints.h \ wifisetup/wirelesssetupmanager.h \ wifisetup/networkmanagercontroler.h \ - libmea-core.h + libmea-core.h \ + models/logsmodelng.h unix { target.path = /usr/lib diff --git a/libmea-core/models/logsmodelng.cpp b/libmea-core/models/logsmodelng.cpp new file mode 100644 index 00000000..68607dec --- /dev/null +++ b/libmea-core/models/logsmodelng.cpp @@ -0,0 +1,297 @@ +#include "logsmodelng.h" +#include +#include +#include + +#include "engine.h" +#include "types/logentry.h" + +LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent) +{ + +} + +int LogsModelNg::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant LogsModelNg::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleTimestamp: + return m_list.at(index.row())->timestamp(); + case RoleValue: + return m_list.at(index.row())->value(); + case RoleDeviceId: + return m_list.at(index.row())->deviceId(); + case RoleTypeId: + return m_list.at(index.row())->typeId(); + case RoleSource: + return m_list.at(index.row())->source(); + case RoleLoggingEventType: + return m_list.at(index.row())->loggingEventType(); + } + return QVariant(); +} + +QHash LogsModelNg::roleNames() const +{ + QHash roles; + roles.insert(RoleTimestamp, "timestamp"); + roles.insert(RoleValue, "value"); + roles.insert(RoleDeviceId, "deviceId"); + roles.insert(RoleTypeId, "typeId"); + roles.insert(RoleSource, "source"); + roles.insert(RoleLoggingEventType, "loggingEventType"); + return roles; +} + +bool LogsModelNg::busy() const +{ + return m_busy; +} + +bool LogsModelNg::live() const +{ + return m_live; +} + +void LogsModelNg::setLive(bool live) +{ + if (m_live != live) { + m_live = live; + emit liveChanged(); + } +} + +QString LogsModelNg::deviceId() const +{ + return m_deviceId; +} + +void LogsModelNg::setDeviceId(const QString &deviceId) +{ + if (m_deviceId != deviceId) { + m_deviceId = deviceId; + emit deviceIdChanged(); + } +} + +QString LogsModelNg::typeId() const +{ + return m_typeId; +} + +void LogsModelNg::setTypeId(const QString &typeId) +{ + if (m_typeId != typeId) { + m_typeId = typeId; + emit typeIdChanged(); + } +} + +QDateTime LogsModelNg::startTime() const +{ + return m_startTime; +} + +void LogsModelNg::setStartTime(const QDateTime &startTime) +{ + if (m_startTime != startTime) { + m_startTime = startTime; + emit startTimeChanged(); + update(); + } +} + +QDateTime LogsModelNg::endTime() const +{ + return m_endTime; +} + +void LogsModelNg::setEndTime(const QDateTime &endTime) +{ + if (m_endTime != endTime) { + m_endTime = endTime; + emit endTimeChanged(); + update(); + } +} + +void LogsModelNg::update() +{ + if (m_busy) { + return; + } + + if (m_startTime.isNull() || m_endTime.isNull()) { + // Need both, startTime and endTime set + return; + } + + m_currentFetchStartTime = QDateTime(); + m_currentFetchEndTime = QDateTime(); + bool haveData = false; + for(int i = 0; i < m_fetchedPeriods.length(); i++) { + if (m_fetchedPeriods.at(i).first < m_startTime) { + if (m_fetchedPeriods.at(i).second == true) { + haveData = true; + continue; + } + if (m_fetchedPeriods.at(i).second == false) { + haveData = false; + continue; + } + } + if (m_fetchedPeriods.at(i).first == m_startTime) { + if (m_fetchedPeriods.at(i).second == true) { + haveData = true; + continue; + } + if (m_fetchedPeriods.at(i).second == false) { + m_currentFetchStartTime = m_startTime; + continue; + } + } + if (m_fetchedPeriods.at(i).first > m_startTime) { + if (m_fetchedPeriods.at(i).second == true) { + if (m_currentFetchStartTime.isNull()) { + m_currentFetchStartTime = m_startTime; + } + m_currentFetchEndTime = m_fetchedPeriods.at(i).first; + break; + } + if (m_fetchedPeriods.at(i).second == false) { + if (m_currentFetchStartTime.isNull()) { + haveData = false; + m_currentFetchStartTime = m_fetchedPeriods.at(i).first; + } + continue; + } + } + } + if (haveData) { + qDebug() << "all the data is fetched"; + m_busy = false; + emit busyChanged(); + return; + } + if (m_currentFetchStartTime.isNull()) { + m_currentFetchStartTime = m_startTime; + } + if (m_currentFetchEndTime.isNull()) { + m_currentFetchEndTime = m_endTime; + } + qDebug() << "Fetching from" << m_currentFetchStartTime << "to" << m_currentFetchEndTime; + + 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_currentFetchStartTime.toSecsSinceEpoch()); + timeFilter.insert("endDate", m_currentFetchEndTime.toSecsSinceEpoch()); + timeFilters.append(timeFilter); + params.insert("timeFilters", timeFilters); + Engine::instance()->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); +} + +void LogsModelNg::logsReply(const QVariantMap &data) +{ + qDebug() << "logs reply";// << data; + + // First update the fetched periods information + int insertIndex = -1; + bool noNeedToInsert = false; + for (int i = 0; i < m_fetchedPeriods.count(); i++) { + if (m_fetchedPeriods.at(i).first < m_currentFetchStartTime) { + // skip it + insertIndex = i+1; + continue; + } + if (m_fetchedPeriods.at(i).first == m_currentFetchStartTime) { + if (m_fetchedPeriods.at(i).second == false) { + // Have an end marker where we start inserting. We can drop the existing end marker and just update the end marker + if (m_fetchedPeriods.count() > i+1) { + if (m_fetchedPeriods.at(i+1).first < m_currentFetchEndTime) { + qWarning() << "Overlap detected!"; + } else if (m_fetchedPeriods.at(i+1).first == m_currentFetchEndTime) { + if (m_fetchedPeriods.at(i+1).second == true) { + m_fetchedPeriods.removeAt(i+1); + } + } + } + m_fetchedPeriods.removeAt(i); + noNeedToInsert = true; + break; + } + } + if (m_fetchedPeriods.at(i).first > m_currentFetchStartTime) { + break; + } + } + + if (!noNeedToInsert) { + if (insertIndex == -1) { + insertIndex = 0; + } + m_fetchedPeriods.insert(insertIndex, qMakePair(m_currentFetchStartTime, true)); + m_fetchedPeriods.insert(insertIndex+1, qMakePair(m_currentFetchEndTime, false)); + } + qDebug() << "new fetched periods:" << m_fetchedPeriods << "insertIndex:" << insertIndex; + m_busy = false; + emit busyChanged(); + + + QList newBlock; + QList logEntries = data.value("params").toMap().value("logEntries").toList(); + foreach (const QVariant &logEntryVariant, logEntries) { + QVariantMap entryMap = logEntryVariant.toMap(); + QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); + QString deviceId = entryMap.value("deviceId").toString(); + QString typeId = entryMap.value("typeId").toString(); + QMetaEnum sourceEnum = QMetaEnum::fromType(); + LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray()); + QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); + LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray()); + QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); + LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); + newBlock.append(entry); + } + + // Now let's find where to insert stuff in the model + if (!newBlock.isEmpty()) { + int indexToInsert = 0; + for (int i = 0; i < m_list.count(); i++) { + LogEntry *entry = m_list.at(i); + if (entry->timestamp() < newBlock.first()->timestamp()) { + continue; + } + indexToInsert = i; + break; + } + + beginInsertRows(QModelIndex(), indexToInsert, indexToInsert + newBlock.count() - 1); + for (int i = 0; i < newBlock.count(); i++) { + m_list.insert(indexToInsert + i, newBlock.at(i)); + } + endInsertRows(); + emit countChanged(); + } + + update(); +} + diff --git a/libmea-core/models/logsmodelng.h b/libmea-core/models/logsmodelng.h new file mode 100644 index 00000000..a3df53ad --- /dev/null +++ b/libmea-core/models/logsmodelng.h @@ -0,0 +1,82 @@ +#ifndef LOGSMODELNG_H +#define LOGSMODELNG_H + +#include +#include +#include + +class LogEntry; + +class LogsModelNg : 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 { + RoleTimestamp, + RoleValue, + RoleDeviceId, + RoleTypeId, + RoleSource, + RoleLoggingEventType + }; + + explicit LogsModelNg(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + bool busy() const; + + bool live() const; + void setLive(bool live); + + 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); + + +signals: + void busyChanged(); + void liveChanged(); + void deviceIdChanged(); + void typeIdChanged(); + void countChanged(); + void startTimeChanged(); + void endTimeChanged(); + +private: + QList m_list; + + bool m_busy = false; + bool m_live = false; + QString m_deviceId; + QString m_typeId; + QDateTime m_startTime; + QDateTime m_endTime; + QDateTime m_currentFetchStartTime; + QDateTime m_currentFetchEndTime; + + QList > m_fetchedPeriods; + + void update(); + Q_INVOKABLE void logsReply(const QVariantMap &data); +}; + + +#endif // LOGSMODELNG_H diff --git a/libnymea-common/types/statetypes.cpp b/libnymea-common/types/statetypes.cpp index e635217e..433d2b07 100644 --- a/libnymea-common/types/statetypes.cpp +++ b/libnymea-common/types/statetypes.cpp @@ -51,7 +51,7 @@ StateType *StateTypes::getStateType(const QUuid &stateTypeId) const return stateType; } } - return 0; + return nullptr; } int StateTypes::rowCount(const QModelIndex &parent) const diff --git a/mea/ui/components/Graph.qml b/mea/ui/components/Graph.qml index 5a3dd0e7..1e180e71 100644 --- a/mea/ui/components/Graph.qml +++ b/mea/ui/components/Graph.qml @@ -17,6 +17,10 @@ Item { } onModelChanged: canvas.requestPaint() + readonly property var device: root.model ? Engine.deviceManager.devices.getDevice(root.model.deviceId) : null + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + readonly property var stateType: deviceClass ? deviceClass.stateTypes.getStateType(root.model.typeId) : null + Label { anchors.centerIn: parent width: parent.width - 2 * app.margins @@ -41,7 +45,7 @@ Item { property int minTemp: { var lower = Math.floor(root.model.minimumValue - 2); var upper = Math.ceil(root.model.maximumValue + 2); - if (lower == undefined || upper == undefined) { + if (isNaN(lower) || isNaN(upper) || lower == undefined || upper == undefined) { return 0 } @@ -57,7 +61,7 @@ Item { property int maxTemp: { var lower = Math.floor(root.model.minimumValue - 2); var upper = Math.ceil(root.model.maximumValue + 2); - if (lower == undefined || upper == undefined) { + if (isNaN(lower) || isNaN(upper) || lower == undefined || upper == undefined) { return 0 } while ((upper - lower) % 10 != 0) { @@ -70,8 +74,8 @@ Item { } property int topMargins: app.margins - property int bottomMargins: app.margins * 4 - property int leftMargins: app.margins * 3 + property int bottomMargins: app.margins + property int leftMargins: app.margins property int rightMargins: app.margins property color gridColor: "#d0d0d0" @@ -87,53 +91,60 @@ Item { } return tmp; } - // Pixel per section - property real pps: contentHeight / sections; onPaint: { + print("painting graph") var ctx = canvas.getContext('2d'); ctx.save(); ctx.reset() - ctx.translate(leftMargins, topMargins) - + ctx.font = "" + app.smallFont + "px Ubuntu"; ctx.globalAlpha = 1//canvas.alpha; //ctx.fillStyle = canvas.fillStyle; - ctx.font = "" + app.smallFont + "px Ubuntu"; - paintGrid(ctx) - enumerate(ctx) + var textSize = ctx.measureText(maxTemp); + var leftTextWidth = textSize.width + app.margins; + var bottomTextHeight = app.smallFont * 2 + app.margins; + var topTextHeight = app.smallFont + app.margins; + ctx.translate(leftMargins + leftTextWidth, topMargins); + var gridWidth = contentWidth - leftTextWidth; + var gridHeight = contentHeight - bottomTextHeight; + + + paintGrid(ctx, gridWidth, gridHeight) + enumerate(ctx, gridWidth, gridHeight) if (root.mode == "bezier") { - paintGraph(ctx) + paintGraph(ctx, gridWidth, gridHeight) } else { - paintBars(ctx) + paintBars(ctx, gridWidth, gridHeight) } ctx.restore(); } - function paintGrid(ctx) { + function paintGrid(ctx, width, height) { ctx.strokeStyle = canvas.gridColor; ctx.fillStyle = Material.foreground ctx.lineWidth = 1; ctx.beginPath(); - ctx.rect(0, 0, contentWidth, contentHeight) + ctx.rect(0, 0, width, height) ctx.stroke(); ctx.closePath(); // Horizontal lines var tempInterval = (maxTemp - minTemp) / sections; + var pps = (height / 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.lineTo(width, i * pps) ctx.stroke(); ctx.closePath(); @@ -153,16 +164,17 @@ Item { ctx.strokeStyle = Material.foreground ctx.fillStyle = Material.foreground ctx.lineWidth = 0; - var label = "°C" + print("blubb", root.stateType.unitString) + var label = root.stateType ? root.stateType.unitString : "" var textSize = ctx.measureText(label) - ctx.text(label, -textSize.width - 1, -1 * pps + 5) + ctx.text(label, -textSize.width - app.margins, height + app.margins + app.smallFont) // ctx.stroke(); ctx.fill() ctx.closePath() } - function enumerate(ctx) { + function enumerate(ctx, width, height) { // enumate x axis ctx.beginPath(); ctx.globalAlpha = 1; @@ -173,12 +185,12 @@ Item { var lastTextX = -1; for (var i = 0; i < model.count; i++) { - var x = contentWidth / (model.count) * i; + var x = width / (model.count) * 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) + ctx.text(label.slice(0,2).concat("."), x, height + app.smallFont + app.margins / 2) switch (model.average) { case ValueLogsProxyModel.AverageQuarterHour: @@ -191,7 +203,7 @@ Item { } textSize = ctx.measureText(label) - ctx.text(label, x, contentHeight + app.smallFont * 2 + app.margins) + ctx.text(label, x, height + app.smallFont * 2 + app.margins) lastTextX = x + textSize.width; } @@ -200,12 +212,13 @@ Item { ctx.closePath(); } - function paintGraph(ctx) { + function paintGraph(ctx, width, height) { if (model.count <= 1) { return; } var tempInterval = (maxTemp - minTemp) / sections; + var pps = (height / sections) ctx.beginPath(); ctx.globalAlpha = 1; @@ -221,8 +234,8 @@ Item { 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; + point.x = (i == 0) ? 0 : (width / (model.count - 2) * i); + point.y = height - (value - minTemp) / tempInterval * pps; points.push(point); } @@ -232,8 +245,8 @@ Item { ctx.beginPath(); paintBezier(ctx, points) - ctx.lineTo(contentWidth, contentHeight); - ctx.lineTo(0, contentHeight); + ctx.lineTo(width, height); + ctx.lineTo(0, height); ctx.fill(); ctx.closePath(); @@ -248,7 +261,7 @@ Item { 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.x = (i == 0) ? 0 : (width / (model.count - 1) * i); point.y = - (dayMaxTemp - maxTemp) / tempInterval * pps; points.push(point); } @@ -312,12 +325,13 @@ Item { } } - function paintBars(ctx) { + function paintBars(ctx, width, height) { if (model.count <= 1) { return; } var tempInterval = (maxTemp - minTemp) / sections; + var pps = (height / sections) ctx.globalAlpha = 1; ctx.lineWidth = 2; @@ -331,21 +345,21 @@ Item { for (var i = 0; i < model.count; i++) { ctx.beginPath(); var value = model.get(i).value; - var x = contentWidth / (model.count) * i; - var y = contentHeight - (value - minTemp) / tempInterval * pps; + var x = width / (model.count) * i; + var y = height - (value - minTemp) / tempInterval * pps; - var slotWidth = contentWidth / model.count - ctx.rect(x,y, slotWidth - 5, contentHeight - y) - ctx.fillRect(x,y, slotWidth - 5, contentHeight - y); + var slotWidth = width / model.count + ctx.rect(x,y, slotWidth - 5, height - y) + ctx.fillRect(x,y, slotWidth - 5, height - y); ctx.stroke(); ctx.fill(); ctx.closePath(); } } - function hourToX(hour) { + function hourToX(hour, width) { var entries = root.day.count; - return canvas.contentWidth / entries * hour + return canvas.width / entries * hour } } } diff --git a/mea/ui/customviews/GenericTypeLogView.qml b/mea/ui/customviews/GenericTypeLogView.qml index 172adec0..6af096da 100644 --- a/mea/ui/customviews/GenericTypeLogView.qml +++ b/mea/ui/customviews/GenericTypeLogView.qml @@ -6,23 +6,12 @@ import "../components" Item { id: root - - property var device: null - property alias typeId: logs.typeId - // %1 will be replaced with count property string text signal addRuleClicked(var value) - readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null - - LogsModel { - id: logs - deviceId: root.device.id - live: true - Component.onCompleted: update() - } + property var logsModel: null ColumnLayout { anchors.fill: parent @@ -32,7 +21,7 @@ Item { Layout.fillWidth: true Layout.margins: app.margins wrapMode: Text.WordWrap - text: root.text.arg(logs.count) + text: root.text.arg(logsModel.count) } ThinDivider {} @@ -40,14 +29,16 @@ Item { RulesFilterModel { id: rulesFilterModel rules: Engine.ruleManager.rules - filterDeviceId: root.device.id + filterDeviceId: root.logsModel.deviceId } ListView { + id: listView Layout.fillWidth: true Layout.fillHeight: true - model: logs + model: logsModel clip: true + onCountChanged: positionViewAtEnd() delegate: ItemDelegate { width: parent.width contentItem: RowLayout { @@ -87,7 +78,7 @@ Item { var rule = rulesFilterModel.get(i); for (var j = 0; j < rule.eventDescriptors.count; j++) { var eventDescriptor = rule.eventDescriptors.get(j); - if (eventDescriptor.eventTypeId === root.deviceClass.eventTypes.findByName("triggered").id) { + if (eventDescriptor.eventTypeId === root.logsModel.typeId) { var matching = true; for (var k = 0; k < eventDescriptor.paramDescriptors.count; k++) { var paramDescriptor = eventDescriptor.paramDescriptors.get(k); @@ -105,6 +96,12 @@ Item { } } } + + BusyIndicator { + anchors.centerIn: parent + visible: root.logsModel.busy + running: visible + } } } } diff --git a/mea/ui/devicepages/ButtonDevicePage.qml b/mea/ui/devicepages/ButtonDevicePage.qml index 7a754b51..2e1c2dd0 100644 --- a/mea/ui/devicepages/ButtonDevicePage.qml +++ b/mea/ui/devicepages/ButtonDevicePage.qml @@ -12,8 +12,13 @@ GenericDevicePage { GenericTypeLogView { anchors.fill: parent text: qsTr("This button has been pressed %1 times in the last 24 hours.") - device: root.device - typeId: root.deviceClass.eventTypes.findByName("pressed").id + + logsModel: LogsModel { + deviceId: root.device.id + live: true + typeId: root.deviceClass.eventTypes.findByName("pressed").id + Component.onCompleted: update() + } onAddRuleClicked: { var rule = Engine.ruleManager.createNewRule(); diff --git a/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml b/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml index 3b06178d..94bca07a 100644 --- a/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml +++ b/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml @@ -36,6 +36,7 @@ Page { id: stateLabel Layout.preferredWidth: parent.width / 2 text: displayName + elide: Text.ElideRight } Loader { @@ -73,6 +74,7 @@ Page { MouseArea { anchors.fill: parent + anchors.margins: -app.margins / 2 onClicked: pageStack.push(Qt.resolvedUrl("StateLogPage.qml"), {device: root.device, stateType: stateType}) } diff --git a/mea/ui/devicepages/InputTriggerDevicePage.qml b/mea/ui/devicepages/InputTriggerDevicePage.qml index 2fb038e9..821c03a7 100644 --- a/mea/ui/devicepages/InputTriggerDevicePage.qml +++ b/mea/ui/devicepages/InputTriggerDevicePage.qml @@ -12,7 +12,12 @@ GenericDevicePage { anchors.fill: parent text: qsTr("This event has appeared %1 times in the last 24 hours.") - device: root.device + logsModel: LogsModel { + deviceId: root.device.id + live: true + Component.onCompleted: update() + typeId: root.deviceClass.eventTypes.findByName("triggered").id; + } onAddRuleClicked: { var rule = Engine.ruleManager.createNewRule(); diff --git a/mea/ui/devicepages/StateLogPage.qml b/mea/ui/devicepages/StateLogPage.qml index 8ae7d365..9b44cc8a 100644 --- a/mea/ui/devicepages/StateLogPage.qml +++ b/mea/ui/devicepages/StateLogPage.qml @@ -11,30 +11,159 @@ Page { property var device: null property var stateType: null + readonly property bool canShowGraph: { + switch (root.stateType.type) { + case "Int": + case "Double": + return true; + } + print("not showing graph for", root.stateType.type) + return false; + } + header: GuhHeader { text: qsTr("History") onBackPressed: pageStack.pop() } - GenericTypeLogView { - anchors.fill: parent - device: root.device + LogsModel { + id: logsModel + deviceId: root.device.id + live: true + Component.onCompleted: update() typeId: root.stateType.id - text: qsTr("%1, %2 has changed %3 times in the last 24h").arg(device.name).arg(stateType.displayName) + } - onAddRuleClicked: { - var rule = Engine.ruleManager.createNewRule(); - rule.createStateEvaluator(); - rule.stateEvaluator.stateDescriptor.deviceId = device.id; - rule.stateEvaluator.stateDescriptor.stateTypeId = root.stateType.id; - rule.stateEvaluator.stateDescriptor.value = value; - rule.stateEvaluator.stateDescriptor.valueOperator = StateDescriptor.ValueOperatorEquals; - rule.name = root.device.name + " - " + stateType.displayName + " = " + value; +// LogsModelNg { +// id: logsModelNg +// deviceId: root.device.id +// typeId: root.stateType.id +// startTime: { +// var date = new Date(); +// date.setHours(new Date().getHours() - 24) +// return date; +// } +// endTime: new Date(); +// } - var rulePage = pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device}); - rulePage.addRule(rule); + ColumnLayout { + anchors.fill: parent + + TabBar { + id: tabBar + Layout.fillWidth: true + visible: root.canShowGraph + TabButton { + text: qsTr("Log") + } + TabButton { + text: qsTr("Graph") + } } + SwipeView { + id: swipeView + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: tabBar.currentIndex + interactive: false + + GenericTypeLogView { + id: logView + width: swipeView.width + height: swipeView.height + text: qsTr("%1, %2 has changed %3 times in the last 24h").arg(device.name).arg(stateType.displayName) + + logsModel: logsModel + + onAddRuleClicked: { + var rule = Engine.ruleManager.createNewRule(); + rule.createStateEvaluator(); + rule.stateEvaluator.stateDescriptor.deviceId = device.id; + rule.stateEvaluator.stateDescriptor.stateTypeId = root.stateType.id; + rule.stateEvaluator.stateDescriptor.value = value; + rule.stateEvaluator.stateDescriptor.valueOperator = StateDescriptor.ValueOperatorEquals; + rule.name = root.device.name + " - " + stateType.displayName + " = " + value; + + var rulePage = pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device}); + rulePage.addRule(rule); + } + } + + ColumnLayout { + width: swipeView.width + height: swipeView.height + TabBar { + id: zoomTabBar + Layout.fillWidth: true + TabButton { + text: qsTr("6 h") + property int avg: ValueLogsProxyModel.AverageQuarterHour + property date startTime: { + var date = new Date(); + date.setHours(new Date().getHours() - 6) + date.setMinutes(0) + date.setSeconds(0) + return date; + } + } + TabButton { + text: qsTr("24 h") + property int avg: ValueLogsProxyModel.AverageHourly + property date startTime: { + var date = new Date(); + date.setHours(new Date().getHours() - 24); + date.setMinutes(0) + date.setSeconds(0) + return date; + } + } + TabButton { + text: qsTr("7 d") + property int avg: ValueLogsProxyModel.AverageDayTime + property date startTime: { + var date = new Date(); + date.setDate(new Date().getDate() - 7); + date.setHours(0) + date.setMinutes(0) + date.setSeconds(0) + return date; + } + } + } + + Graph { + Layout.fillWidth: true + Layout.fillHeight: true + mode: settings.graphStyle + color: app.guhAccent + + Timer { + id: updateTimer + interval: 10 + repeat: false + onTriggered: { + graphModel.update() + } + } + + model: ValueLogsProxyModel { + id: graphModel + 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(); + + // Live doesn't work yet with ValueLogsProxyModel + // live: true + } + } + } + + } } }