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"