diff --git a/kcms/desktoptheme/kcm.cpp b/kcms/desktoptheme/kcm.cpp index 989e33730..478ee0e37 100644 --- a/kcms/desktoptheme/kcm.cpp +++ b/kcms/desktoptheme/kcm.cpp @@ -1,274 +1,284 @@ /* This file is part of the KDE Project Copyright (c) 2014 Marco Martin Copyright (c) 2014 Vishesh Handa Copyright (c) 2016 David Rosca This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(KCM_DESKTOP_THEME, "kcm_desktoptheme") K_PLUGIN_FACTORY_WITH_JSON(KCMDesktopThemeFactory, "kcm_desktoptheme.json", registerPlugin();) KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const QVariantList &args) : KQuickAddons::ConfigModule(parent, args) , m_defaultTheme(new Plasma::Theme(this)) { //This flag seems to be needed in order for QQuickWidget to work //see https://bugreports.qt-project.org/browse/QTBUG-40765 //also, it seems to work only if set in the kcm, not in the systemsettings' main qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); qmlRegisterType(); KAboutData* about = new KAboutData(QStringLiteral("kcm_desktoptheme"), i18n("Configure Desktop Theme"), QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); about->addAuthor(i18n("David Rosca"), QString(), QStringLiteral("nowrep@gmail.com")); setAboutData(about); setButtons(Apply | Default | Help); m_model = new QStandardItemModel(this); QHash roles = m_model->roleNames(); roles[PluginNameRole] = QByteArrayLiteral("pluginName"); roles[ThemeNameRole] = QByteArrayLiteral("themeName"); roles[IsLocalRole] = QByteArrayLiteral("isLocal"); m_model->setItemRoleNames(roles); } KCMDesktopTheme::~KCMDesktopTheme() { delete m_defaultTheme; } QStandardItemModel *KCMDesktopTheme::desktopThemeModel() const { return m_model; } QString KCMDesktopTheme::selectedPlugin() const { return m_selectedPlugin; } void KCMDesktopTheme::setSelectedPlugin(const QString &plugin) { if (m_selectedPlugin == plugin) { return; } m_selectedPlugin = plugin; Q_EMIT selectedPluginChanged(m_selectedPlugin); updateNeedsSave(); } void KCMDesktopTheme::getNewThemes() { KNS3::DownloadDialog *dialog = new KNS3::DownloadDialog(QStringLiteral("plasma-themes.knsrc")); dialog->open(); connect(dialog, &QDialog::accepted, this, [this, dialog]() { if (!dialog->changedEntries().isEmpty()) { load(); delete dialog; } }); } void KCMDesktopTheme::installThemeFromFile(const QUrl &file) { qCDebug(KCM_DESKTOP_THEME) << "Installing ... " << file; QString program = QStringLiteral("plasmapkg2"); QStringList arguments; arguments << QStringLiteral("-t") << QStringLiteral("theme") << QStringLiteral("-i") << file.toLocalFile(); qCDebug(KCM_DESKTOP_THEME) << program << arguments.join(QStringLiteral(" ")); QProcess *myProcess = new QProcess(this); connect(myProcess, static_cast(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus); if (exitCode == 0) { qCDebug(KCM_DESKTOP_THEME) << "Theme installed successfully :)"; load(); Q_EMIT showInfoMessage(i18n("Theme installed successfully.")); } else { qCWarning(KCM_DESKTOP_THEME) << "Theme installation failed." << exitCode; Q_EMIT showInfoMessage(i18n("Theme installation failed.")); } }); connect(myProcess, static_cast(&QProcess::error), this, [this](QProcess::ProcessError e) { qCWarning(KCM_DESKTOP_THEME) << "Theme installation failed: " << e; Q_EMIT showInfoMessage(i18n("Theme installation failed.")); }); 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) { return; } Plasma::Theme *theme = m_themes[themeName]; if (!theme) { theme = new Plasma::Theme(themeName, this); m_themes[themeName] = theme; } Q_FOREACH (Plasma::Svg *svg, item->findChildren()) { svg->setTheme(theme); svg->setUsingRenderingCache(false); } } +Q_INVOKABLE 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(); // Get all desktop themes QStringList themes; const QStringList &packs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/desktoptheme"), QStandardPaths::LocateDirectory); Q_FOREACH (const QString &ppath, packs) { const QDir cd(ppath); const QStringList &entries = cd.entryList(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot); Q_FOREACH (const QString &pack, entries) { const QString _metadata = ppath + QLatin1Char('/') + pack + QStringLiteral("/metadata.desktop"); if (QFile::exists(_metadata)) { themes << _metadata; } } } m_model->clear(); Q_FOREACH (const QString &theme, themes) { int themeSepIndex = theme.lastIndexOf('/', -1); const QString themeRoot = theme.left(themeSepIndex); int themeNameSepIndex = themeRoot.lastIndexOf('/', -1); const QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); KDesktopFile df(theme); if (df.noDisplay()) { continue; } QString name = df.readName(); if (name.isEmpty()) { name = packageName; } const bool isLocal = QFileInfo(theme).isWritable(); if (m_model->findItems(packageName).isEmpty()) { QStandardItem *item = new QStandardItem; item->setText(packageName); item->setData(packageName, PluginNameRole); item->setData(name, ThemeNameRole); item->setData(isLocal, IsLocalRole); m_model->appendRow(item); } } KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("plasmarc")), "Theme"); setSelectedPlugin(cg.readEntry("name", m_defaultTheme->themeName())); updateNeedsSave(); } void KCMDesktopTheme::save() { if (m_defaultTheme->themeName() == m_selectedPlugin) { return; } m_defaultTheme->setThemeName(m_selectedPlugin); removeThemes(); updateNeedsSave(); } void KCMDesktopTheme::defaults() { setSelectedPlugin(QStringLiteral("default")); } void KCMDesktopTheme::updateNeedsSave() { setNeedsSave(!m_pendingRemoval.isEmpty() || m_selectedPlugin != m_defaultTheme->themeName()); } void KCMDesktopTheme::removeThemes() { 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(" ")); QProcess *process = new QProcess(this); connect(process, static_cast(&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 showInfoMessage(i18n("Theme removal failed.")); } process->deleteLater(); }); connect(process, static_cast(&QProcess::error), this, [this, process](QProcess::ProcessError e) { qCWarning(KCM_DESKTOP_THEME) << "Theme removal failed: " << e; Q_EMIT showInfoMessage(i18n("Theme removal failed.")); process->deleteLater(); }); process->start(program, arguments); } } #include "kcm.moc" diff --git a/kcms/desktoptheme/kcm.h b/kcms/desktoptheme/kcm.h index 2a2cd9266..999878262 100644 --- a/kcms/desktoptheme/kcm.h +++ b/kcms/desktoptheme/kcm.h @@ -1,83 +1,85 @@ /* Copyright (c) 2014 Marco Martin Copyright (c) 2014 Vishesh Handa Copyright (c) 2016 David Rosca This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KCM_DESKTOPTHEME_H #define _KCM_DESKTOPTHEME_H #include namespace Plasma { class Svg; class Theme; } class QStandardItemModel; class KCMDesktopTheme : public KQuickAddons::ConfigModule { Q_OBJECT Q_PROPERTY(QStandardItemModel *desktopThemeModel READ desktopThemeModel CONSTANT) Q_PROPERTY(QString selectedPlugin READ selectedPlugin WRITE setSelectedPlugin NOTIFY selectedPluginChanged) public: enum Roles { PluginNameRole = Qt::UserRole + 1, ThemeNameRole, IsLocalRole }; Q_ENUM(Roles) KCMDesktopTheme(QObject *parent, const QVariantList &args); ~KCMDesktopTheme(); QStandardItemModel *desktopThemeModel() const; QString selectedPlugin() const; void setSelectedPlugin(const QString &plugin); Q_INVOKABLE void getNewThemes(); 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 int indexOf(const QString &themeName) const; + Q_SIGNALS: void selectedPluginChanged(const QString &plugin); void showInfoMessage(const QString &infoMessage); public Q_SLOTS: void load(); void save(); void defaults(); private: void removeThemes(); void updateNeedsSave(); QStandardItemModel *m_model; QString m_selectedPlugin; QStringList m_pendingRemoval; Plasma::Theme *m_defaultTheme; QHash m_themes; }; Q_DECLARE_LOGGING_CATEGORY(KCM_DESKTOP_THEME) #endif // _KCM_DESKTOPTHEME_H diff --git a/kcms/desktoptheme/package/contents/ui/main.qml b/kcms/desktoptheme/package/contents/ui/main.qml index d22853aa1..3e28804e4 100644 --- a/kcms/desktoptheme/package/contents/ui/main.qml +++ b/kcms/desktoptheme/package/contents/ui/main.qml @@ -1,221 +1,220 @@ /* Copyright (c) 2014 Marco Martin Copyright (c) 2016 David Rosca This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.0 import QtQuick.Controls.Private 1.0 import QtQuick.Controls 1.0 as QtControls import org.kde.kcm 1.0 import org.kde.plasma.core 2.0 // for units Item { implicitWidth: units.gridUnit * 20 implicitHeight: units.gridUnit * 20 ConfigModule.quickHelp: i18n("This module lets you configure the desktop theme.") SystemPalette { id: syspal } ColumnLayout { anchors.fill: parent QtControls.ScrollView { Layout.fillWidth: true Layout.fillHeight: true verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn GridView { id: grid model: kcm.desktopThemeModel cellWidth: Math.floor(grid.width / Math.max(Math.floor(grid.width / (units.gridUnit * 12)), 3)) cellHeight: cellWidth / 1.6 + onCountChanged: { + grid.currentIndex = kcm.indexOf(kcm.selectedPlugin); + grid.positionViewAtIndex(grid.currentIndex, GridView.Visible) + } + delegate: Item { property bool isLocal : model.isLocal property string pluginName : model.pluginName width: grid.cellWidth height: grid.cellHeight Rectangle { anchors { fill: parent margins: units.smallSpacing } Connections { target: kcm onSelectedPluginChanged: { if (kcm.selectedPlugin == model.pluginName) { makeCurrentTimer.pendingIndex = index } } } Component.onCompleted: { if (kcm.selectedPlugin == model.pluginName) { makeCurrentTimer.pendingIndex = index } } Item { anchors { fill: parent margins: units.smallSpacing * 2 } ThemePreview { id: preview anchors { top: parent.top left: parent.left right: parent.right bottom: label.top } themeName: model.pluginName } QtControls.Label { id: label anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter leftMargin: units.smallSpacing * 2 rightMargin: units.smallSpacing * 2 } height: paintedHeight width: parent.width color: "black" text: model.themeName elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter } } Rectangle { opacity: grid.currentIndex == index ? 1.0 : 0 anchors.fill: parent border.width: units.smallSpacing * 2 border.color: syspal.highlight color: "transparent" Behavior on opacity { PropertyAnimation { duration: units.longDuration easing.type: Easing.OutQuad } } } MouseArea { anchors.fill: parent hoverEnabled: true onClicked: { grid.currentIndex = index kcm.selectedPlugin = model.pluginName } Timer { interval: 1000 running: parent.containsMouse && !parent.pressedButtons onTriggered: { Tooltip.showText(parent, Qt.point(parent.mouseX, parent.mouseY), model.themeName); } } } } } Timer { id: makeCurrentTimer interval: 100 repeat: false property int pendingIndex onPendingIndexChanged: makeCurrentTimer.restart() onTriggered: { grid.currentIndex = pendingIndex } } } } RowLayout { QtControls.Button { text: i18n("Get new Theme") iconName: "get-hot-new-stuff" onClicked: kcm.getNewThemes() } QtControls.Button { text: i18n("Install from File") iconName: "document-import" onClicked: fileDialogLoader.active = true; } QtControls.Button { text: i18n("Remove Theme") iconName: "edit-delete" enabled: grid.currentItem && grid.currentItem.isLocal onClicked: { kcm.removeTheme(grid.currentItem.pluginName); - updateSelectedPluginTimer.restart(); + kcm.selectedPlugin = grid.currentItem.pluginName } } Item { Layout.fillWidth: true } QtControls.Label { id: infoLabel } } } Connections { target: kcm onShowInfoMessage: { infoLabel.text = infoMessage; hideInfoMessageTimer.restart(); } } - Timer { - id: updateSelectedPluginTimer - interval: 100 - onTriggered: kcm.selectedPlugin = grid.currentItem.pluginName - } - Timer { id: hideInfoMessageTimer interval: 20 * 1000 onTriggered: { infoLabel.text = "" } } Loader { id: fileDialogLoader active: false sourceComponent: FileDialog { visible: true title: i18n("Open Theme") folder: shortcuts.home nameFilters: [ i18n("Theme Files (*.zip *.tar.gz *.tar.bz2)") ] onAccepted: { kcm.installThemeFromFile(fileUrls[0]) fileDialogLoader.active = false } onRejected: { fileDialogLoader.active = false } } } }