[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.
This commit is contained in:
parent
2034e7e875
commit
7da6d3a41e
8 changed files with 269 additions and 36 deletions
|
@ -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)
|
||||
|
|
|
@ -26,7 +26,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <KDecoration2/DecorationShadow>
|
||||
// KDE
|
||||
#include <KConfigGroup>
|
||||
#include <KConfigLoader>
|
||||
#include <KConfigDialogManager>
|
||||
#include <KDesktopFile>
|
||||
#include <KLocalizedTranslator>
|
||||
#include <KPluginFactory>
|
||||
#include <KSharedConfig>
|
||||
#include <KService>
|
||||
|
@ -55,11 +58,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <QQmlEngine>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QUiLoader>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
K_PLUGIN_FACTORY_WITH_JSON(AuroraeDecoFactory,
|
||||
"aurorae.json",
|
||||
registerPlugin<Aurorae::Decoration>();
|
||||
registerPlugin<Aurorae::ThemeFinder>(QStringLiteral("themes"));
|
||||
registerPlugin<Aurorae::ConfigurationModule>(QStringLiteral("kcmodule"));
|
||||
)
|
||||
|
||||
namespace Aurorae
|
||||
|
@ -223,6 +229,18 @@ void Helper::init()
|
|||
qmlRegisterType<KDecoration2::DecoratedClient>();
|
||||
}
|
||||
|
||||
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<void (ConfigurationModule::*)()>(&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"
|
||||
|
|
|
@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <KDecoration2/Decoration>
|
||||
#include <QVariant>
|
||||
#include <QMutex>
|
||||
#include <KCModule>
|
||||
|
||||
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
|
||||
|
|
|
@ -25,11 +25,16 @@
|
|||
#include <KDecoration2/DecoratedClient>
|
||||
#include <KDecoration2/Decoration>
|
||||
|
||||
#include <KCModule>
|
||||
#include <KPluginLoader>
|
||||
#include <KPluginFactory>
|
||||
#include <KPluginTrader>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace KDecoration2
|
||||
{
|
||||
|
@ -169,5 +174,55 @@ DecorationButton *PreviewBridge::createButton(KDecoration2::Decoration *decorati
|
|||
return m_factory->create<KDecoration2::DecorationButton>(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<KCModule>(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<void(KCModule::*)(bool)>(&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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<int, QByteArray> 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));
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ private:
|
|||
QString pluginName;
|
||||
QString themeName;
|
||||
QString visibleName;
|
||||
bool configuration = false;
|
||||
};
|
||||
std::vector<Data> m_plugins;
|
||||
QMap<QString, QString> m_knsProvides;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue