From 7da6d3a41e263d4b8df339610c592c6ff74039a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Fri, 5 Dec 2014 13:44:16 +0100 Subject: [PATCH] [kcmkwin/deco] Add configuration for decoration plugin/themes This brings back the configuration for decoration plugins. As a change to the old variant the configure button is moved into the list view together with the preview. It is enabled/disabled depending on data provided by the DecorationModel. For a plugin the DecorationModel queries for a boolean "kcmodule" key in the metadata. For a theme it invokes the slot hasConfiguration with the theme name which returns whether the theme provides configuration. The actual opening of the configuration is triggered from the PreviewBridge, which uses the existing KPluginFactory to load the KCModule. The decoration plugin must provide the keyword "kcmodule" for it. So far Aurorae is adjusted and provides configuration for the Plastik decoration. The interaction with the configuration module works, but the configuration itself for Plastik seems to be currently broken. --- clients/aurorae/src/CMakeLists.txt | 3 + clients/aurorae/src/aurorae.cpp | 121 +++++++++++++++++- clients/aurorae/src/aurorae.h | 25 ++++ .../declarative-plugin/previewbridge.cpp | 55 ++++++++ .../declarative-plugin/previewbridge.h | 3 + kcmkwin/kwindecoration/decorationmodel.cpp | 20 ++- kcmkwin/kwindecoration/decorationmodel.h | 1 + kcmkwin/kwindecoration/qml/main.qml | 77 +++++++---- 8 files changed, 269 insertions(+), 36 deletions(-) diff --git a/clients/aurorae/src/CMakeLists.txt b/clients/aurorae/src/CMakeLists.txt index f1306f9757..f4d85fa12e 100644 --- a/clients/aurorae/src/CMakeLists.txt +++ b/clients/aurorae/src/CMakeLists.txt @@ -16,9 +16,12 @@ add_library(kwin5_aurorae MODULE ${kwin5_aurorae_PART_SRCS}) target_link_libraries(kwin5_aurorae KDecoration2::KDecoration + KF5::ConfigWidgets + KF5::I18n KF5::Service KF5::WindowSystem Qt5::Quick + Qt5::UiTools ) install(TARGETS kwin5_aurorae DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) diff --git a/clients/aurorae/src/aurorae.cpp b/clients/aurorae/src/aurorae.cpp index 23a6071f26..8b8f6c5bc3 100644 --- a/clients/aurorae/src/aurorae.cpp +++ b/clients/aurorae/src/aurorae.cpp @@ -26,7 +26,10 @@ along with this program. If not, see . #include // KDE #include +#include +#include #include +#include #include #include #include @@ -55,11 +58,14 @@ along with this program. If not, see . #include #include #include +#include +#include K_PLUGIN_FACTORY_WITH_JSON(AuroraeDecoFactory, "aurorae.json", registerPlugin(); registerPlugin(QStringLiteral("themes")); + registerPlugin(QStringLiteral("kcmodule")); ) namespace Aurorae @@ -223,6 +229,18 @@ void Helper::init() qmlRegisterType(); } +static QString findTheme(const QVariantList &args) +{ + if (args.isEmpty()) { + return QString(); + } + const auto map = args.first().toMap(); + auto it = map.constFind(QStringLiteral("theme")); + if (it == map.constEnd()) { + return QString(); + } + return it.value().toString(); +} Decoration::Decoration(QObject *parent, const QVariantList &args) : KDecoration2::Decoration(parent, args) @@ -234,13 +252,7 @@ Decoration::Decoration(QObject *parent, const QVariantList &args) , m_themeName(s_defaultTheme) , m_mutex(QMutex::Recursive) { - if (!args.isEmpty()) { - const auto map = args.first().toMap(); - auto it = map.constFind(QStringLiteral("theme")); - if (it != map.constEnd()) { - m_themeName = it.value().toString(); - } - } + m_themeName = findTheme(args); Helper::instance().ref(); } @@ -646,6 +658,101 @@ void ThemeFinder::findAllSvgThemes() } } +static const QString s_configUiPath = QStringLiteral("kwin/decorations/%1/contents/ui/config.ui"); +static const QString s_configXmlPath = QStringLiteral("kwin/decorations/%1/contents/config/main.xml"); + +bool ThemeFinder::hasConfiguration(const QString &theme) const +{ + if (theme.startsWith(QLatin1String("__aurorae__svg__"))) { + return false; + } + const QString ui = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + s_configUiPath.arg(theme)); + const QString xml = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + s_configXmlPath.arg(theme)); + return !(ui.isEmpty() || xml.isEmpty()); +} + +ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args) + : KCModule(parent, args) + , m_theme(findTheme(args)) +{ + setLayout(new QVBoxLayout(this)); + init(); +} + +void ConfigurationModule::init() +{ + const QString ui = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + s_configUiPath.arg(m_theme)); + const QString xml = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + s_configXmlPath.arg(m_theme)); + if (ui.isEmpty() || xml.isEmpty()) { + return; + } + KLocalizedTranslator *translator = new KLocalizedTranslator(this); + QCoreApplication::instance()->installTranslator(translator); + const KDesktopFile metaData(QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QStringLiteral("kwin/decorations/%1/metadata.desktop").arg(m_theme))); + const QString translationDomain = metaData.desktopGroup().readEntry("X-KWin-Config-TranslationDomain", QString()); + if (!translationDomain.isEmpty()) { + translator->setTranslationDomain(translationDomain); + } + // load the KConfigSkeleton + QFile configFile(xml); + KSharedConfigPtr auroraeConfig = KSharedConfig::openConfig("auroraerc"); + KConfigGroup configGroup = auroraeConfig->group(m_theme); + m_skeleton = new KConfigLoader(configGroup, &configFile, this); + // load the ui file + QUiLoader *loader = new QUiLoader(this); + loader->setLanguageChangeEnabled(true); + QFile uiFile(ui); + uiFile.open(QFile::ReadOnly); + QWidget *customConfigForm = loader->load(&uiFile, this); + translator->addContextToMonitor(customConfigForm->objectName()); + uiFile.close(); + layout()->addWidget(customConfigForm); + // connect the ui file with the skeleton + m_configManager = new KConfigDialogManager(customConfigForm, m_skeleton); + m_configManager->updateWidgets(); + connect(m_configManager, &KConfigDialogManager::widgetModified, + this, static_cast(&KCModule::changed)); + + // send a custom event to the translator to retranslate using our translator + QEvent le(QEvent::LanguageChange); + QCoreApplication::sendEvent(customConfigForm, &le); +} + +void ConfigurationModule::defaults() +{ + if (m_configManager) { + m_configManager->updateWidgetsDefault(); + } + KCModule::defaults(); +} + +void ConfigurationModule::load() +{ + if (m_skeleton) { + m_skeleton->load(); + } + if (m_configManager) { + m_configManager->updateWidgets(); + } + KCModule::load(); +} + +void ConfigurationModule::save() +{ + if (m_configManager) { + m_configManager->updateSettings(); + } + if (m_skeleton) { + m_skeleton->save(); + } + KCModule::save(); +} + } #include "aurorae.moc" diff --git a/clients/aurorae/src/aurorae.h b/clients/aurorae/src/aurorae.h index aa49d86b6d..8daf677a9d 100644 --- a/clients/aurorae/src/aurorae.h +++ b/clients/aurorae/src/aurorae.h @@ -21,6 +21,7 @@ along with this program. If not, see . #include #include #include +#include class QOffscreenSurface; class QOpenGLContext; @@ -32,6 +33,9 @@ class QQuickRenderControl; class QQuickWindow; class QWindow; +class KConfigLoader; +class KConfigDialogManager; + namespace KWin { class Borders; @@ -99,6 +103,9 @@ public: return m_themes; } +public Q_SLOTS: + bool hasConfiguration(const QString &theme) const; + private: void init(); void findAllQmlThemes(); @@ -106,6 +113,24 @@ private: QVariantMap m_themes; }; +class ConfigurationModule : public KCModule +{ + Q_OBJECT +public: + ConfigurationModule(QWidget *parent, const QVariantList &args); + +public Q_SLOTS: + void defaults() override; + void load() override; + void save() override; + +private: + void init(); + QString m_theme; + KConfigLoader *m_skeleton = nullptr; + KConfigDialogManager *m_configManager = nullptr; +}; + } #endif diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewbridge.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewbridge.cpp index b749e0a2eb..5aeae3c491 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewbridge.cpp +++ b/kcmkwin/kwindecoration/declarative-plugin/previewbridge.cpp @@ -25,11 +25,16 @@ #include #include +#include #include #include #include #include +#include +#include +#include +#include namespace KDecoration2 { @@ -169,5 +174,55 @@ DecorationButton *PreviewBridge::createButton(KDecoration2::Decoration *decorati return m_factory->create(QStringLiteral("button"), parent, QVariantList({QVariant::fromValue(type), QVariant::fromValue(decoration)})); } +void PreviewBridge::configure() +{ + if (!m_valid) { + return; + } + //setup the UI + QDialog dialog; + if (m_lastCreatedClient) { + dialog.setWindowTitle(m_lastCreatedClient->caption()); + } + + // create the KCModule through the plugintrader + QVariantMap args; + if (!m_theme.isNull()) { + args.insert(QStringLiteral("theme"), m_theme); + } + KCModule *kcm = m_factory->create(QStringLiteral("kcmodule"), &dialog, QVariantList({args})); + if (!kcm) { + return; + } + + connect(&dialog, &QDialog::accepted, kcm, &KCModule::save); + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel | + QDialogButtonBox::Apply | + QDialogButtonBox::RestoreDefaults | + QDialogButtonBox::Reset, + &dialog); + + QPushButton *apply = buttons->button(QDialogButtonBox::Apply); + QPushButton *reset = buttons->button(QDialogButtonBox::Reset); + apply->setEnabled(false); + reset->setEnabled(false); + // Here we connect our buttons with the dialog + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + connect(apply, &QPushButton::clicked, kcm, &KCModule::save); + connect(reset, &QPushButton::clicked, kcm, &KCModule::load); + auto changedSignal = static_cast(&KCModule::changed); + connect(kcm, changedSignal, apply, &QPushButton::setEnabled); + connect(kcm, changedSignal, reset, &QPushButton::setEnabled); + connect(buttons->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, kcm, &KCModule::defaults); + + QVBoxLayout *layout = new QVBoxLayout(&dialog); + layout->addWidget(kcm); + layout->addWidget(buttons); + dialog.exec(); +} + } } diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewbridge.h b/kcmkwin/kwindecoration/declarative-plugin/previewbridge.h index 318d4148a8..fc3c9e073d 100644 --- a/kcmkwin/kwindecoration/declarative-plugin/previewbridge.h +++ b/kcmkwin/kwindecoration/declarative-plugin/previewbridge.h @@ -69,6 +69,9 @@ public: KDecoration2::Decoration *createDecoration(QObject *parent = nullptr); KDecoration2::DecorationButton *createButton(KDecoration2::Decoration *decoration, KDecoration2::DecorationButtonType type, QObject *parent = nullptr); +public Q_SLOTS: + void configure(); + Q_SIGNALS: void pluginChanged(); void themeChanged(); diff --git a/kcmkwin/kwindecoration/decorationmodel.cpp b/kcmkwin/kwindecoration/decorationmodel.cpp index 6d3476113b..2bd1735901 100644 --- a/kcmkwin/kwindecoration/decorationmodel.cpp +++ b/kcmkwin/kwindecoration/decorationmodel.cpp @@ -64,6 +64,8 @@ QVariant DecorationsModel::data(const QModelIndex &index, int role) const return d.pluginName; case Qt::UserRole +5: return d.themeName; + case Qt::UserRole +6: + return d.configuration; } return QVariant(); @@ -74,7 +76,8 @@ QHash< int, QByteArray > DecorationsModel::roleNames() const QHash roles({ {Qt::DisplayRole, QByteArrayLiteral("display")}, {Qt::UserRole + 4, QByteArrayLiteral("plugin")}, - {Qt::UserRole + 5, QByteArrayLiteral("theme")} + {Qt::UserRole + 5, QByteArrayLiteral("theme")}, + {Qt::UserRole +6, QByteArrayLiteral("configureable")} }); return roles; } @@ -88,6 +91,15 @@ static bool isThemeEngine(const QVariantMap &decoSettingsMap) return it.value().toBool(); } +static bool isConfigureable(const QVariantMap &decoSettingsMap) +{ + auto it = decoSettingsMap.find(QStringLiteral("kcmodule")); + if (it == decoSettingsMap.end()) { + return false; + } + return it.value().toBool(); +} + static QString themeListKeyword(const QVariantMap &decoSettingsMap) { auto it = decoSettingsMap.find(QStringLiteral("themeListKeyword")); @@ -118,6 +130,7 @@ void DecorationsModel::init() continue; } auto metadata = loader.metaData().value(QStringLiteral("MetaData")).toObject().value(s_pluginName); + bool config = false; if (!metadata.isUndefined()) { const auto decoSettingsMap = metadata.toObject().toVariantMap(); const QString &kns = findKNewStuff(decoSettingsMap); @@ -144,16 +157,21 @@ void DecorationsModel::init() d.pluginName = info.pluginName(); d.themeName = it.value().toString(); d.visibleName = it.key(); + QMetaObject::invokeMethod(themeFinder.data(), "hasConfiguration", + Q_RETURN_ARG(bool, d.configuration), + Q_ARG(QString, d.themeName)); m_plugins.emplace_back(std::move(d)); } // it's a theme engine, we don't want to show this entry continue; } + config = isConfigureable(decoSettingsMap); } Data data; data.pluginName = info.pluginName(); data.visibleName = info.name().isEmpty() ? info.pluginName() : info.name(); + data.configuration = config; m_plugins.emplace_back(std::move(data)); } diff --git a/kcmkwin/kwindecoration/decorationmodel.h b/kcmkwin/kwindecoration/decorationmodel.h index 4e0778a07d..55c5f5c1fb 100644 --- a/kcmkwin/kwindecoration/decorationmodel.h +++ b/kcmkwin/kwindecoration/decorationmodel.h @@ -53,6 +53,7 @@ private: QString pluginName; QString themeName; QString visibleName; + bool configuration = false; }; std::vector m_plugins; QMap m_knsProvides; diff --git a/kcmkwin/kwindecoration/qml/main.qml b/kcmkwin/kwindecoration/qml/main.qml index d940bba641..f5b8bbbd29 100644 --- a/kcmkwin/kwindecoration/qml/main.qml +++ b/kcmkwin/kwindecoration/qml/main.qml @@ -19,6 +19,7 @@ */ import QtQuick 2.1 import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 import org.kde.kwin.private.kdecoration 1.0 as KDecoration ScrollView { @@ -48,34 +49,6 @@ ScrollView { bridge: bridgeItem borderSizesIndex: listView.borderSizesIndex } - KDecoration.Decoration { - id: inactivePreview - bridge: bridgeItem - settings: settingsItem - anchors.fill: parent - Component.onCompleted: { - client.caption = Qt.binding(function() { return model["display"]; }); - client.active = false; - anchors.leftMargin = Qt.binding(function() { return 40 - (inactivePreview.shadow ? inactivePreview.shadow.paddingLeft : 0);}); - anchors.rightMargin = Qt.binding(function() { return 10 - (inactivePreview.shadow ? inactivePreview.shadow.paddingRight : 0);}); - anchors.topMargin = Qt.binding(function() { return 10 - (inactivePreview.shadow ? inactivePreview.shadow.paddingTop : 0);}); - anchors.bottomMargin = Qt.binding(function() { return 40 - (inactivePreview.shadow ? inactivePreview.shadow.paddingBottom : 0);}); - } - } - KDecoration.Decoration { - id: activePreview - bridge: bridgeItem - settings: settingsItem - anchors.fill: parent - Component.onCompleted: { - client.caption = Qt.binding(function() { return model["display"]; }); - client.active = true; - anchors.leftMargin = Qt.binding(function() { return 10 - (activePreview.shadow ? activePreview.shadow.paddingLeft : 0);}); - anchors.rightMargin = Qt.binding(function() { return 40 - (activePreview.shadow ? activePreview.shadow.paddingRight : 0);}); - anchors.topMargin = Qt.binding(function() { return 40 - (activePreview.shadow ? activePreview.shadow.paddingTop : 0);}); - anchors.bottomMargin = Qt.binding(function() { return 10 - (activePreview.shadow ? activePreview.shadow.paddingBottom : 0);}); - } - } MouseArea { hoverEnabled: false anchors.fill: parent @@ -83,6 +56,54 @@ ScrollView { listView.currentIndex = index; } } + RowLayout { + anchors.fill: parent + Item { + KDecoration.Decoration { + id: inactivePreview + bridge: bridgeItem + settings: settingsItem + anchors.fill: parent + Component.onCompleted: { + client.caption = Qt.binding(function() { return model["display"]; }); + client.active = false; + anchors.leftMargin = Qt.binding(function() { return 40 - (inactivePreview.shadow ? inactivePreview.shadow.paddingLeft : 0);}); + anchors.rightMargin = Qt.binding(function() { return 10 - (inactivePreview.shadow ? inactivePreview.shadow.paddingRight : 0);}); + anchors.topMargin = Qt.binding(function() { return 10 - (inactivePreview.shadow ? inactivePreview.shadow.paddingTop : 0);}); + anchors.bottomMargin = Qt.binding(function() { return 40 - (inactivePreview.shadow ? inactivePreview.shadow.paddingBottom : 0);}); + } + } + KDecoration.Decoration { + id: activePreview + bridge: bridgeItem + settings: settingsItem + anchors.fill: parent + Component.onCompleted: { + client.caption = Qt.binding(function() { return model["display"]; }); + client.active = true; + anchors.leftMargin = Qt.binding(function() { return 10 - (activePreview.shadow ? activePreview.shadow.paddingLeft : 0);}); + anchors.rightMargin = Qt.binding(function() { return 40 - (activePreview.shadow ? activePreview.shadow.paddingRight : 0);}); + anchors.topMargin = Qt.binding(function() { return 40 - (activePreview.shadow ? activePreview.shadow.paddingTop : 0);}); + anchors.bottomMargin = Qt.binding(function() { return 10 - (activePreview.shadow ? activePreview.shadow.paddingBottom : 0);}); + } + } + MouseArea { + hoverEnabled: false + anchors.fill: parent + onClicked: { + listView.currentIndex = index; + } + } + Layout.fillWidth: true + Layout.fillHeight: true + } + Button { + id: configureButton + enabled: model["configureable"] + iconName: "configure" + onClicked: bridgeItem.configure() + } + } } } }