Add media and energy views
This commit is contained in:
parent
57d2986948
commit
0f9caa03df
@ -343,7 +343,7 @@ void DeviceManager::getConfiguredDevicesResponse(const QVariantMap ¶ms)
|
||||
QVariantList stateVariantList = deviceVariant.toMap().value("states").toList();
|
||||
foreach (const QVariant &stateMap, stateVariantList) {
|
||||
QString stateTypeId = stateMap.toMap().value("stateTypeId").toString();
|
||||
StateType *st = device->deviceClass()->stateTypes()->getStateType(stateTypeId);
|
||||
StateType *st = device->thingClass()->stateTypes()->getStateType(stateTypeId);
|
||||
if (!st) {
|
||||
qWarning() << "Can't find a statetype for this state";
|
||||
continue;
|
||||
|
||||
@ -72,24 +72,24 @@ QVariant Devices::data(const QModelIndex &index, int role) const
|
||||
if (index.row() < 0 || index.row() >= m_devices.count())
|
||||
return QVariant();
|
||||
|
||||
Device *device = m_devices.at(index.row());
|
||||
Device *thing = m_devices.at(index.row());
|
||||
switch (role) {
|
||||
case RoleName:
|
||||
return device->name();
|
||||
return thing->name();
|
||||
case RoleId:
|
||||
return device->id().toString();
|
||||
return thing->id().toString();
|
||||
case RoleDeviceClass:
|
||||
return device->deviceClassId().toString();
|
||||
return thing->deviceClassId().toString();
|
||||
case RoleParentDeviceId:
|
||||
return device->parentDeviceId().toString();
|
||||
return thing->parentDeviceId().toString();
|
||||
case RoleSetupStatus:
|
||||
return device->setupStatus();
|
||||
return thing->setupStatus();
|
||||
case RoleSetupDisplayMessage:
|
||||
return device->setupDisplayMessage();
|
||||
return thing->setupDisplayMessage();
|
||||
case RoleInterfaces:
|
||||
return device->deviceClass()->interfaces();
|
||||
return thing->thingClass()->interfaces();
|
||||
case RoleBaseInterface:
|
||||
return device->deviceClass()->baseInterface();
|
||||
return thing->thingClass()->baseInterface();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
@ -592,7 +592,7 @@ void JsonRpcClient::helloReply(const QVariantMap ¶ms)
|
||||
|
||||
qDebug() << "Handshake reply:" << "Protocol version:" << protoVersionString << "InitRequired:" << m_initialSetupRequired << "AuthRequired:" << m_authenticationRequired << "PushButtonAvailable:" << m_pushButtonAuthAvailable;;
|
||||
|
||||
QVersionNumber minimumRequiredVersion = QVersionNumber(1, 0);
|
||||
QVersionNumber minimumRequiredVersion = QVersionNumber(1, 10);
|
||||
if (m_jsonRpcVersion < minimumRequiredVersion) {
|
||||
qWarning() << "Nymea core doesn't support minimum required version. Required:" << minimumRequiredVersion << "Found:" << m_jsonRpcVersion;
|
||||
m_connection->disconnect();
|
||||
|
||||
@ -65,7 +65,8 @@
|
||||
#include "types/browseritem.h"
|
||||
#include "models/logsmodel.h"
|
||||
#include "models/logsmodelng.h"
|
||||
#include "models/valuelogsproxymodel.h"
|
||||
#include "models/barseriesadapter.h"
|
||||
#include "models/xyseriesadapter.h"
|
||||
#include "models/interfacesproxy.h"
|
||||
#include "configuration/nymeaconfiguration.h"
|
||||
#include "configuration/serverconfiguration.h"
|
||||
@ -246,8 +247,9 @@ void registerQmlTypes() {
|
||||
|
||||
qmlRegisterType<LogsModel>(uri, 1, 0, "LogsModel");
|
||||
qmlRegisterType<LogsModelNg>(uri, 1, 0, "LogsModelNg");
|
||||
qmlRegisterType<ValueLogsProxyModel>(uri, 1, 0, "ValueLogsProxyModel");
|
||||
qmlRegisterUncreatableType<LogEntry>(uri, 1, 0, "LogEntry", "Get them from LogsModel");
|
||||
qmlRegisterType<BarSeriesAdapter>(uri, 1, 0, "BarSeriesAdapter");
|
||||
qmlRegisterType<XYSeriesAdapter>(uri, 1, 0, "XYSeriesAdapter");
|
||||
|
||||
qmlRegisterUncreatableType<TagsManager>(uri, 1, 0, "TagsManager", "Get it from Engine");
|
||||
qmlRegisterUncreatableType<Tags>(uri, 1, 0, "Tags", "Get it from TagsManager");
|
||||
|
||||
@ -27,7 +27,9 @@ INCLUDEPATH += $$top_srcdir/QtZeroConf
|
||||
SOURCES += \
|
||||
configuration/networkmanager.cpp \
|
||||
engine.cpp \
|
||||
models/barseriesadapter.cpp \
|
||||
models/sortfilterproxymodel.cpp \
|
||||
models/xyseriesadapter.cpp \
|
||||
ruletemplates/calendaritemtemplate.cpp \
|
||||
ruletemplates/timedescriptortemplate.cpp \
|
||||
ruletemplates/timeeventitemtemplate.cpp \
|
||||
@ -128,7 +130,6 @@ SOURCES += \
|
||||
rulemanager.cpp \
|
||||
models/rulesfiltermodel.cpp \
|
||||
models/logsmodel.cpp \
|
||||
models/valuelogsproxymodel.cpp \
|
||||
logmanager.cpp \
|
||||
wifisetup/bluetoothdevice.cpp \
|
||||
wifisetup/bluetoothdeviceinfo.cpp \
|
||||
@ -163,7 +164,9 @@ SOURCES += \
|
||||
HEADERS += \
|
||||
configuration/networkmanager.h \
|
||||
engine.h \
|
||||
models/barseriesadapter.h \
|
||||
models/sortfilterproxymodel.h \
|
||||
models/xyseriesadapter.h \
|
||||
ruletemplates/calendaritemtemplate.h \
|
||||
ruletemplates/timedescriptortemplate.h \
|
||||
ruletemplates/timeeventitemtemplate.h \
|
||||
@ -265,7 +268,6 @@ HEADERS += \
|
||||
rulemanager.h \
|
||||
models/rulesfiltermodel.h \
|
||||
models/logsmodel.h \
|
||||
models/valuelogsproxymodel.h \
|
||||
logmanager.h \
|
||||
wifisetup/bluetoothdevice.h \
|
||||
wifisetup/bluetoothdeviceinfo.h \
|
||||
|
||||
203
libnymea-app/models/barseriesadapter.cpp
Normal file
203
libnymea-app/models/barseriesadapter.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
#include "barseriesadapter.h"
|
||||
|
||||
BarSeriesAdapter::BarSeriesAdapter(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
LogsModel *BarSeriesAdapter::logsModel() const
|
||||
{
|
||||
return m_logsModel;
|
||||
}
|
||||
|
||||
void BarSeriesAdapter::setLogsModel(LogsModel *logsModel)
|
||||
{
|
||||
if (m_logsModel != logsModel) {
|
||||
m_logsModel = logsModel;
|
||||
emit logsModelChanged();
|
||||
update();
|
||||
connect(logsModel, &LogsModel::logEntryAdded, this, &BarSeriesAdapter::logEntryAdded);
|
||||
}
|
||||
}
|
||||
|
||||
QtCharts::QAbstractBarSeries *BarSeriesAdapter::barSeries() const
|
||||
{
|
||||
return m_barSeries;
|
||||
}
|
||||
|
||||
void BarSeriesAdapter::setBarSeries(QtCharts::QAbstractBarSeries *barSeries)
|
||||
{
|
||||
if (m_barSeries != barSeries) {
|
||||
m_barSeries = barSeries;
|
||||
emit barSeriesChanged();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
BarSeriesAdapter::Interval BarSeriesAdapter::interval() const
|
||||
{
|
||||
return m_interval;
|
||||
}
|
||||
|
||||
void BarSeriesAdapter::setInterval(BarSeriesAdapter::Interval interval)
|
||||
{
|
||||
if (m_interval != interval) {
|
||||
m_interval = interval;
|
||||
emit intervalChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BarSeriesAdapter::update()
|
||||
{
|
||||
if (!m_barSeries || !m_logsModel) {
|
||||
return;
|
||||
}
|
||||
m_set = new QtCharts::QBarSet(m_barSeries->name());
|
||||
m_barSeries->append(m_set);
|
||||
|
||||
for (int i = 0; i < m_logsModel->rowCount(); i++) {
|
||||
LogEntry *entry = m_logsModel->get(i);
|
||||
qDebug() << "have entry" << entry->timestamp().toString();
|
||||
}
|
||||
}
|
||||
|
||||
void BarSeriesAdapter::ensureSlots(const QDateTime &start, const QDateTime &end)
|
||||
{
|
||||
if (!m_barSeries || !m_logsModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDateTime startTime = start;
|
||||
switch (m_interval) {
|
||||
case IntervalMinutes:
|
||||
startTime.setTime(QTime(startTime.time().hour(), startTime.time().minute()));
|
||||
break;
|
||||
case IntervalHours:
|
||||
startTime.setTime(QTime(startTime.time().hour(), 0));
|
||||
break;
|
||||
case IntervalDays:
|
||||
startTime.setTime(QTime(0, 0));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
QDateTime endTime = end;
|
||||
if (!endTime.isValid()) {
|
||||
endTime = QDateTime::currentDateTime();
|
||||
}
|
||||
endTime.setTime(QTime(endTime.time().hour(), endTime.time().minute()));
|
||||
|
||||
QDateTime oldestExistingSlot;
|
||||
if (m_timeslots.isEmpty()) {
|
||||
oldestExistingSlot = endTime;
|
||||
} else {
|
||||
oldestExistingSlot = m_timeslots.first().datetime;
|
||||
}
|
||||
|
||||
if (startTime < oldestExistingSlot) {
|
||||
long duration = oldestExistingSlot.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch();
|
||||
long slotCount = duration / (m_interval * 1000);
|
||||
qDebug() << "Need" << slotCount << "new slots appended";
|
||||
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
QDateTime slotTime = oldestExistingSlot.addSecs(-m_interval * (i + 1));
|
||||
// qDebug() << "Adding" << slotTime.toString();
|
||||
TimeSlot timeslot;
|
||||
timeslot.datetime = slotTime;
|
||||
m_set->insert(0, 0);
|
||||
m_timeslots.prepend(timeslot);
|
||||
}
|
||||
}
|
||||
|
||||
QDateTime newestExistingSlot;
|
||||
if (m_timeslots.isEmpty()) {
|
||||
newestExistingSlot = startTime;
|
||||
} else {
|
||||
newestExistingSlot = m_timeslots.last().datetime;
|
||||
}
|
||||
|
||||
if (endTime > newestExistingSlot) {
|
||||
long duration = endTime.toMSecsSinceEpoch() - newestExistingSlot.toMSecsSinceEpoch();
|
||||
long slotCount = duration / (m_interval * 1000);
|
||||
// qDebug() << "Need" << slotCount << "new slots prepended";
|
||||
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
QDateTime slotTime = newestExistingSlot.addSecs(m_interval * (i + 1));
|
||||
TimeSlot timeslot;
|
||||
timeslot.datetime = slotTime;
|
||||
m_set->append(0);
|
||||
m_timeslots.append(timeslot);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_timeslots.isEmpty()) {
|
||||
// qDebug() << "Need to initialize list with 1 entry";
|
||||
TimeSlot timeslot;
|
||||
timeslot.datetime = startTime;
|
||||
m_set->append(0);
|
||||
m_timeslots.append(timeslot);
|
||||
}
|
||||
|
||||
qDebug() << "Ensuring slots from" << start << "to" << end << "oldest" << oldestExistingSlot << "newest" << newestExistingSlot;
|
||||
|
||||
}
|
||||
|
||||
void BarSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||
{
|
||||
qDebug() << "****" << m_barSeries << m_logsModel;
|
||||
if (!m_barSeries || !m_logsModel) {
|
||||
return;
|
||||
}
|
||||
ensureSlots(QDateTime::fromMSecsSinceEpoch(qMin(m_logsModel->startTime().toMSecsSinceEpoch(), entry->timestamp().toMSecsSinceEpoch())), QDateTime::fromMSecsSinceEpoch(qMax(m_logsModel->endTime().toMSecsSinceEpoch(), entry->timestamp().toMSecsSinceEpoch())));
|
||||
|
||||
QDateTime timestamp = entry->timestamp();
|
||||
|
||||
QDateTime timeSlotStart = timestamp;
|
||||
switch (m_interval) {
|
||||
case IntervalMinutes:
|
||||
timeSlotStart.setTime(QTime(timestamp.time().hour(), timestamp.time().minute()));
|
||||
break;
|
||||
case IntervalHours:
|
||||
timeSlotStart.setTime(QTime(timestamp.time().hour(), 0));
|
||||
break;
|
||||
case IntervalDays:
|
||||
timeSlotStart.setTime(QTime(0, 0));
|
||||
break;
|
||||
}
|
||||
|
||||
// qDebug() << "Item time:" << timeSlotStart;
|
||||
|
||||
TimeSlot first = m_timeslots.first();
|
||||
TimeSlot last = m_timeslots.last();
|
||||
long slotIdx = (timeSlotStart.toMSecsSinceEpoch() - first.datetime.toMSecsSinceEpoch()) / (m_interval * 1000);
|
||||
qDebug() << "first" << first.datetime.toString();
|
||||
qDebug() << "last" << last.datetime.toString();
|
||||
qDebug() << "this" << timeSlotStart.toString();
|
||||
qDebug() << "idx" << slotIdx;
|
||||
|
||||
|
||||
m_timeslots[slotIdx].entries.append(entry);
|
||||
m_set->replace(slotIdx, m_timeslots[slotIdx].value());
|
||||
qDebug() << "Adding entry" << entry->timestamp() << "timestlot" << timeSlotStart << "at" << slotIdx << "value" << m_timeslots[slotIdx].value();
|
||||
|
||||
// if (!m_timeslots.contains(timeSlotStart)) {
|
||||
// TimeSlot timeslot;
|
||||
// timeslot.startTime = timeSlotStart;
|
||||
|
||||
|
||||
// }
|
||||
// TimeSlot timeslot = m_timeslots.value(timeSlotStart);
|
||||
// timeslot.entries.append(entry);
|
||||
}
|
||||
|
||||
qreal BarSeriesAdapter::TimeSlot::value() const
|
||||
{
|
||||
qreal value = 0;
|
||||
foreach (LogEntry *entry, entries) {
|
||||
value += entry->value().toDouble();
|
||||
}
|
||||
if (entries.count() > 1) {
|
||||
value /= entries.count();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
66
libnymea-app/models/barseriesadapter.h
Normal file
66
libnymea-app/models/barseriesadapter.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef BARSERIESADAPTER_H
|
||||
#define BARSERIESADAPTER_H
|
||||
|
||||
#include "logsmodel.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QBarSeries>
|
||||
#include <QBarSet>
|
||||
|
||||
class BarSeriesAdapter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(LogsModel* logsModel READ logsModel WRITE setLogsModel NOTIFY logsModelChanged)
|
||||
Q_PROPERTY(QtCharts::QAbstractBarSeries* barSeries READ barSeries WRITE setBarSeries NOTIFY barSeriesChanged)
|
||||
|
||||
Q_PROPERTY(Interval interval READ interval WRITE setInterval NOTIFY intervalChanged)
|
||||
|
||||
public:
|
||||
enum Interval {
|
||||
IntervalMinutes = 60,
|
||||
IntervalHours = 60 * 60,
|
||||
IntervalDays = 24 * 60 * 60
|
||||
};
|
||||
Q_ENUM(Interval)
|
||||
|
||||
explicit BarSeriesAdapter(QObject *parent = nullptr);
|
||||
|
||||
LogsModel *logsModel() const;
|
||||
void setLogsModel(LogsModel *logsModel);
|
||||
|
||||
QtCharts::QAbstractBarSeries *barSeries() const;
|
||||
void setBarSeries(QtCharts::QAbstractBarSeries *barSeries);
|
||||
|
||||
Interval interval() const;
|
||||
void setInterval(Interval interval);
|
||||
|
||||
signals:
|
||||
void logsModelChanged();
|
||||
void barSeriesChanged();
|
||||
void intervalChanged();
|
||||
|
||||
private:
|
||||
void update();
|
||||
|
||||
void ensureSlots(const QDateTime &start, const QDateTime &end);
|
||||
|
||||
private slots:
|
||||
void logEntryAdded(LogEntry *entry);
|
||||
|
||||
private:
|
||||
class TimeSlot {
|
||||
public:
|
||||
QDateTime datetime;
|
||||
QList<LogEntry*> entries;
|
||||
qreal value() const;
|
||||
};
|
||||
|
||||
LogsModel *m_logsModel = nullptr;
|
||||
QtCharts::QAbstractBarSeries *m_barSeries = nullptr;
|
||||
QtCharts::QBarSet *m_set = nullptr;
|
||||
Interval m_interval = IntervalMinutes;
|
||||
|
||||
QList<TimeSlot> m_timeslots;
|
||||
};
|
||||
|
||||
#endif // BARSERIESADAPTER_H
|
||||
@ -49,35 +49,35 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const
|
||||
return m_list.at(index.row());
|
||||
}
|
||||
if (role == RoleType) {
|
||||
StateType* stateType = m_device->deviceClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||
StateType* stateType = m_device->thingClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||
if (stateType) {
|
||||
return TypeStateType;
|
||||
}
|
||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
ActionType* actionType = m_device->thingClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
if (actionType) {
|
||||
return TypeActionType;
|
||||
}
|
||||
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
||||
EventType* eventType = m_device->thingClass()->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()));
|
||||
StateType* stateType = m_device->thingClass()->stateTypes()->getStateType(m_list.at(index.row()));
|
||||
if (stateType) {
|
||||
return stateType->displayName();
|
||||
}
|
||||
ActionType* actionType = m_device->deviceClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
ActionType* actionType = m_device->thingClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
if (actionType) {
|
||||
return actionType->displayName();
|
||||
}
|
||||
EventType* eventType = m_device->deviceClass()->eventTypes()->getEventType(m_list.at(index.row()));
|
||||
EventType* eventType = m_device->thingClass()->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()));
|
||||
ActionType* actionType = m_device->thingClass()->actionTypes()->getActionType(m_list.at(index.row()));
|
||||
return actionType != nullptr;
|
||||
}
|
||||
return QVariant();
|
||||
@ -169,22 +169,22 @@ void DeviceModel::updateList()
|
||||
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());
|
||||
for (int i = 0; i < m_device->thingClass()->stateTypes()->rowCount(); i++) {
|
||||
m_list.append(m_device->thingClass()->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());
|
||||
for (int i = 0; i < m_device->thingClass()->actionTypes()->rowCount(); i++) {
|
||||
if (!m_list.contains(m_device->thingClass()->actionTypes()->get(i)->id())) {
|
||||
m_list.append(m_device->thingClass()->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());
|
||||
for (int i = 0; i < m_device->thingClass()->eventTypes()->rowCount(); i++) {
|
||||
if (!m_list.contains(m_device->thingClass()->eventTypes()->get(i)->id())) {
|
||||
m_list.append(m_device->thingClass()->eventTypes()->get(i)->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,11 +103,11 @@ bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_devicesFilter->rowCount(); i++) {
|
||||
Device *d = m_devicesFilter->get(i);
|
||||
if (!d->deviceClass()) {
|
||||
if (!d->thingClass()) {
|
||||
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
|
||||
return false;
|
||||
}
|
||||
if (d->deviceClass()->interfaces().contains(interfaceName)) {
|
||||
if (d->thingClass()->interfaces().contains(interfaceName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@ -121,11 +121,11 @@ bool InterfacesProxy::filterAcceptsRow(int source_row, const QModelIndex &source
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_devicesProxyFilter->rowCount(); i++) {
|
||||
Device *d = m_devicesProxyFilter->get(i);
|
||||
if (!d->deviceClass()) {
|
||||
qWarning() << "Cannot find DeviceClass for device:" << d->id() << d->name();
|
||||
if (!d->thingClass()) {
|
||||
qWarning() << "Cannot find ThingClass for thing:" << d->id() << d->name();
|
||||
return false;
|
||||
}
|
||||
if (d->deviceClass()->interfaces().contains(interfaceName)) {
|
||||
if (d->thingClass()->interfaces().contains(interfaceName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -29,14 +29,18 @@
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "logsmodel.h"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QMetaEnum>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "engine.h"
|
||||
#include "types/logentry.h"
|
||||
#include "logmanager.h"
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
LogsModel::LogsModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Engine *LogsModel::engine() const
|
||||
@ -53,11 +57,6 @@ void LogsModel::setEngine(Engine *engine)
|
||||
}
|
||||
}
|
||||
|
||||
bool LogsModel::busy() const
|
||||
{
|
||||
return m_busy;
|
||||
}
|
||||
|
||||
int LogsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
@ -72,6 +71,7 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const
|
||||
case RoleValue:
|
||||
return m_list.at(index.row())->value();
|
||||
case RoleThingId:
|
||||
case RoleDeviceId:
|
||||
return m_list.at(index.row())->thingId();
|
||||
case RoleTypeId:
|
||||
return m_list.at(index.row())->typeId();
|
||||
@ -88,13 +88,19 @@ QHash<int, QByteArray> LogsModel::roleNames() const
|
||||
QHash<int, QByteArray> roles;
|
||||
roles.insert(RoleTimestamp, "timestamp");
|
||||
roles.insert(RoleValue, "value");
|
||||
roles.insert(RoleThingId, "deviceId");
|
||||
roles.insert(RoleThingId, "thingId");
|
||||
roles.insert(RoleDeviceId, "deviceId");
|
||||
roles.insert(RoleTypeId, "typeId");
|
||||
roles.insert(RoleSource, "source");
|
||||
roles.insert(RoleLoggingEventType, "loggingEventType");
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool LogsModel::busy() const
|
||||
{
|
||||
return m_busy;
|
||||
}
|
||||
|
||||
bool LogsModel::live() const
|
||||
{
|
||||
return m_live;
|
||||
@ -108,29 +114,42 @@ void LogsModel::setLive(bool live)
|
||||
}
|
||||
}
|
||||
|
||||
QString LogsModel::deviceId() const
|
||||
QUuid LogsModel::thingId() const
|
||||
{
|
||||
return m_deviceId;
|
||||
return m_thingId;
|
||||
}
|
||||
|
||||
void LogsModel::setDeviceId(const QString &deviceId)
|
||||
void LogsModel::setThingId(const QUuid &thingId)
|
||||
{
|
||||
if (m_deviceId != deviceId) {
|
||||
m_deviceId = deviceId;
|
||||
emit deviceIdChanged();
|
||||
if (m_thingId != thingId) {
|
||||
m_thingId = thingId;
|
||||
emit thingIdChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList LogsModel::typeIds() const
|
||||
{
|
||||
return m_typeIds;
|
||||
QStringList strings;
|
||||
foreach (const QUuid &id, m_typeIds) {
|
||||
strings.append(id.toString());
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
void LogsModel::setTypeIds(const QStringList &typeIds)
|
||||
{
|
||||
if (m_typeIds != typeIds) {
|
||||
m_typeIds = typeIds;
|
||||
QList<QUuid> fixedTypeIds;
|
||||
foreach (const QString &id, typeIds) {
|
||||
fixedTypeIds.append(QUuid(id));
|
||||
}
|
||||
if (m_typeIds != fixedTypeIds) {
|
||||
m_typeIds = fixedTypeIds;
|
||||
emit typeIdsChanged();
|
||||
beginResetModel();
|
||||
qDeleteAll(m_list);
|
||||
m_list.clear();
|
||||
endResetModel();
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,6 +179,24 @@ void LogsModel::setEndTime(const QDateTime &endTime)
|
||||
}
|
||||
}
|
||||
|
||||
QDateTime LogsModel::viewStartTime() const
|
||||
{
|
||||
return m_viewStartTime;
|
||||
}
|
||||
|
||||
void LogsModel::setViewStartTime(const QDateTime &viewStartTime)
|
||||
{
|
||||
if (m_viewStartTime != viewStartTime) {
|
||||
m_viewStartTime = viewStartTime;
|
||||
emit viewStartTimeChanged();
|
||||
if (m_list.count() == 0 || m_list.last()->timestamp() > m_viewStartTime) {
|
||||
if (m_canFetchMore) {
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogEntry *LogsModel::get(int index) const
|
||||
{
|
||||
if (index >= 0 && index < m_list.count()) {
|
||||
@ -168,163 +205,172 @@ LogEntry *LogsModel::get(int index) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LogsModel::notificationReceived(const QVariantMap &data)
|
||||
{
|
||||
qDebug() << "KLogModel notificatiion" << data;
|
||||
}
|
||||
|
||||
void LogsModel::update()
|
||||
{
|
||||
if (!m_engine) {
|
||||
qWarning() << "LogsModel: Can't update, no engine set";
|
||||
return;
|
||||
}
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
m_busy = true;
|
||||
emit busyChanged();
|
||||
|
||||
QVariantMap params;
|
||||
if (!m_deviceId.isEmpty()) {
|
||||
QVariantList deviceIds;
|
||||
deviceIds.append(m_deviceId);
|
||||
params.insert("deviceIds", deviceIds);
|
||||
}
|
||||
if (!m_typeIds.isEmpty()) {
|
||||
QVariantList typeIds;
|
||||
foreach (const QString &typeId, m_typeIds) {
|
||||
typeIds.append(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);
|
||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||
}
|
||||
|
||||
void LogsModel::fetchEarlier(int hours)
|
||||
{
|
||||
if (!m_engine) {
|
||||
return;
|
||||
}
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
m_busy = true;
|
||||
emit busyChanged();
|
||||
|
||||
QVariantMap params;
|
||||
if (!m_deviceId.isEmpty()) {
|
||||
QVariantList deviceIds;
|
||||
deviceIds.append(m_deviceId);
|
||||
params.insert("deviceIds", deviceIds);
|
||||
}
|
||||
if (!m_typeIds.isEmpty()) {
|
||||
QVariantList typeIds;
|
||||
foreach (const QString &typeId, m_typeIds) {
|
||||
typeIds.append(typeId);
|
||||
}
|
||||
params.insert("typeIds", typeIds);
|
||||
}
|
||||
QVariantList timeFilters;
|
||||
QVariantMap timeFilter;
|
||||
timeFilter.insert("endDate", m_startTime.toSecsSinceEpoch());
|
||||
m_startTime = m_startTime.addSecs(-60*60*hours);
|
||||
timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch());
|
||||
timeFilters.append(timeFilter);
|
||||
params.insert("timeFilters", timeFilters);
|
||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "fetchEarlierReply");
|
||||
}
|
||||
|
||||
void LogsModel::logsReply(const QVariantMap &data)
|
||||
{
|
||||
// qDebug() << "logs reply" << data;
|
||||
beginResetModel();
|
||||
qDeleteAll(m_list);
|
||||
m_list.clear();
|
||||
int offset = data.value("params").toMap().value("offset").toInt();
|
||||
int count = data.value("params").toMap().value("count").toInt();
|
||||
|
||||
// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(data).toJson());
|
||||
|
||||
QList<LogEntry*> newBlock;
|
||||
QList<QVariant> 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 thingId;
|
||||
if (m_engine->jsonRpcClient()->ensureServerVersion("5.0")) {
|
||||
thingId = entryMap.value("thingId").toString();
|
||||
} else {
|
||||
thingId = entryMap.value("deviceId").toString();
|
||||
}
|
||||
QString typeId = entryMap.value("typeId").toString();
|
||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
||||
LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray());
|
||||
LogEntry::LoggingSource loggingSource = static_cast<LogEntry::LoggingSource>(sourceEnum.keyToValue(entryMap.value("source").toByteArray()));
|
||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||
LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray());
|
||||
LogEntry::LoggingEventType loggingEventType = static_cast<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);
|
||||
m_list.append(entry);
|
||||
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this);
|
||||
newBlock.append(entry);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
emit countChanged();
|
||||
// qDebug() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count();
|
||||
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
}
|
||||
|
||||
void LogsModel::fetchEarlierReply(const QVariantMap &data)
|
||||
{
|
||||
// qDebug() << "logs reply" << data;
|
||||
|
||||
QList<QVariant> logEntries = data.value("params").toMap().value("logEntries").toList();
|
||||
QList<LogEntry*> newEntries;
|
||||
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>();
|
||||
LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray());
|
||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||
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);
|
||||
newEntries.append(entry);
|
||||
if (count < m_blockSize) {
|
||||
m_canFetchMore = false;
|
||||
}
|
||||
|
||||
if (newBlock.isEmpty()) {
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1);
|
||||
for (int i = 0; i < newBlock.count(); i++) {
|
||||
LogEntry *entry = newBlock.at(i);
|
||||
m_list.insert(offset + i, entry);
|
||||
emit logEntryAdded(entry);
|
||||
}
|
||||
beginInsertRows(QModelIndex(), 0, newEntries.count() - 1);
|
||||
newEntries.append(m_list);
|
||||
m_list = newEntries;
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
|
||||
if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && m_canFetchMore) {
|
||||
fetchMore();
|
||||
}
|
||||
}
|
||||
|
||||
void LogsModel::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
if (!m_engine) {
|
||||
qWarning() << "Cannot update. Engine not set";
|
||||
return;
|
||||
}
|
||||
if (m_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!m_startTime.isNull() && m_endTime.isNull()) || (m_startTime.isNull() && !m_endTime.isNull())) {
|
||||
qDebug() << "Need neither or both, startTime and endTime set";
|
||||
return;
|
||||
}
|
||||
|
||||
m_busy = true;
|
||||
emit busyChanged();
|
||||
|
||||
|
||||
QVariantMap params;
|
||||
if (!m_thingId.isNull()) {
|
||||
QVariantList thingIds;
|
||||
thingIds.append(m_thingId);
|
||||
if (m_engine->jsonRpcClient()->ensureServerVersion("5.0")) {
|
||||
params.insert("thingIds", thingIds);
|
||||
} else {
|
||||
params.insert("deviceIds", thingIds);
|
||||
}
|
||||
}
|
||||
if (!m_typeIds.isEmpty()) {
|
||||
QVariantList typeIds;
|
||||
foreach (const QUuid &typeId, m_typeIds) {
|
||||
typeIds.append(typeId);
|
||||
}
|
||||
params.insert("typeIds", typeIds);
|
||||
}
|
||||
if (!m_startTime.isNull() && !m_endTime.isNull()) {
|
||||
QVariantList timeFilters;
|
||||
QVariantMap timeFilter;
|
||||
timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch());
|
||||
timeFilter.insert("endDate", m_endTime.toSecsSinceEpoch());
|
||||
timeFilters.append(timeFilter);
|
||||
params.insert("timeFilters", timeFilters);
|
||||
}
|
||||
|
||||
params.insert("limit", m_blockSize);
|
||||
params.insert("offset", m_list.count());
|
||||
|
||||
qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize;
|
||||
|
||||
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
|
||||
// qDebug() << "GetLogEntries called";
|
||||
}
|
||||
|
||||
void LogsModel::classBegin()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LogsModel::componentComplete()
|
||||
{
|
||||
m_busy = false;
|
||||
fetchMore();
|
||||
}
|
||||
|
||||
bool LogsModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
// qDebug() << "canFetchMore" << (m_engine && m_canFetchMore);
|
||||
return m_engine && m_canFetchMore;
|
||||
}
|
||||
|
||||
void LogsModel::newLogEntryReceived(const QVariantMap &data)
|
||||
{
|
||||
// qDebug() << "***** model NG" << data << m_live;
|
||||
if (!m_live) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap entryMap = data;
|
||||
QString deviceId = entryMap.value("deviceId").toString();
|
||||
if (!m_deviceId.isNull() && deviceId != m_deviceId) {
|
||||
QUuid thingId;
|
||||
if (m_engine->jsonRpcClient()->ensureServerVersion("5.0")) {
|
||||
thingId = entryMap.value("deviceId").toUuid();
|
||||
} else {
|
||||
thingId = entryMap.value("thingId").toUuid();
|
||||
}
|
||||
if (!m_thingId.isNull() && thingId != m_thingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString typeId = entryMap.value("typeId").toString();
|
||||
QUuid typeId = entryMap.value("typeId").toUuid();
|
||||
if (!m_typeIds.isEmpty() && !m_typeIds.contains(typeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
|
||||
beginInsertRows(QModelIndex(), 0, 0);
|
||||
QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong());
|
||||
QMetaEnum sourceEnum = QMetaEnum::fromType<LogEntry::LoggingSource>();
|
||||
LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray());
|
||||
LogEntry::LoggingSource loggingSource = static_cast<LogEntry::LoggingSource>(sourceEnum.keyToValue(entryMap.value("source").toByteArray()));
|
||||
QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType<LogEntry::LoggingEventType>();
|
||||
LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray());
|
||||
LogEntry::LoggingEventType loggingEventType = static_cast<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);
|
||||
m_list.append(entry);
|
||||
LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this);
|
||||
m_list.prepend(entry);
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
|
||||
emit logEntryAdded(entry);
|
||||
|
||||
}
|
||||
|
||||
@ -32,26 +32,26 @@
|
||||
#define LOGSMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlParserStatus>
|
||||
|
||||
#include "jsonrpc/jsonhandler.h"
|
||||
#include "types/logentry.h"
|
||||
|
||||
class Engine;
|
||||
|
||||
class LogsModel : public QAbstractListModel
|
||||
class LogsModel : public QAbstractListModel, public QQmlParserStatus
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
|
||||
|
||||
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(bool busy READ busy NOTIFY busyChanged)
|
||||
Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged)
|
||||
Q_PROPERTY(QUuid thingId READ thingId WRITE setThingId NOTIFY thingIdChanged)
|
||||
Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged)
|
||||
Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
||||
Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
||||
// Q_PROPERTY(int paginationCount READ paginationCount WRITE setPaginationCount NOTIFY paginationCountChanged)
|
||||
|
||||
Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged)
|
||||
Q_PROPERTY(QDateTime viewStartTime READ viewStartTime WRITE setViewStartTime NOTIFY viewStartTimeChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
@ -72,12 +72,16 @@ public:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent = QModelIndex()) override;
|
||||
void classBegin() override;
|
||||
void componentComplete() override;
|
||||
|
||||
bool live() const;
|
||||
void setLive(bool live);
|
||||
|
||||
QString deviceId() const;
|
||||
void setDeviceId(const QString &deviceId);
|
||||
QUuid thingId() const;
|
||||
void setThingId(const QUuid &deviceId);
|
||||
|
||||
QStringList typeIds() const;
|
||||
void setTypeIds(const QStringList &typeIds);
|
||||
@ -88,44 +92,43 @@ public:
|
||||
QDateTime endTime() const;
|
||||
void setEndTime(const QDateTime &endTime);
|
||||
|
||||
// int paginationCount() const;
|
||||
// void setPaginationCount(int paginationCount);
|
||||
QDateTime viewStartTime() const;
|
||||
void setViewStartTime(const QDateTime &viewStartTime);
|
||||
|
||||
Q_INVOKABLE LogEntry* get(int index) const;
|
||||
|
||||
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
|
||||
|
||||
signals:
|
||||
void engineChanged();
|
||||
void busyChanged();
|
||||
void liveChanged();
|
||||
void countChanged();
|
||||
void deviceIdChanged();
|
||||
void thingIdChanged();
|
||||
void typeIdsChanged();
|
||||
void startTimeChanged();
|
||||
void endTimeChanged();
|
||||
// void paginationCountChanged();
|
||||
void viewStartTimeChanged();
|
||||
|
||||
public slots:
|
||||
virtual void update();
|
||||
virtual void fetchEarlier(int hours);
|
||||
// virtual void fetchLater(int hours);
|
||||
void logEntryAdded(LogEntry *entry);
|
||||
|
||||
private slots:
|
||||
virtual void logsReply(const QVariantMap &data);
|
||||
virtual void fetchEarlierReply(const QVariantMap &data);
|
||||
void newLogEntryReceived(const QVariantMap &data);
|
||||
|
||||
protected:
|
||||
Engine *m_engine = nullptr;
|
||||
QList<LogEntry*> m_list;
|
||||
QString m_deviceId;
|
||||
QStringList m_typeIds;
|
||||
QDateTime m_startTime = QDateTime::currentDateTime().addDays(-1);
|
||||
QDateTime m_endTime = QDateTime::currentDateTime();
|
||||
QUuid m_thingId;
|
||||
QList<QUuid> m_typeIds;
|
||||
QDateTime m_startTime;
|
||||
QDateTime m_endTime;
|
||||
QDateTime m_viewStartTime;
|
||||
|
||||
bool m_busy = false;
|
||||
bool m_busy = true;
|
||||
bool m_live = false;
|
||||
int m_blockSize = 100;
|
||||
|
||||
bool m_canFetchMore = true;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -278,7 +278,7 @@ void LogsModelNg::logsReply(const QVariantMap &data)
|
||||
continue;
|
||||
}
|
||||
|
||||
StateType *entryStateType = dev->deviceClass()->stateTypes()->getStateType(entry->typeId());
|
||||
StateType *entryStateType = dev->thingClass()->stateTypes()->getStateType(entry->typeId());
|
||||
|
||||
if (m_graphSeries) {
|
||||
if (entryStateType->type().toLower() == "bool") {
|
||||
@ -453,9 +453,9 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data)
|
||||
|
||||
Device *dev = m_engine->thingManager()->devices()->getDevice(entry->thingId());
|
||||
|
||||
StateType *entryStateType = dev->deviceClass()->stateTypes()->getStateType(entry->typeId());
|
||||
StateType *entryStateType = dev->thingClass()->stateTypes()->getStateType(entry->typeId());
|
||||
|
||||
if (dev && dev->deviceClass()->stateTypes()->getStateType(entry->typeId())->type().toLower() == "bool") {
|
||||
if (dev && dev->thingClass()->stateTypes()->getStateType(entry->typeId())->type().toLower() == "bool") {
|
||||
// First, remove the 2 rightmost (newest on the timeline) values. They're the ones in the future we added to extend the graph and making it end at 1
|
||||
if (m_graphSeries->count() > 1) {
|
||||
m_graphSeries->removePoints(0, 2);
|
||||
|
||||
@ -1,182 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "valuelogsproxymodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
ValueLogsProxyModel::ValueLogsProxyModel(QObject *parent) : LogsModel(parent)
|
||||
{
|
||||
m_minimumValue = QVariant(0);
|
||||
m_maximumValue = QVariant(0);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
qDebug() << "logs reply";
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_minimumValue = QVariant();
|
||||
m_maximumValue = QVariant();
|
||||
|
||||
int stepSize = 1;
|
||||
switch (m_average) {
|
||||
case AverageMonth:
|
||||
stepSize *= 30;
|
||||
// fall through
|
||||
case AverageDay:
|
||||
stepSize *= 8;
|
||||
// fall through
|
||||
case AverageDayTime:
|
||||
stepSize *= 3;
|
||||
// fall through
|
||||
case AverageHourly:
|
||||
stepSize *= 4;
|
||||
// fall through
|
||||
case AverageQuarterHour:
|
||||
stepSize *= 15;
|
||||
// fall through
|
||||
case AverageMinute:
|
||||
stepSize *= 60;
|
||||
}
|
||||
int totalSlots = startTime().secsTo(endTime()) / stepSize;
|
||||
qDebug() << "slots" << totalSlots;
|
||||
|
||||
QHash<int, QList<QVariant> > entries;
|
||||
|
||||
QList<QVariant> 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<QVariant> tmp = entries[slot];
|
||||
QVariant value = entryMap.value("value");
|
||||
value.convert(QVariant::Double);
|
||||
tmp.append(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<QVariant> 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().toDouble();
|
||||
} else if (m_list.count() > 0){
|
||||
avg = m_list.last()->value().toDouble();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
LogEntry *entry = new LogEntry(startTime().addSecs(stepSize * i)/*.addSecs(stepSize * .5)*/, avg, m_deviceId, QString(), LogEntry::LoggingSourceStates, LogEntry::LoggingEventTypeTrigger, this);
|
||||
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());
|
||||
}
|
||||
// qDebug() << "filling slot" << i << "average:" << avg << entry->timestamp().toString() << "min:" << m_minimumValue << "max:" << m_maximumValue;
|
||||
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
|
||||
if (m_minimumValue.isNull()) {
|
||||
m_minimumValue = 0;
|
||||
}
|
||||
if (m_maximumValue.isNull()) {
|
||||
m_maximumValue = 0;
|
||||
}
|
||||
|
||||
emit minimumValueChanged();
|
||||
emit maximumValueChanged();
|
||||
emit countChanged();
|
||||
qDebug() << "min" << minimumValue() << "max" << maximumValue();
|
||||
|
||||
m_busy = false;
|
||||
emit busyChanged();
|
||||
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef VALUELOGSPROXYMODEL_H
|
||||
#define VALUELOGSPROXYMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#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
|
||||
178
libnymea-app/models/xyseriesadapter.cpp
Normal file
178
libnymea-app/models/xyseriesadapter.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
#include "xyseriesadapter.h"
|
||||
|
||||
XYSeriesAdapter::XYSeriesAdapter(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
LogsModel *XYSeriesAdapter::logsModel() const
|
||||
{
|
||||
return m_model;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::setLogsModel(LogsModel *logsModel)
|
||||
{
|
||||
if (m_model != logsModel) {
|
||||
m_model = logsModel;
|
||||
emit logsModelChanged();
|
||||
// update();
|
||||
connect(logsModel, &LogsModel::logEntryAdded, this, &XYSeriesAdapter::logEntryAdded);
|
||||
}
|
||||
}
|
||||
|
||||
QtCharts::QXYSeries *XYSeriesAdapter::xySeries() const
|
||||
{
|
||||
return m_series;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::setXySeries(QtCharts::QXYSeries *series)
|
||||
{
|
||||
if (m_series != series) {
|
||||
m_series = series;
|
||||
emit xySeriesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QtCharts::QXYSeries *XYSeriesAdapter::baseSeries() const
|
||||
{
|
||||
return m_baseSeries;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::setBaseSeries(QtCharts::QXYSeries *series)
|
||||
{
|
||||
if (m_baseSeries != series) {
|
||||
m_baseSeries = series;
|
||||
emit baseSeriesChanged();
|
||||
|
||||
connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){
|
||||
if (m_series->count() > index) {
|
||||
m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index));
|
||||
}
|
||||
});
|
||||
connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){
|
||||
if (m_series->count() > index) {
|
||||
m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesAdapter::SampleRate XYSeriesAdapter::sampleRate() const
|
||||
{
|
||||
return m_sampleRate;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::setSampleRate(XYSeriesAdapter::SampleRate sampleRate)
|
||||
{
|
||||
if (m_sampleRate != sampleRate) {
|
||||
m_sampleRate = sampleRate;
|
||||
emit sampleRateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool XYSeriesAdapter::smooth() const
|
||||
{
|
||||
return m_smooth;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::setSmooth(bool smooth)
|
||||
{
|
||||
if (m_smooth != smooth) {
|
||||
m_smooth = smooth;
|
||||
emit smoothChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qreal XYSeriesAdapter::maxValue() const
|
||||
{
|
||||
return m_maxValue;
|
||||
}
|
||||
|
||||
qreal XYSeriesAdapter::minValue() const
|
||||
{
|
||||
return m_minValue;
|
||||
}
|
||||
|
||||
void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
|
||||
{
|
||||
if (!m_series) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_samples.isEmpty()) {
|
||||
Sample *sample = new Sample();
|
||||
sample->timestamp = entry->timestamp().addSecs(m_sampleRate);
|
||||
sample->entries.append(entry);
|
||||
sample->last = entry;
|
||||
m_newestSample = sample->timestamp;
|
||||
m_oldestSample = m_newestSample;
|
||||
m_samples.append(sample);
|
||||
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), entry->value().toDouble()));
|
||||
return;
|
||||
}
|
||||
|
||||
while (entry->timestamp() > m_newestSample) {
|
||||
Sample *sample = new Sample();
|
||||
sample->timestamp = m_newestSample.addSecs(m_sampleRate);
|
||||
sample->last = m_samples.value(0)->last;
|
||||
m_newestSample = sample->timestamp;
|
||||
m_samples.prepend(sample);
|
||||
m_series->insert(0, QPointF(sample->timestamp.toMSecsSinceEpoch(), sample->last->value().toDouble()));
|
||||
}
|
||||
|
||||
while (entry->timestamp() < m_oldestSample.addSecs(m_sampleRate)) {
|
||||
Sample *sample = new Sample();
|
||||
sample->timestamp = m_oldestSample.addSecs(-m_sampleRate);
|
||||
sample->last = entry;
|
||||
m_oldestSample = sample->timestamp;
|
||||
m_samples.append(sample);
|
||||
m_series->append(sample->timestamp.toMSecsSinceEpoch(), sample->last->value().toDouble());
|
||||
}
|
||||
|
||||
|
||||
int idx = entry->timestamp().secsTo(m_newestSample) / m_sampleRate;
|
||||
if (idx > m_samples.count()) {
|
||||
qWarning() << "Overflowing integer size for XYSeriesAdapter!";
|
||||
return;
|
||||
}
|
||||
Sample *sample = m_samples.at(static_cast<int>(idx));
|
||||
sample->entries.append(entry);
|
||||
|
||||
qreal value = calculateSampleValue(idx);
|
||||
m_series->replace(idx, sample->timestamp.toMSecsSinceEpoch(), value);
|
||||
|
||||
if (value < m_minValue) {
|
||||
m_minValue = value;
|
||||
emit minValueChanged();
|
||||
}
|
||||
if (value > m_maxValue) {
|
||||
m_maxValue = value;
|
||||
emit maxValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qreal XYSeriesAdapter::calculateSampleValue(int index)
|
||||
{
|
||||
Sample *sample = m_samples.at(index);
|
||||
qreal value = 0;
|
||||
int count = 0;
|
||||
if (m_samples.length() > index + 1) {
|
||||
Sample *previousSample = m_samples.at(static_cast<int>(index) + 1);
|
||||
value = previousSample->last->value().toDouble();
|
||||
count++;
|
||||
}
|
||||
foreach (LogEntry *entry, sample->entries) {
|
||||
value += entry->value().toDouble();
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
value /= count;
|
||||
}
|
||||
|
||||
if (m_baseSeries && m_baseSeries->count() > index) {
|
||||
value += m_baseSeries->at(index).y();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
87
libnymea-app/models/xyseriesadapter.h
Normal file
87
libnymea-app/models/xyseriesadapter.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef XYSERIESADAPTER_H
|
||||
#define XYSERIESADAPTER_H
|
||||
|
||||
#include "logsmodel.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QXYSeries>
|
||||
|
||||
class XYSeriesAdapter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(LogsModel* logsModel READ logsModel WRITE setLogsModel NOTIFY logsModelChanged)
|
||||
Q_PROPERTY(QtCharts::QXYSeries* xySeries READ xySeries WRITE setXySeries NOTIFY xySeriesChanged)
|
||||
Q_PROPERTY(QtCharts::QXYSeries* baseSeries READ baseSeries WRITE setBaseSeries NOTIFY baseSeriesChanged)
|
||||
|
||||
Q_PROPERTY(SampleRate sampleRate READ sampleRate WRITE setSampleRate NOTIFY sampleRateChanged)
|
||||
Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged)
|
||||
|
||||
Q_PROPERTY(qreal maxValue READ maxValue NOTIFY maxValueChanged)
|
||||
Q_PROPERTY(qreal minValue READ minValue NOTIFY minValueChanged)
|
||||
|
||||
public:
|
||||
enum SampleRate {
|
||||
SampleRateSecond = 1,
|
||||
SampleRateMinute = 60,
|
||||
SampleRateHour = 60 * 60,
|
||||
SampleRateDays = 24 * 60 * 60
|
||||
};
|
||||
Q_ENUM(SampleRate)
|
||||
|
||||
explicit XYSeriesAdapter(QObject *parent = nullptr);
|
||||
|
||||
LogsModel* logsModel() const;
|
||||
void setLogsModel(LogsModel *logsModel);
|
||||
|
||||
QtCharts::QXYSeries* xySeries() const;
|
||||
void setXySeries(QtCharts::QXYSeries *series);
|
||||
|
||||
QtCharts::QXYSeries* baseSeries() const;
|
||||
void setBaseSeries(QtCharts::QXYSeries *series);
|
||||
|
||||
SampleRate sampleRate() const;
|
||||
void setSampleRate(SampleRate sampleRate);
|
||||
|
||||
bool smooth() const;
|
||||
void setSmooth(bool smooth);
|
||||
|
||||
qreal maxValue() const;
|
||||
qreal minValue() const;
|
||||
|
||||
signals:
|
||||
void xySeriesChanged();
|
||||
void logsModelChanged();
|
||||
void baseSeriesChanged();
|
||||
void sampleRateChanged();
|
||||
void smoothChanged();
|
||||
void maxValueChanged();
|
||||
void minValueChanged();
|
||||
|
||||
private slots:
|
||||
void logEntryAdded(LogEntry *entry);
|
||||
|
||||
private:
|
||||
qreal calculateSampleValue(int index);
|
||||
|
||||
private:
|
||||
class Sample {
|
||||
public:
|
||||
QDateTime timestamp; // The timestamp where this sample *ends*
|
||||
QList<LogEntry*> entries; // all log entries in this sample, that is, from timestamp - m_sampleRate
|
||||
LogEntry *last = nullptr;
|
||||
};
|
||||
LogsModel* m_model = nullptr;
|
||||
QtCharts::QXYSeries* m_series = nullptr;
|
||||
QtCharts::QXYSeries* m_baseSeries = nullptr;
|
||||
SampleRate m_sampleRate = SampleRateSecond;
|
||||
bool m_smooth = true;
|
||||
|
||||
QVector<Sample*> m_samples;
|
||||
QDateTime m_newestSample;
|
||||
QDateTime m_oldestSample;
|
||||
|
||||
qreal m_maxValue = 0;
|
||||
qreal m_minValue = 0;
|
||||
};
|
||||
|
||||
#endif // XYSERIESADAPTER_H
|
||||
@ -302,7 +302,7 @@ bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelInde
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_filterDevicesProxy->rowCount(); i++) {
|
||||
// qDebug() << "Checking device:" << m_filterDevicesProxy->get(i)->deviceClass()->interfaces();
|
||||
if (m_filterDevicesProxy->get(i)->deviceClass()->interfaces().contains(toBeFound)) {
|
||||
if (m_filterDevicesProxy->get(i)->thingClass()->interfaces().contains(toBeFound)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ void CodeCompletion::update()
|
||||
if (thingIdExp.exactMatch(blockText)) {
|
||||
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
||||
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
||||
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->deviceClass()->interfaces().join(",")));
|
||||
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->thingClass()->interfaces().join(",")));
|
||||
}
|
||||
blockText.remove(QRegExp(".*thingId: \""));
|
||||
m_model->update(entries);
|
||||
@ -182,7 +182,7 @@ void CodeCompletion::update()
|
||||
if (deviceIdExp.exactMatch(blockText)) {
|
||||
for (int i = 0; i < m_engine->deviceManager()->devices()->rowCount(); i++) {
|
||||
Device *dev = m_engine->deviceManager()->devices()->get(i);
|
||||
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->deviceClass()->interfaces().join(",")));
|
||||
entries.append(CompletionModel::Entry(dev->id().toString() + "\" // " + dev->name(), dev->name(), "thing", dev->thingClass()->interfaces().join(",")));
|
||||
}
|
||||
blockText.remove(QRegExp(".*deviceId: \""));
|
||||
m_model->update(entries);
|
||||
@ -209,8 +209,8 @@ void CodeCompletion::update()
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *stateType = device->deviceClass()->stateTypes()->get(i);
|
||||
for (int i = 0; i < device->thingClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *stateType = device->thingClass()->stateTypes()->get(i);
|
||||
entries.append(CompletionModel::Entry(stateType->id().toString() + "\" // " + stateType->name(), stateType->name(), "stateType"));
|
||||
}
|
||||
blockText.remove(QRegExp(".*stateTypeId: \""));
|
||||
@ -241,8 +241,8 @@ void CodeCompletion::update()
|
||||
}
|
||||
qDebug() << "Device is" << device->name();
|
||||
|
||||
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *stateType = device->deviceClass()->stateTypes()->get(i);
|
||||
for (int i = 0; i < device->thingClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *stateType = device->thingClass()->stateTypes()->get(i);
|
||||
entries.append(CompletionModel::Entry(stateType->name() + "\"", stateType->name(), "stateType"));
|
||||
}
|
||||
blockText.remove(QRegExp(".*stateName: \""));
|
||||
@ -270,8 +270,8 @@ void CodeCompletion::update()
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device->deviceClass()->actionTypes()->rowCount(); i++) {
|
||||
ActionType *actionType = device->deviceClass()->actionTypes()->get(i);
|
||||
for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) {
|
||||
ActionType *actionType = device->thingClass()->actionTypes()->get(i);
|
||||
entries.append(CompletionModel::Entry(actionType->id().toString() + "\" // " + actionType->name(), actionType->name(), "actionType"));
|
||||
}
|
||||
blockText.remove(QRegExp(".*actionTypeId: \""));
|
||||
@ -299,8 +299,8 @@ void CodeCompletion::update()
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device->deviceClass()->actionTypes()->rowCount(); i++) {
|
||||
ActionType *actionType = device->deviceClass()->actionTypes()->get(i);
|
||||
for (int i = 0; i < device->thingClass()->actionTypes()->rowCount(); i++) {
|
||||
ActionType *actionType = device->thingClass()->actionTypes()->get(i);
|
||||
entries.append(CompletionModel::Entry(actionType->name() + "\"", actionType->name(), "actionType"));
|
||||
}
|
||||
blockText.remove(QRegExp(".*actionName: \""));
|
||||
@ -328,8 +328,8 @@ void CodeCompletion::update()
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device->deviceClass()->eventTypes()->rowCount(); i++) {
|
||||
EventType *eventType = device->deviceClass()->eventTypes()->get(i);
|
||||
for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) {
|
||||
EventType *eventType = device->thingClass()->eventTypes()->get(i);
|
||||
entries.append(CompletionModel::Entry(eventType->id().toString() + "\" // " + eventType->name(), eventType->name(), "eventType"));
|
||||
}
|
||||
blockText.remove(QRegExp(".*eventTypeId: \""));
|
||||
@ -358,8 +358,8 @@ void CodeCompletion::update()
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device->deviceClass()->eventTypes()->rowCount(); i++) {
|
||||
EventType *eventType = device->deviceClass()->eventTypes()->get(i);
|
||||
for (int i = 0; i < device->thingClass()->eventTypes()->rowCount(); i++) {
|
||||
EventType *eventType = device->thingClass()->eventTypes()->get(i);
|
||||
entries.append(CompletionModel::Entry(eventType->name() + "\"", eventType->name(), "eventType"));
|
||||
}
|
||||
blockText.remove(QRegExp(".*eventName: \""));
|
||||
|
||||
@ -389,9 +389,15 @@ void SystemController::notificationReceived(const QVariantMap &data)
|
||||
emit powerManagementAvailableChanged();
|
||||
emit updateManagementAvailableChanged();
|
||||
} else if (notification == "System.TimeConfigurationChanged") {
|
||||
qDebug() << "System time configuration changed";
|
||||
qDebug() << "System time configuration changed" << data.value("params").toMap().value("timeZone").toByteArray();
|
||||
m_serverTime = QDateTime::fromSecsSinceEpoch(data.value("params").toMap().value("time").toUInt());
|
||||
m_serverTime.setTimeZone(QTimeZone(data.value("params").toMap().value("timeZone").toByteArray()));
|
||||
|
||||
// NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android
|
||||
// Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string
|
||||
// https://bugreports.qt.io/browse/QTBUG-83438
|
||||
// m_serverTime.setTimeZone(QTimeZone(data.value("params").toMap().value("timeZone").toByteArray()));
|
||||
m_serverTimeZone = data.value("params").toMap().value("timeZone").toString();
|
||||
|
||||
emit serverTimeChanged();
|
||||
emit serverTimeZoneChanged();
|
||||
m_automaticTimeAvailable = data.value("params").toMap().value("automaticTimeAvailable").toBool();
|
||||
|
||||
@ -82,7 +82,7 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
|
||||
if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
|
||||
continue;
|
||||
}
|
||||
ActionType *actionType = device->deviceClass()->actionTypes()->findByName(actionName);
|
||||
ActionType *actionType = device->thingClass()->actionTypes()->findByName(actionName);
|
||||
if (!actionType) {
|
||||
continue;
|
||||
}
|
||||
@ -110,8 +110,8 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par
|
||||
|
||||
void ThingGroup::syncStates()
|
||||
{
|
||||
for (int i = 0; i < deviceClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *stateType = deviceClass()->stateTypes()->get(i);
|
||||
for (int i = 0; i < thingClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *stateType = thingClass()->stateTypes()->get(i);
|
||||
State *state = states()->getState(stateType->id());
|
||||
|
||||
qDebug() << "syncing state" << stateType->name();
|
||||
@ -121,13 +121,13 @@ void ThingGroup::syncStates()
|
||||
for (int j = 0; j < m_devices->rowCount(); j++) {
|
||||
Device *d = m_devices->get(j);
|
||||
// Skip things that don't have the required state
|
||||
StateType *ds = d->deviceClass()->stateTypes()->findByName(stateType->name());
|
||||
StateType *ds = d->thingClass()->stateTypes()->findByName(stateType->name());
|
||||
if (!ds) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip disconnected things
|
||||
StateType *connectedStateType = d->deviceClass()->stateTypes()->findByName("connected");
|
||||
StateType *connectedStateType = d->thingClass()->stateTypes()->findByName("connected");
|
||||
if (connectedStateType) {
|
||||
if (!d->stateValue(connectedStateType->id()).toBool()) {
|
||||
continue;
|
||||
|
||||
@ -34,11 +34,11 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Device::Device(DeviceManager *deviceManager, DeviceClass *deviceClass, const QUuid &parentDeviceId, QObject *parent) :
|
||||
Device::Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_deviceManager(deviceManager),
|
||||
m_parentDeviceId(parentDeviceId),
|
||||
m_deviceClass(deviceClass)
|
||||
m_parentId(parentId),
|
||||
m_thingClass(thingClass)
|
||||
{
|
||||
}
|
||||
|
||||
@ -65,22 +65,22 @@ void Device::setId(const QUuid &id)
|
||||
|
||||
QUuid Device::deviceClassId() const
|
||||
{
|
||||
return m_deviceClass->id();
|
||||
return m_thingClass->id();
|
||||
}
|
||||
|
||||
QUuid Device::thingClassId() const
|
||||
{
|
||||
return m_deviceClass->id();
|
||||
return m_thingClass->id();
|
||||
}
|
||||
|
||||
QUuid Device::parentDeviceId() const
|
||||
{
|
||||
return m_parentDeviceId;
|
||||
return m_parentId;
|
||||
}
|
||||
|
||||
bool Device::isChild() const
|
||||
{
|
||||
return !m_parentDeviceId.isNull();
|
||||
return !m_parentId.isNull();
|
||||
}
|
||||
|
||||
Device::DeviceSetupStatus Device::setupStatus() const
|
||||
@ -153,14 +153,23 @@ void Device::setStates(States *states)
|
||||
}
|
||||
}
|
||||
|
||||
DeviceClass *Device::deviceClass() const
|
||||
State *Device::state(const QUuid &stateTypeId) const
|
||||
{
|
||||
return m_deviceClass;
|
||||
return m_states->getState(stateTypeId);
|
||||
}
|
||||
|
||||
State *Device::stateByName(const QString &stateName) const
|
||||
{
|
||||
StateType *st = m_thingClass->stateTypes()->findByName(stateName);
|
||||
if (!st) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_states->getState(st->id());
|
||||
}
|
||||
|
||||
DeviceClass *Device::thingClass() const
|
||||
{
|
||||
return m_deviceClass;
|
||||
return m_thingClass;
|
||||
}
|
||||
|
||||
bool Device::hasState(const QUuid &stateTypeId)
|
||||
@ -195,7 +204,7 @@ void Device::setStateValue(const QUuid &stateTypeId, const QVariant &value)
|
||||
|
||||
int Device::executeAction(const QString &actionName, const QVariantList ¶ms)
|
||||
{
|
||||
ActionType *actionType = m_deviceClass->actionTypes()->findByName(actionName);
|
||||
ActionType *actionType = m_thingClass->actionTypes()->findByName(actionName);
|
||||
|
||||
QVariantList finalParams;
|
||||
foreach (const QVariant ¶mVariant, params) {
|
||||
@ -209,30 +218,30 @@ int Device::executeAction(const QString &actionName, const QVariantList ¶ms)
|
||||
return m_deviceManager->executeAction(m_id, actionType->id(), finalParams);
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug &dbg, Device *device)
|
||||
QDebug operator<<(QDebug &dbg, Device *thing)
|
||||
{
|
||||
dbg.nospace() << "Device: " << device->name() << " (" << device->id().toString() << ") Class:" << device->deviceClass()->name() << " (" << device->deviceClassId().toString() << ")" << endl;
|
||||
for (int i = 0; i < device->deviceClass()->paramTypes()->rowCount(); i++) {
|
||||
ParamType *pt = device->deviceClass()->paramTypes()->get(i);
|
||||
Param *p = device->params()->getParam(pt->id().toString());
|
||||
dbg.nospace() << "Thing: " << thing->name() << " (" << thing->id().toString() << ") Class:" << thing->thingClass()->name() << " (" << thing->thingClassId().toString() << ")" << endl;
|
||||
for (int i = 0; i < thing->thingClass()->paramTypes()->rowCount(); i++) {
|
||||
ParamType *pt = thing->thingClass()->paramTypes()->get(i);
|
||||
Param *p = thing->params()->getParam(pt->id().toString());
|
||||
if (p) {
|
||||
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
|
||||
} else {
|
||||
dbg << " Param " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < device->deviceClass()->settingsTypes()->rowCount(); i++) {
|
||||
ParamType *pt = device->deviceClass()->settingsTypes()->get(i);
|
||||
Param *p = device->settings()->getParam(pt->id().toString());
|
||||
for (int i = 0; i < thing->thingClass()->settingsTypes()->rowCount(); i++) {
|
||||
ParamType *pt = thing->thingClass()->settingsTypes()->get(i);
|
||||
Param *p = thing->settings()->getParam(pt->id().toString());
|
||||
if (p) {
|
||||
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << p->value() << endl;
|
||||
} else {
|
||||
dbg << " Setting " << i << ": " << pt->id().toString() << ": " << pt->name() << " = " << "*** Unknown value ***" << endl;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < device->deviceClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *st = device->deviceClass()->stateTypes()->get(i);
|
||||
State *s = device->states()->getState(st->id());
|
||||
for (int i = 0; i < thing->thingClass()->stateTypes()->rowCount(); i++) {
|
||||
StateType *st = thing->thingClass()->stateTypes()->get(i);
|
||||
State *s = thing->states()->getState(st->id());
|
||||
dbg << " State " << i << ": " << st->id() << ": " << st->name() << " = " << s->value() << endl;
|
||||
}
|
||||
return dbg;
|
||||
|
||||
@ -55,7 +55,7 @@ class Device : public QObject
|
||||
Q_PROPERTY(Params *params READ params NOTIFY paramsChanged)
|
||||
Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged)
|
||||
Q_PROPERTY(States *states READ states NOTIFY statesChanged)
|
||||
Q_PROPERTY(DeviceClass *deviceClass READ deviceClass CONSTANT)
|
||||
Q_PROPERTY(DeviceClass *deviceClass READ thingClass CONSTANT)
|
||||
Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT)
|
||||
|
||||
public:
|
||||
@ -67,7 +67,7 @@ public:
|
||||
};
|
||||
Q_ENUM(DeviceSetupStatus)
|
||||
|
||||
explicit Device(DeviceManager *deviceManager, DeviceClass *deviceClass, const QUuid &parentDeviceId = QUuid(), QObject *parent = nullptr);
|
||||
explicit Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId = QUuid(), QObject *parent = nullptr);
|
||||
|
||||
QUuid id() const;
|
||||
void setId(const QUuid &id);
|
||||
@ -93,10 +93,11 @@ public:
|
||||
States *states() const;
|
||||
void setStates(States *states);
|
||||
|
||||
DeviceClass *deviceClass() const;
|
||||
DeviceClass *thingClass() const;
|
||||
|
||||
Q_INVOKABLE bool hasState(const QUuid &stateTypeId);
|
||||
Q_INVOKABLE State *state(const QUuid &stateTypeId) const;
|
||||
Q_INVOKABLE State *stateByName(const QString &stateName) const;
|
||||
|
||||
Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId);
|
||||
void setStateValue(const QUuid &stateTypeId, const QVariant &value);
|
||||
@ -117,15 +118,15 @@ protected:
|
||||
DeviceManager *m_deviceManager = nullptr;
|
||||
QString m_name;
|
||||
QUuid m_id;
|
||||
QUuid m_parentDeviceId;
|
||||
QUuid m_parentId;
|
||||
DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone;
|
||||
QString m_setupDisplayMessage;
|
||||
Params *m_params = nullptr;
|
||||
Params *m_settings = nullptr;
|
||||
States *m_states = nullptr;
|
||||
DeviceClass *m_deviceClass = nullptr;
|
||||
DeviceClass *m_thingClass = nullptr;
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug &dbg, Device* device);
|
||||
QDebug operator<<(QDebug &dbg, Device* thing);
|
||||
|
||||
#endif // DEVICE_H
|
||||
|
||||
@ -99,7 +99,7 @@
|
||||
<file>ui/images/media-seek-forward.svg</file>
|
||||
<file>ui/images/media-skip-backward.svg</file>
|
||||
<file>ui/images/media-skip-forward.svg</file>
|
||||
<file>ui/images/mediaplayer-app-symbolic.svg</file>
|
||||
<file>ui/images/media.svg</file>
|
||||
<file>ui/images/navigation-menu.svg</file>
|
||||
<file>ui/images/network-secure.svg</file>
|
||||
<file>ui/images/network-vpn.svg</file>
|
||||
|
||||
@ -50,18 +50,13 @@
|
||||
<file>ui/customviews/CustomViewBase.qml</file>
|
||||
<file>ui/customviews/WeatherView.qml</file>
|
||||
<file>ui/customviews/MediaControllerView.qml</file>
|
||||
<file>ui/customviews/SensorView.qml</file>
|
||||
<file>ui/customviews/NotificationsView.qml</file>
|
||||
<file>ui/customviews/ExtendedVolumeController.qml</file>
|
||||
<file>ui/devicepages/MediaDevicePage.qml</file>
|
||||
<file>ui/devicepages/ButtonDevicePage.qml</file>
|
||||
<file>ui/devicepages/GenericDevicePage.qml</file>
|
||||
<file>ui/devicepages/WeatherDevicePagePre110.qml</file>
|
||||
<file>ui/devicepages/WeatherDevicePagePost110.qml</file>
|
||||
<file>ui/devicepages/WeatherDevicePage.qml</file>
|
||||
<file>ui/devicepages/SensorDevicePage.qml</file>
|
||||
<file>ui/devicepages/SensorDevicePagePre110.qml</file>
|
||||
<file>ui/devicepages/SensorDevicePagePost110.qml</file>
|
||||
<file>ui/devicepages/DevicePageBase.qml</file>
|
||||
<file>ui/devicepages/InputTriggerDevicePage.qml</file>
|
||||
<file>ui/devicepages/StateLogPage.qml</file>
|
||||
@ -134,7 +129,6 @@
|
||||
<file>../LICENSE.OFL</file>
|
||||
<file>../LICENSE.OpenSSL</file>
|
||||
<file>../LICENSE.LGPL3</file>
|
||||
<file>ui/customviews/GenericTypeGraphPre110.qml</file>
|
||||
<file>ui/customviews/GenericTypeGraph.qml</file>
|
||||
<file>ui/devicepages/SmartMeterDevicePage.qml</file>
|
||||
<file>ui/devicelistpages/SmartMeterDeviceListPage.qml</file>
|
||||
@ -222,5 +216,8 @@
|
||||
<file>ui/mainviews/EnergyView.qml</file>
|
||||
<file>ui/components/MainViewBase.qml</file>
|
||||
<file>ui/components/SmartMeterChart.qml</file>
|
||||
<file>ui/mainviews/MediaView.qml</file>
|
||||
<file>ui/components/ShuffleRepeatVolumeControl.qml</file>
|
||||
<file>ui/components/MediaBrowser.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -120,6 +120,7 @@ Page {
|
||||
ListElement { name: "scenes"; source: "ScenesView"; displayName: qsTr("Scenes"); icon: "slideshow" }
|
||||
ListElement { name: "garages"; source: "GaragesView"; displayName: qsTr("Garages"); icon: "garage/garage-100" }
|
||||
ListElement { name: "energy"; source: "EnergyView"; displayName: qsTr("Energy"); icon: "smartmeter" }
|
||||
ListElement { name: "media"; source: "MediaView"; displayName: qsTr("Media"); icon: "media" }
|
||||
}
|
||||
|
||||
ListModel {
|
||||
|
||||
@ -259,7 +259,7 @@ ApplicationWindow {
|
||||
case "mediacontroller":
|
||||
case "extendedmediacontroller":
|
||||
case "mediaplayer":
|
||||
return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg")
|
||||
return Qt.resolvedUrl("images/media.svg")
|
||||
case "powersocket":
|
||||
return Qt.resolvedUrl("images/powersocket.svg")
|
||||
case "button":
|
||||
|
||||
@ -36,13 +36,13 @@ import Nymea 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property Device device: null
|
||||
property Thing thing: null
|
||||
|
||||
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
|
||||
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
|
||||
readonly property StateType artworkStateType: thing ? thing.thingClass.stateTypes.findByName("artwork") : null
|
||||
readonly property State artworkState: artworkStateType ? thing.states.getState(artworkStateType.id) : null
|
||||
|
||||
readonly property StateType playerTypeStateType: device ? device.deviceClass.stateTypes.findByName("playerType") : null
|
||||
readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null
|
||||
readonly property StateType playerTypeStateType: thing ? thing.thingClass.stateTypes.findByName("playerType") : null
|
||||
readonly property State playerTypeState: playerTypeStateType ? thing.states.getState(playerTypeStateType.id) : null
|
||||
|
||||
Pane {
|
||||
Material.elevation: 2
|
||||
|
||||
101
nymea-app/ui/components/MediaBrowser.qml
Normal file
101
nymea-app/ui/components/MediaBrowser.qml
Normal file
@ -0,0 +1,101 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtGraphicalEffects 1.0
|
||||
import Nymea 1.0
|
||||
import "../delegates"
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Thing thing: null
|
||||
|
||||
function backPressed() {
|
||||
if (internalPageStack.depth > 1) {
|
||||
internalPageStack.pop();
|
||||
} else {
|
||||
swipeView.currentIndex--
|
||||
}
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: internalPageStack
|
||||
anchors.fill: parent
|
||||
initialItem: internalBrowserPage
|
||||
|
||||
Component {
|
||||
id: internalBrowserPage
|
||||
ListView {
|
||||
id: listView
|
||||
model: browserItems
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
property string nodeId: ""
|
||||
|
||||
// Need to keep a explicit property here or the GC will eat it too early
|
||||
property BrowserItems browserItems: null
|
||||
Component.onCompleted: {
|
||||
browserItems = engine.thingManager.browseDevice(root.thing.id, nodeId);
|
||||
}
|
||||
|
||||
delegate: BrowserItemDelegate {
|
||||
iconName: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg"
|
||||
busy: d.pendingItemId === model.id
|
||||
device: root.thing
|
||||
|
||||
onClicked: {
|
||||
print("clicked:", model.id)
|
||||
if (model.executable) {
|
||||
root.executeBrowserItem(model.id)
|
||||
} else if (model.browsable) {
|
||||
internalPageStack.push(internalBrowserPage, {device: root.thing, nodeId: model.id})
|
||||
}
|
||||
}
|
||||
|
||||
onContextMenuActionTriggered: {
|
||||
root.executeBrowserItemAction(model.id, actionTypeId, params)
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: listView.model.busy
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,15 +38,18 @@ RowLayout {
|
||||
id: root
|
||||
implicitHeight: iconSize + app.margins
|
||||
|
||||
property Device device: null
|
||||
property Thing thing: null
|
||||
property int iconSize: app.iconSize * 1.5
|
||||
|
||||
readonly property StateType playbackStateType: device ? device.deviceClass.stateTypes.findByName("playbackStatus") : null
|
||||
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
|
||||
readonly property StateType playbackStateType: thing ? thing.thingClass.stateTypes.findByName("playbackStatus") : null
|
||||
readonly property State playbackState: playbackStateType ? thing.states.getState(playbackStateType.id) : null
|
||||
|
||||
function executeAction(actionName, params) {
|
||||
var actionTypeId = device.deviceClass.actionTypes.findByName(actionName).id;
|
||||
engine.deviceManager.executeAction(device.id, actionTypeId, params)
|
||||
if (params === undefined) {
|
||||
params = []
|
||||
}
|
||||
var actionTypeId = thing.thingClass.actionTypes.findByName(actionName).id;
|
||||
engine.thingManager.executeAction(thing.id, actionTypeId, params)
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
@ -68,7 +71,7 @@ RowLayout {
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: root,iconSize
|
||||
Layout.preferredHeight: root.iconSize
|
||||
Layout.preferredWidth: height
|
||||
imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
|
||||
longpressImageSource: "../images/media-playback-stop.svg"
|
||||
|
||||
145
nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
Normal file
145
nymea-app/ui/components/ShuffleRepeatVolumeControl.qml
Normal file
@ -0,0 +1,145 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtCharts 2.2
|
||||
import Nymea 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property Thing thing: null
|
||||
|
||||
property State repeatState: thing.stateByName("repeat")
|
||||
property State shuffleState: thing.stateByName("shuffle")
|
||||
|
||||
property State volumeState: thing.stateByName("volume")
|
||||
property State muteState: thing.stateByName("mute")
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.fillWidth: true
|
||||
visible: root.repeatState !== null
|
||||
|
||||
HeaderButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: root.repeatState.value === "One" ? "../images/media-playlist-repeat-one.svg" : "../images/media-playlist-repeat.svg"
|
||||
color: root.repeatState.value === "None" ? keyColor : app.accentColor
|
||||
property var allowedValues: ["None", "All", "One"]
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.repeatState.stateTypeId;
|
||||
param["value"] = allowedValues[(allowedValues.indexOf(root.repeatState.value) + 1) % 3]
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.repeatState.stateTypeId, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.fillWidth: true
|
||||
visible: root.shuffleState !== null
|
||||
|
||||
HeaderButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: "../images/media-playlist-shuffle.svg"
|
||||
color: root.shuffleState.value === true ? app.accentColor: keyColor
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.shuffleState.stateTypeId
|
||||
param["value"] = !root.shuffleState.value
|
||||
params.push(param)
|
||||
engine.thingManager.executeAction(root.thing.id, root.shuffleState.stateTypeId, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: volumeButtonContainer
|
||||
Layout.fillWidth: true; Layout.fillHeight: true
|
||||
HeaderButton {
|
||||
id: volumeButton
|
||||
anchors.centerIn: parent
|
||||
imageSource: "../images/audio-speakers-symbolic.svg"
|
||||
onClicked: {
|
||||
print(volumeButton.x, volumeButton.y)
|
||||
print(Qt.point(volumeButton.x, volumeButton.y))
|
||||
print(volumeButton.mapToItem(root, volumeButton.x,0))
|
||||
var buttonPosition = root.mapFromItem(volumeButtonContainer, volumeButton.x, 0)
|
||||
var sliderHeight = 200
|
||||
var props = {}
|
||||
props["x"] = buttonPosition.x
|
||||
props["y"] = buttonPosition.y - sliderHeight
|
||||
props["height"] = sliderHeight
|
||||
var sliderPane = volumeSliderPaneComponent.createObject(root, props)
|
||||
sliderPane.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: volumeSliderPaneComponent
|
||||
Dialog {
|
||||
id: volumeSliderDialog
|
||||
|
||||
leftPadding: 0
|
||||
topPadding: app.margins / 2
|
||||
rightPadding: 0
|
||||
bottomPadding: app.margins / 2
|
||||
modal: true
|
||||
|
||||
property int pendingVolumeValue: -1
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
ThrottledSlider {
|
||||
Layout.fillHeight: true
|
||||
from: 0
|
||||
to: 100
|
||||
value: root.volumeState.value
|
||||
orientation: Qt.Vertical
|
||||
onMoved: engine.thingManager.executeAction(root.thing.id, root.volumeState.stateTypeId, [{paramTypeId: root.volumeState.stateTypeId, value: value}])
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
imageSource: "../images/audio-speakers-muted-symbolic.svg"
|
||||
color: root.muteState.value === true ? app.accentColor : keyColor
|
||||
onClicked: engine.thingManager.executeAction(root.thing.id, root.muteState.stateTypeId, [{paramTypeId: root.muteState.stateTypeId, value: !root.muteState.value}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,25 +64,19 @@ ChartView {
|
||||
function refresh() {
|
||||
pieSeries.clear();
|
||||
d.sliceMap = {}
|
||||
print("calculating", chart.multiplier)
|
||||
for (var i = 0; i < meters.count; i++) {
|
||||
var thing = meters.get(i);
|
||||
print("thing:", thing.name)
|
||||
var value = 0;
|
||||
var totalConsumedStateType = thing.thingClass.stateTypes.findByName("totalEnergyConsumed")
|
||||
if (totalConsumedStateType) {
|
||||
var totalConsumedState = thing.states.getState(totalConsumedStateType.id)
|
||||
value = value + (totalConsumedState.value * chart.multiplier)
|
||||
print("Adding", totalConsumedState.value * chart.multiplier, value)
|
||||
}
|
||||
var totalProducedStateType = thing.thingClass.stateTypes.findByName("totalEnergyProduced")
|
||||
if (totalProducedStateType) {
|
||||
var totalProducedState = thing.states.getState(totalProducedStateType.id)
|
||||
value = value - (totalProducedState.value * chart.multiplier)
|
||||
print("removing", totalProducedState.value * chart.multiplier, value)
|
||||
}
|
||||
print("consumed", totalConsumedState.value, "produced", totalProducedState.value)
|
||||
print("value", value)
|
||||
var slice = pieSeries.append(thing.name, Math.max(0, value))
|
||||
var color = app.accentColor
|
||||
for (var j = 0; j < i; j+=2) {
|
||||
|
||||
@ -36,6 +36,8 @@ Item {
|
||||
implicitHeight: slider.implicitHeight
|
||||
implicitWidth: slider.implicitWidth
|
||||
|
||||
property alias orientation: slider.orientation
|
||||
|
||||
property real value: 0
|
||||
property alias from: slider.from
|
||||
property alias to: slider.to
|
||||
@ -49,6 +51,7 @@ Item {
|
||||
Slider {
|
||||
id: slider
|
||||
anchors.left: parent.left; anchors.right: parent.right
|
||||
anchors.top: parent.top; anchors.bottom: parent.bottom
|
||||
from: 0
|
||||
to: 100
|
||||
property var lastSentTime: new Date()
|
||||
|
||||
@ -143,8 +143,8 @@ Item {
|
||||
}
|
||||
}
|
||||
min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
|
||||
onMinChanged: print("min set to", min)
|
||||
onMaxChanged: print("max set to", min)
|
||||
onMinChanged: applyNiceNumbers();
|
||||
onMaxChanged: applyNiceNumbers();
|
||||
labelsFont.pixelSize: app.smallFont
|
||||
labelFormat: {
|
||||
switch (root.stateType.type.toLowerCase()) {
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
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"
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var device: null
|
||||
property var stateType: null
|
||||
|
||||
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.accentColor
|
||||
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 10
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
graphModel.update()
|
||||
}
|
||||
}
|
||||
|
||||
model: ValueLogsProxyModel {
|
||||
id: graphModel
|
||||
deviceId: root.device.id
|
||||
typeIds: [stateType.id]
|
||||
average: zoomTabBar.currentItem.avg
|
||||
startTime: zoomTabBar.currentItem.startTime
|
||||
Component.onCompleted: updateTimer.start();
|
||||
onAverageChanged: updateTimer.start()
|
||||
onStartTimeChanged: updateTimer.start();
|
||||
engine: _engine
|
||||
|
||||
// Live doesn't work yet with ValueLogsProxyModel
|
||||
// live: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../components"
|
||||
import Nymea 1.0
|
||||
|
||||
CustomViewBase {
|
||||
id: root
|
||||
implicitHeight: grid.implicitHeight + app.margins * 2
|
||||
|
||||
property string interfaceName
|
||||
|
||||
readonly property string stateTypeName: {
|
||||
switch (interfaceName) {
|
||||
case "lightsensor":
|
||||
return "lightIntensity";
|
||||
default:
|
||||
return interfaceName.replace("sensor", "");
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var stateType: deviceClass.stateTypes.findByName(stateTypeName)
|
||||
readonly property var deviceState: device.states.getState(stateType.id)
|
||||
|
||||
ValueLogsProxyModel {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
deviceId: root.device.id
|
||||
typeIds: [stateType.id]
|
||||
average: zoomTabBar.currentItem.avg
|
||||
startTime: zoomTabBar.currentItem.startTime
|
||||
Component.onCompleted: updateTimer.start();
|
||||
onAverageChanged: updateTimer.start()
|
||||
onStartTimeChanged: updateTimer.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 10
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
print("updating:", logsModel.startTime)
|
||||
logsModel.update()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
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: Types.toUiValue(deviceState.value, stateType.unit) + " " + Types.toUiUnit(stateType.unit)
|
||||
font.pixelSize: app.largeFont
|
||||
}
|
||||
|
||||
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.preferredHeight: 200
|
||||
model: logsModel
|
||||
mode: settings.graphStyle
|
||||
color: app.interfaceToColor(root.interfaceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ MainPageTile {
|
||||
|
||||
MediaControls {
|
||||
iconSize: app.iconSize * 1.2
|
||||
device: inlineMediaControl.currentDevice
|
||||
thing: inlineMediaControl.currentDevice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@ DeviceListPageBase {
|
||||
}
|
||||
MediaControls {
|
||||
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
|
||||
device: itemDelegate.device
|
||||
thing: itemDelegate.device
|
||||
}
|
||||
}
|
||||
Item {
|
||||
|
||||
@ -141,12 +141,7 @@ Page {
|
||||
return;
|
||||
}
|
||||
|
||||
var source;
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
|
||||
} else {
|
||||
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
|
||||
}
|
||||
var source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
|
||||
setSource(source, {device: root.device, stateType: stateType})
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ DevicePageBase {
|
||||
MediaArtworkImage {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: parent.width / parent.columns
|
||||
device: root.device
|
||||
thing: root.device
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@ -208,7 +208,7 @@ DevicePageBase {
|
||||
}
|
||||
|
||||
MediaControls {
|
||||
device: root.device
|
||||
thing: root.device
|
||||
iconSize: app.iconSize * 2
|
||||
}
|
||||
}
|
||||
@ -220,63 +220,8 @@ DevicePageBase {
|
||||
Component {
|
||||
id: browserComponent
|
||||
|
||||
Item {
|
||||
|
||||
function backPressed() {
|
||||
if (internalPageStack.depth > 1) {
|
||||
internalPageStack.pop();
|
||||
} else {
|
||||
swipeView.currentIndex--
|
||||
}
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: internalPageStack
|
||||
anchors.fill: parent
|
||||
initialItem: internalBrowserPage
|
||||
|
||||
Component {
|
||||
id: internalBrowserPage
|
||||
ListView {
|
||||
id: listView
|
||||
model: browserItems
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
property string nodeId: ""
|
||||
|
||||
// Need to keep a explicit property here or the GC will eat it too early
|
||||
property BrowserItems browserItems: null
|
||||
Component.onCompleted: {
|
||||
browserItems = engine.deviceManager.browseDevice(root.device.id, nodeId);
|
||||
}
|
||||
|
||||
delegate: BrowserItemDelegate {
|
||||
iconName: "../images/browser/" + (model.mediaIcon && model.mediaIcon !== "MediaBrowserIconNone" ? model.mediaIcon : model.icon) + ".svg"
|
||||
busy: d.pendingItemId === model.id
|
||||
device: root.device
|
||||
|
||||
onClicked: {
|
||||
print("clicked:", model.id)
|
||||
if (model.executable) {
|
||||
root.executeBrowserItem(model.id)
|
||||
} else if (model.browsable) {
|
||||
internalPageStack.push(internalBrowserPage, {device: root.device, nodeId: model.id})
|
||||
}
|
||||
}
|
||||
|
||||
onContextMenuActionTriggered: {
|
||||
root.executeBrowserItemAction(model.id, actionTypeId, params)
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: listView.model.busy
|
||||
visible: running
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MediaBrowser {
|
||||
thing: root.device
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +232,6 @@ DevicePageBase {
|
||||
swipeView.currentIndex--;
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
@ -300,49 +244,13 @@ DevicePageBase {
|
||||
|
||||
MediaControls {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
thing: root.device
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: volumeSliderPaneComponent
|
||||
Dialog {
|
||||
|
||||
leftPadding: 0
|
||||
topPadding: app.margins / 2
|
||||
rightPadding: 0
|
||||
bottomPadding: app.margins / 2
|
||||
modal: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Slider {
|
||||
Layout.fillHeight: true
|
||||
orientation: Qt.Vertical
|
||||
from: 0
|
||||
to: 100
|
||||
value: d.pendingVolumeValue != -1 ? d.pendingVolumeValue : root.stateValue("volume")
|
||||
onMoved: root.adjustVolume(value)
|
||||
}
|
||||
HeaderButton {
|
||||
imageSource: "../images/audio-speakers-muted-symbolic.svg"
|
||||
color: root.stateValue("mute") ? app.accentColor : keyColor
|
||||
onClicked: {
|
||||
var params = []
|
||||
var muteParam = {}
|
||||
muteParam["paramTypeId"] = root.deviceClass.actionTypes.findByName("mute").id
|
||||
muteParam["value"] = !root.stateValue("mute");
|
||||
params.push(muteParam)
|
||||
root.executeAction("mute", params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: Pane {
|
||||
Material.elevation: 1
|
||||
height: 52
|
||||
@ -374,64 +282,11 @@ DevicePageBase {
|
||||
onClicked: swipeView.currentIndex--
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true; Layout.fillHeight: true
|
||||
visible: root.deviceClass.interfaces.indexOf("shufflerepeat") >= 0
|
||||
HeaderButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: root.stateValue("repeat") === "One" ? "../images/media-playlist-repeat-one.svg" : "../images/media-playlist-repeat.svg"
|
||||
color: root.stateValue("repeat") === "None" ? keyColor : app.accentColor
|
||||
property var allowedValues: ["None", "All", "One"]
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.deviceClass.actionTypes.findByName("repeat").id;
|
||||
param["value"] = allowedValues[(allowedValues.indexOf(root.stateValue("repeat")) + 1) % 3]
|
||||
params.push(param)
|
||||
root.executeAction("repeat", params)
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true; Layout.fillHeight: true
|
||||
visible: root.deviceClass.interfaces.indexOf("shufflerepeat") >= 0
|
||||
HeaderButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: "../images/media-playlist-shuffle.svg"
|
||||
color: root.stateValue("shuffle") ? app.accentColor: keyColor
|
||||
onClicked: {
|
||||
var params = []
|
||||
var param = {}
|
||||
param["paramTypeId"] = root.deviceClass.actionTypes.findByName("shuffle").id;
|
||||
param["value"] = !root.stateValue("shuffle")
|
||||
params.push(param)
|
||||
root.executeAction("shuffle", params)
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: volumeButtonContainer
|
||||
Layout.fillWidth: true; Layout.fillHeight: true
|
||||
HeaderButton {
|
||||
id: volumeButton
|
||||
anchors.centerIn: parent
|
||||
imageSource: "../images/audio-speakers-symbolic.svg"
|
||||
onClicked: {
|
||||
print("...");
|
||||
print(volumeButton.x, volumeButton.y)
|
||||
print(Qt.point(volumeButton.x, volumeButton.y))
|
||||
print(volumeButton.mapToItem(root, volumeButton.x,0))
|
||||
var buttonPosition = root.mapFromItem(volumeButtonContainer, volumeButton.x, 0)
|
||||
var sliderHeight = 200
|
||||
var props = {}
|
||||
props["x"] = buttonPosition.x
|
||||
props["y"] = root.height - sliderHeight - root.footer.height
|
||||
props["height"] = sliderHeight
|
||||
var sliderPane = volumeSliderPaneComponent.createObject(root, props)
|
||||
sliderPane.open()
|
||||
}
|
||||
}
|
||||
ShuffleRepeatVolumeControl {
|
||||
Layout.fillWidth: true
|
||||
thing: root.device
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: swipeView.count > 1 && swipeView.currentIndex < swipeView.count - 1 ? parent.width / 4 : 0
|
||||
|
||||
@ -38,16 +38,160 @@ import "../customviews"
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
Component.onCompleted: {
|
||||
var src
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
src = "SensorDevicePagePost110.qml"
|
||||
} else {
|
||||
src = "SensorDevicePagePre110.qml"
|
||||
Flickable {
|
||||
id: listView
|
||||
anchors { fill: parent }
|
||||
interactive: contentHeight > height
|
||||
contentHeight: contentGrid.implicitHeight
|
||||
|
||||
GridLayout {
|
||||
id: contentGrid
|
||||
width: parent.width
|
||||
columns: width / 300
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
|
||||
for (var i = 0; i < supportedInterfaces.length; i++) {
|
||||
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
|
||||
append({name: supportedInterfaces[i]});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Loader {
|
||||
id: loader
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: item.implicitHeight
|
||||
|
||||
property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
|
||||
property string interfaceName: modelData
|
||||
|
||||
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
|
||||
sourceComponent: graphComponent
|
||||
|
||||
property var interfaceStateMap: {
|
||||
"temperaturesensor": "temperature",
|
||||
"humiditysensor": "humidity",
|
||||
"pressuresensor": "pressure",
|
||||
"moisturesensor": "moisture",
|
||||
"lightsensor": "lightIntensity",
|
||||
"conductivitysensor": "conductivity",
|
||||
"noisesensor": "noise",
|
||||
"co2sensor": "co2",
|
||||
"presencesensor": "isPresent",
|
||||
"daylightsensor": "daylight",
|
||||
"closablesensor": "closed"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Component {
|
||||
id: graphComponent
|
||||
|
||||
GenericTypeGraph {
|
||||
device: root.device
|
||||
color: app.interfaceToColor(interfaceName)
|
||||
iconSource: app.interfaceToIcon(interfaceName)
|
||||
implicitHeight: width * .6
|
||||
property string interfaceName: parent.interfaceName
|
||||
stateType: parent.stateType
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: boolComponent
|
||||
GridLayout {
|
||||
id: boolView
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
height: listView.height
|
||||
columns: app.landscape ? 2 : 1
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumWidth: app.iconSize * 5
|
||||
Layout.rowSpan: app.landscape ? 5 : 1
|
||||
ColorIcon {
|
||||
anchors.centerIn: parent
|
||||
height: app.iconSize * 4
|
||||
width: height
|
||||
name: {
|
||||
switch (boolView.interfaceName) {
|
||||
case "closablesensor":
|
||||
return device.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg")
|
||||
default:
|
||||
return app.interfaceToIcon(boolView.interfaceName)
|
||||
}
|
||||
}
|
||||
color: {
|
||||
switch (boolView.interfaceName) {
|
||||
case "closablesensor":
|
||||
return device.states.getState(boolView.stateType.id).value === true ? "green" : "red"
|
||||
default:
|
||||
device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType lastSeenStateType: root.deviceClass.stateTypes.findByName("lastSeenTime")
|
||||
property State lastSeenState: lastSeenStateType ? root.device.states.getState(lastSeenStateType.id) : null
|
||||
visible: lastSeenStateType !== null
|
||||
Label {
|
||||
text: qsTr("Last seen:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.lastSeenState ? Qt.formatDateTime(new Date(parent.lastSeenState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType sunriseStateType: root.deviceClass.stateTypes.findByName("sunriseTime")
|
||||
property State sunriseState: sunriseStateType ? root.device.states.getState(sunriseStateType.id) : null
|
||||
visible: sunriseStateType !== null
|
||||
Label {
|
||||
text: qsTr("Sunrise:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.sunriseStateType ? Qt.formatDateTime(new Date(parent.sunriseState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType sunsetStateType: root.deviceClass.stateTypes.findByName("sunsetTime")
|
||||
property State sunsetState: sunsetStateType ? root.device.states.getState(sunsetStateType.id) : null
|
||||
visible: sunsetStateType !== null
|
||||
Label {
|
||||
text: qsTr("Sunset:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.sunsetStateType ? Qt.formatDateTime(new Date(parent.sunsetState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,192 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
Flickable {
|
||||
id: listView
|
||||
anchors { fill: parent }
|
||||
interactive: contentHeight > height
|
||||
contentHeight: contentGrid.implicitHeight
|
||||
|
||||
GridLayout {
|
||||
id: contentGrid
|
||||
width: parent.width
|
||||
columns: width / 300
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
|
||||
for (var i = 0; i < supportedInterfaces.length; i++) {
|
||||
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
|
||||
append({name: supportedInterfaces[i]});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Loader {
|
||||
id: loader
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: item.implicitHeight
|
||||
|
||||
property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
|
||||
property string interfaceName: modelData
|
||||
|
||||
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
|
||||
sourceComponent: graphComponent
|
||||
|
||||
property var interfaceStateMap: {
|
||||
"temperaturesensor": "temperature",
|
||||
"humiditysensor": "humidity",
|
||||
"pressuresensor": "pressure",
|
||||
"moisturesensor": "moisture",
|
||||
"lightsensor": "lightIntensity",
|
||||
"conductivitysensor": "conductivity",
|
||||
"noisesensor": "noise",
|
||||
"co2sensor": "co2",
|
||||
"presencesensor": "isPresent",
|
||||
"daylightsensor": "daylight",
|
||||
"closablesensor": "closed"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Component {
|
||||
id: graphComponent
|
||||
|
||||
GenericTypeGraph {
|
||||
device: root.device
|
||||
color: app.interfaceToColor(interfaceName)
|
||||
iconSource: app.interfaceToIcon(interfaceName)
|
||||
implicitHeight: width * .6
|
||||
property string interfaceName: parent.interfaceName
|
||||
stateType: parent.stateType
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: boolComponent
|
||||
GridLayout {
|
||||
id: boolView
|
||||
property string interfaceName: parent.interfaceName
|
||||
property StateType stateType: parent.stateType
|
||||
height: listView.height
|
||||
columns: app.landscape ? 2 : 1
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumWidth: app.iconSize * 5
|
||||
Layout.rowSpan: app.landscape ? 5 : 1
|
||||
ColorIcon {
|
||||
anchors.centerIn: parent
|
||||
height: app.iconSize * 4
|
||||
width: height
|
||||
name: {
|
||||
switch (boolView.interfaceName) {
|
||||
case "closablesensor":
|
||||
return device.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg")
|
||||
default:
|
||||
return app.interfaceToIcon(boolView.interfaceName)
|
||||
}
|
||||
}
|
||||
color: {
|
||||
switch (boolView.interfaceName) {
|
||||
case "closablesensor":
|
||||
return device.states.getState(boolView.stateType.id).value === true ? "green" : "red"
|
||||
default:
|
||||
device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType lastSeenStateType: root.deviceClass.stateTypes.findByName("lastSeenTime")
|
||||
property State lastSeenState: lastSeenStateType ? root.device.states.getState(lastSeenStateType.id) : null
|
||||
visible: lastSeenStateType !== null
|
||||
Label {
|
||||
text: qsTr("Last seen:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.lastSeenState ? Qt.formatDateTime(new Date(parent.lastSeenState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType sunriseStateType: root.deviceClass.stateTypes.findByName("sunriseTime")
|
||||
property State sunriseState: sunriseStateType ? root.device.states.getState(sunriseStateType.id) : null
|
||||
visible: sunriseStateType !== null
|
||||
Label {
|
||||
text: qsTr("Sunrise:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.sunriseStateType ? Qt.formatDateTime(new Date(parent.sunriseState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property StateType sunsetStateType: root.deviceClass.stateTypes.findByName("sunsetTime")
|
||||
property State sunsetState: sunsetStateType ? root.device.states.getState(sunsetStateType.id) : null
|
||||
visible: sunsetStateType !== null
|
||||
Label {
|
||||
text: qsTr("Sunset:")
|
||||
font.bold: true
|
||||
}
|
||||
Label {
|
||||
text: parent.sunsetStateType ? Qt.formatDateTime(new Date(parent.sunsetState.value * 1000)) : ""
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
ListView {
|
||||
anchors { fill: parent }
|
||||
|
||||
property var device
|
||||
property var deviceClass
|
||||
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor"]
|
||||
for (var i = 0; i < supportedInterfaces.length; i++) {
|
||||
print("checking", root.deviceClass.name, root.deviceClass.interfaces)
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -38,16 +38,55 @@ import "../customviews"
|
||||
DevicePageBase {
|
||||
id: root
|
||||
|
||||
Loader {
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
Component.onCompleted: {
|
||||
var src
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
src = "WeatherDevicePagePost110.qml"
|
||||
} else {
|
||||
src = "WeatherDevicePagePre110.qml"
|
||||
clip: true
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
width: parent.width
|
||||
|
||||
WeatherView {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: content
|
||||
Layout.fillWidth: true
|
||||
columns: Math.min(width / 300, 4)
|
||||
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("temperature")
|
||||
iconSource: app.interfaceToIcon("temperaturesensor")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("humidity")
|
||||
iconSource: app.interfaceToIcon("humiditysensor")
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("pressure")
|
||||
iconSource: app.interfaceToIcon("pressuresensor")
|
||||
color: app.interfaceToColor("pressuresensor")
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("windSpeed")
|
||||
iconSource: app.interfaceToIcon("windspeedsensor")
|
||||
color: app.interfaceToColor("windspeedsensor")
|
||||
}
|
||||
}
|
||||
setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: contentColumn.implicitHeight
|
||||
|
||||
property var device
|
||||
property var deviceClass
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
width: parent.width
|
||||
|
||||
WeatherView {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: content
|
||||
Layout.fillWidth: true
|
||||
columns: Math.min(width / 300, 4)
|
||||
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("temperature")
|
||||
iconSource: app.interfaceToIcon("temperaturesensor")
|
||||
color: app.interfaceToColor("temperaturesensor")
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("humidity")
|
||||
iconSource: app.interfaceToIcon("humiditysensor")
|
||||
color: app.interfaceToColor("humiditysensor")
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("pressure")
|
||||
iconSource: app.interfaceToIcon("pressuresensor")
|
||||
color: app.interfaceToColor("pressuresensor")
|
||||
}
|
||||
GenericTypeGraph {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
stateType: root.deviceClass.stateTypes.findByName("windSpeed")
|
||||
iconSource: app.interfaceToIcon("windspeedsensor")
|
||||
color: app.interfaceToColor("windspeedsensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../customviews"
|
||||
|
||||
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: content.implicitHeight
|
||||
|
||||
property var device
|
||||
property var deviceClass
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
WeatherView {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
}
|
||||
SensorView {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
interfaceName: "temperaturesensor"
|
||||
}
|
||||
SensorView {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
interfaceName: "humiditysensor"
|
||||
}
|
||||
SensorView {
|
||||
Layout.fillWidth: true
|
||||
device: root.device
|
||||
deviceClass: root.deviceClass
|
||||
interfaceName: "pressuresensor"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
../mediaplayer-app-symbolic.svg
|
||||
../media.svg
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@ -70,6 +70,7 @@ MainViewBase {
|
||||
width: parent.width
|
||||
visible: consumers.count > 0
|
||||
columns: Math.floor(root.width / 300)
|
||||
rowSpacing: 0
|
||||
|
||||
SmartMeterChart {
|
||||
Layout.fillWidth: true
|
||||
@ -78,6 +79,285 @@ MainViewBase {
|
||||
title: qsTr("Total consumed energy")
|
||||
visible: consumers.count > 0
|
||||
}
|
||||
|
||||
ChartView {
|
||||
id: chartView
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: width * .75
|
||||
legend.alignment: Qt.AlignBottom
|
||||
legend.font.pixelSize: app.smallFont
|
||||
legend.visible: false
|
||||
backgroundColor: app.backgroundColor
|
||||
|
||||
property var startTime: xAxis.min
|
||||
property var endTime: xAxis.max
|
||||
|
||||
property int sampleRate: XYSeriesAdapter.SampleRateMinute
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: running
|
||||
running: {
|
||||
for (var i = 0; i < consumersRepeater.count; i++) {
|
||||
if (consumersRepeater.itemAt(i).model.busy) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: consumersRepeater
|
||||
model: consumers
|
||||
|
||||
delegate: Item {
|
||||
id: consumer
|
||||
property Thing thing: consumers.get(index)
|
||||
|
||||
property var model: LogsModel {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
thingId: consumer.thing.id
|
||||
typeIds: [consumer.thing.thingClass.stateTypes.findByName("currentPower").id]
|
||||
viewStartTime: xAxis.min
|
||||
live: true
|
||||
}
|
||||
property XYSeriesAdapter adapter: XYSeriesAdapter {
|
||||
id: seriesAdapter
|
||||
logsModel: logsModel
|
||||
sampleRate: chartView.sampleRate
|
||||
xySeries: upperSeries
|
||||
}
|
||||
property XYSeries lineSeries: LineSeries {
|
||||
id: upperSeries
|
||||
onPointAdded: {
|
||||
var newPoint = upperSeries.at(index)
|
||||
|
||||
if (newPoint.x > lowerSeries.at(0).x) {
|
||||
lowerSeries.replace(0, newPoint.x, 0)
|
||||
}
|
||||
if (newPoint.x < lowerSeries.at(1).x) {
|
||||
lowerSeries.replace(1, newPoint.x, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
LineSeries {
|
||||
id: lowerSeries
|
||||
XYPoint { x: xAxis.max.getTime(); y: 0 }
|
||||
XYPoint { x: xAxis.min.getTime(); y: 0 }
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
print("creating series")
|
||||
var areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, consumer.thing.name, xAxis, yAxis)
|
||||
areaSeries.upperSeries = upperSeries;
|
||||
if (index > 0) {
|
||||
areaSeries.lowerSeries = consumersRepeater.itemAt(index - 1).lineSeries
|
||||
seriesAdapter.baseSeries = consumersRepeater.itemAt(index - 1).lineSeries
|
||||
} else {
|
||||
areaSeries.lowerSeries = lowerSeries;
|
||||
}
|
||||
|
||||
var color = app.accentColor
|
||||
for (var j = 0; j < index; j+=2) {
|
||||
if (index % 2 == 0) {
|
||||
color = Qt.lighter(color, 1.2);
|
||||
} else {
|
||||
color = Qt.darker(color, 1.2)
|
||||
}
|
||||
}
|
||||
areaSeries.color = color;
|
||||
areaSeries.borderColor = color;
|
||||
areaSeries.borderWidth = 0;
|
||||
|
||||
seriesAdapter.xySeries = series;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueAxis {
|
||||
id: yAxis
|
||||
readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter;
|
||||
max: Math.ceil(adapter.maxValue + Math.abs(adapter.maxValue * .05))
|
||||
min: Math.floor(adapter.minValue - Math.abs(adapter.minValue * .05))
|
||||
onMinChanged: applyNiceNumbers();
|
||||
onMaxChanged: applyNiceNumbers();
|
||||
labelsFont.pixelSize: app.smallFont
|
||||
labelFormat: {
|
||||
return "%d";
|
||||
// switch (root.stateType.type.toLowerCase()) {
|
||||
// case "bool":
|
||||
// return "x";
|
||||
// default:
|
||||
// return "%d";
|
||||
// }
|
||||
}
|
||||
labelsColor: app.foregroundColor
|
||||
// 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
|
||||
}
|
||||
|
||||
DateTimeAxis {
|
||||
id: xAxis
|
||||
gridVisible: false
|
||||
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
|
||||
tickCount: chartView.width / 70
|
||||
labelsFont.pixelSize: app.smallFont
|
||||
labelsColor: app.foregroundColor
|
||||
property int timeDiff: (xAxis.max.getTime() - xAxis.min.getTime()) / 1000
|
||||
|
||||
function getTimeSpanString() {
|
||||
var td = timeDiff
|
||||
if (td < 60) {
|
||||
return qsTr("%1 seconds").arg(Math.round(td));
|
||||
}
|
||||
td = td / 60
|
||||
if (td < 60) {
|
||||
return qsTr("%1 minutes").arg(Math.round(td));
|
||||
}
|
||||
td = td / 60
|
||||
if (td < 48) {
|
||||
return qsTr("%1 hours").arg(Math.round(td));
|
||||
}
|
||||
td = td / 24;
|
||||
if (td < 14) {
|
||||
return qsTr("%1 days").arg(Math.round(td));
|
||||
}
|
||||
td = td / 7
|
||||
if (td < 9) {
|
||||
return qsTr("%1 weeks").arg(Math.round(td));
|
||||
}
|
||||
td = td * 7 / 30
|
||||
if (td < 24) {
|
||||
return qsTr("%1 months").arg(Math.round(td));
|
||||
}
|
||||
td = td * 30 / 356
|
||||
return qsTr("%1 years").arg(Math.round(td))
|
||||
}
|
||||
|
||||
titleText: {
|
||||
if (xAxis.min.getYear() === xAxis.max.getYear()
|
||||
&& xAxis.min.getMonth() === xAxis.max.getMonth()
|
||||
&& xAxis.min.getDate() === xAxis.max.getDate()) {
|
||||
return Qt.formatDate(xAxis.min) + " (" + getTimeSpanString() + ")"
|
||||
}
|
||||
return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + " (" + getTimeSpanString() + ")"
|
||||
}
|
||||
titleBrush: app.foregroundColor
|
||||
format: {
|
||||
if (timeDiff < 60) { // one minute
|
||||
return "mm:ss"
|
||||
}
|
||||
if (timeDiff < 60 * 60) { // one hour
|
||||
return "hh:mm"
|
||||
}
|
||||
if (timeDiff < 60 * 60 * 24 * 2) { // two day
|
||||
return "hh:mm"
|
||||
}
|
||||
if (timeDiff < 60 * 60 * 24 * 7) { // one week
|
||||
return "ddd hh:mm"
|
||||
}
|
||||
if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month
|
||||
return "dd.MM."
|
||||
}
|
||||
return "MMM yy"
|
||||
}
|
||||
|
||||
min: {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() - (1000 * 60 * 60 * 24) + 2000);
|
||||
return date;
|
||||
}
|
||||
max: {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + 2000)
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scrollMouseArea
|
||||
x: chartView.plotArea.x
|
||||
y: chartView.plotArea.y
|
||||
width: chartView.plotArea.width
|
||||
height: chartView.plotArea.height
|
||||
property int lastX: 0
|
||||
property int startX: 0
|
||||
preventStealing: false
|
||||
|
||||
property bool autoScroll: true
|
||||
|
||||
function scrollRightLimited(dx) {
|
||||
chartView.animationOptions = ChartView.NoAnimation
|
||||
var now = new Date()
|
||||
// if we're already at the limit, don't even start scrolling
|
||||
if (dx < 0 || xAxis.max < now) {
|
||||
chartView.scrollRight(dx)
|
||||
}
|
||||
// figure out if we scrolled too far
|
||||
var overshoot = xAxis.max.getTime() - now.getTime()
|
||||
// print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot))
|
||||
if (overshoot > 0) {
|
||||
var range = xAxis.max - xAxis.min
|
||||
xAxis.max = now
|
||||
xAxis.min = new Date(xAxis.max.getTime() - range)
|
||||
}
|
||||
// If the user scrolled closer than 5 pixels to the right edge, enable autoscroll
|
||||
autoScroll = overshoot > -5;
|
||||
|
||||
chartView.animationOptions = ChartView.SeriesAnimations
|
||||
}
|
||||
|
||||
function zoomInLimited(dy) {
|
||||
chartView.animationOptions = ChartView.NoAnimation
|
||||
var oldMax = xAxis.max;
|
||||
chartView.scrollRight(dy);
|
||||
xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 1000 * 2)
|
||||
chartView.animationOptions = ChartView.SeriesAnimations
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
lastX = mouse.x
|
||||
startX = mouse.x
|
||||
preventStealing = true
|
||||
}
|
||||
onClicked: {
|
||||
// var pt = chartView.mapToValue(Qt.point(mouse.x + chartView.plotArea.x, mouse.y + chartView.plotArea.y), mainSeries)
|
||||
// mainSeries.markClosestPoint(pt)
|
||||
}
|
||||
|
||||
onWheel: {
|
||||
scrollRightLimited(-wheel.pixelDelta.x)
|
||||
// zoomInLimited(wheel.pixelDelta.y)
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (lastX !== mouse.x) {
|
||||
scrollRightLimited(lastX - mouseX)
|
||||
lastX = mouse.x
|
||||
}
|
||||
|
||||
if (Math.abs(startX - mouse.x) > 10) {
|
||||
preventStealing = true;
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: preventStealing = false;
|
||||
|
||||
|
||||
Timer {
|
||||
running: scrollMouseArea.autoScroll
|
||||
interval: 1000
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
scrollMouseArea.scrollRightLimited(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SmartMeterChart {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: width * .7
|
||||
|
||||
@ -484,7 +484,7 @@ MainViewBase {
|
||||
}
|
||||
|
||||
// involve count in the statement to make the binding re-evaluate when the group is changed
|
||||
device: mediaControllers.count > 0 ? mediaControllers.get(0) : null
|
||||
thing: mediaControllers.count > 0 ? mediaControllers.get(0) : null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
186
nymea-app/ui/mainviews/MediaView.qml
Normal file
186
nymea-app/ui/mainviews/MediaView.qml
Normal file
@ -0,0 +1,186 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtCharts 2.2
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
import "../delegates"
|
||||
|
||||
MainViewBase {
|
||||
id: root
|
||||
|
||||
ThingsProxy {
|
||||
id: mediaDevices
|
||||
engine: _engine
|
||||
shownInterfaces: ["mediaplayer"]
|
||||
}
|
||||
|
||||
EmptyViewPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - app.margins * 2
|
||||
visible: !engine.thingManager.fetchingData && mediaDevices.count == 0
|
||||
title: qsTr("There are no media players set up.")
|
||||
text: qsTr("Connect your media players in order to control them from here.")
|
||||
imageSource: "../images/media.svg"
|
||||
buttonText: qsTr("Add things")
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
currentIndex: pageIndicator.currentIndex
|
||||
|
||||
Repeater {
|
||||
model: mediaDevices
|
||||
delegate: Item {
|
||||
id: playerDelegate
|
||||
height: swipeView.height
|
||||
width: swipeView.width
|
||||
property Thing thing: mediaDevices.get(index)
|
||||
property State titleState: thing.stateByName("title")
|
||||
property State artistState: thing.stateByName("artist")
|
||||
property State collectionState: thing.stateByName("collection")
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
columns: 1
|
||||
rowSpacing: app.margins
|
||||
|
||||
MediaArtworkImage {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
thing: playerDelegate.thing
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: app.margins
|
||||
Label {
|
||||
text: playerDelegate.titleState.value
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: app.largeFont
|
||||
}
|
||||
Label {
|
||||
text: playerDelegate.artistState.value
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Label {
|
||||
text: playerDelegate.collectionState.value
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
MediaControls {
|
||||
Layout.fillWidth: true
|
||||
thing: playerDelegate.thing
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.fillWidth: true
|
||||
visible: playerDelegate.thing.thingClass.browsable
|
||||
|
||||
HeaderButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: "../images/navigationpad.svg"
|
||||
onClicked: {
|
||||
pageStack.push(navigationPadPage)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: navigationPadPage
|
||||
Page {
|
||||
header: NymeaHeader { text: playerDelegate.thing.name; onBackPressed: pageStack.pop() }
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins
|
||||
spacing: app.margins
|
||||
|
||||
NavigationPad { Layout.fillWidth: true; Layout.fillHeight: true; device: playerDelegate.thing }
|
||||
MediaControls { Layout.fillWidth: true; thing: playerDelegate.thing }
|
||||
ShuffleRepeatVolumeControl { Layout.fillWidth: true; Layout.fillHeight: false; Layout.preferredHeight: app.iconSize; thing: playerDelegate.thing }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShuffleRepeatVolumeControl {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: false
|
||||
Layout.preferredHeight: app.iconSize
|
||||
thing: playerDelegate.thing
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.fillWidth: true
|
||||
visible: playerDelegate.thing.thingClass.interfaces.indexOf("navigationpad") >= 0
|
||||
|
||||
HeaderButton {
|
||||
anchors.centerIn: parent
|
||||
imageSource: "../images/folder-symbolic.svg"
|
||||
onClicked: {
|
||||
pageStack.push(browserPage)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: browserPage
|
||||
Page {
|
||||
header: NymeaHeader { text: playerDelegate.thing.name; onBackPressed: pageStack.pop() }
|
||||
MediaBrowser { anchors.fill: parent; thing: playerDelegate.thing }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PageIndicator {
|
||||
id: pageIndicator
|
||||
count: swipeView.count
|
||||
currentIndex: swipeView.currentIndex
|
||||
interactive: true
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
@ -56,23 +56,6 @@ Page {
|
||||
LogsModel {
|
||||
id: logsModel
|
||||
engine: _engine
|
||||
startTime: {
|
||||
var date = new Date();
|
||||
date.setHours(new Date().getHours() - 2);
|
||||
return date;
|
||||
}
|
||||
endTime: new Date()
|
||||
live: true
|
||||
onCountChanged: {
|
||||
if (root.autoScroll) {
|
||||
listView.positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogsModelNg {
|
||||
id: logsModelNg
|
||||
engine: _engine
|
||||
live: true
|
||||
}
|
||||
|
||||
@ -83,13 +66,11 @@ Page {
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
model: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel
|
||||
model: logsModel
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
|
||||
Component.onCompleted: model.update()
|
||||
|
||||
onDraggingChanged: {
|
||||
if (dragging) {
|
||||
root.autoScroll = false;
|
||||
@ -103,14 +84,6 @@ Page {
|
||||
visible: listView.model.busy
|
||||
}
|
||||
|
||||
onContentYChanged: {
|
||||
if (!engine.jsonRpcClient.ensureServerVersion("1.10")) {
|
||||
if (!logsModel.busy && contentY - originY < 5 * height) {
|
||||
logsModel.fetchEarlier(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: delegate
|
||||
width: parent.width
|
||||
|
||||
Reference in New Issue
Block a user