[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.
icc-effect-5.14.5
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
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)

View File

@ -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"

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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));
}

View File

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

View File

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