[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:
Martin Gräßlin 2014-12-05 13:44:16 +01:00
parent 2034e7e875
commit 7da6d3a41e
8 changed files with 269 additions and 36 deletions

View file

@ -16,9 +16,12 @@ add_library(kwin5_aurorae MODULE ${kwin5_aurorae_PART_SRCS})
target_link_libraries(kwin5_aurorae target_link_libraries(kwin5_aurorae
KDecoration2::KDecoration KDecoration2::KDecoration
KF5::ConfigWidgets
KF5::I18n
KF5::Service KF5::Service
KF5::WindowSystem KF5::WindowSystem
Qt5::Quick Qt5::Quick
Qt5::UiTools
) )
install(TARGETS kwin5_aurorae DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) install(TARGETS kwin5_aurorae DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2)

View file

@ -26,7 +26,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KDecoration2/DecorationShadow> #include <KDecoration2/DecorationShadow>
// KDE // KDE
#include <KConfigGroup> #include <KConfigGroup>
#include <KConfigLoader>
#include <KConfigDialogManager>
#include <KDesktopFile> #include <KDesktopFile>
#include <KLocalizedTranslator>
#include <KPluginFactory> #include <KPluginFactory>
#include <KSharedConfig> #include <KSharedConfig>
#include <KService> #include <KService>
@ -55,11 +58,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QQmlEngine> #include <QQmlEngine>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTimer> #include <QTimer>
#include <QUiLoader>
#include <QVBoxLayout>
K_PLUGIN_FACTORY_WITH_JSON(AuroraeDecoFactory, K_PLUGIN_FACTORY_WITH_JSON(AuroraeDecoFactory,
"aurorae.json", "aurorae.json",
registerPlugin<Aurorae::Decoration>(); registerPlugin<Aurorae::Decoration>();
registerPlugin<Aurorae::ThemeFinder>(QStringLiteral("themes")); registerPlugin<Aurorae::ThemeFinder>(QStringLiteral("themes"));
registerPlugin<Aurorae::ConfigurationModule>(QStringLiteral("kcmodule"));
) )
namespace Aurorae namespace Aurorae
@ -223,6 +229,18 @@ void Helper::init()
qmlRegisterType<KDecoration2::DecoratedClient>(); 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) Decoration::Decoration(QObject *parent, const QVariantList &args)
: KDecoration2::Decoration(parent, args) : KDecoration2::Decoration(parent, args)
@ -234,13 +252,7 @@ Decoration::Decoration(QObject *parent, const QVariantList &args)
, m_themeName(s_defaultTheme) , m_themeName(s_defaultTheme)
, m_mutex(QMutex::Recursive) , m_mutex(QMutex::Recursive)
{ {
if (!args.isEmpty()) { m_themeName = findTheme(args);
const auto map = args.first().toMap();
auto it = map.constFind(QStringLiteral("theme"));
if (it != map.constEnd()) {
m_themeName = it.value().toString();
}
}
Helper::instance().ref(); 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" #include "aurorae.moc"

View file

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KDecoration2/Decoration> #include <KDecoration2/Decoration>
#include <QVariant> #include <QVariant>
#include <QMutex> #include <QMutex>
#include <KCModule>
class QOffscreenSurface; class QOffscreenSurface;
class QOpenGLContext; class QOpenGLContext;
@ -32,6 +33,9 @@ class QQuickRenderControl;
class QQuickWindow; class QQuickWindow;
class QWindow; class QWindow;
class KConfigLoader;
class KConfigDialogManager;
namespace KWin namespace KWin
{ {
class Borders; class Borders;
@ -99,6 +103,9 @@ public:
return m_themes; return m_themes;
} }
public Q_SLOTS:
bool hasConfiguration(const QString &theme) const;
private: private:
void init(); void init();
void findAllQmlThemes(); void findAllQmlThemes();
@ -106,6 +113,24 @@ private:
QVariantMap m_themes; 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 #endif

View file

@ -25,11 +25,16 @@
#include <KDecoration2/DecoratedClient> #include <KDecoration2/DecoratedClient>
#include <KDecoration2/Decoration> #include <KDecoration2/Decoration>
#include <KCModule>
#include <KPluginLoader> #include <KPluginLoader>
#include <KPluginFactory> #include <KPluginFactory>
#include <KPluginTrader> #include <KPluginTrader>
#include <QDebug> #include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
namespace KDecoration2 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)})); 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();
}
} }
} }

View file

