Commit 03f29a85 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇

[Desktop Theme KCM] Adjust to changes in other KCMs

* Implement undo delete feature
* Introduce currentIndex property instead of indexOf() method
* Make GHNS dialog window modal instead of application modal
* Support drag and drop to install themes
* Use InlineMessage for installation error/success notifications

Differential Revision: https://phabricator.kde.org/D12503
parent 9a333b48
......@@ -2,6 +2,7 @@
Copyright (c) 2014 Marco Martin <mart@kde.org>
Copyright (c) 2014 Vishesh Handa <me@vhanda.in>
Copyright (c) 2016 David Rosca <nowrep@gmail.com>
Copyright (c) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
......@@ -32,6 +33,7 @@
#include <QDebug>
#include <QProcess>
#include <QQuickItem>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QStandardItemModel>
......@@ -64,6 +66,7 @@ KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const QVariantList &args)
roles[ThemeNameRole] = QByteArrayLiteral("themeName");
roles[DescriptionRole] = QByteArrayLiteral("description");
roles[IsLocalRole] = QByteArrayLiteral("isLocal");
roles[PendingDeletionRole] = QByteArrayLiteral("pendingDeletion");
m_model->setItemRoleNames(roles);
m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty();
......@@ -90,21 +93,51 @@ void KCMDesktopTheme::setSelectedPlugin(const QString &plugin)
return;
}
m_selectedPlugin = plugin;
Q_EMIT selectedPluginChanged(m_selectedPlugin);
emit selectedPluginChanged(m_selectedPlugin);
emit selectedPluginIndexChanged();
updateNeedsSave();
}
void KCMDesktopTheme::getNewThemes()
int KCMDesktopTheme::selectedPluginIndex() const
{
KNS3::DownloadDialog *dialog = new KNS3::DownloadDialog(QStringLiteral("plasma-themes.knsrc"));
dialog->open();
const auto results = m_model->match(m_model->index(0, 0), PluginNameRole, m_selectedPlugin);
if (results.count() == 1) {
return results.first().row();
}
connect(dialog, &QDialog::accepted, this, [this, dialog]() {
if (!dialog->changedEntries().isEmpty()) {
load();
delete dialog;
}
});
return -1;
}
void KCMDesktopTheme::setPendingDeletion(int index, bool pending)
{
QModelIndex idx = m_model->index(index, 0);
m_model->setData(idx, pending, PendingDeletionRole);
if (pending && selectedPluginIndex() == index) {
// move to the next non-pending theme
const auto nonPending = m_model->match(idx, PendingDeletionRole, false);
setSelectedPlugin(nonPending.first().data(PluginNameRole).toString());
}
updateNeedsSave();
}
void KCMDesktopTheme::getNewStuff(QQuickItem *ctx)
{
if (!m_newStuffDialog) {
m_newStuffDialog = new KNS3::DownloadDialog(QStringLiteral("plasma-themes.knsrc"));
m_newStuffDialog.data()->setWindowTitle(i18n("Download New Desktop Themes"));
m_newStuffDialog->setWindowModality(Qt::WindowModal);
m_newStuffDialog->winId(); // so it creates the windowHandle();
connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::accepted, this, &KCMDesktopTheme::load);
}
if (ctx && ctx->window()) {
m_newStuffDialog->windowHandle()->setTransientParent(ctx->window());
}
m_newStuffDialog.data()->show();
}
void KCMDesktopTheme::installThemeFromFile(const QUrl &file)
......@@ -117,15 +150,13 @@ void KCMDesktopTheme::installThemeFromFile(const QUrl &file)
qCDebug(KCM_DESKTOP_THEME) << program << arguments.join(QStringLiteral(" "));
QProcess *myProcess = new QProcess(this);
connect(myProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
this, [this, myProcess](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitStatus);
if (exitCode == 0) {
qCDebug(KCM_DESKTOP_THEME) << "Theme installed successfully :)";
load();
Q_EMIT showSuccessMessage(i18n("Theme installed successfully."));
} else {
qCWarning(KCM_DESKTOP_THEME) << "Theme installation failed." << exitCode;
Q_EMIT showErrorMessage(i18n("Theme installation failed."));
}
});
......@@ -138,17 +169,6 @@ void KCMDesktopTheme::installThemeFromFile(const QUrl &file)
myProcess->start(program, arguments);
}
void KCMDesktopTheme::removeTheme(const QString &name)
{
Q_ASSERT(!m_pendingRemoval.contains(name));
Q_ASSERT(!m_model->findItems(name).isEmpty());
m_pendingRemoval.append(name);
m_model->removeRow(m_model->findItems(name).at(0)->row());
updateNeedsSave();
}
void KCMDesktopTheme::applyPlasmaTheme(QQuickItem *item, const QString &themeName)
{
if (!item) {
......@@ -167,16 +187,6 @@ void KCMDesktopTheme::applyPlasmaTheme(QQuickItem *item, const QString &themeNam
}
}
int KCMDesktopTheme::indexOf(const QString &themeName) const
{
for (int i = 0; i < m_model->rowCount(); ++i) {
if (m_model->data(m_model->index(i, 0), PluginNameRole).toString() == themeName) {
return i;
}
}
return -1;
}
void KCMDesktopTheme::load()
{
m_pendingRemoval.clear();
......@@ -222,6 +232,7 @@ void KCMDesktopTheme::load()
item->setData(name, ThemeNameRole);
item->setData(df.readComment(), DescriptionRole);
item->setData(isLocal, IsLocalRole);
item->setData(false, PendingDeletionRole);
m_model->appendRow(item);
}
}
......@@ -234,18 +245,23 @@ void KCMDesktopTheme::load()
void KCMDesktopTheme::save()
{
if (m_defaultTheme->themeName() == m_selectedPlugin) {
return;
if (m_defaultTheme->themeName() != m_selectedPlugin) {
m_defaultTheme->setThemeName(m_selectedPlugin);
}
m_defaultTheme->setThemeName(m_selectedPlugin);
removeThemes();
processPendingDeletions();
updateNeedsSave();
}
void KCMDesktopTheme::defaults()
{
setSelectedPlugin(QStringLiteral("default"));
// can this be done more elegantly?
const auto pendingDeletions = m_model->match(m_model->index(0, 0), PendingDeletionRole, true);
for (const QModelIndex &idx : pendingDeletions) {
m_model->setData(idx, false, PendingDeletionRole);
}
}
bool KCMDesktopTheme::canEditThemes() const
......@@ -260,38 +276,46 @@ void KCMDesktopTheme::editTheme(const QString &theme)
void KCMDesktopTheme::updateNeedsSave()
{
setNeedsSave(!m_pendingRemoval.isEmpty() || m_selectedPlugin != m_defaultTheme->themeName());
setNeedsSave(!m_model->match(m_model->index(0, 0), PendingDeletionRole, true).isEmpty()
|| m_selectedPlugin != m_defaultTheme->themeName());
}
void KCMDesktopTheme::removeThemes()
void KCMDesktopTheme::processPendingDeletions()
{
const QString program = QStringLiteral("plasmapkg2");
Q_FOREACH (const QString &name, m_pendingRemoval) {
const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), name};
qCDebug(KCM_DESKTOP_THEME) << program << arguments.join(QStringLiteral(" "));
const auto pendingDeletions = m_model->match(m_model->index(0, 0), PendingDeletionRole, true, -1 /*all*/);
QVector<QPersistentModelIndex> persistentPendingDeletions;
// turn into persistent model index so we can delete as we go
std::transform(pendingDeletions.begin(), pendingDeletions.end(),
std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) {
return QPersistentModelIndex(idx);
});
for (const QPersistentModelIndex &idx : persistentPendingDeletions) {
const QString pluginName = idx.data(PluginNameRole).toString();
const QString displayName = idx.data(Qt::DisplayRole).toString();
Q_ASSERT(pluginName != m_selectedPlugin);
const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), pluginName};
QProcess *process = new QProcess(this);
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [this, process](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitStatus);
if (exitCode == 0) {
qCDebug(KCM_DESKTOP_THEME) << "Theme removed successfully :)";
load();
} else {
qCWarning(KCM_DESKTOP_THEME) << "Theme removal failed." << exitCode;
Q_EMIT showErrorMessage(i18n("Theme removal failed."));
}
process->deleteLater();
});
connect(process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
this, [this, process](QProcess::ProcessError e) {
qCWarning(KCM_DESKTOP_THEME) << "Theme removal failed: " << e;
Q_EMIT showErrorMessage(i18n("Theme removal failed."));
process->deleteLater();
});
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[this, process, idx, pluginName, displayName](int exitCode, QProcess::ExitStatus exitStatus) {
Q_UNUSED(exitStatus);
if (exitCode == 0) {
m_model->removeRow(idx.row());
} else {
emit showErrorMessage(i18n("Removing theme failed: %1",
QString::fromLocal8Bit(process->readAllStandardOutput().trimmed())));
m_model->setData(idx, false, PendingDeletionRole);
}
process->deleteLater();
});
process->start(program, arguments);
process->waitForFinished(); // needed so it deletes fine when "OK" is clicked and the dialog destroyed
}
}
......
......@@ -2,6 +2,7 @@
Copyright (c) 2014 Marco Martin <mart@kde.org>
Copyright (c) 2014 Vishesh Handa <me@vhanda.in>
Copyright (c) 2016 David Rosca <nowrep@gmail.com>
Copyright (c) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
......@@ -23,11 +24,14 @@
#include <KQuickAddons/ConfigModule>
#include <KNewStuff3/KNS3/DownloadDialog>
namespace Plasma {
class Svg;
class Theme;
}
class QQuickItem;
class QStandardItemModel;
class KCMDesktopTheme : public KQuickAddons::ConfigModule
......@@ -35,6 +39,7 @@ class KCMDesktopTheme : public KQuickAddons::ConfigModule
Q_OBJECT
Q_PROPERTY(QStandardItemModel *desktopThemeModel READ desktopThemeModel CONSTANT)
Q_PROPERTY(QString selectedPlugin READ selectedPlugin WRITE setSelectedPlugin NOTIFY selectedPluginChanged)
Q_PROPERTY(int selectedPluginIndex READ selectedPluginIndex NOTIFY selectedPluginIndexChanged)
Q_PROPERTY(bool canEditThemes READ canEditThemes CONSTANT)
public:
......@@ -42,7 +47,8 @@ public:
PluginNameRole = Qt::UserRole + 1,
ThemeNameRole,
DescriptionRole,
IsLocalRole
IsLocalRole,
PendingDeletionRole
};
Q_ENUM(Roles)
......@@ -53,20 +59,22 @@ public:
QString selectedPlugin() const;
void setSelectedPlugin(const QString &plugin);
int selectedPluginIndex() const;
bool canEditThemes() const;
Q_INVOKABLE void getNewThemes();
Q_INVOKABLE void getNewStuff(QQuickItem *ctx);
Q_INVOKABLE void installThemeFromFile(const QUrl &file);
Q_INVOKABLE void removeTheme(const QString &name);
Q_INVOKABLE void applyPlasmaTheme(QQuickItem *item, const QString &themeName);
Q_INVOKABLE void setPendingDeletion(int index, bool pending);
Q_INVOKABLE int indexOf(const QString &themeName) const;
Q_INVOKABLE void applyPlasmaTheme(QQuickItem *item, const QString &themeName);
Q_INVOKABLE void editTheme(const QString &themeName);
Q_SIGNALS:
void selectedPluginChanged(const QString &plugin);
void selectedPluginIndexChanged();
void showSuccessMessage(const QString &message);
void showErrorMessage(const QString &message);
......@@ -76,15 +84,18 @@ public Q_SLOTS:
void defaults() override;
private:
void removeThemes();
void updateNeedsSave();
void processPendingDeletions();
QStandardItemModel *m_model;
QString m_selectedPlugin;
QStringList m_pendingRemoval;
Plasma::Theme *m_defaultTheme;
QHash<QString, Plasma::Theme*> m_themes;
bool m_haveThemeExplorerInstalled;
QPointer<KNS3::DownloadDialog> m_newStuffDialog;
};
Q_DECLARE_LOGGING_CATEGORY(KCM_DESKTOP_THEME)
......
......@@ -29,7 +29,17 @@ KCM.GridViewKCM {
KCM.ConfigModule.quickHelp: i18n("This module lets you configure the desktop theme.")
view.model: kcm.desktopThemeModel
view.currentIndex: kcm.indexOf(kcm.selectedPlugin)
view.currentIndex: kcm.selectedPluginIndex
DropArea {
anchors.fill: parent
onEntered: {
if (!drag.hasUrls) {
drag.accepted = false;
}
}
onDropped: kcm.installThemeFromFile(drop.urls[0])
}
view.remove: Transition {
ParallelAnimation {
......@@ -52,6 +62,11 @@ KCM.GridViewKCM {
text: model.themeName
toolTip: model.description || model.themeName
opacity: model.pendingDeletion ? 0.3 : 1
Behavior on opacity {
NumberAnimation { duration: Kirigami.Units.longDuration }
}
thumbnailAvailable: true
thumbnail: ThemePreview {
id: preview
......@@ -63,6 +78,7 @@ KCM.GridViewKCM {
Kirigami.Action {
iconName: "document-edit"
tooltip: i18n("Edit Theme")
enabled: !model.pendingDeletion
visible: kcm.canEditThemes
onTriggered: kcm.editTheme(model.pluginName)
},
......@@ -70,7 +86,14 @@ KCM.GridViewKCM {
iconName: "edit-delete"
tooltip: i18n("Remove Theme")
enabled: model.isLocal
onTriggered: kcm.removeTheme(model.pluginName)
visible: !model.pendingDeletion
onTriggered: kcm.setPendingDeletion(model.index, true);
},
Kirigami.Action {
iconName: "edit-undo"
tooltip: i18n("Restore Theme")
visible: model.pendingDeletion
onTriggered: kcm.setPendingDeletion(model.index, false);
}
]
......@@ -114,7 +137,7 @@ KCM.GridViewKCM {
QtControls.Button {
text: i18n("Get New Themes...")
icon.name: "get-hot-new-stuff"
onClicked: kcm.getNewThemes()
onClicked: kcm.getNewStuff(this)
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment