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"