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