// SPDX-FileCopyrightText: 2022 Devin Lin // SPDX-License-Identifier: GPL-2.0-or-later #include "pinnedmodel.h" #include #include #include PinnedModel::PinnedModel(QObject *parent) : QAbstractListModel{parent} { } PinnedModel::~PinnedModel() = default; PinnedModel *PinnedModel::self() { static PinnedModel *inst = new PinnedModel(); return inst; } int PinnedModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return m_applications.count(); } QVariant PinnedModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case IsFolderRole: return m_folders.at(index.row()) != nullptr; case ApplicationRole: return QVariant::fromValue(m_applications.at(index.row())); case FolderRole: return QVariant::fromValue(m_folders.at(index.row())); } return QVariant(); } QHash PinnedModel::roleNames() const { return {{IsFolderRole, "isFolder"}, {ApplicationRole, "application"}, {FolderRole, "folder"}}; } void PinnedModel::addApp(const QString &storageId, int row) { if (row < 0 || row > m_applications.size()) { return; } if (KService::Ptr service = KService::serviceByStorageId(storageId)) { Application *app = new Application(this, service); beginInsertRows(QModelIndex(), row, row); m_applications.insert(row, app); m_folders.insert(row, nullptr); // maintain indicies endInsertRows(); save(); } } void PinnedModel::addFolder(QString name, int row) { if (row < 0 || row > m_applications.size()) { return; } ApplicationFolder *folder = new ApplicationFolder(this, name); connect(folder, &ApplicationFolder::saveRequested, this, &PinnedModel::save); connect(folder, &ApplicationFolder::moveAppOutRequested, this, &PinnedModel::addAppFromFolder); beginInsertRows(QModelIndex(), row, row); m_applications.insert(row, nullptr); m_folders.insert(row, folder); endInsertRows(); save(); } void PinnedModel::removeEntry(int row) { if (row < 0 || row >= m_applications.size()) { return; } beginRemoveRows(QModelIndex(), row, row); if (m_folders[row]) { m_folders[row]->deleteLater(); } if (m_applications[row]) { m_applications[row]->deleteLater(); } m_applications.removeAt(row); m_folders.removeAt(row); endRemoveRows(); save(); } void PinnedModel::moveEntry(int fromRow, int toRow) { if (fromRow < 0 || toRow < 0 || fromRow >= m_applications.length() || toRow >= m_applications.length() || fromRow == toRow) { return; } if (toRow > fromRow) { ++toRow; } beginMoveRows(QModelIndex(), fromRow, fromRow, QModelIndex(), toRow); if (toRow > fromRow) { Application *app = m_applications.at(fromRow); m_applications.insert(toRow, app); m_applications.takeAt(fromRow); ApplicationFolder *folder = m_folders.at(fromRow); m_folders.insert(toRow, folder); m_folders.takeAt(fromRow); } else { Application *app = m_applications.takeAt(fromRow); m_applications.insert(toRow, app); ApplicationFolder *folder = m_folders.takeAt(fromRow); m_folders.insert(toRow, folder); } endMoveRows(); save(); } void PinnedModel::createFolderFromApps(int sourceAppRow, int draggedAppRow) { if (sourceAppRow < 0 || sourceAppRow >= m_applications.size() || draggedAppRow < 0 || draggedAppRow >= m_applications.size()) { return; } if (sourceAppRow == draggedAppRow || !m_applications[sourceAppRow] || !m_applications[draggedAppRow]) { return; } // replace source app with folder containing both apps ApplicationFolder *folder = new ApplicationFolder(this, i18nc("Default application folder name.", "Folder")); connect(folder, &ApplicationFolder::saveRequested, this, &PinnedModel::save); connect(folder, &ApplicationFolder::moveAppOutRequested, this, &PinnedModel::addAppFromFolder); folder->addApp(m_applications[sourceAppRow]->storageId(), 0); folder->addApp(m_applications[draggedAppRow]->storageId(), 0); m_applications[sourceAppRow]->deleteLater(); m_applications[sourceAppRow] = nullptr; m_folders[sourceAppRow] = folder; Q_EMIT dataChanged(index(sourceAppRow, 0), index(sourceAppRow, 0), {IsFolderRole, ApplicationRole, FolderRole}); save(); // remove dragged app after removeEntry(draggedAppRow); } void PinnedModel::addAppToFolder(int appRow, int folderRow) { if (appRow < 0 || appRow >= m_applications.size() || folderRow < 0 || folderRow >= m_applications.size()) { return; } if (!m_applications[appRow] || !m_folders[folderRow]) { return; } ApplicationFolder *folder = m_folders[folderRow]; Application *app = m_applications[appRow]; folder->addApp(app->storageId(), folder->applications() ? folder->applications()->rowCount() : 0); removeEntry(appRow); } void PinnedModel::load() { if (!m_applet) { return; } QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pinned", "{}").toUtf8()); beginResetModel(); for (QJsonValueRef r : doc.array()) { QJsonObject obj = r.toObject(); if (obj[QStringLiteral("type")].toString() == "application") { // read application Application *app = Application::fromJson(obj, this); if (app) { m_applications.append(app); m_folders.append(nullptr); } } else if (obj[QStringLiteral("type")].toString() == "folder") { // read folder ApplicationFolder *folder = ApplicationFolder::fromJson(obj, this); connect(folder, &ApplicationFolder::saveRequested, this, &PinnedModel::save); connect(folder, &ApplicationFolder::moveAppOutRequested, this, &PinnedModel::addAppFromFolder); if (folder) { m_applications.append(nullptr); m_folders.append(folder); } } } endResetModel(); } void PinnedModel::save() { if (!m_applet) { return; } QJsonArray arr; for (int i = 0; i < m_applications.size() && i < m_folders.size(); i++) { if (m_applications[i]) { arr.push_back(m_applications[i]->toJson()); } else if (m_folders[i]) { arr.push_back(m_folders[i]->toJson()); } } QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact); m_applet->config().writeEntry("Pinned", QString::fromStdString(data.toStdString())); Q_EMIT m_applet->configNeedsSaving(); } void PinnedModel::addAppFromFolder(const QString &storageId) { addApp(storageId, 0); } Plasma::Applet *PinnedModel::applet() { return m_applet; } void PinnedModel::setApplet(Plasma::Applet *applet) { m_applet = applet; Q_EMIT appletChanged(); load(); }