Changeset View
Changeset View
Standalone View
Standalone View
kcms/desktoptheme/kcm.cpp
Show All 19 Lines | 1 | /* This file is part of the KDE Project | |||
---|---|---|---|---|---|
20 | Boston, MA 02110-1301, USA. | 20 | Boston, MA 02110-1301, USA. | ||
21 | */ | 21 | */ | ||
22 | 22 | | |||
23 | #include "kcm.h" | 23 | #include "kcm.h" | ||
24 | 24 | | |||
25 | #include <KPluginFactory> | 25 | #include <KPluginFactory> | ||
26 | #include <KAboutData> | 26 | #include <KAboutData> | ||
27 | #include <KLocalizedString> | 27 | #include <KLocalizedString> | ||
28 | #include <KDesktopFile> | | |||
29 | 28 | | |||
30 | #include <KIO/FileCopyJob> | 29 | #include <KIO/FileCopyJob> | ||
31 | #include <KIO/JobUiDelegate> | 30 | #include <KIO/JobUiDelegate> | ||
32 | 31 | | |||
33 | #include <Plasma/Theme> | 32 | #include <Plasma/Theme> | ||
34 | #include <Plasma/Svg> | 33 | #include <Plasma/Svg> | ||
35 | 34 | | |||
36 | #include <QDebug> | 35 | #include <QDebug> | ||
37 | #include <QProcess> | 36 | #include <QProcess> | ||
38 | #include <QQuickItem> | 37 | #include <QQuickItem> | ||
39 | #include <QQuickWindow> | 38 | #include <QQuickWindow> | ||
40 | #include <QStandardPaths> | 39 | #include <QStandardPaths> | ||
41 | #include <QStandardItemModel> | 40 | #include <QStandardItemModel> | ||
42 | #include <QTemporaryFile> | 41 | #include <QTemporaryFile> | ||
43 | 42 | | |||
44 | #include <KNewStuff3/KNS3/DownloadDialog> | 43 | #include <KNewStuff3/KNS3/DownloadDialog> | ||
45 | 44 | | |||
46 | #include "desktopthemesettings.h" | 45 | #include "desktopthemesettings.h" | ||
46 | #include "filterproxymodel.h" | ||||
47 | #include "themesmodel.h" | ||||
47 | 48 | | |||
48 | Q_LOGGING_CATEGORY(KCM_DESKTOP_THEME, "kcm_desktoptheme") | 49 | Q_LOGGING_CATEGORY(KCM_DESKTOP_THEME, "kcm_desktoptheme") | ||
49 | 50 | | |||
50 | K_PLUGIN_FACTORY_WITH_JSON(KCMDesktopThemeFactory, "kcm_desktoptheme.json", registerPlugin<KCMDesktopTheme>();) | 51 | K_PLUGIN_FACTORY_WITH_JSON(KCMDesktopThemeFactory, "kcm_desktoptheme.json", registerPlugin<KCMDesktopTheme>();) | ||
51 | 52 | | |||
52 | KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const QVariantList &args) | 53 | KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const QVariantList &args) | ||
53 | : KQuickAddons::ManagedConfigModule(parent, args) | 54 | : KQuickAddons::ManagedConfigModule(parent, args) | ||
54 | , m_settings(new DesktopThemeSettings(this)) | 55 | , m_settings(new DesktopThemeSettings(this)) | ||
56 | , m_model(new ThemesModel(this)) | ||||
57 | , m_filteredModel(new FilterProxyModel(this)) | ||||
55 | , m_haveThemeExplorerInstalled(false) | 58 | , m_haveThemeExplorerInstalled(false) | ||
56 | { | 59 | { | ||
57 | qmlRegisterType<DesktopThemeSettings>(); | 60 | qmlRegisterType<DesktopThemeSettings>(); | ||
58 | qmlRegisterType<QStandardItemModel>(); | 61 | qmlRegisterUncreatableType<ThemesModel>("org.kde.private.kcms.desktoptheme", 1, 0, "ThemesModel", "Cannot create ThemesModel"); | ||
62 | qmlRegisterUncreatableType<FilterProxyModel>("org.kde.private.kcms.desktoptheme", 1, 0, "FilterProxyModel", "Cannot create FilterProxyModel"); | ||||
59 | 63 | | |||
60 | KAboutData* about = new KAboutData(QStringLiteral("kcm_desktoptheme"), i18n("Plasma Style"), | 64 | KAboutData* about = new KAboutData(QStringLiteral("kcm_desktoptheme"), i18n("Plasma Style"), | ||
61 | QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); | 65 | QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); | ||
62 | about->addAuthor(i18n("David Rosca"), QString(), QStringLiteral("nowrep@gmail.com")); | 66 | about->addAuthor(i18n("David Rosca"), QString(), QStringLiteral("nowrep@gmail.com")); | ||
63 | setAboutData(about); | 67 | setAboutData(about); | ||
64 | setButtons(Apply | Default | Help); | 68 | setButtons(Apply | Default | Help); | ||
65 | 69 | | |||
66 | m_model = new QStandardItemModel(this); | | |||
67 | QHash<int, QByteArray> roles = m_model->roleNames(); | | |||
68 | roles[PluginNameRole] = QByteArrayLiteral("pluginName"); | | |||
69 | roles[ThemeNameRole] = QByteArrayLiteral("themeName"); | | |||
70 | roles[DescriptionRole] = QByteArrayLiteral("description"); | | |||
71 | roles[FollowsSystemColorsRole] = QByteArrayLiteral("followsSystemColors"); | | |||
72 | roles[IsLocalRole] = QByteArrayLiteral("isLocal"); | | |||
73 | roles[PendingDeletionRole] = QByteArrayLiteral("pendingDeletion"); | | |||
74 | m_model->setItemRoleNames(roles); | | |||
75 | | ||||
76 | m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty(); | 70 | m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty(); | ||
71 | | ||||
72 | connect(m_model, &ThemesModel::pendingDeletionsChanged, this, &KCMDesktopTheme::settingsChanged); | ||||
crossi: Should connect to KCMDesktopTheme::settingsChanged slot to request ManagedConfigModule to… | |||||
73 | | ||||
74 | connect(m_model, &ThemesModel::selectedThemeChanged, this, [this](const QString &pluginName) { | ||||
75 | m_settings->setName(pluginName); | ||||
76 | }); | ||||
77 | | ||||
78 | connect(m_settings, &DesktopThemeSettings::nameChanged, this, [this] { | ||||
79 | m_model->setSelectedTheme(m_settings->name()); | ||||
80 | }); | ||||
81 | | ||||
82 | connect(m_model, &ThemesModel::selectedThemeChanged, m_filteredModel, &FilterProxyModel::setSelectedTheme); | ||||
83 | | ||||
84 | m_filteredModel->setSourceModel(m_model); | ||||
77 | } | 85 | } | ||
78 | 86 | | |||
79 | KCMDesktopTheme::~KCMDesktopTheme() | 87 | KCMDesktopTheme::~KCMDesktopTheme() | ||
80 | { | 88 | { | ||
81 | } | 89 | } | ||
82 | 90 | | |||
83 | DesktopThemeSettings *KCMDesktopTheme::desktopThemeSettings() const | 91 | DesktopThemeSettings *KCMDesktopTheme::desktopThemeSettings() const | ||
84 | { | 92 | { | ||
85 | return m_settings; | 93 | return m_settings; | ||
86 | } | 94 | } | ||
87 | 95 | | |||
88 | QStandardItemModel *KCMDesktopTheme::desktopThemeModel() const | 96 | ThemesModel *KCMDesktopTheme::desktopThemeModel() const | ||
89 | { | 97 | { | ||
90 | return m_model; | 98 | return m_model; | ||
91 | } | 99 | } | ||
92 | 100 | | |||
93 | int KCMDesktopTheme::pluginIndex(const QString &pluginName) const | 101 | FilterProxyModel *KCMDesktopTheme::filteredModel() const | ||
94 | { | 102 | { | ||
95 | const auto results = m_model->match(m_model->index(0, 0), PluginNameRole, pluginName); | 103 | return m_filteredModel; | ||
96 | if (results.count() == 1) { | | |||
97 | return results.first().row(); | | |||
98 | } | | |||
99 | | ||||
100 | return -1; | | |||
101 | } | 104 | } | ||
102 | 105 | | |||
103 | bool KCMDesktopTheme::downloadingFile() const | 106 | bool KCMDesktopTheme::downloadingFile() const | ||
104 | { | 107 | { | ||
105 | return m_tempCopyJob; | 108 | return m_tempCopyJob; | ||
106 | } | 109 | } | ||
107 | 110 | | |||
108 | void KCMDesktopTheme::setPendingDeletion(int index, bool pending) | | |||
109 | { | | |||
110 | QModelIndex idx = m_model->index(index, 0); | | |||
111 | | ||||
112 | m_model->setData(idx, pending, PendingDeletionRole); | | |||
113 | | ||||
114 | if (pending && pluginIndex(m_settings->name()) == index) { | | |||
115 | // move to the next non-pending theme | | |||
116 | const auto nonPending = m_model->match(idx, PendingDeletionRole, false); | | |||
117 | m_settings->setName(nonPending.first().data(PluginNameRole).toString()); | | |||
118 | } | | |||
119 | | ||||
120 | settingsChanged(); | | |||
121 | } | | |||
122 | | ||||
123 | void KCMDesktopTheme::getNewStuff(QQuickItem *ctx) | 111 | void KCMDesktopTheme::getNewStuff(QQuickItem *ctx) | ||
124 | { | 112 | { | ||
125 | if (!m_newStuffDialog) { | 113 | if (!m_newStuffDialog) { | ||
126 | m_newStuffDialog = new KNS3::DownloadDialog(QStringLiteral("plasma-themes.knsrc")); | 114 | m_newStuffDialog = new KNS3::DownloadDialog(QStringLiteral("plasma-themes.knsrc")); | ||
127 | m_newStuffDialog.data()->setWindowTitle(i18n("Download New Plasma Styles")); | 115 | m_newStuffDialog.data()->setWindowTitle(i18n("Download New Plasma Styles")); | ||
128 | m_newStuffDialog->setWindowModality(Qt::WindowModal); | 116 | m_newStuffDialog->setWindowModality(Qt::WindowModal); | ||
129 | m_newStuffDialog->winId(); // so it creates the windowHandle(); | 117 | m_newStuffDialog->winId(); // so it creates the windowHandle(); | ||
130 | connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::accepted, this, &KCMDesktopTheme::load); | 118 | connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::accepted, this, &KCMDesktopTheme::load); | ||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Line(s) | 205 | Q_FOREACH (Plasma::Svg *svg, item->findChildren<Plasma::Svg*>()) { | |||
218 | svg->setTheme(theme); | 206 | svg->setTheme(theme); | ||
219 | svg->setUsingRenderingCache(false); | 207 | svg->setUsingRenderingCache(false); | ||
220 | } | 208 | } | ||
221 | } | 209 | } | ||
222 | 210 | | |||
223 | void KCMDesktopTheme::load() | 211 | void KCMDesktopTheme::load() | ||
224 | { | 212 | { | ||
225 | ManagedConfigModule::load(); | 213 | ManagedConfigModule::load(); | ||
226 | 214 | m_model->load(); | |||
227 | m_pendingRemoval.clear(); | 215 | m_model->setSelectedTheme(m_settings->name()); | ||
228 | | ||||
229 | // Get all desktop themes | | |||
230 | QStringList themes; | | |||
231 | const QStringList &packs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/desktoptheme"), QStandardPaths::LocateDirectory); | | |||
232 | Q_FOREACH (const QString &ppath, packs) { | | |||
233 | const QDir cd(ppath); | | |||
234 | const QStringList &entries = cd.entryList(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot); | | |||
235 | Q_FOREACH (const QString &pack, entries) { | | |||
236 | const QString _metadata = ppath + QLatin1Char('/') + pack + QStringLiteral("/metadata.desktop"); | | |||
237 | if (QFile::exists(_metadata)) { | | |||
238 | themes << _metadata; | | |||
239 | } | | |||
240 | } | | |||
241 | } | | |||
242 | | ||||
243 | m_model->clear(); | | |||
244 | | ||||
245 | Q_FOREACH (const QString &theme, themes) { | | |||
246 | int themeSepIndex = theme.lastIndexOf(QLatin1Char('/'), -1); | | |||
247 | const QString themeRoot = theme.left(themeSepIndex); | | |||
248 | int themeNameSepIndex = themeRoot.lastIndexOf(QLatin1Char('/'), -1); | | |||
249 | const QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); | | |||
250 | | ||||
251 | KDesktopFile df(theme); | | |||
252 | | ||||
253 | if (df.noDisplay()) { | | |||
254 | continue; | | |||
255 | } | | |||
256 | | ||||
257 | QString name = df.readName(); | | |||
258 | if (name.isEmpty()) { | | |||
259 | name = packageName; | | |||
260 | } | | |||
261 | const bool isLocal = QFileInfo(theme).isWritable(); | | |||
262 | // Plasma Theme creates a KColorScheme out of the "color" file and falls back to system colors if there is none | | |||
263 | const bool followsSystemColors = !QFileInfo::exists(themeRoot + QLatin1String("/colors")); | | |||
264 | | ||||
265 | if (m_model->findItems(packageName).isEmpty()) { | | |||
266 | QStandardItem *item = new QStandardItem; | | |||
267 | item->setText(packageName); | | |||
268 | item->setData(packageName, PluginNameRole); | | |||
269 | item->setData(name, ThemeNameRole); | | |||
270 | item->setData(df.readComment(), DescriptionRole); | | |||
271 | item->setData(followsSystemColors, FollowsSystemColorsRole); | | |||
272 | item->setData(isLocal, IsLocalRole); | | |||
273 | item->setData(false, PendingDeletionRole); | | |||
274 | m_model->appendRow(item); | | |||
275 | } | | |||
276 | } | | |||
277 | | ||||
278 | m_model->setSortRole(ThemeNameRole); // FIXME the model should really be just using Qt::DisplayRole | | |||
279 | m_model->sort(0 /*column*/); | | |||
280 | | ||||
281 | // Model has been cleared so pretend the theme name changed to force view update | | |||
282 | emit m_settings->nameChanged(); | | |||
283 | } | 216 | } | ||
284 | 217 | | |||
285 | void KCMDesktopTheme::save() | 218 | void KCMDesktopTheme::save() | ||
286 | { | 219 | { | ||
287 | ManagedConfigModule::save(); | 220 | ManagedConfigModule::save(); | ||
288 | Plasma::Theme().setThemeName(m_settings->name()); | 221 | Plasma::Theme().setThemeName(m_settings->name()); | ||
289 | processPendingDeletions(); | 222 | processPendingDeletions(); | ||
290 | } | 223 | } | ||
291 | 224 | | |||
292 | void KCMDesktopTheme::defaults() | 225 | void KCMDesktopTheme::defaults() | ||
293 | { | 226 | { | ||
294 | ManagedConfigModule::defaults(); | 227 | ManagedConfigModule::defaults(); | ||
295 | 228 | | |||
296 | // can this be done more elegantly? | 229 | // can this be done more elegantly? | ||
297 | const auto pendingDeletions = m_model->match(m_model->index(0, 0), PendingDeletionRole, true); | 230 | const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true); | ||
298 | for (const QModelIndex &idx : pendingDeletions) { | 231 | for (const QModelIndex &idx : pendingDeletions) { | ||
299 | m_model->setData(idx, false, PendingDeletionRole); | 232 | m_model->setData(idx, false, ThemesModel::PendingDeletionRole); | ||
300 | } | 233 | } | ||
301 | } | 234 | } | ||
302 | 235 | | |||
303 | bool KCMDesktopTheme::canEditThemes() const | 236 | bool KCMDesktopTheme::canEditThemes() const | ||
304 | { | 237 | { | ||
305 | return m_haveThemeExplorerInstalled; | 238 | return m_haveThemeExplorerInstalled; | ||
306 | } | 239 | } | ||
307 | 240 | | |||
308 | void KCMDesktopTheme::editTheme(const QString &theme) | 241 | void KCMDesktopTheme::editTheme(const QString &theme) | ||
309 | { | 242 | { | ||
310 | QProcess::startDetached(QStringLiteral("plasmathemeexplorer -t ") % theme); | 243 | QProcess::startDetached(QStringLiteral("plasmathemeexplorer -t ") % theme); | ||
311 | } | 244 | } | ||
312 | 245 | | |||
313 | bool KCMDesktopTheme::isSaveNeeded() const | 246 | bool KCMDesktopTheme::isSaveNeeded() const | ||
Why removing this ? It is necessary for ManagedConfigModule to enable the apply button when items are marked for deletion. crossi: Why removing this ?
It is necessary for ManagedConfigModule to enable the apply button when… | |||||
314 | { | 247 | { | ||
315 | return !m_model->match(m_model->index(0, 0), PendingDeletionRole, true).isEmpty(); | 248 | return !m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true).isEmpty(); | ||
316 | } | 249 | } | ||
317 | 250 | | |||
318 | void KCMDesktopTheme::processPendingDeletions() | 251 | void KCMDesktopTheme::processPendingDeletions() | ||
319 | { | 252 | { | ||
320 | const QString program = QStringLiteral("plasmapkg2"); | 253 | const QString program = QStringLiteral("plasmapkg2"); | ||
321 | 254 | | |||
322 | const auto pendingDeletions = m_model->match(m_model->index(0, 0), PendingDeletionRole, true, -1 /*all*/); | 255 | const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true, -1 /*all*/); | ||
323 | QVector<QPersistentModelIndex> persistentPendingDeletions; | 256 | QVector<QPersistentModelIndex> persistentPendingDeletions; | ||
324 | // turn into persistent model index so we can delete as we go | 257 | // turn into persistent model index so we can delete as we go | ||
325 | std::transform(pendingDeletions.begin(), pendingDeletions.end(), | 258 | std::transform(pendingDeletions.begin(), pendingDeletions.end(), | ||
326 | std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) { | 259 | std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) { | ||
327 | return QPersistentModelIndex(idx); | 260 | return QPersistentModelIndex(idx); | ||
328 | }); | 261 | }); | ||
329 | 262 | | |||
330 | for (const QPersistentModelIndex &idx : persistentPendingDeletions) { | 263 | for (const QPersistentModelIndex &idx : persistentPendingDeletions) { | ||
331 | const QString pluginName = idx.data(PluginNameRole).toString(); | 264 | const QString pluginName = idx.data(ThemesModel::PluginNameRole).toString(); | ||
332 | const QString displayName = idx.data(Qt::DisplayRole).toString(); | 265 | const QString displayName = idx.data(Qt::DisplayRole).toString(); | ||
333 | 266 | | |||
334 | Q_ASSERT(pluginName != m_settings->name()); | 267 | Q_ASSERT(pluginName != m_settings->name()); | ||
335 | 268 | | |||
336 | const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), pluginName}; | 269 | const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), pluginName}; | ||
337 | 270 | | |||
338 | QProcess *process = new QProcess(this); | 271 | QProcess *process = new QProcess(this); | ||
339 | connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, | 272 | connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, | ||
340 | [this, process, idx, pluginName, displayName](int exitCode, QProcess::ExitStatus exitStatus) { | 273 | [this, process, idx, pluginName, displayName](int exitCode, QProcess::ExitStatus exitStatus) { | ||
341 | Q_UNUSED(exitStatus) | 274 | Q_UNUSED(exitStatus) | ||
342 | if (exitCode == 0) { | 275 | if (exitCode == 0) { | ||
343 | m_model->removeRow(idx.row()); | 276 | m_model->removeRow(idx.row()); | ||
344 | } else { | 277 | } else { | ||
345 | emit showErrorMessage(i18n("Removing theme failed: %1", | 278 | emit showErrorMessage(i18n("Removing theme failed: %1", | ||
346 | QString::fromLocal8Bit(process->readAllStandardOutput().trimmed()))); | 279 | QString::fromLocal8Bit(process->readAllStandardOutput().trimmed()))); | ||
347 | m_model->setData(idx, false, PendingDeletionRole); | 280 | m_model->setData(idx, false, ThemesModel::PendingDeletionRole); | ||
348 | } | 281 | } | ||
349 | process->deleteLater(); | 282 | process->deleteLater(); | ||
350 | }); | 283 | }); | ||
351 | 284 | | |||
352 | process->start(program, arguments); | 285 | process->start(program, arguments); | ||
353 | process->waitForFinished(); // needed so it deletes fine when "OK" is clicked and the dialog destroyed | 286 | process->waitForFinished(); // needed so it deletes fine when "OK" is clicked and the dialog destroyed | ||
354 | } | 287 | } | ||
355 | } | 288 | } | ||
356 | 289 | | |||
357 | #include "kcm.moc" | 290 | #include "kcm.moc" |
Should connect to KCMDesktopTheme::settingsChanged slot to request ManagedConfigModule to reassess isSaveNeeded().