Warning, file /plasma/plasma-workspace/kcms/desktoptheme/kcm.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2014 Vishesh Handa <me@vhanda.in>
0004     SPDX-FileCopyrightText: 2016 David Rosca <nowrep@gmail.com>
0005     SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
0006     SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens@enioka.com>
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 <KIO/FileCopyJob>
0017 #include <KIO/JobUiDelegate>
0018 
0019 #include <Plasma/Svg>
0020 #include <Plasma/Theme>
0021 
0022 #include <QDBusConnection>
0023 #include <QDBusMessage>
0024 #include <QDebug>
0025 #include <QProcess>
0026 #include <QQuickItem>
0027 #include <QQuickWindow>
0028 #include <QStandardItemModel>
0029 #include <QStandardPaths>
0030 #include <QTemporaryFile>
0031 
0032 #include "desktopthemedata.h"
0033 #include "filterproxymodel.h"
0034 
0035 Q_LOGGING_CATEGORY(KCM_DESKTOP_THEME, "kcm_desktoptheme")
0036 
0037 K_PLUGIN_FACTORY_WITH_JSON(KCMDesktopThemeFactory, "kcm_desktoptheme.json", registerPlugin<KCMDesktopTheme>(); registerPlugin<DesktopThemeData>();)
0038 
0039 KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
0040     : KQuickAddons::ManagedConfigModule(parent, data, args)
0041     , m_data(new DesktopThemeData(this))
0042     , m_model(new ThemesModel(this))
0043     , m_filteredModel(new FilterProxyModel(this))
0044     , m_haveThemeExplorerInstalled(false)
0045 {
0046     qmlRegisterAnonymousType<DesktopThemeSettings>("org.kde.private.kcms.desktoptheme", 1);
0047     qmlRegisterUncreatableType<ThemesModel>("org.kde.private.kcms.desktoptheme", 1, 0, "ThemesModel", QStringLiteral("Cannot create ThemesModel"));
0048     qmlRegisterUncreatableType<FilterProxyModel>("org.kde.private.kcms.desktoptheme",
0049                                                  1,
0050                                                  0,
0051                                                  "FilterProxyModel",
0052                                                  QStringLiteral("Cannot create FilterProxyModel"));
0053 
0054     setButtons(Apply | Default | Help);
0055 
0056     m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty();
0057 
0058     connect(m_model, &ThemesModel::pendingDeletionsChanged, this, &KCMDesktopTheme::settingsChanged);
0059 
0060     connect(m_model, &ThemesModel::selectedThemeChanged, this, [this](const QString &pluginName) {
0061         desktopThemeSettings()->setName(pluginName);
0062     });
0063 
0064     connect(desktopThemeSettings(), &DesktopThemeSettings::nameChanged, this, [this] {
0065         m_model->setSelectedTheme(desktopThemeSettings()->name());
0066     });
0067 
0068     connect(m_model, &ThemesModel::selectedThemeChanged, m_filteredModel, &FilterProxyModel::setSelectedTheme);
0069 
0070     m_filteredModel->setSourceModel(m_model);
0071 }
0072 
0073 KCMDesktopTheme::~KCMDesktopTheme()
0074 {
0075 }
0076 
0077 DesktopThemeSettings *KCMDesktopTheme::desktopThemeSettings() const
0078 {
0079     return m_data->settings();
0080 }
0081 
0082 ThemesModel *KCMDesktopTheme::desktopThemeModel() const
0083 {
0084     return m_model;
0085 }
0086 
0087 FilterProxyModel *KCMDesktopTheme::filteredModel() const
0088 {
0089     return m_filteredModel;
0090 }
0091 
0092 bool KCMDesktopTheme::downloadingFile() const
0093 {
0094     return m_tempCopyJob;
0095 }
0096 
0097 void KCMDesktopTheme::installThemeFromFile(const QUrl &url)
0098 {
0099     if (url.isLocalFile()) {
0100         installTheme(url.toLocalFile());
0101         return;
0102     }
0103 
0104     if (m_tempCopyJob) {
0105         return;
0106     }
0107 
0108     m_tempInstallFile.reset(new QTemporaryFile());
0109     if (!m_tempInstallFile->open()) {
0110         Q_EMIT showErrorMessage(i18n("Unable to create a temporary file."));
0111         m_tempInstallFile.reset();
0112         return;
0113     }
0114 
0115     m_tempCopyJob = KIO::file_copy(url, QUrl::fromLocalFile(m_tempInstallFile->fileName()), -1, KIO::Overwrite);
0116     m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true);
0117     Q_EMIT downloadingFileChanged();
0118 
0119     connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) {
0120         if (job->error() != KJob::NoError) {
0121             Q_EMIT showErrorMessage(i18n("Unable to download the theme: %1", job->errorText()));
0122             return;
0123         }
0124 
0125         installTheme(m_tempInstallFile->fileName());
0126         m_tempInstallFile.reset();
0127     });
0128     connect(m_tempCopyJob, &QObject::destroyed, this, &KCMDesktopTheme::downloadingFileChanged);
0129 }
0130 
0131 void KCMDesktopTheme::installTheme(const QString &path)
0132 {
0133     qCDebug(KCM_DESKTOP_THEME) << "Installing ... " << path;
0134 
0135     const QString program = QStringLiteral("kpackagetool5");
0136     const QStringList arguments = {QStringLiteral("--type"), QStringLiteral("Plasma/Theme"), QStringLiteral("--install"), path};
0137 
0138     qCDebug(KCM_DESKTOP_THEME) << program << arguments.join(QLatin1Char(' '));
0139     QProcess *myProcess = new QProcess(this);
0140     connect(myProcess,
0141             static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
0142             this,
0143             [this](int exitCode, QProcess::ExitStatus exitStatus) {
0144                 Q_UNUSED(exitStatus)
0145                 if (exitCode == 0) {
0146                     Q_EMIT showSuccessMessage(i18n("Theme installed successfully."));
0147                     load();
0148                 } else {
0149                     Q_EMIT showErrorMessage(i18n("Theme installation failed."));
0150                 }
0151             });
0152 
0153     connect(myProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError e) {
0154         qCWarning(KCM_DESKTOP_THEME) << "Theme installation failed: " << e;
0155         Q_EMIT showErrorMessage(i18n("Theme installation failed."));
0156     });
0157 
0158     myProcess->start(program, arguments);
0159 }
0160 
0161 void KCMDesktopTheme::applyPlasmaTheme(QQuickItem *item, const QString &themeName)
0162 {
0163     if (!item) {
0164         return;
0165     }
0166 
0167     Plasma::Theme *theme = m_themes[themeName];
0168     if (!theme) {
0169         theme = new Plasma::Theme(themeName, this);
0170         m_themes[themeName] = theme;
0171     }
0172 
0173     Q_FOREACH (Plasma::Svg *svg, item->findChildren<Plasma::Svg *>()) {
0174         svg->setTheme(theme);
0175         svg->setUsingRenderingCache(false);
0176     }
0177 }
0178 
0179 void KCMDesktopTheme::load()
0180 {
0181     ManagedConfigModule::load();
0182     m_model->load();
0183     m_model->setSelectedTheme(desktopThemeSettings()->name());
0184 }
0185 
0186 void KCMDesktopTheme::save()
0187 {
0188     auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
0189                                               QStringLiteral("/org/kde/KWin/BlendChanges"),
0190                                               QStringLiteral("org.kde.KWin.BlendChanges"),
0191                                               QStringLiteral("start"));
0192     // Plasma theme changes are known to be slow, so make the blend take a while
0193     msg << 1000;
0194     // This is deliberately blocking so that we ensure Kwin has processed the
0195     // animation start event before we potentially trigger client side changes
0196     QDBusConnection::sessionBus().call(msg);
0197 
0198     ManagedConfigModule::save();
0199     Plasma::Theme().setThemeName(desktopThemeSettings()->name());
0200     processPendingDeletions();
0201 }
0202 
0203 void KCMDesktopTheme::defaults()
0204 {
0205     ManagedConfigModule::defaults();
0206 
0207     // can this be done more elegantly?
0208     const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true);
0209     for (const QModelIndex &idx : pendingDeletions) {
0210         m_model->setData(idx, false, ThemesModel::PendingDeletionRole);
0211     }
0212 }
0213 
0214 bool KCMDesktopTheme::canEditThemes() const
0215 {
0216     return m_haveThemeExplorerInstalled;
0217 }
0218 
0219 void KCMDesktopTheme::editTheme(const QString &theme)
0220 {
0221     QProcess::startDetached(QStringLiteral("plasmathemeexplorer"), {QStringLiteral("-t"), theme});
0222 }
0223 
0224 bool KCMDesktopTheme::isSaveNeeded() const
0225 {
0226     return !m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true).isEmpty();
0227 }
0228 
0229 void KCMDesktopTheme::processPendingDeletions()
0230 {
0231     const QString program = QStringLiteral("plasmapkg2");
0232 
0233     const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true, -1 /*all*/);
0234     QVector<QPersistentModelIndex> persistentPendingDeletions;
0235     // turn into persistent model index so we can delete as we go
0236     std::transform(pendingDeletions.begin(), pendingDeletions.end(), std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) {
0237         return QPersistentModelIndex(idx);
0238     });
0239 
0240     for (const QPersistentModelIndex &idx : persistentPendingDeletions) {
0241         const QString pluginName = idx.data(ThemesModel::PluginNameRole).toString();
0242         const QString displayName = idx.data(Qt::DisplayRole).toString();
0243 
0244         Q_ASSERT(pluginName != desktopThemeSettings()->name());
0245 
0246         const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), pluginName};
0247 
0248         QProcess *process = new QProcess(this);
0249         connect(process,
0250                 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
0251                 this,
0252                 [this, process, idx, pluginName, displayName](int exitCode, QProcess::ExitStatus exitStatus) {
0253                     Q_UNUSED(exitStatus)
0254                     if (exitCode == 0) {
0255                         m_model->removeRow(idx.row());
0256                     } else {
0257                         Q_EMIT showErrorMessage(i18n("Removing theme failed: %1", QString::fromLocal8Bit(process->readAllStandardOutput().trimmed())));
0258                         m_model->setData(idx, false, ThemesModel::PendingDeletionRole);
0259                     }
0260                     process->deleteLater();
0261                 });
0262 
0263         process->start(program, arguments);
0264         process->waitForFinished(); // needed so it deletes fine when "OK" is clicked and the dialog destroyed
0265     }
0266 }
0267 
0268 #include "kcm.moc"