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() + } + } } } }