@ -69,6 +69,9 @@ public:
KDecoration2::Decoration *createDecoration(QObject *parent = nullptr); KDecoration2::Decoration *createDecoration(QObject *parent = nullptr);
KDecoration2::DecorationButton *createButton(KDecoration2::Decoration *decoration, KDecoration2::DecorationButtonType type, QObject *parent = nullptr); KDecoration2::DecorationButton *createButton(KDecoration2::Decoration *decoration, KDecoration2::DecorationButtonType type, QObject *parent = nullptr);
public Q_SLOTS:
void configure();
Q_SIGNALS: Q_SIGNALS:
void pluginChanged(); void pluginChanged();
void themeChanged(); void themeChanged();

View file

@ -64,6 +64,8 @@ QVariant DecorationsModel::data(const QModelIndex &index, int role) const
return d.pluginName; return d.pluginName;
case Qt::UserRole +5: case Qt::UserRole +5:
return d.themeName; return d.themeName;
case Qt::UserRole +6:
return d.configuration;
} }
return QVariant(); return QVariant();
@ -74,7 +76,8 @@ QHash< int, QByteArray > DecorationsModel::roleNames() const
QHash<int, QByteArray> roles({ QHash<int, QByteArray> roles({
{Qt::DisplayRole, QByteArrayLiteral("display")}, {Qt::DisplayRole, QByteArrayLiteral("display")},
{Qt::UserRole + 4, QByteArrayLiteral("plugin")}, {Qt::UserRole + 4, QByteArrayLiteral("plugin")},
{Qt::UserRole + 5, QByteArrayLiteral("theme")} {Qt::UserRole + 5, QByteArrayLiteral("theme")},
{Qt::UserRole +6, QByteArrayLiteral("configureable")}
}); });
return roles; return roles;
} }
@ -88,6 +91,15 @@ static bool isThemeEngine(const QVariantMap &decoSettingsMap)
return it.value().toBool(); 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) static QString themeListKeyword(const QVariantMap &decoSettingsMap)
{ {
auto it = decoSettingsMap.find(QStringLiteral("themeListKeyword")); auto it = decoSettingsMap.find(QStringLiteral("themeListKeyword"));
@ -118,6 +130,7 @@ void DecorationsModel::init()
continue; continue;
} }
auto metadata = loader.metaData().value(QStringLiteral("MetaData")).toObject().value(s_pluginName); auto metadata = loader.metaData().value(QStringLiteral("MetaData")).toObject().value(s_pluginName);
bool config = false;
if (!metadata.isUndefined()) { if (!metadata.isUndefined()) {
const auto decoSettingsMap = metadata.toObject().toVariantMap(); const auto decoSettingsMap = metadata.toObject().toVariantMap();
const QString &kns = findKNewStuff(decoSettingsMap); const QString &kns = findKNewStuff(decoSettingsMap);
@ -144,16 +157,21 @@ void DecorationsModel::init()
d.pluginName = info.pluginName(); d.pluginName = info.pluginName();
d.themeName = it.value().toString(); d.themeName = it.value().toString();
d.visibleName = it.key(); 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)); m_plugins.emplace_back(std::move(d));
} }
// it's a theme engine, we don't want to show this entry // it's a theme engine, we don't want to show this entry
continue; continue;
} }
config = isConfigureable(decoSettingsMap);
} }
Data data; Data data;
data.pluginName = info.pluginName(); data.pluginName = info.pluginName();
data.visibleName = info.name().isEmpty() ? info.pluginName() : info.name(); data.visibleName = info.name().isEmpty() ? info.pluginName() : info.name();
data.configuration = config;
m_plugins.emplace_back(std::move(data)); m_plugins.emplace_back(std::move(data));
} }

View file

@ -53,6 +53,7 @@ private:
QString pluginName; QString pluginName;
QString themeName; QString themeName;
QString visibleName; QString visibleName;
bool configuration = false;
}; };
std::vector<Data> m_plugins; std::vector<Data> m_plugins;
QMap<QString, QString> m_knsProvides; QMap<QString, QString> m_knsProvides;

View file

@ -19,6 +19,7 @@
*/ */
import QtQuick 2.1 import QtQuick 2.1
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import org.kde.kwin.private.kdecoration 1.0 as KDecoration import org.kde.kwin.private.kdecoration 1.0 as KDecoration
ScrollView { ScrollView {
@ -48,34 +49,6 @@ ScrollView {
bridge: bridgeItem bridge: bridgeItem
borderSizesIndex: listView.borderSizesIndex 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 { MouseArea {
hoverEnabled: false hoverEnabled: false
anchors.fill: parent anchors.fill: parent
@ -83,6 +56,54 @@ ScrollView {
listView.currentIndex = index; 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()
}
}
} }
} }
} }