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"