Commit edeb3d3b authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

[Mouse Cursor KCM] Improve installation workflow

* Support drag and drop install
* Open file dialog directly rather than opening a dialog with an input field and then having to click "Browse" again
* Use inline message instead of message box for "you need to restart for changes to take effect" (if applying fails)
* Port from deprecated KIO::NetAccess to using QTemporaryFile and make downloading async

Differential Revision: https://phabricator.kde.org/D12491
parent 6bc448a8
......@@ -33,8 +33,10 @@
#include <KConfigGroup>
#include <KMessageBox>
#include <KUrlRequesterDialog>
#include <KIO/Job>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/Job>
#include <KIO/JobUiDelegate>
#include <kio/netaccess.h>
#include <KTar>
#include <KGlobalSettings>
......@@ -359,9 +361,7 @@ void CursorThemeConfig::save()
c.sync();
if (!applyTheme(theme, m_preferredSize)) {
KMessageBox::information(0,
i18n("You have to restart the Plasma session for these changes to take effect."),
i18n("Cursor Settings Changed"), "CursorSettingsChanged");
emit showInfoMessage(i18n("You have to restart the Plasma session for these changes to take effect."));
}
m_appliedIndex = selectedIndex();
......@@ -454,92 +454,39 @@ void CursorThemeConfig::getNewClicked()
}
}
void CursorThemeConfig::installClicked()
void CursorThemeConfig::installThemeFromFile(const QUrl &url)
{
// Get the URL for the theme we're going to install
QUrl url = KUrlRequesterDialog::getUrl(QUrl(), 0, i18n("Drag or Type Theme URL"));
if (url.isEmpty())
return;
QString tempFile;
if (!KIO::NetAccess::download(url, tempFile, 0)) {
QString text;
if (url.isLocalFile()) {
text = i18n("Unable to find the cursor theme archive %1.",
url.toDisplayString());
} else {
text = i18n("Unable to download the cursor theme archive; "
"please check that the address %1 is correct.",
url.toDisplayString());
}
KMessageBox::sorry(0, text);
if (url.isLocalFile()) {
installThemeFile(url.toLocalFile());
return;
}
if (!installThemes(tempFile)) {
KMessageBox::error(0, i18n("The file %1 does not appear to be a valid "
"cursor theme archive.", url.fileName()));
}
KIO::NetAccess::removeTempFile(tempFile);
}
void CursorThemeConfig::removeTheme(int row)
{
QModelIndex idx = m_proxyModel->index(row, 0);
if (!idx.isValid()) {
m_tempInstallFile.reset(new QTemporaryFile());
if (!m_tempInstallFile->open()) {
emit showErrorMessage(i18n("Unable to create a temporary file."));
m_tempInstallFile.reset();
return;
}
const CursorTheme *theme = m_proxyModel->theme(idx);
KIO::FileCopyJob *job = KIO::file_copy(url,QUrl::fromLocalFile(m_tempInstallFile->fileName()),
-1, KIO::Overwrite);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
// Don't let the user delete the currently configured theme
if (idx == m_appliedIndex) {
KMessageBox::sorry(0, i18n("<qt>You cannot delete the theme you are currently "
"using.<br />You have to switch to another theme first.</qt>"));
return;
}
// Get confirmation from the user
QString question = i18n("<qt>Are you sure you want to remove the "
"<i>%1</i> cursor theme?<br />"
"This will delete all the files installed by this theme.</qt>",
theme->title());
int answer = KMessageBox::warningContinueCancel(0, question,
i18n("Confirmation"), KStandardGuiItem::del());
if (answer != KMessageBox::Continue) {
return;
}
// Delete the theme from the harddrive
KIO::del(QUrl::fromLocalFile(theme->path())); // async
// Remove the theme from the model
m_proxyModel->removeTheme(idx);
connect(job, &KIO::FileCopyJob::result, this, [this, url](KJob *job) {
if (job->error() != KJob::NoError) {
emit showErrorMessage(i18n("Unable to download the icon theme archive: %1", job->errorText()));
return;
}
// TODO:
// Since it's possible to substitute cursors in a system theme by adding a local
// theme with the same name, we shouldn't remove the theme from the list if it's
// still available elsewhere. We could add a
// bool CursorThemeModel::tryAddTheme(const QString &name), and call that, but
// since KIO::del() is an asynchronos operation, the theme we're deleting will be
// readded to the list again before KIO has removed it.
installThemeFile(m_tempInstallFile->fileName());
m_tempInstallFile.reset();
});
}
bool CursorThemeConfig::installThemes(const QString &file)
void CursorThemeConfig::installThemeFile(const QString &path)
{
KTar archive(file);
if (!archive.open(QIODevice::ReadOnly)) {
return false;
}
KTar archive(path);
archive.open(QIODevice::ReadOnly);
const KArchiveDirectory *archiveDir = archive.directory();
QStringList themeDirs;
......@@ -557,12 +504,16 @@ bool CursorThemeConfig::installThemes(const QString &file)
}
if (themeDirs.isEmpty()) {
return false;
emit showErrorMessage(i18n("The file is not a valid icon theme archive."));
return;
}
// The directory we'll install the themes to
QString destDir = QDir::homePath() + "/.icons/";
QDir().mkpath(destDir); // Make sure the directory exists
if (!QDir().mkpath(destDir)) {
emit showErrorMessage(i18n("Failed to create 'icons' folder."));
return;
};
// Process each cursor theme in the archive
foreach (const QString &dirName, themeDirs) {
......@@ -597,7 +548,54 @@ bool CursorThemeConfig::installThemes(const QString &file)
}
archive.close();
return true;
emit showSuccessMessage(i18n("Theme installed successfully."));
m_model->refreshList();
}
void CursorThemeConfig::removeTheme(int row)
{
QModelIndex idx = m_proxyModel->index(row, 0);
if (!idx.isValid()) {
return;
}
const CursorTheme *theme = m_proxyModel->theme(idx);
// Don't let the user delete the currently configured theme
if (idx == m_appliedIndex) {
KMessageBox::sorry(0, i18n("<qt>You cannot delete the theme you are currently "
"using.<br />You have to switch to another theme first.</qt>"));
return;
}
// Get confirmation from the user
QString question = i18n("<qt>Are you sure you want to remove the "
"<i>%1</i> cursor theme?<br />"
"This will delete all the files installed by this theme.</qt>",
theme->title());
int answer = KMessageBox::warningContinueCancel(0, question,
i18n("Confirmation"), KStandardGuiItem::del());
if (answer != KMessageBox::Continue) {
return;
}
// Delete the theme from the harddrive
KIO::del(QUrl::fromLocalFile(theme->path())); // async
// Remove the theme from the model
m_proxyModel->removeTheme(idx);
// TODO:
// Since it's possible to substitute cursors in a system theme by adding a local
// theme with the same name, we shouldn't remove the theme from the list if it's
// still available elsewhere. We could add a
// bool CursorThemeModel::tryAddTheme(const QString &name), and call that, but
// since KIO::del() is an asynchronos operation, the theme we're deleting will be
// readded to the list again before KIO has removed it.
}
#include "kcmcursortheme.moc"
......@@ -20,8 +20,10 @@
#define KCMCURSORTHEME_H
#include <KQuickAddons/ConfigModule>
#include <QScopedPointer>
class QStandardItemModel;
class QTemporaryFile;
class CursorThemeModel;
class SortProxyModel;
......@@ -75,9 +77,13 @@ Q_SIGNALS:
void selectedThemeRowChanged();
void preferredSizeChanged();
void showSuccessMessage(const QString &message);
void showInfoMessage(const QString &message);
void showErrorMessage(const QString &message);
public Q_SLOTS:
void getNewClicked();
void installClicked();
void installThemeFromFile(const QUrl &url);
void removeTheme(int row);
private Q_SLOTS:
......@@ -92,7 +98,7 @@ private Q_SLOTS:
private:
QModelIndex selectedIndex() const;
bool installThemes(const QString &file);
void installThemeFile(const QString &path);
/** Applies a given theme, using XFixes, XCursor and KGlobalSettings.
@param theme The cursor theme to be applied. It is save to pass 0 here
(will result in \e false as return value).
......@@ -127,6 +133,8 @@ private:
bool m_canInstall;
bool m_canResize;
bool m_canConfigure;
QScopedPointer<QTemporaryFile> m_tempInstallFile;
};
#endif
......@@ -18,16 +18,14 @@
import QtQuick 2.7
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2 as QQC1
import QtQuick.Controls 2.2 as QtControls
import org.kde.kirigami 2.2 as Kirigami
import QtQuick.Dialogs 1.1 as QtDialogs
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kcm 1.1 as KCM
import org.kde.private.kcm_cursortheme 1.0
KCM.GridViewKCM {
KCM.ConfigModule.quickHelp: i18n("This module lets you configure the mouse cursor theme used.")
view.model: kcm.cursorsModel
......@@ -37,6 +35,16 @@ KCM.GridViewKCM {
view.positionViewAtIndex(view.currentIndex, view.GridView.Beginning);
}
DropArea {
anchors.fill: parent
onEntered: {
if (!drag.hasUrls) {
drag.accepted = false;
}
}
onDropped: kcm.installThemeFromFile(drop.urls[0])
}
Connections {
target: kcm
onSelectedThemeRowChanged: view.currentIndex = kcm.selectedThemeRow;
......@@ -44,6 +52,33 @@ KCM.GridViewKCM {
footer: ColumnLayout {
id: footerLayout
Kirigami.InlineMessage {
id: infoLabel
Layout.fillWidth: true
showCloseButton: true
Connections {
target: kcm
onShowSuccessMessage: {
infoLabel.type = Kirigami.MessageType.Positive;
infoLabel.text = message;
infoLabel.visible = true;
}
onShowInfoMessage: {
infoLabel.type = Kirigami.MessageType.Information;
infoLabel.text = message;
infoLabel.visible = true;
}
onShowErrorMessage: {
infoLabel.type = Kirigami.MessageType.Error;
infoLabel.text = message;
infoLabel.visible = true;
}
}
}
RowLayout {
id: row1
//spacer
......@@ -70,15 +105,14 @@ KCM.GridViewKCM {
}
RowLayout {
parent: footerLayout.x + footerLayout.width - comboLayout.width > width ? row1 : row2
//TODO: port to QQC2 buttons as soon they have icons
QQC1.Button {
iconName: "document-import"
QtControls.Button {
icon.name: "document-import"
text: i18n("&Install From File...")
onClicked: kcm.installClicked();
onClicked: fileDialogLoader.active = true;
enabled: kcm.canInstall
}
QQC1.Button {
iconName: "get-hot-new-stuff"
QtControls.Button {
icon.name: "get-hot-new-stuff"
text: i18n("&Get New Theme...")
onClicked: kcm.getNewClicked();
enabled: kcm.canInstall
......@@ -95,5 +129,23 @@ KCM.GridViewKCM {
}
}
}
Loader {
id: fileDialogLoader
active: false
sourceComponent: QtDialogs.FileDialog {
visible: true
title: i18n("Open Theme")
folder: shortcuts.home
nameFilters: [ i18n("Cursor Theme Files (*.tar.gz *.tar.bz2)") ]
onAccepted: {
kcm.installThemeFromFile(fileUrls[0])
fileDialogLoader.active = false
}
onRejected: {
fileDialogLoader.active = false
}
}
}
}
Supports Markdown
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