diff --git a/kcms/cursortheme/kcmcursortheme.h b/kcms/cursortheme/kcmcursortheme.h --- a/kcms/cursortheme/kcmcursortheme.h +++ b/kcms/cursortheme/kcmcursortheme.h @@ -20,8 +20,10 @@ #define KCMCURSORTHEME_H #include +#include class QStandardItemModel; +class QTemporaryFile; class CursorThemeModel; class SortProxyModel; @@ -75,9 +77,13 @@ 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: 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 @@ bool m_canInstall; bool m_canResize; bool m_canConfigure; + + QScopedPointer m_tempInstallFile; }; #endif diff --git a/kcms/cursortheme/kcmcursortheme.cpp b/kcms/cursortheme/kcmcursortheme.cpp --- a/kcms/cursortheme/kcmcursortheme.cpp +++ b/kcms/cursortheme/kcmcursortheme.cpp @@ -33,8 +33,10 @@ #include #include #include -#include +#include #include +#include +#include #include #include #include @@ -359,9 +361,7 @@ 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::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("You cannot delete the theme you are currently " - "using.
You have to switch to another theme first.
")); - return; - } - - // Get confirmation from the user - QString question = i18n("Are you sure you want to remove the " - "%1 cursor theme?
" - "This will delete all the files installed by this theme.
", - 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 @@ } 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 @@ } 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("You cannot delete the theme you are currently " + "using.
You have to switch to another theme first.
")); + return; + } + + // Get confirmation from the user + QString question = i18n("Are you sure you want to remove the " + "%1 cursor theme?
" + "This will delete all the files installed by this theme.
", + 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" diff --git a/kcms/cursortheme/package/contents/ui/main.qml b/kcms/cursortheme/package/contents/ui/main.qml --- a/kcms/cursortheme/package/contents/ui/main.qml +++ b/kcms/cursortheme/package/contents/ui/main.qml @@ -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,13 +35,50 @@ 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; } 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 @@ } 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 @@ } } } + + 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 + } + } + } }