diff --git a/kcms/style/CMakeLists.txt b/kcms/style/CMakeLists.txt --- a/kcms/style/CMakeLists.txt +++ b/kcms/style/CMakeLists.txt @@ -3,7 +3,15 @@ ########### next target ############### -set(kcm_style_PART_SRCS ../krdb/krdb.cpp styleconfdialog.cpp kcmstyle.cpp stylesmodel.cpp previewitem.cpp) +set(kcm_style_PART_SRCS + ../krdb/krdb.cpp + styleconfdialog.cpp + kcmstyle.cpp + stylesmodel.cpp + gtkthemesmodel.cpp + gtkpage.cpp + previewitem.cpp +) set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml) qt5_add_dbus_interface(kcm_style_PART_SRCS ${klauncher_xml} klauncher_iface) @@ -26,11 +34,14 @@ KF5::GuiAddons KF5::QuickAddons KF5::WindowSystem + KF5::Archive + KF5::NewStuff ) kcoreaddons_desktop_to_json(kcm_style "kcm_style.desktop") install(FILES stylesettings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) +install(FILES cgctheme.knsrc cgcgtk3.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR}) install(FILES kcm_style.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS kcm_style DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) diff --git a/kcms/style/cgcgtk3.knsrc b/kcms/style/cgcgtk3.knsrc new file mode 100644 --- /dev/null +++ b/kcms/style/cgcgtk3.knsrc @@ -0,0 +1,46 @@ +[KNewStuff3] +Name=GTK 3.x Themes +Name[ar]=سمات جتك الثّالثة +Name[ast]=Estilos pa GTK 3.x +Name[ca]=Temes GTK 3.x +Name[ca@valencia]=Temes GTK 3.x +Name[cs]=Motivy GTK 3.x +Name[da]=GTK 3.x-temaer +Name[de]=GTK 3.x-Designs +Name[el]=Θέματα GTK 3.x +Name[en_GB]=GTK 3.x Themes +Name[es]=Temas de GTK 3.x +Name[et]=GTK 3.x teemad +Name[eu]=GTK 3.x gaiak +Name[fi]=GTK 3.x -teemat +Name[fr]=Thèmes GTK 3.x +Name[gl]=Temas de GTK 3.x +Name[he]=ערכות נושא של GTK 3.x +Name[hu]=GTK 3.x témák +Name[id]=Tema GTK 3.x +Name[it]=Temi GTK 3.x +Name[ko]=GTK 3.x 테마 +Name[lt]=GTK 3.x apipavidalinimai +Name[nl]=GTK 3.x thema's +Name[nn]=GTK 3.x-tema +Name[pl]=Wygląd GTK 3.x +Name[pt]=Temas do GTK 3.x +Name[pt_BR]=Temas GTK 3.x +Name[ru]=Темы GTK 3.x +Name[sk]=Témy GTK 3.x +Name[sl]=Teme GTK 3.x +Name[sr]=ГТК 3.x теме +Name[sr@ijekavian]=ГТК 3.x теме +Name[sr@ijekavianlatin]=GTK 3.x teme +Name[sr@latin]=GTK 3.x teme +Name[sv]=GTK 3.x-teman +Name[tg]=Мавзӯъҳои GTK 3.x +Name[tr]=GTK 3.x Temaları +Name[uk]=Теми GTK 3.x +Name[x-test]=xxGTK 3.x Themesxx +Name[zh_CN]=GTK 3.x 主题 +Name[zh_TW]=GTK 3.x 主題 + +Categories=GTK 3.x Theme/Style +Uncompress=always +InstallPath=.themes diff --git a/kcms/style/cgctheme.knsrc b/kcms/style/cgctheme.knsrc new file mode 100644 --- /dev/null +++ b/kcms/style/cgctheme.knsrc @@ -0,0 +1,46 @@ +[KNewStuff3] +Name=GTK 2.x Themes +Name[ar]=سمات جتك الثّانية +Name[ast]=Estilos pa GTK 2.x +Name[ca]=Temes GTK 2.x +Name[ca@valencia]=Temes GTK 2.x +Name[cs]=Motivy GTK 2.x +Name[da]=GTK 2.x-temaer +Name[de]=GTK 2.x-Designs +Name[el]=Θέματα GTK 2.x +Name[en_GB]=GTK 2.x Themes +Name[es]=Temas de GTK 2.x +Name[et]=GTK 2.x teemad +Name[eu]=GTK 2.x gaiak +Name[fi]=GTK 2.x -teemat +Name[fr]=Thèmes GTK 2.x +Name[gl]=Temas de GTK 2.x +Name[he]=ערכות נושא של GTK 2.x +Name[hu]=GTK 2.x témák +Name[id]=Tema GTK 2.x +Name[it]=Temi GTK 2.x +Name[ko]=GTK 2.x 테마 +Name[lt]=GTK 2.x apipavidalinimai +Name[nl]=GTK 2.x thema's +Name[nn]=GTK 2.x-tema +Name[pl]=Wygląd GTK 2.x +Name[pt]=Temas do GTK 2.x +Name[pt_BR]=Temas GTK 2.x +Name[ru]=Темы GTK 2.x +Name[sk]=Témy GTK 2.x +Name[sl]=Teme GTK 2.x +Name[sr]=ГТК 2.x теме +Name[sr@ijekavian]=ГТК 2.x теме +Name[sr@ijekavianlatin]=GTK 2.x teme +Name[sr@latin]=GTK 2.x teme +Name[sv]=GTK 2.x-teman +Name[tg]=Мавзӯъҳои GTK 2.x +Name[tr]=GTK 2.x Temaları +Name[uk]=Теми GTK 2.x +Name[x-test]=xxGTK 2.x Themesxx +Name[zh_CN]=GTK 2.x 主题 +Name[zh_TW]=GTK 2.x 主題 + +Categories=GTK 2.x Theme/Style +Uncompress=always +InstallPath=.themes diff --git a/kcms/style/gtkpage.h b/kcms/style/gtkpage.h new file mode 100644 --- /dev/null +++ b/kcms/style/gtkpage.h @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "gtkthemesmodel.h" + +class GtkPage : public QObject +{ + Q_OBJECT + + Q_PROPERTY(GtkThemesModel *gtk2ThemesModel MEMBER m_gtk2ThemesModel NOTIFY gtk2ThemesModelChanged) + Q_PROPERTY(GtkThemesModel *gtk3ThemesModel MEMBER m_gtk3ThemesModel NOTIFY gtk3ThemesModelChanged) + +public: + GtkPage(QObject *parent = nullptr); + ~GtkPage(); + + Q_SLOT void load(); + void save(); + void defaults(); + +public Q_SLOTS: + QString gtk2ThemeFromConfig(); + QString gtk3ThemeFromConfig(); + + void showGtk2Preview(); + void showGtk3Preview(); + + void installGtkThemeFromFile(const QUrl &fileUrl); + void installGtk2ThemeFromGHNS(); + void installGtk3ThemeFromGHNS(); + + void onThemeRemoved(); + +Q_SIGNALS: + void gtk2ThemesModelChanged(GtkThemesModel *model); + void gtk3ThemesModelChanged(GtkThemesModel *model); + + void showErrorMessage(const QString &message); + void selectGtk2ThemeInCombobox(const QString &themeName); + void selectGtk3ThemeInCombobox(const QString &themeName); + + void gtkThemeSettingsChanged(); + +private: + GtkThemesModel *m_gtk2ThemesModel; + GtkThemesModel *m_gtk3ThemesModel; + + QDBusInterface gtkConfigInterface; +}; diff --git a/kcms/style/gtkpage.cpp b/kcms/style/gtkpage.cpp new file mode 100644 --- /dev/null +++ b/kcms/style/gtkpage.cpp @@ -0,0 +1,161 @@ +/* + * Copyright 2020 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "gtkpage.h" + +GtkPage::GtkPage(QObject *parent) + : QObject(parent) + , m_gtk2ThemesModel(new GtkThemesModel(this)) + , m_gtk3ThemesModel(new GtkThemesModel(this)) + , gtkConfigInterface( + QStringLiteral("org.kde.GtkConfig"), + QStringLiteral("/GtkConfig"), + QStringLiteral("org.kde.GtkConfig") + ) +{ + connect(m_gtk2ThemesModel, &GtkThemesModel::themesListNeedsUpdate, this, &GtkPage::load); + connect(m_gtk3ThemesModel, &GtkThemesModel::themesListNeedsUpdate, this, &GtkPage::load); + connect(m_gtk2ThemesModel, &GtkThemesModel::themeRemoved, this, &GtkPage::onThemeRemoved); + connect(m_gtk3ThemesModel, &GtkThemesModel::themeRemoved, this, &GtkPage::onThemeRemoved); + + connect(m_gtk2ThemesModel, &GtkThemesModel::selectedThemeChanged, this, [this](){ + Q_EMIT gtkThemeSettingsChanged(); + }); + connect(m_gtk3ThemesModel, &GtkThemesModel::selectedThemeChanged, this, [this](){ + Q_EMIT gtkThemeSettingsChanged(); + }); +} + +GtkPage::~GtkPage() +{ + delete m_gtk2ThemesModel; + delete m_gtk3ThemesModel; +} + +QString GtkPage::gtk2ThemeFromConfig() +{ + QDBusReply dbusReply = gtkConfigInterface.call(QStringLiteral("gtk2Theme")); + return dbusReply.value(); +} + +QString GtkPage::gtk3ThemeFromConfig() +{ + QDBusReply dbusReply = gtkConfigInterface.call(QStringLiteral("gtk3Theme")); + return dbusReply.value(); +} + +void GtkPage::showGtk2Preview() +{ + gtkConfigInterface.call(QStringLiteral("showGtk2ThemePreview"), m_gtk2ThemesModel->selectedTheme()); +} + +void GtkPage::showGtk3Preview() +{ + gtkConfigInterface.call(QStringLiteral("showGtk3ThemePreview"), m_gtk3ThemesModel->selectedTheme()); +} + + +void GtkPage::installGtk2ThemeFromGHNS() +{ + KNS3::DownloadDialog downloadDialog(QStringLiteral("cgctheme.knsrc")); + downloadDialog.setWindowTitle(i18n("Download New GNOME/GTK2 Application Styles")); + downloadDialog.setWindowModality(Qt::WindowModal); + if (downloadDialog.exec()) { + m_gtk2ThemesModel->requestThemesListUpdate(); + } +} + +void GtkPage::installGtk3ThemeFromGHNS() +{ + KNS3::DownloadDialog downloadDialog(QStringLiteral("cgcgtk3.knsrc")); + downloadDialog.setWindowTitle(i18n("Download New GNOME/GTK3 Application Styles")); + downloadDialog.setWindowModality(Qt::WindowModal); + if (downloadDialog.exec()) { + m_gtk3ThemesModel->requestThemesListUpdate(); + } +} + +void GtkPage::onThemeRemoved() +{ + load(); + defaults(); + save(); +} + +void GtkPage::installGtkThemeFromFile(const QUrl &fileUrl) +{ + QString themesInstallDirectoryPath(QDir::homePath() + QStringLiteral("/.themes")); + QDir::home().mkpath(themesInstallDirectoryPath); + KTar themeArchive(fileUrl.path()); + themeArchive.open(QIODevice::ReadOnly); + + auto showError = [this, fileUrl]() { + Q_EMIT showErrorMessage(i18n("%1 is not a valid GTK Theme archive.", fileUrl.fileName())); + }; + + QString firstEntryName = themeArchive.directory()->entries().first(); + const KArchiveEntry *possibleThemeDirectory = themeArchive.directory()->entry(firstEntryName); + if (possibleThemeDirectory->isDirectory()) { + const KArchiveDirectory *themeDirectory = static_cast(possibleThemeDirectory); + QStringList archiveSubitems = themeDirectory->entries(); + + if (!archiveSubitems.contains(QStringLiteral("gtk-2.0")) && archiveSubitems.indexOf(QRegExp("gtk-3.*")) == -1) { + showError(); + return; + } + } else { + showError(); + return; + } + + themeArchive.directory()->copyTo(themesInstallDirectoryPath); + + // We need to send request from only one model, because models are connected to each other + m_gtk2ThemesModel->requestThemesListUpdate(); +} + +void GtkPage::save() +{ + gtkConfigInterface.call(QStringLiteral("setGtk2Theme"), m_gtk2ThemesModel->selectedTheme()); + gtkConfigInterface.call(QStringLiteral("setGtk3Theme"), m_gtk3ThemesModel->selectedTheme()); +} + +void GtkPage::defaults() +{ + Q_EMIT selectGtk2ThemeInCombobox(QStringLiteral("Breeze")); + Q_EMIT selectGtk3ThemeInCombobox(QStringLiteral("Breeze")); +} + +void GtkPage::load() +{ + m_gtk2ThemesModel->loadGtk2(); + m_gtk3ThemesModel->loadGtk3(); + Q_EMIT selectGtk2ThemeInCombobox(gtk2ThemeFromConfig()); + Q_EMIT selectGtk3ThemeInCombobox(gtk3ThemeFromConfig()); +} diff --git a/kcms/style/gtkthemesmodel.h b/kcms/style/gtkthemesmodel.h new file mode 100644 --- /dev/null +++ b/kcms/style/gtkthemesmodel.h @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +class QStringList; +class QString; + +class GtkThemesModel : public QAbstractListModel { + Q_OBJECT + + Q_PROPERTY(QString selectedTheme READ selectedTheme WRITE setSelectedTheme NOTIFY selectedThemeChanged) + +public: + GtkThemesModel(QObject *parent = nullptr); + ~GtkThemesModel() override = default; + + enum Roles { + ThemeNameRole = Qt::UserRole + 1, + ThemePathRole + }; + + void loadGtk2(); + void loadGtk3(); + + void setThemesList(const QMap &themes); + QMap themesList(); + + void setSelectedTheme(const QString &themeName); + QString selectedTheme(); + Q_SIGNAL void selectedThemeChanged(const QString &themeName); + + bool containsTheme(const QString &themeName); + Q_INVOKABLE bool selectedThemeRemovable(); + Q_INVOKABLE void removeSelectedTheme(); + Q_INVOKABLE int findThemeIndex(const QString &themeName); + Q_INVOKABLE void setSelectedThemeDirty(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role = Roles::ThemeNameRole) const override; + QHash roleNames() const override; + + void requestThemesListUpdate(); + +Q_SIGNALS: + void themesListNeedsUpdate(); + void themeRemoved(); + +private: + QStringList possiblePathsToThemes(); + QString themePath(const QString &themeName); + + QString m_selectedTheme; + QMap m_themesList; +}; + diff --git a/kcms/style/gtkthemesmodel.cpp b/kcms/style/gtkthemesmodel.cpp new file mode 100644 --- /dev/null +++ b/kcms/style/gtkthemesmodel.cpp @@ -0,0 +1,191 @@ +/* + * Copyright 2020 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include + +#include "gtkthemesmodel.h" + +GtkThemesModel::GtkThemesModel(QObject* parent) + : QAbstractListModel(parent) + , m_selectedTheme(QStringLiteral("Breeze")) + , m_themesList() +{ + +} + +void GtkThemesModel::loadGtk2() +{ + QMap gtk2ThemesNames; + + for (const QString &possibleThemePath : possiblePathsToThemes()) { + // If the directory has a gtk-2.0 directory inside, it is the GTK2 theme for sure + QDir possibleThemeDirectory(possibleThemePath); + bool hasGtk2DirectoryInside = possibleThemeDirectory.exists(QStringLiteral("gtk-2.0")); + if (hasGtk2DirectoryInside) { + gtk2ThemesNames.insert(possibleThemeDirectory.dirName(), possibleThemeDirectory.path()); + } + } + + setThemesList(gtk2ThemesNames); +} + +void GtkThemesModel::loadGtk3() +{ + QMap gtk3ThemesNames; + + static const QStringList gtk3SubdirPattern(QStringLiteral("gtk-3.*")); + for (const QString &possibleThemePath : possiblePathsToThemes()) { + // If the directory contains any of gtk-3.X folders, it is the GTK3 theme for sure + QDir possibleThemeDirectory(possibleThemePath); + if (!possibleThemeDirectory.entryList(gtk3SubdirPattern, QDir::Dirs).isEmpty()) { + gtk3ThemesNames.insert(possibleThemeDirectory.dirName(), possibleThemeDirectory.path()); + } + } + + setThemesList(gtk3ThemesNames); +} + +QString GtkThemesModel::themePath(const QString &themeName) { + if (themeName.isEmpty()) { + return QString(); + } else { + return m_themesList.find(themeName).value(); + } +} + +QVariant GtkThemesModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::DisplayRole || role == Roles::ThemeNameRole) { + if (index.row() < 0 || index.row() > m_themesList.count()) { + return QVariant(); + } + + return m_themesList.keys().at(index.row()); + } else if (role == Roles::ThemePathRole) { + if (index.row() < 0 || index.row() > m_themesList.count()) { + return QVariant(); + } + + return m_themesList.values().at(index.row()); + } else { + return QVariant(); + } +} + +QHash GtkThemesModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles[Roles::ThemeNameRole] = "theme-name"; + roles[Roles::ThemePathRole] = "theme-path"; + + return roles; +} + +void GtkThemesModel::requestThemesListUpdate() +{ + Q_EMIT themesListNeedsUpdate(); +} + +int GtkThemesModel::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent) + return m_themesList.count(); +} + +int GtkThemesModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent) + return 2; +} + +void GtkThemesModel::setThemesList(const QMap& themes) +{ + beginResetModel(); + m_themesList = themes; + endResetModel(); +} + +QMap GtkThemesModel::themesList() { + return m_themesList; +} + +void GtkThemesModel::setSelectedTheme(const QString &themeName) +{ + m_selectedTheme = themeName; +} + +QString GtkThemesModel::selectedTheme() +{ + return m_selectedTheme; +} + +bool GtkThemesModel::containsTheme(const QString &themeName) +{ + return m_themesList.contains(themeName); +} + +QStringList GtkThemesModel::possiblePathsToThemes() +{ + QStringList possibleThemesPaths; + + QStringList themesLocationsPaths = QStandardPaths::locateAll( + QStandardPaths::GenericDataLocation, + QStringLiteral("themes"), + QStandardPaths::LocateDirectory); + themesLocationsPaths << QDir::homePath() + QStringLiteral("/.themes"); + + for (const QString& themesLocationPath : themesLocationsPaths) { + QStringList possibleThemesDirectoriesNames = QDir(themesLocationPath).entryList(QDir::NoDotAndDotDot | QDir::AllDirs); + for (const QString &possibleThemeDirectoryName : possibleThemesDirectoriesNames) { + possibleThemesPaths += themesLocationPath + '/' + possibleThemeDirectoryName; + } + } + + return possibleThemesPaths; +} + +bool GtkThemesModel::selectedThemeRemovable() +{ + return themePath(m_selectedTheme).contains(QDir::homePath()); +} + +void GtkThemesModel::removeSelectedTheme() +{ + QString path = themePath(m_selectedTheme); + KIO::DeleteJob* deleteJob = KIO::del(QUrl::fromLocalFile(path), KIO::HideProgressInfo); + connect(deleteJob, &KJob::finished, this, [this](){ + Q_EMIT themeRemoved(); + }); +} + +int GtkThemesModel::findThemeIndex(const QString &themeName) +{ + return static_cast(std::distance(m_themesList.begin(), m_themesList.find(themeName))); +} + +void GtkThemesModel::setSelectedThemeDirty() +{ + Q_EMIT selectedThemeChanged(m_selectedTheme); +} diff --git a/kcms/style/kcmstyle.h b/kcms/style/kcmstyle.h --- a/kcms/style/kcmstyle.h +++ b/kcms/style/kcmstyle.h @@ -34,6 +34,8 @@ #include +#include "gtkpage.h" + class QQuickItem; class StyleSettings; @@ -44,6 +46,7 @@ { Q_OBJECT + Q_PROPERTY(GtkPage *gtkPage MEMBER m_gtkPage CONSTANT) Q_PROPERTY(StylesModel *model READ model CONSTANT) Q_PROPERTY(StyleSettings *styleSettings READ styleSettings CONSTANT) Q_PROPERTY(ToolBarStyle mainToolBarStyle READ mainToolBarStyle WRITE setMainToolBarStyle NOTIFY mainToolBarStyleChanged) @@ -60,9 +63,7 @@ TextUnderIcon }; Q_ENUM(ToolBarStyle) - StylesModel *model() const; - StyleSettings *styleSettings() const; ToolBarStyle mainToolBarStyle() const; @@ -74,6 +75,7 @@ Q_SIGNAL void otherToolBarStyleChanged(); Q_INVOKABLE void configure(const QString &styleName, QQuickItem *ctx = nullptr); + Q_INVOKABLE bool gtkConfigKdedModuleLoaded(); void load() override; void save() override; @@ -88,14 +90,14 @@ StyleSettings *m_settings; StylesModel *m_model; - QString m_previousStyle; bool m_effectsDirty = false; ToolBarStyle m_mainToolBarStyle = NoText; ToolBarStyle m_otherToolBarStyle = NoText; QPointer m_styleConfigDialog; + GtkPage *m_gtkPage; }; #endif // __KCMSTYLE_H diff --git a/kcms/style/kcmstyle.cpp b/kcms/style/kcmstyle.cpp --- a/kcms/style/kcmstyle.cpp +++ b/kcms/style/kcmstyle.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -53,8 +54,10 @@ #include "../krdb/krdb.h" #include "stylesmodel.h" +#include "gtkthemesmodel.h" #include "previewitem.h" #include "stylesettings.h" +#include "gtkpage.h" K_PLUGIN_FACTORY_WITH_JSON(KCMStyleFactory, "kcm_style.json", registerPlugin();) @@ -79,6 +82,7 @@ : KQuickAddons::ManagedConfigModule(parent, args) , m_settings(new StyleSettings(this)) , m_model(new StylesModel(this)) + , m_gtkPage() { qmlRegisterUncreatableType("org.kde.private.kcms.style", 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM")); qmlRegisterType(); @@ -95,6 +99,12 @@ about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@broulik.de")); setAboutData(about); + if (gtkConfigKdedModuleLoaded()) { + m_gtkPage = new GtkPage(this); + connect(m_gtkPage, &GtkPage::gtkThemeSettingsChanged, this, [this](){ + setNeedsSave(true); + }); + } connect(m_model, &StylesModel::selectedStyleChanged, this, [this](const QString &style) { m_settings->setWidgetStyle(style); }); @@ -229,8 +239,23 @@ m_styleConfigDialog->show(); } +bool KCMStyle::gtkConfigKdedModuleLoaded() +{ + QDBusInterface kdedInterface( + QStringLiteral("org.kde.kded5"), + QStringLiteral("/kded"), + QStringLiteral("org.kde.kded5") + ); + QDBusReply loadedKdedModules = kdedInterface.call(QStringLiteral("loadedModules")); + return loadedKdedModules.value().contains(QStringLiteral("gtkconfig")); +} + void KCMStyle::load() { + if (m_gtkPage) { + m_gtkPage->load(); + } + ManagedConfigModule::load(); m_model->load(); m_previousStyle = m_settings->widgetStyle(); @@ -242,6 +267,10 @@ void KCMStyle::save() { + if (m_gtkPage) { + m_gtkPage->save(); + } + // Check whether the new style can actually be loaded before saving it. // Otherwise apps will use the default style despite something else having been written to the config bool newStyleLoaded = false; @@ -290,6 +319,10 @@ void KCMStyle::defaults() { + if (m_gtkPage) { + m_gtkPage->defaults(); + } + // TODO the old code had a fallback chain but do we actually support not having Breeze for Plasma? // defaultStyle() -> oxygen -> plastique -> windows -> platinum -> motif diff --git a/kcms/style/package/contents/ui/GtkStylePage.qml b/kcms/style/package/contents/ui/GtkStylePage.qml new file mode 100644 --- /dev/null +++ b/kcms/style/package/contents/ui/GtkStylePage.qml @@ -0,0 +1,198 @@ +/* + * Copyright 2020 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.0 as QtDialogs +import QtQuick.Controls 2.10 as QtControls +import org.kde.kirigami 2.10 as Kirigami +import org.kde.private.kcms.style 1.0 as Private +import org.kde.kcm 1.2 as KCM + +Kirigami.Page { + id: gtkStylePage + title: i18n("GNOME/GTK Application Style") + + ColumnLayout { + anchors.fill: parent + + Kirigami.InlineMessage { + id: infoLabel + Layout.fillWidth: true + + showCloseButton: true + visible: false + + Connections { + target: kcm.gtkPage + onShowErrorMessage: { + infoLabel.type = Kirigami.MessageType.Error; + infoLabel.text = message; + infoLabel.visible = true; + } + } + } + + Kirigami.FormLayout { + wideMode: true + + Row { + Kirigami.FormData.label: i18n("GTK2 theme:") + + Flow { + spacing: Kirigami.Units.smallSpacing + + QtControls.ComboBox { + id: gtk2ThemeCombo + model: kcm.gtkPage.gtk2ThemesModel + currentIndex: model.findThemeIndex(kcm.gtkPage.gtk2ThemeFromConfig()) + onCurrentTextChanged: function() { + model.selectedTheme = currentText + gtk2RemoveButton.enabled = model.selectedThemeRemovable() + } + onActivated: model.setSelectedThemeDirty() + textRole: "theme-name" + + Connections { + target: kcm.gtkPage + onSelectGtk2ThemeInCombobox: function(themeName) { + gtk2ThemeCombo.currentIndex = gtk2ThemeCombo.model.findThemeIndex(themeName) + } + } + } + + QtControls.Button { + id: gtk2RemoveButton + icon.name: "edit-delete" + onClicked: gtk2ThemeCombo.model.removeSelectedTheme() + } + + QtControls.Button { + icon.name: "preview" + text: i18n("Preview...") + onClicked: kcm.gtkPage.showGtk2Preview() + } + } + } + + Row { + Kirigami.FormData.label: i18n("GTK3 theme:") + + Flow { + spacing: Kirigami.Units.smallSpacing + + QtControls.ComboBox { + id: gtk3ThemeCombo + model: kcm.gtkPage.gtk3ThemesModel + currentIndex: model.findThemeIndex(kcm.gtkPage.gtk3ThemeFromConfig()) + onCurrentTextChanged: function() { + model.selectedTheme = currentText + gtk3RemoveButton.enabled = model.selectedThemeRemovable() + } + onActivated: model.setSelectedThemeDirty() + textRole: "theme-name" + + Connections { + target: kcm.gtkPage + onSelectGtk3ThemeInCombobox: function(themeName) { + gtk3ThemeCombo.currentIndex = gtk3ThemeCombo.model.findThemeIndex(themeName) + } + } + } + + QtControls.Button { + id: gtk3RemoveButton + icon.name: "edit-delete" + onClicked: gtk3ThemeCombo.model.removeSelectedTheme() + } + + QtControls.Button { + icon.name: "preview" + text: i18n("Preview...") + onClicked: kcm.gtkPage.showGtk3Preview() + } + + } + } + + } + + Item { + Layout.fillHeight: true + } + + RowLayout { + Item { + Layout.fillWidth: true + } + + QtControls.Button { + icon.name: "document-import" + text: i18n("Install from File...") + onClicked: fileDialogLoader.active = true + } + + QtControls.Button { + icon.name: "get-hot-new-stuff" + text: i18n("Get New GNOME/GTK Application Styles...") + onClicked: ghnsMenu.open() + + QtControls.Menu { + id: ghnsMenu + + QtControls.MenuItem { + icon.name: "get-hot-new-stuff" + text: i18n("Get New GNOME/GTK2 Application Styles...") + onClicked: function() { + ghnsMenu.close() + kcm.gtkPage.installGtk2ThemeFromGHNS() + } + } + QtControls.MenuItem { + icon.name: "get-hot-new-stuff" + text: i18n("Get New GNOME/GTK3 Application Styles...") + onClicked: function() { + ghnsMenu.close() + kcm.gtkPage.installGtk3ThemeFromGHNS() + } + } + } + } + } + } + + Loader { + id: fileDialogLoader + active: false + sourceComponent: QtDialogs.FileDialog { + title: i18n("Select GTK Theme Archive") + folder: shortcuts.home + nameFilters: [ i18n("GTK Theme Archive (*.tar.xz *.tar.gz *.tar.bz2)") ] + Component.onCompleted: open() + onAccepted: { + kcm.gtkPage.installGtkThemeFromFile(fileUrls[0]) + fileDialogLoader.active = false + } + onRejected: { + fileDialogLoader.active = false + } + } + } +} diff --git a/kcms/style/package/contents/ui/main.qml b/kcms/style/package/contents/ui/main.qml --- a/kcms/style/package/contents/ui/main.qml +++ b/kcms/style/package/contents/ui/main.qml @@ -35,6 +35,10 @@ view.enabled: !kcm.styleSettings.isImmutable("widgetStyle") + function openGtkStyleSettings() { + kcm.push("GtkStylePage.qml"); + } + Component.onCompleted: { // The widget thumbnails are a bit more elaborate and need more room, especially when translated view.implicitCellWidth = Kirigami.Units.gridUnit * 20; @@ -111,18 +115,28 @@ } } - footer: RowLayout { - Layout.fillWidth: true - - QtControls.Button { - id: effectSettingsButton - text: i18n("Configure Icons and Toolbars") - icon.name: "configure-toolbars" // proper icon? - checkable: true - checked: effectSettingsPopupLoader.item && effectSettingsPopupLoader.item.opened - onClicked: { - effectSettingsPopupLoader.active = true; - effectSettingsPopupLoader.item.open(); + footer: ColumnLayout { + RowLayout { + Layout.alignment: Qt.AlignLeft + + QtControls.Button { + id: effectSettingsButton + text: i18n("Configure Icons and Toolbars") + icon.name: "configure-toolbars" // proper icon? + checkable: true + checked: effectSettingsPopupLoader.item && effectSettingsPopupLoader.item.opened + onClicked: { + effectSettingsPopupLoader.active = true; + effectSettingsPopupLoader.item.open(); + } + } + + QtControls.Button { + id: gtkSettingsButton + visible: kcm.gtkConfigKdedModuleLoaded() + text: i18n("Configure GNOME/GTK Application Style...") + icon.name: "configure" + onClicked: root.openGtkStyleSettings() } } }