// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-app.
*
* nymea-app is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-app 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 nymea-app. If not, see .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "dashboardmodel.h"
#include "dashboarditem.h"
#include
#include
DashboardModel::DashboardModel(QObject *parent) : QAbstractListModel(parent)
{
}
int DashboardModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return static_cast(m_list.count());
}
QVariant DashboardModel::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleType:
return m_list.at(index.row())->type();
case RoleColumnSpan:
return m_list.at(index.row())->columnSpan();
case RoleRowSpan:
return m_list.at(index.row())->rowSpan();
}
Q_ASSERT_X(false, "DashboardModel", "Unhandled role");
return QVariant();
}
QHash DashboardModel::roleNames() const
{
QHash roles;
roles.insert(RoleType, "type");
roles.insert(RoleColumnSpan, "columnSpan");
roles.insert(RoleRowSpan, "rowSpan");
return roles;
}
DashboardItem *DashboardModel::get(int index) const
{
if (index < 0 || index >= m_list.size()) {
return nullptr;
}
return m_list.at(index);
}
void DashboardModel::addThingItem(const QUuid &thingId, int index)
{
DashboardThingItem *item = new DashboardThingItem(thingId, this);
addItem(item, index);
}
void DashboardModel::addFolderItem(const QString &name, const QString &icon, int index)
{
DashboardFolderItem *item = new DashboardFolderItem(name, icon, this);
connect(item->model(), &DashboardModel::save, this, &DashboardModel::save);
addItem(item, index);
}
void DashboardModel::addGraphItem(const QUuid &thingId, const QUuid &stateTypeId, int index)
{
DashboardGraphItem *item = new DashboardGraphItem(thingId, stateTypeId, this);
item->setColumnSpan(2);
addItem(item, index);
}
void DashboardModel::addSceneItem(const QUuid &ruleId, int index)
{
DashboardSceneItem *item = new DashboardSceneItem(ruleId, this);
addItem(item, index);
}
void DashboardModel::addWebViewItem(const QUrl &url, int columnSpan, int rowSpan, bool interactive, int index)
{
QUrl fixedUrl = url;
// Correct url if no scheme is given as it would end up being qrc:// by default which no user will want...
if (fixedUrl.scheme().isEmpty()) {
fixedUrl.setScheme("https");
}
DashboardWebViewItem *item = new DashboardWebViewItem(fixedUrl, interactive, this);
item->setColumnSpan(columnSpan);
item->setRowSpan(rowSpan);
addItem(item, index);
}
void DashboardModel::addStateItem(const QUuid &thingId, const QUuid &stateTypeId, int index)
{
DashboardStateItem *item = new DashboardStateItem(thingId, stateTypeId, this);
addItem(item, index);
}
void DashboardModel::addSensorItem(const QUuid &thingId, const QStringList &interfaces, int index)
{
DashboardSensorItem *item = new DashboardSensorItem(thingId, interfaces, this);
addItem(item, index);
}
void DashboardModel::removeItem(int index)
{
qWarning() << "removing" << index;
beginRemoveRows(QModelIndex(), index, index);
m_list.removeAt(index);
endRemoveRows();
emit changed();
emit countChanged();
}
void DashboardModel::move(int from, int to)
{
// QList's and QAbstractItemModel's move implementation differ when moving an item up the list :/
// While QList needs the index in the resulting list, beginMoveRows expects it to be in the current list
// adjust the model's index by +1 in case we're moving upwards
int newModelIndex = to > from ? to+1 : to;
beginMoveRows(QModelIndex(), from, from, QModelIndex(), newModelIndex);
m_list.move(from, to);
endMoveRows();
emit changed();
}
void DashboardModel::loadFromJson(const QByteArray &json)
{
if (toJson() == json) {
return;
}
beginResetModel();
foreach (DashboardItem *item, m_list)
item->deleteLater();
m_list.clear();
QJsonDocument jsonDoc = QJsonDocument::fromJson(json);
qCritical() << "dashboard:" << qUtf8Printable(jsonDoc.toJson());
foreach (const QVariant &itemVariant, jsonDoc.toVariant().toList()) {
QVariantMap itemMap = itemVariant.toMap();
QString type = itemMap.value("type").toString();
DashboardItem *item;
if (type == "folder") {
DashboardFolderItem *folderItem = new DashboardFolderItem(itemMap.value("name").toString(), itemMap.value("icon", "folder").toString(), this);
folderItem->model()->loadFromJson(QJsonDocument::fromVariant(itemMap.value("model").toList()).toJson(QJsonDocument::Compact));
connect(folderItem->model(), &DashboardModel::save, this, &DashboardModel::save);
item = folderItem;
} else if (type == "thing") {
item = new DashboardThingItem(itemMap.value("thingId").toUuid(), this);
} else if (type == "graph") {
item = new DashboardGraphItem(itemMap.value("thingId").toUuid(), itemMap.value("stateTypeId").toUuid(), this);
} else if (type == "scene") {
item = new DashboardSceneItem(itemMap.value("ruleId").toUuid(), this);
} else if (type == "webview") {
item = new DashboardWebViewItem(itemMap.value("url").toUrl(), itemMap.value("interactive", false).toBool(), this);
} else if (type == "state") {
item = new DashboardStateItem(itemMap.value("thingId").toUuid(), itemMap.value("stateTypeId").toUuid(), this);
} else if (type == "sensor") {
item = new DashboardSensorItem(itemMap.value("thingId").toUuid(), itemMap.value("interfaces").toStringList(), this);
} else {
qWarning() << "Dashboard item type" << type << "is not implemented. Skipping...";
continue;
}
item->setColumnSpan(itemMap.value("columnSpan", 1).toInt());
item->setRowSpan(itemMap.value("rowSpan", 1).toInt());
addItem(item);
// connect(item, &DashboardItem::changed, this, &DashboardModel::changed);
// m_list.append(item);
}
endResetModel();
emit countChanged();
}
QByteArray DashboardModel::toJson() const
{
QVariantList list;
foreach (DashboardItem* item, m_list) {
QVariantMap map;
map.insert("type", item->type());
if (item->type() == "thing") {
DashboardThingItem *thingItem = dynamic_cast(item);
map.insert("thingId", thingItem->thingId());
} else if (item->type() == "folder") {
DashboardFolderItem *folderItem = dynamic_cast(item);
map.insert("name", folderItem->name());
map.insert("icon", folderItem->icon());
QJsonDocument modelDoc = QJsonDocument::fromJson(folderItem->model()->toJson());
map.insert("model", modelDoc.toVariant());
} else if (item->type() == "graph") {
DashboardGraphItem *grapItem = dynamic_cast(item);
map.insert("thingId", grapItem->thingId());
map.insert("stateTypeId", grapItem->stateTypeId());
} else if (item->type() == "scene") {
DashboardSceneItem *sceneItem = dynamic_cast(item);
map.insert("ruleId", sceneItem->ruleId());
} else if (item->type() == "webview") {
DashboardWebViewItem *webViewItem = dynamic_cast(item);
map.insert("url", webViewItem->url());
if (webViewItem->interactive()) {
map.insert("interactive", true);
}
} else if (item->type() == "sensor") {
DashboardSensorItem *sensorItem = dynamic_cast(item);
map.insert("thingId", sensorItem->thingId());
map.insert("interfaces", sensorItem->interfaces());
} else if (item->type() == "state") {
DashboardStateItem *stateItem = dynamic_cast(item);
map.insert("thingId", stateItem->thingId());
map.insert("stateTypeId", stateItem->stateTypeId());
} else {
Q_ASSERT_X(false, Q_FUNC_INFO, "Type " + item->type().toUtf8() + " not implemented!");
continue;
}
if (item->columnSpan() != 1) {
map.insert("columnSpan", item->columnSpan());
}
if (item->rowSpan() != 1) {
map.insert("rowSpan", item->rowSpan());
}
list.append(map);
}
QJsonDocument jsonDoc = QJsonDocument::fromVariant(list);
return jsonDoc.toJson(QJsonDocument::Compact);
}
void DashboardModel::addItem(DashboardItem *item, int index)
{
if (index < 0 || index > m_list.size()) {
index = static_cast(m_list.size());
}
connect(item, &DashboardItem::rowSpanChanged, this, [this, item](){
int idx = static_cast(static_cast(m_list.indexOf(item)));
if (idx >= 0) {
emit dataChanged(this->index(idx), this->index(idx), {RoleRowSpan});
}
});
connect(item, &DashboardItem::columnSpanChanged, this, [this, item](){
int idx = static_cast(static_cast(m_list.indexOf(item)));
if (idx >= 0) {
emit dataChanged(this->index(idx), this->index(idx), {RoleColumnSpan});
}
});
connect(item, &DashboardItem::changed, this, [this]() {
emit changed();
});
beginInsertRows(QModelIndex(), index, index);
m_list.insert(index, item);
endInsertRows();
emit changed();
emit countChanged();
}