File indexing completed on 2024-05-12 05:35:47

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0004     SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
0005     SPDX-FileCopyrightText: 2019 Cyril Rossi <cyril.rossi@enioka.com>
0006     SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include "kcm.h"
0012 
0013 #include <KLocalizedString>
0014 #include <KPluginFactory>
0015 
0016 #include <QDir>
0017 #include <QProcess>
0018 #include <QSortFilterProxyModel>
0019 #include <QStandardItemModel>
0020 #include <QStandardPaths>
0021 
0022 #include <KPackage/PackageJob>
0023 #include <KPackage/PackageLoader>
0024 #include <KPackage/PackageStructure>
0025 
0026 #include "splashscreendata.h"
0027 #include "splashscreensettings.h"
0028 
0029 K_PLUGIN_FACTORY_WITH_JSON(KCMSplashScreenFactory, "kcm_splashscreen.json", registerPlugin<KCMSplashScreen>(); registerPlugin<SplashScreenData>();)
0030 
0031 const QLatin1String s_nonePluginName("None");
0032 
0033 class SplashScreenSortModel : public QSortFilterProxyModel
0034 {
0035     using QSortFilterProxyModel::QSortFilterProxyModel;
0036     bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override
0037     {
0038         // The "None" entry should be the last item of the model, BUG: 451422
0039         if (source_left.data(KCMSplashScreen::PluginNameRole).toString() == s_nonePluginName) {
0040             return false;
0041         }
0042         if (source_right.data(KCMSplashScreen::PluginNameRole).toString() == s_nonePluginName) {
0043             return true;
0044         }
0045         return QSortFilterProxyModel::lessThan(source_left, source_right);
0046     }
0047 };
0048 
0049 KCMSplashScreen::KCMSplashScreen(QObject *parent, const KPluginMetaData &metaData)
0050     : KQuickManagedConfigModule(parent, metaData)
0051     , m_data(new SplashScreenData(this))
0052     , m_model(new QStandardItemModel(this))
0053 {
0054     qmlRegisterAnonymousType<SplashScreenSettings>("org.kde.plasma.splash.kcm", 0);
0055     qmlRegisterAnonymousType<QStandardItemModel>("org.kde.plasma.splash.kcm", 0);
0056 
0057     setButtons(Help | Apply | Default);
0058 
0059     QHash<int, QByteArray> roles = m_model->roleNames();
0060     roles[PluginNameRole] = "pluginName";
0061     roles[ScreenshotRole] = "screenshot";
0062     roles[DescriptionRole] = "description";
0063     roles[UninstallableRole] = "uninstallable";
0064     roles[PendingDeletionRole] = "pendingDeletion";
0065     m_model->setItemRoleNames(roles);
0066 
0067     m_sortModel = new SplashScreenSortModel(this);
0068     m_sortModel->setSourceModel(m_model);
0069     m_sortModel->setSortLocaleAware(true);
0070     m_sortModel->setSortRole(Qt::DisplayRole);
0071     m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0072     m_sortModel->setDynamicSortFilter(true);
0073 
0074     connect(m_model, &QAbstractItemModel::dataChanged, this, [this] {
0075         bool hasPendingDeletions = !pendingDeletions().isEmpty();
0076         setNeedsSave(m_data->settings()->isSaveNeeded() || hasPendingDeletions);
0077         setRepresentsDefaults(m_data->settings()->isDefaults() && !hasPendingDeletions);
0078     });
0079 }
0080 
0081 QList<KPackage::Package> KCMSplashScreen::availablePackages(const QString &component)
0082 {
0083     QList<KPackage::Package> packages;
0084     QStringList paths;
0085     const QStringList dataPaths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0086 
0087     for (const QString &path : dataPaths) {
0088         QDir dir(path + QStringLiteral("/plasma/look-and-feel"));
0089         paths << dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
0090     }
0091 
0092     for (const QString &path : std::as_const(paths)) {
0093         KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0094         pkg.setPath(path);
0095         pkg.setFallbackPackage(KPackage::Package());
0096         if (component.isEmpty() || !pkg.filePath(component.toUtf8()).isEmpty()) {
0097             packages << pkg;
0098         }
0099     }
0100 
0101     return packages;
0102 }
0103 
0104 SplashScreenSettings *KCMSplashScreen::splashScreenSettings() const
0105 {
0106     return m_data->settings();
0107 }
0108 
0109 QAbstractProxyModel *KCMSplashScreen::splashSortedModel() const
0110 {
0111     return m_sortModel;
0112 }
0113 
0114 void KCMSplashScreen::ghnsEntryChanged(const KNSCore::Entry &entry)
0115 {
0116     auto removeItemFromModel = [this](const QStringList &files) {
0117         if (!files.isEmpty()) {
0118             const QString guessedPluginId = QFileInfo(files.constFirst()).fileName();
0119             const int index = pluginIndex(guessedPluginId);
0120             if (index != -1) {
0121                 m_model->removeRows(index, 1);
0122             }
0123         }
0124     };
0125 
0126     if (entry.status() == KNSCore::Entry::Deleted) {
0127         removeItemFromModel(entry.uninstalledFiles());
0128     } else if (entry.status() == KNSCore::Entry::Installed) {
0129         removeItemFromModel(entry.installedFiles());
0130         KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
0131         pkg.setPath(entry.installedFiles().constFirst());
0132         addKPackageToModel(pkg);
0133         m_sortModel->sort(Qt::DisplayRole);
0134     }
0135 }
0136 
0137 void KCMSplashScreen::addKPackageToModel(const KPackage::Package &pkg)
0138 {
0139     const static QString writableLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
0140     QStandardItem *row = new QStandardItem(pkg.metadata().name());
0141     row->setData(pkg.metadata().pluginId(), PluginNameRole);
0142     row->setData(pkg.fileUrl("previews", QStringLiteral("splash.png")), ScreenshotRole);
0143     row->setData(pkg.metadata().description(), DescriptionRole);
0144     row->setData(pkg.path().startsWith(writableLocation), UninstallableRole);
0145     row->setData(false, PendingDeletionRole);
0146     m_packageRoot = writableLocation + QLatin1Char('/') + pkg.defaultPackageRoot();
0147     m_model->appendRow(row);
0148 }
0149 
0150 void KCMSplashScreen::load()
0151 {
0152     m_data->settings()->load();
0153     m_model->clear();
0154 
0155     const QList<KPackage::Package> pkgs = availablePackages(QStringLiteral("splashmainscript"));
0156     for (const KPackage::Package &pkg : pkgs) {
0157         addKPackageToModel(pkg);
0158     }
0159     m_sortModel->sort(Qt::DisplayRole);
0160 
0161     QStandardItem *row = new QStandardItem(i18n("None"));
0162     row->setData(s_nonePluginName, PluginNameRole);
0163     row->setData(QUrl(), ScreenshotRole);
0164     row->setData(i18n("No splash screen will be shown"), DescriptionRole);
0165     row->setData(false, UninstallableRole);
0166     row->setData(false, PendingDeletionRole);
0167     m_model->insertRow(0, row);
0168 
0169     if (-1 == pluginIndex(m_data->settings()->theme())) {
0170         defaults();
0171     }
0172 
0173     Q_EMIT m_data->settings()->themeChanged();
0174 }
0175 
0176 void KCMSplashScreen::save()
0177 {
0178     using namespace KPackage;
0179     const QStringList pendingDeletionPlugins = pendingDeletions();
0180     for (const QString &plugin : pendingDeletionPlugins) {
0181         if (plugin == m_data->settings()->theme()) {
0182             Q_EMIT error(i18n("You cannot delete the currently selected splash screen"));
0183             m_model->setData(m_model->index(pluginIndex(plugin), 0), false, Roles::PendingDeletionRole);
0184             continue;
0185         }
0186 
0187         KJob *uninstallJob = KPackage::PackageJob::uninstall(QStringLiteral("Plasma/LookAndFeel"), plugin, m_packageRoot);
0188         connect(uninstallJob, &KJob::result, this, [this, uninstallJob, plugin]() {
0189             if (uninstallJob->error()) {
0190                 Q_EMIT error(uninstallJob->errorString());
0191             } else {
0192                 m_model->removeRows(pluginIndex(plugin), 1);
0193             }
0194         });
0195     }
0196     m_data->settings()->setEngine(m_data->settings()->theme() == QStringLiteral("None") ? QStringLiteral("none") : QStringLiteral("KSplashQML"));
0197     KQuickManagedConfigModule::save();
0198 }
0199 
0200 int KCMSplashScreen::sortModelPluginIndex(const QString &pluginName) const
0201 {
0202     return m_sortModel->mapFromSource(m_model->index(pluginIndex(pluginName), 0)).row();
0203 }
0204 
0205 int KCMSplashScreen::pluginIndex(const QString &pluginName) const
0206 {
0207     const auto results = m_model->match(m_model->index(0, 0), PluginNameRole, pluginName, 1, Qt::MatchExactly);
0208     if (results.count() == 1) {
0209         return results.first().row();
0210     }
0211 
0212     return -1;
0213 }
0214 
0215 bool KCMSplashScreen::testing() const
0216 {
0217     return m_testProcess;
0218 }
0219 
0220 void KCMSplashScreen::test(const QString &plugin)
0221 {
0222     if (plugin.isEmpty() || plugin == QLatin1String("None") || m_testProcess) {
0223         return;
0224     }
0225 
0226     m_testProcess = new QProcess(this);
0227     connect(m_testProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
0228         Q_UNUSED(error)
0229         Q_EMIT testingFailed(QString::fromLocal8Bit(m_testProcess->readAllStandardError()));
0230     });
0231     connect(m_testProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
0232         Q_UNUSED(exitCode)
0233         Q_UNUSED(exitStatus)
0234 
0235         m_testProcess->deleteLater();
0236         m_testProcess = nullptr;
0237         Q_EMIT testingChanged();
0238     });
0239 
0240     Q_EMIT testingChanged();
0241     m_testProcess->start(QStringLiteral("ksplashqml"), {plugin, QStringLiteral("--test")});
0242 }
0243 
0244 QStringList KCMSplashScreen::pendingDeletions()
0245 {
0246     QStringList pendingDeletions;
0247     for (int i = 0, count = m_model->rowCount(); i < count; ++i) {
0248         if (m_model->item(i)->data(Roles::PendingDeletionRole).toBool()) {
0249             pendingDeletions << m_model->item(i)->data(Roles::PluginNameRole).toString();
0250         }
0251     }
0252     return pendingDeletions;
0253 }
0254 
0255 void KCMSplashScreen::defaults()
0256 {
0257     KQuickManagedConfigModule::defaults();
0258     // Make sure we clear all pending deletions
0259     for (int i = 0, count = m_model->rowCount(); i < count; ++i) {
0260         m_model->item(i)->setData(false, Roles::PendingDeletionRole);
0261     }
0262 }
0263 
0264 #include "kcm.moc"
0265 #include "moc_kcm.cpp"