File indexing completed on 2024-12-29 05:06:05

0001 // SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
0002 // SPDX-License-Identifier: GPL-2.0-or-later
0003 
0004 #include "pinnedmodel.h"
0005 
0006 #include <QJsonArray>
0007 #include <QJsonDocument>
0008 
0009 #include <KLocalizedString>
0010 
0011 PinnedModel::PinnedModel(QObject *parent)
0012     : QAbstractListModel{parent}
0013 {
0014 }
0015 
0016 PinnedModel::~PinnedModel() = default;
0017 
0018 PinnedModel *PinnedModel::self()
0019 {
0020     static PinnedModel *inst = new PinnedModel();
0021     return inst;
0022 }
0023 
0024 int PinnedModel::rowCount(const QModelIndex &parent) const
0025 {
0026     return m_applications.count();
0027 }
0028 
0029 QVariant PinnedModel::data(const QModelIndex &index, int role) const
0030 {
0031     if (!index.isValid()) {
0032         return QVariant();
0033     }
0034 
0035     switch (role) {
0036     case IsFolderRole:
0037         return m_folders.at(index.row()) != nullptr;
0038     case ApplicationRole:
0039         return QVariant::fromValue(m_applications.at(index.row()));
0040     case FolderRole:
0041         return QVariant::fromValue(m_folders.at(index.row()));
0042     }
0043 
0044     return QVariant();
0045 }
0046 
0047 QHash<int, QByteArray> PinnedModel::roleNames() const
0048 {
0049     return {{IsFolderRole, "isFolder"}, {ApplicationRole, "application"}, {FolderRole, "folder"}};
0050 }
0051 
0052 void PinnedModel::addApp(const QString &storageId, int row)
0053 {
0054     if (row < 0 || row > m_applications.size()) {
0055         return;
0056     }
0057 
0058     if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
0059         Application *app = new Application(this, service);
0060 
0061         beginInsertRows(QModelIndex(), row, row);
0062         m_applications.insert(row, app);
0063         m_folders.insert(row, nullptr); // maintain indicies
0064         endInsertRows();
0065 
0066         save();
0067     }
0068 }
0069 
0070 void PinnedModel::addFolder(QString name, int row)
0071 {
0072     if (row < 0 || row > m_applications.size()) {
0073         return;
0074     }
0075 
0076     ApplicationFolder *folder = new ApplicationFolder(this, name);
0077     connect(folder, &ApplicationFolder::saveRequested, this, &PinnedModel::save);
0078     connect(folder, &ApplicationFolder::moveAppOutRequested, this, &PinnedModel::addAppFromFolder);
0079 
0080     beginInsertRows(QModelIndex(), row, row);
0081     m_applications.insert(row, nullptr);
0082     m_folders.insert(row, folder);
0083     endInsertRows();
0084 
0085     save();
0086 }
0087 
0088 void PinnedModel::removeEntry(int row)
0089 {
0090     if (row < 0 || row >= m_applications.size()) {
0091         return;
0092     }
0093 
0094     beginRemoveRows(QModelIndex(), row, row);
0095     if (m_folders[row]) {
0096         m_folders[row]->deleteLater();
0097     }
0098     if (m_applications[row]) {
0099         m_applications[row]->deleteLater();
0100     }
0101     m_applications.removeAt(row);
0102     m_folders.removeAt(row);
0103     endRemoveRows();
0104 
0105     save();
0106 }
0107 
0108 void PinnedModel::moveEntry(int fromRow, int toRow)
0109 {
0110     if (fromRow < 0 || toRow < 0 || fromRow >= m_applications.length() || toRow >= m_applications.length() || fromRow == toRow) {
0111         return;
0112     }
0113     if (toRow > fromRow) {
0114         ++toRow;
0115     }
0116 
0117     beginMoveRows(QModelIndex(), fromRow, fromRow, QModelIndex(), toRow);
0118     if (toRow > fromRow) {
0119         Application *app = m_applications.at(fromRow);
0120         m_applications.insert(toRow, app);
0121         m_applications.takeAt(fromRow);
0122 
0123         ApplicationFolder *folder = m_folders.at(fromRow);
0124         m_folders.insert(toRow, folder);
0125         m_folders.takeAt(fromRow);
0126 
0127     } else {
0128         Application *app = m_applications.takeAt(fromRow);
0129         m_applications.insert(toRow, app);
0130 
0131         ApplicationFolder *folder = m_folders.takeAt(fromRow);
0132         m_folders.insert(toRow, folder);
0133     }
0134     endMoveRows();
0135 
0136     save();
0137 }
0138 
0139 void PinnedModel::createFolderFromApps(int sourceAppRow, int draggedAppRow)
0140 {
0141     if (sourceAppRow < 0 || sourceAppRow >= m_applications.size() || draggedAppRow < 0 || draggedAppRow >= m_applications.size()) {
0142         return;
0143     }
0144 
0145     if (sourceAppRow == draggedAppRow || !m_applications[sourceAppRow] || !m_applications[draggedAppRow]) {
0146         return;
0147     }
0148 
0149     // replace source app with folder containing both apps
0150     ApplicationFolder *folder = new ApplicationFolder(this, i18nc("Default application folder name.", "Folder"));
0151     connect(folder, &ApplicationFolder::saveRequested, this, &PinnedModel::save);
0152     connect(folder, &ApplicationFolder::moveAppOutRequested, this, &PinnedModel::addAppFromFolder);
0153 
0154     folder->addApp(m_applications[sourceAppRow]->storageId(), 0);
0155     folder->addApp(m_applications[draggedAppRow]->storageId(), 0);
0156 
0157     m_applications[sourceAppRow]->deleteLater();
0158     m_applications[sourceAppRow] = nullptr;
0159     m_folders[sourceAppRow] = folder;
0160 
0161     Q_EMIT dataChanged(index(sourceAppRow, 0), index(sourceAppRow, 0), {IsFolderRole, ApplicationRole, FolderRole});
0162     save();
0163 
0164     // remove dragged app after
0165     removeEntry(draggedAppRow);
0166 }
0167 
0168 void PinnedModel::addAppToFolder(int appRow, int folderRow)
0169 {
0170     if (appRow < 0 || appRow >= m_applications.size() || folderRow < 0 || folderRow >= m_applications.size()) {
0171         return;
0172     }
0173 
0174     if (!m_applications[appRow] || !m_folders[folderRow]) {
0175         return;
0176     }
0177 
0178     ApplicationFolder *folder = m_folders[folderRow];
0179     Application *app = m_applications[appRow];
0180     folder->addApp(app->storageId(), folder->applications() ? folder->applications()->rowCount() : 0);
0181 
0182     removeEntry(appRow);
0183 }
0184 
0185 void PinnedModel::load()
0186 {
0187     if (!m_applet) {
0188         return;
0189     }
0190 
0191     QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pinned", "{}").toUtf8());
0192 
0193     beginResetModel();
0194 
0195     for (QJsonValueRef r : doc.array()) {
0196         QJsonObject obj = r.toObject();
0197 
0198         if (obj[QStringLiteral("type")].toString() == "application") {
0199             // read application
0200             Application *app = Application::fromJson(obj, this);
0201             if (app) {
0202                 m_applications.append(app);
0203                 m_folders.append(nullptr);
0204             }
0205 
0206         } else if (obj[QStringLiteral("type")].toString() == "folder") {
0207             // read folder
0208             ApplicationFolder *folder = ApplicationFolder::fromJson(obj, this);
0209             connect(folder, &ApplicationFolder::saveRequested, this, &PinnedModel::save);
0210             connect(folder, &ApplicationFolder::moveAppOutRequested, this, &PinnedModel::addAppFromFolder);
0211 
0212             if (folder) {
0213                 m_applications.append(nullptr);
0214                 m_folders.append(folder);
0215             }
0216         }
0217     }
0218 
0219     endResetModel();
0220 }
0221 
0222 void PinnedModel::save()
0223 {
0224     if (!m_applet) {
0225         return;
0226     }
0227 
0228     QJsonArray arr;
0229     for (int i = 0; i < m_applications.size() && i < m_folders.size(); i++) {
0230         if (m_applications[i]) {
0231             arr.push_back(m_applications[i]->toJson());
0232         } else if (m_folders[i]) {
0233             arr.push_back(m_folders[i]->toJson());
0234         }
0235     }
0236     QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
0237 
0238     m_applet->config().writeEntry("Pinned", QString::fromStdString(data.toStdString()));
0239     Q_EMIT m_applet->configNeedsSaving();
0240 }
0241 
0242 void PinnedModel::addAppFromFolder(const QString &storageId)
0243 {
0244     addApp(storageId, 0);
0245 }
0246 
0247 Plasma::Applet *PinnedModel::applet()
0248 {
0249     return m_applet;
0250 }
0251 
0252 void PinnedModel::setApplet(Plasma::Applet *applet)
0253 {
0254     m_applet = applet;
0255     Q_EMIT appletChanged();
0256     load();
0257 }