// 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(); }