File indexing completed on 2024-05-12 17:07:20

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