[kcmkwin/kwindecoration] Rewrite the KWin decorations settings as a ConfigModule

Summary:
* Wrote new KCM based on KQuickAddons::ConfigModule.
* Remade QMLs for Buttons and Themes tabs.
* Updated bridge model code for new plugin lookup API (fixes warnings).
* Fixed decoration shadow changing messing with the previews sizes.
* Fixed button drag and drop issues (see D18104).
* Fixed default settings button behavior and detection of settings changes.
* Updated Get Hot New Stuff.
* Removed apply button in previewbridge.cpp: After applying changes, a theme's KCModule is invalidated.

BUG: 389431
BUG: 350122
BUG: 346222
BUG: 342816
BUG: 397595

{F6574963} | {F6574962} | {F6574961} | {F6574960}

Test Plan:
* Verified saving and loading for every setting
* Checked shadows of Breeze and Oxygen
* Tested all possible drag&drop operations on both sides of the fake titlebar
* Changed color schemes (with `kcmshell5 colors`) while showing the Themes tab to see if all previews update correctly their palettes
* Tested on a fresh Neon-developer account, via kcmshell and systemsettings

Reviewers: #vdg, abetts, ngraham, #kwin, davidedmundson

Reviewed By: #vdg, #kwin, davidedmundson

Subscribers: zzag, GB_2, ngraham, broulik, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D18458
This commit is contained in:
Valerio Pilo 2019-02-07 16:32:05 +01:00
parent 698b40dbbd
commit 8350c0f2ce
25 changed files with 1109 additions and 1048 deletions

View file

@ -3,39 +3,28 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcmkwindecoration\")
add_subdirectory(declarative-plugin)
set(kcm_kwindecoration_PART_SRCS
set(kcmkwindecoration_SRCS
kcm.cpp
utils.cpp
decorationmodel.cpp
declarative-plugin/buttonsmodel.cpp
)
ki18n_wrap_ui(kcm_kwindecoration_PART_SRCS
kcm.ui
)
add_library(kcm_kwindecoration MODULE ${kcmkwindecoration_SRCS})
add_library(kcm_kwindecoration MODULE ${kcm_kwindecoration_PART_SRCS})
target_link_libraries(kcm_kwindecoration
KDecoration2::KDecoration
Qt5::DBus
Qt5::Quick
Qt5::QuickWidgets
Qt5::UiTools
KF5::Completion
KF5::ConfigWidgets
KF5::Declarative
KF5::I18n
KF5::QuickAddons
KF5::NewStuff
KF5::WindowSystem
KF5::Service
Qt5::Quick
)
install(TARGETS kcm_kwindecoration DESTINATION ${PLUGIN_INSTALL_DIR} )
########### install files ###############
kcoreaddons_desktop_to_json(kcm_kwindecoration "kwindecoration.desktop" SERVICE_TYPES kcmodule.desktop)
install( FILES kwindecoration.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
install( FILES
qml/main.qml
qml/Buttons.qml
qml/ButtonGroup.qml
qml/Previews.qml
DESTINATION ${DATA_INSTALL_DIR}/kwin/kcm_kwindecoration)
# This desktop file is installed only for retrocompatibility with sycoca
install(FILES kwindecoration.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
install(FILES window-decorations.knsrc DESTINATION ${KDE_INSTALL_CONFDIR})
install(TARGETS kcm_kwindecoration DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
kpackage_install_package(package kcm_kwindecoration kcms)

6
kcmkwin/kwindecoration/Messages.sh Normal file → Executable file
View file

@ -1,4 +1,4 @@
#!bin/sh
$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp
$XGETTEXT `find . -name \*.qml -o -name \*.cpp -o -name \*.h` -o $podir/kcmkwindecoration.pot
#! /usr/bin/env bash
$EXTRACTRC `find . -name "*.ui"` >> rc.cpp || exit 11
$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcmkwindecoration.pot
rm -f rc.cpp

View file

@ -153,8 +153,8 @@ void ButtonsModel::add(DecorationButtonType type)
void ButtonsModel::add(int index, int type)
{
beginInsertRows(QModelIndex(), index + 1, index+1);
m_buttons.insert(index+1, KDecoration2::DecorationButtonType(type));
beginInsertRows(QModelIndex(), index, index);
m_buttons.insert(index, KDecoration2::DecorationButtonType(type));
endInsertRows();
}
@ -178,6 +178,24 @@ void ButtonsModel::move(int sourceIndex, int targetIndex)
endMoveRows();
}
void ButtonsModel::clear()
{
beginResetModel();
m_buttons.clear();
endResetModel();
}
void ButtonsModel::replace(const QVector< DecorationButtonType > &buttons)
{
if (buttons.isEmpty()) {
return;
}
beginResetModel();
m_buttons = buttons;
endResetModel();
}
}
}

View file

@ -45,11 +45,13 @@ public:
return m_buttons;
}
Q_INVOKABLE void clear();
Q_INVOKABLE void remove(int index);
Q_INVOKABLE void up(int index);
Q_INVOKABLE void down(int index);
Q_INVOKABLE void move(int sourceIndex, int targetIndex);
void replace(const QVector< DecorationButtonType > &buttons);
void add(DecorationButtonType type);
Q_INVOKABLE void add(int index, int type);

View file

@ -97,7 +97,6 @@ void PreviewBridge::setPlugin(const QString &plugin)
return;
}
m_plugin = plugin;
qDebug() << "Plugin changed to: " << m_plugin;
emit pluginChanged();
}
@ -123,22 +122,20 @@ QString PreviewBridge::plugin() const
void PreviewBridge::createFactory()
{
m_factory.clear();
if (m_plugin.isNull()) {
setValid(false);
qDebug() <<"Plugin not set";
qWarning() << "Plugin not set";
return;
}
const auto offers = KPluginTrader::self()->query(s_pluginName,
s_pluginName,
QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(m_plugin));
if (offers.isEmpty()) {
setValid(false);
qDebug() << "no offers";
return;
const auto offers = KPluginTrader::self()->query(s_pluginName, s_pluginName);
auto item = std::find_if(offers.constBegin(), offers.constEnd(), [this](const auto &plugin) { return plugin.pluginName() == m_plugin; });
if (item != offers.constEnd()) {
KPluginLoader loader(item->libraryPath());
m_factory = loader.factory();
}
KPluginLoader loader(offers.first().libraryPath());
m_factory = loader.factory();
qDebug() << "Factory: " << !m_factory.isNull();
setValid(!m_factory.isNull());
}
@ -212,22 +209,17 @@ void PreviewBridge::configure()
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, this, 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);

View file

@ -24,7 +24,7 @@
#include <KDecoration2/Decoration>
#include <QDebug>
#include <QPainter>
namespace KDecoration2
{
@ -132,7 +132,17 @@ void PreviewButtonItem::paint(QPainter *painter)
if (!m_button) {
return;
}
m_button->paint(painter, QRect(0, 0, width(), height()));
QRect size { 0, 0, (int)width(), (int)height() };
m_button->paint(painter, size);
painter->setCompositionMode(QPainter::CompositionMode_SourceAtop);
painter->fillRect(size, m_color);
}
void PreviewButtonItem::setColor(const QColor color)
{
m_color = color;
m_color.setAlpha(127);
update();
}
}

View file

@ -21,6 +21,7 @@
#define KDECOARTIONS_PREVIEW_BUTTON_ITEM_H
#include <QQuickPaintedItem>
#include <QColor>
#include <QPointer>
#include <KDecoration2/DecorationButton>
@ -39,6 +40,7 @@ class PreviewButtonItem : public QQuickPaintedItem
Q_PROPERTY(KDecoration2::Preview::PreviewBridge *bridge READ bridge WRITE setBridge NOTIFY bridgeChanged)
Q_PROPERTY(KDecoration2::Preview::Settings *settings READ settings WRITE setSettings NOTIFY settingsChanged)
Q_PROPERTY(int type READ typeAsInt WRITE setType NOTIFY typeChanged)
Q_PROPERTY(QColor color READ color WRITE setColor)
public:
explicit PreviewButtonItem(QQuickItem *parent = nullptr);
@ -56,6 +58,9 @@ public:
void setType(KDecoration2::DecorationButtonType type);
void setType(int type);
const QColor &color() const { return m_color; }
void setColor(const QColor color);
Q_SIGNALS:
void bridgeChanged();
void typeChanged();
@ -67,6 +72,7 @@ protected:
private:
void createButton();
void syncGeometry();
QColor m_color;
QPointer<KDecoration2::Preview::PreviewBridge> m_bridge;
QPointer<KDecoration2::Preview::Settings> m_settings;
KDecoration2::Decoration *m_decoration = nullptr;

View file

@ -69,7 +69,6 @@ PreviewClient::PreviewClient(DecoratedClient *c, Decoration *decoration)
connect(this, &PreviewClient::maximizedVerticallyChanged, c, &DecoratedClient::maximizedVerticallyChanged);
connect(this, &PreviewClient::maximizedHorizontallyChanged, c, &DecoratedClient::maximizedHorizontallyChanged);
connect(this, &PreviewClient::minimizableChanged, c, &DecoratedClient::minimizeableChanged);
// connect(this, &PreviewClient::modalChanged, c, &DecoratedClient::modalChanged);
connect(this, &PreviewClient::movableChanged, c, &DecoratedClient::moveableChanged);
connect(this, &PreviewClient::onAllDesktopsChanged, c, &DecoratedClient::onAllDesktopsChanged);
connect(this, &PreviewClient::resizableChanged, c, &DecoratedClient::resizeableChanged);
@ -81,7 +80,6 @@ PreviewClient::PreviewClient(DecoratedClient *c, Decoration *decoration)
connect(this, &PreviewClient::heightChanged, c, &DecoratedClient::heightChanged);
connect(this, &PreviewClient::iconChanged, c, &DecoratedClient::iconChanged);
connect(this, &PreviewClient::paletteChanged, c, &DecoratedClient::paletteChanged);
// connect(this, &PreviewClient::, c, &DecoratedClient::);
connect(this, &PreviewClient::maximizedVerticallyChanged, this,
[this]() {
emit maximizedChanged(isMaximized());
@ -353,12 +351,11 @@ void PreviewClient::setBordersTopEdge(bool enabled)
void PreviewClient::requestShowToolTip(const QString &text)
{
qDebug() << "tooltip show requested with text:" << text;
Q_UNUSED(text);
}
void PreviewClient::requestHideToolTip()
{
qDebug() << "tooltip hide requested";
}
void PreviewClient::requestClose()
@ -368,7 +365,6 @@ void PreviewClient::requestClose()
void PreviewClient::requestContextHelp()
{
qDebug() << "context help requested";
}
void PreviewClient::requestToggleMaximization(Qt::MouseButtons buttons)
@ -431,7 +427,6 @@ void PreviewClient::name(type variable) \
if (m_##variable == variable) { \
return; \
} \
qDebug() << "Setting " << #variable << ":" << variable;\
m_##variable = variable; \
emit variable##Changed(m_##variable); \
}

View file

@ -78,15 +78,9 @@ void PreviewItem::createDecoration()
if (m_bridge.isNull() || m_settings.isNull() || m_decoration) {
return;
}
m_decoration = m_bridge->createDecoration(0);
if (!m_decoration) {
return;
}
m_decoration->setProperty("visualParent", QVariant::fromValue(this));
Decoration *decoration = m_bridge->createDecoration(0);
m_client = m_bridge->lastCreatedClient();
connect(m_decoration, &Decoration::bordersChanged, this, &PreviewItem::syncSize);
connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::syncSize);
emit decorationChanged(m_decoration);
setDecoration(decoration);
}
Decoration *PreviewItem::decoration() const
@ -96,41 +90,15 @@ Decoration *PreviewItem::decoration() const
void PreviewItem::setDecoration(Decoration *deco)
{
if (m_decoration == deco) {
if (!deco || m_decoration == deco) {
return;
}
auto updateSlot = static_cast<void (QQuickItem::*)()>(&QQuickItem::update);
if (m_decoration) {
disconnect(m_decoration, &Decoration::bordersChanged, this, updateSlot);
}
m_decoration = deco;
m_decoration->setProperty("visualParent", QVariant::fromValue(this));
connect(m_decoration, &Decoration::bordersChanged, this, updateSlot);
connect(m_decoration, &Decoration::sectionUnderMouseChanged, this,
[this](Qt::WindowFrameSection section) {
switch (section) {
case Qt::TopRightSection:
case Qt::BottomLeftSection:
setCursor(Qt::SizeBDiagCursor);
return;
case Qt::TopLeftSection:
case Qt::BottomRightSection:
setCursor(Qt::SizeFDiagCursor);
return;
case Qt::TopSection:
case Qt::BottomSection:
setCursor(Qt::SizeVerCursor);
return;
case Qt::LeftSection:
case Qt::RightSection:
setCursor(Qt::SizeHorCursor);
return;
default:
setCursor(Qt::ArrowCursor);
}
}
);
connect(m_decoration, &KDecoration2::Decoration::shadowChanged, this, &PreviewItem::shadowChanged);
connect(m_decoration, &Decoration::bordersChanged, this, &PreviewItem::syncSize);
connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::syncSize);
connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::shadowChanged);
emit decorationChanged(m_decoration);
}

View file

@ -60,14 +60,13 @@ QVariant DecorationsModel::data(const QModelIndex &index, int role) const
switch (role) {
case Qt::DisplayRole:
return d.visibleName;
case Qt::UserRole +4:
case PluginNameRole:
return d.pluginName;
case Qt::UserRole +5:
case ThemeNameRole:
return d.themeName;
case Qt::UserRole +6:
case ConfigurationRole:
return d.configuration;
}
return QVariant();
}
@ -75,9 +74,9 @@ 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 +6, QByteArrayLiteral("configureable")}
{PluginNameRole, QByteArrayLiteral("plugin")},
{ThemeNameRole, QByteArrayLiteral("theme")},
{ConfigurationRole, QByteArrayLiteral("configureable")}
});
return roles;
}
@ -134,8 +133,8 @@ void DecorationsModel::init()
if (!metadata.isUndefined()) {
const auto decoSettingsMap = metadata.toObject().toVariantMap();
const QString &kns = findKNewStuff(decoSettingsMap);
if (!kns.isEmpty()) {
m_knsProvides.insert(kns, info.name().isEmpty() ? info.pluginName() : info.name());
if (!kns.isEmpty() && !m_knsProviders.contains(kns)) {
m_knsProviders.append(kns);
}
if (isThemeEngine(decoSettingsMap)) {
const QString keyword = themeListKeyword(decoSettingsMap);
@ -171,6 +170,7 @@ void DecorationsModel::init()
Data data;
data.pluginName = info.pluginName();
data.visibleName = info.name().isEmpty() ? info.pluginName() : info.name();
data.themeName = data.visibleName;
data.configuration = config;
m_plugins.emplace_back(std::move(data));

View file

@ -31,6 +31,13 @@ namespace Configuration
class DecorationsModel : public QAbstractListModel
{
Q_OBJECT
public:
enum DecorationRole {
PluginNameRole = Qt::UserRole + 1,
ThemeNameRole,
ConfigurationRole,
};
public:
explicit DecorationsModel(QObject *parent = nullptr);
virtual ~DecorationsModel();
@ -41,8 +48,8 @@ public:
QModelIndex findDecoration(const QString &pluginName, const QString &themeName = QString()) const;
QMap<QString, QString> knsProviders() const {
return m_knsProvides;
QStringList knsProviders() const {
return m_knsProviders;
}
public Q_SLOTS:
@ -56,7 +63,7 @@ private:
bool configuration = false;
};
std::vector<Data> m_plugins;
QMap<QString, QString> m_knsProvides;
QStringList m_knsProviders;
};
}

View file

@ -1,432 +1,314 @@
/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
* Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kcm.h"
#include "decorationmodel.h"
#include "declarative-plugin/buttonsmodel.h"
#include <config-kwin.h>
// KDE
#include <KAboutData>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KSharedConfig>
#include <KDecoration2/DecorationButton>
#include <KNewStuff3/KNS3/DownloadDialog>
#include <kdeclarative/kdeclarative.h>
// Qt
#include <KNSCore/Engine>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QFontDatabase>
#include <QMenu>
#include <QQmlContext>
#include <QQmlEngine>
#include <QDebug>
#include <QQuickItem>
#include <QQuickView>
#include <QQuickWindow>
#include <QSortFilterProxyModel>
#include <QStandardPaths>
#include <QVBoxLayout>
K_PLUGIN_FACTORY(KDecorationFactory,
registerPlugin<KDecoration2::Configuration::ConfigurationModule>();
)
#include <KNewStuff3/KNS3/DownloadDialog>
K_PLUGIN_FACTORY_WITH_JSON(KCMKWinDecorationFactory, "kwindecoration.json", registerPlugin<KCMKWinDecoration>();)
Q_DECLARE_METATYPE(KDecoration2::BorderSize)
namespace KDecoration2
{
namespace Configuration
namespace
{
static const QString s_pluginName = QStringLiteral("org.kde.kdecoration2");
const QString s_configFile { QStringLiteral("kwinrc") };
const QString s_configGroup { QStringLiteral("org.kde.kdecoration2") };
const QString s_configPlugin { QStringLiteral("library") };
const QString s_configTheme { QStringLiteral("theme") };
const QString s_configBorderSize { QStringLiteral("BorderSize") };
const QString s_configCloseOnDoubleClickOnMenu { QStringLiteral("CloseOnDoubleClickOnMenu") };
const QString s_configDecoButtonsOnLeft { QStringLiteral("ButtonsOnLeft") };
const QString s_configDecoButtonsOnRight { QStringLiteral("ButtonsOnRight") };
const KDecoration2::BorderSize s_defaultBorderSize = KDecoration2::BorderSize::Normal;
const bool s_defaultCloseOnDoubleClickOnMenu = false;
const DecorationButtonsList s_defaultDecoButtonsOnLeft {
KDecoration2::DecorationButtonType::Menu,
KDecoration2::DecorationButtonType::OnAllDesktops
};
const DecorationButtonsList s_defaultDecoButtonsOnRight {
KDecoration2::DecorationButtonType::ContextHelp,
KDecoration2::DecorationButtonType::Minimize,
KDecoration2::DecorationButtonType::Maximize,
KDecoration2::DecorationButtonType::Close
};
#if HAVE_BREEZE_DECO
static const QString s_defaultPlugin = QStringLiteral(BREEZE_KDECORATION_PLUGIN_ID);
static const QString s_defaultTheme;
const QString s_defaultPlugin { QStringLiteral(BREEZE_KDECORATION_PLUGIN_ID) };
const QString s_defaultTheme { QStringLiteral("Breeze") };
#else
static const QString s_defaultPlugin = QStringLiteral("org.kde.kwin.aurorae");
static const QString s_defaultTheme = QStringLiteral("kwin4_decoration_qml_plastik");
const QString s_defaultPlugin { QStringLiteral("org.kde.kwin.aurorae") };
const QString s_defaultTheme { QStringLiteral("kwin4_decoration_qml_plastik") };
#endif
static const QString s_borderSizeNormal = QStringLiteral("Normal");
static const QString s_ghnsIcon = QStringLiteral("get-hot-new-stuff");
ConfigurationForm::ConfigurationForm(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
}
static bool s_loading = false;
ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args)
: KCModule(parent, args)
, m_model(new DecorationsModel(this))
, m_proxyModel(new QSortFilterProxyModel(this))
, m_ui(new ConfigurationForm(this))
, m_leftButtons(new Preview::ButtonsModel(QVector<DecorationButtonType>(), this))
, m_rightButtons(new Preview::ButtonsModel(QVector<DecorationButtonType>(), this))
, m_availableButtons(new Preview::ButtonsModel(this))
KCMKWinDecoration::KCMKWinDecoration(QObject *parent, const QVariantList &arguments)
: KQuickAddons::ConfigModule(parent, arguments)
, m_themesModel(new KDecoration2::Configuration::DecorationsModel(this))
, m_proxyThemesModel(new QSortFilterProxyModel(this))
, m_leftButtonsModel(new KDecoration2::Preview::ButtonsModel(DecorationButtonsList(), this))
, m_rightButtonsModel(new KDecoration2::Preview::ButtonsModel(DecorationButtonsList(), this))
, m_availableButtonsModel(new KDecoration2::Preview::ButtonsModel(this))
, m_savedSettings{ s_defaultBorderSize, -2 /* for setTheme() */, false, s_defaultDecoButtonsOnLeft, s_defaultDecoButtonsOnRight }
, m_currentSettings(m_savedSettings)
{
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_proxyModel->sort(0);
connect(m_ui->filter, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString);
auto about = new KAboutData(QStringLiteral("kcm_kwindecoration"),
i18n("Configure window titlebars and borders"),
QStringLiteral("1.0"),
QString(),
KAboutLicense::GPL);
about->addAuthor(i18n("Valerio Pilo"),
i18n("Author"),
QStringLiteral("vpilo@coldshock.net"));
setAboutData(about);
m_quickView = new QQuickView(0);
KDeclarative::KDeclarative kdeclarative;
kdeclarative.setDeclarativeEngine(m_quickView->engine());
kdeclarative.setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
kdeclarative.setupContext();
kdeclarative.setupEngine(m_quickView->engine());
qmlRegisterType<QAbstractListModel>();
qmlRegisterType<QSortFilterProxyModel>();
qmlRegisterType<QAbstractItemModel>();
QWidget *widget = QWidget::createWindowContainer(m_quickView, this);
QVBoxLayout* layout = new QVBoxLayout(m_ui->view);
layout->setContentsMargins(0,0,0,0);
layout->addWidget(widget);
m_proxyThemesModel->setSourceModel(m_themesModel);
m_proxyThemesModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_proxyThemesModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_proxyThemesModel->sort(0);
m_quickView->rootContext()->setContextProperty(QStringLiteral("decorationsModel"), m_proxyModel);
updateColors();
m_quickView->rootContext()->setContextProperty("_borderSizesIndex", 3); // 3 is normal
m_quickView->rootContext()->setContextProperty("leftButtons", m_leftButtons);
m_quickView->rootContext()->setContextProperty("rightButtons", m_rightButtons);
m_quickView->rootContext()->setContextProperty("availableButtons", m_availableButtons);
m_quickView->rootContext()->setContextProperty("initialThemeIndex", -1);
connect(m_leftButtonsModel, &QAbstractItemModel::rowsInserted, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_leftButtonsModel, &QAbstractItemModel::rowsMoved, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_leftButtonsModel, &QAbstractItemModel::rowsRemoved, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_leftButtonsModel, &QAbstractItemModel::modelReset, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_rightButtonsModel, &QAbstractItemModel::rowsInserted, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_rightButtonsModel, &QAbstractItemModel::rowsMoved, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_rightButtonsModel, &QAbstractItemModel::rowsRemoved, this, &KCMKWinDecoration::updateNeedsSave);
connect(m_rightButtonsModel, &QAbstractItemModel::modelReset, this, &KCMKWinDecoration::updateNeedsSave);
m_quickView->rootContext()->setContextProperty("titleFont", QFontDatabase::systemFont(QFontDatabase::TitleFont));
m_quickView->setResizeMode(QQuickView::SizeRootObjectToView);
m_quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/kcm_kwindecoration/main.qml"))));
if (m_quickView->status() == QQuickView::Ready) {
auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView");
if (listView) {
connect(listView, SIGNAL(userChangedSelection()), this, SLOT(changed()));
// Update the themes when the color scheme or a theme's settings change
QDBusConnection::sessionBus()
.connect(QString(), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"),
this,
SLOT(reloadKWinSettings()));
QMetaObject::invokeMethod(m_themesModel, "init", Qt::QueuedConnection);
}
void KCMKWinDecoration::reloadKWinSettings()
{
QMetaObject::invokeMethod(m_themesModel, "init", Qt::QueuedConnection);
}
void KCMKWinDecoration::getNewStuff(QQuickItem *context)
{
if (!m_newStuffDialog) {
m_newStuffDialog = new KNS3::DownloadDialog(QStringLiteral("window-decorations.knsrc"));
m_newStuffDialog->setWindowTitle(i18n("Download New Window Decorations"));
m_newStuffDialog->setWindowModality(Qt::WindowModal);
connect(m_newStuffDialog, &KNS3::DownloadDialog::accepted, this, &KCMKWinDecoration::load);
}
if (context && context->window()) {
m_newStuffDialog->winId(); // so it creates the windowHandle()
m_newStuffDialog->windowHandle()->setTransientParent(context->window());
}
m_newStuffDialog->show();
}
void KCMKWinDecoration::load()
{
const KConfigGroup config = KSharedConfig::openConfig(s_configFile)->group(s_configGroup);
const QString plugin = config.readEntry(s_configPlugin, s_defaultPlugin);
const QString theme = config.readEntry(s_configTheme, s_defaultTheme);
int themeIndex = m_proxyThemesModel->mapFromSource(m_themesModel->findDecoration(plugin, theme)).row();
if (themeIndex < 0) {
qWarning() << "Plugin" << plugin << "and theme" << theme << "not found";
} else {
qDebug() << "Current theme: plugin" << plugin << "and theme" << theme;
}
setTheme(themeIndex);
setCloseOnDoubleClickOnMenu(config.readEntry(s_configCloseOnDoubleClickOnMenu, s_defaultCloseOnDoubleClickOnMenu));
const QString defaultSizeName = Utils::borderSizeToString(s_defaultBorderSize);
setBorderSize(Utils::stringToBorderSize(config.readEntry(s_configBorderSize, defaultSizeName)));
m_leftButtonsModel->replace(Utils::readDecorationButtons(config, s_configDecoButtonsOnLeft, s_defaultDecoButtonsOnLeft));
m_rightButtonsModel->replace(Utils::readDecorationButtons(config, s_configDecoButtonsOnRight, s_defaultDecoButtonsOnRight));
m_currentSettings.buttonsOnLeft = m_leftButtonsModel->buttons();
m_currentSettings.buttonsOnRight = m_rightButtonsModel->buttons();
m_savedSettings = m_currentSettings;
updateNeedsSave();
}
void KCMKWinDecoration::save()
{
KConfigGroup config = KSharedConfig::openConfig(s_configFile)->group(s_configGroup);
if (m_currentSettings.themeIndex >= 0) {
const QModelIndex index = m_proxyThemesModel->index(m_currentSettings.themeIndex, 0);
if (index.isValid()) {
const QString plugin = index.data(KDecoration2::Configuration::DecorationsModel::PluginNameRole).toString();
const QString theme = index.data(KDecoration2::Configuration::DecorationsModel::ThemeNameRole).toString();
config.writeEntry(s_configPlugin, plugin);
config.writeEntry(s_configTheme, theme);
qDebug() << "Saved theme: plugin" << plugin << "and theme" << theme;
} else {
qWarning() << "Cannot match theme index" << m_currentSettings.themeIndex << "in model";
}
}
m_ui->tabWidget->tabBar()->disconnect();
auto setCurrentTab = [this](int index) {
if (index == 0)
m_ui->doubleClickMessage->hide();
m_ui->filter->setVisible(index == 0);
m_ui->knsButton->setVisible(index == 0);
if (auto themeList = m_quickView->rootObject()->findChild<QQuickItem*>("themeList")) {
themeList->setVisible(index == 0);
}
m_ui->borderSizesLabel->setVisible(index == 0);
m_ui->borderSizesCombo->setVisible(index == 0);
m_ui->closeWindowsDoubleClick->setVisible(index == 1);
if (auto buttonLayout = m_quickView->rootObject()->findChild<QQuickItem*>("buttonLayout")) {
buttonLayout->setVisible(index == 1);
}
};
connect(m_ui->tabWidget->tabBar(), &QTabBar::currentChanged, this, setCurrentTab);
setCurrentTab(0);
m_ui->doubleClickMessage->setVisible(false);
m_ui->doubleClickMessage->setText(i18n("Close by double clicking:\n To open the menu, keep the button pressed until it appears."));
m_ui->doubleClickMessage->setCloseButtonVisible(true);
m_ui->borderSizesCombo->setItemData(0, QVariant::fromValue(BorderSize::None));
m_ui->borderSizesCombo->setItemData(1, QVariant::fromValue(BorderSize::NoSides));
m_ui->borderSizesCombo->setItemData(2, QVariant::fromValue(BorderSize::Tiny));
m_ui->borderSizesCombo->setItemData(3, QVariant::fromValue(BorderSize::Normal));
m_ui->borderSizesCombo->setItemData(4, QVariant::fromValue(BorderSize::Large));
m_ui->borderSizesCombo->setItemData(5, QVariant::fromValue(BorderSize::VeryLarge));
m_ui->borderSizesCombo->setItemData(6, QVariant::fromValue(BorderSize::Huge));
m_ui->borderSizesCombo->setItemData(7, QVariant::fromValue(BorderSize::VeryHuge));
m_ui->borderSizesCombo->setItemData(8, QVariant::fromValue(BorderSize::Oversized));
m_ui->knsButton->setIcon(QIcon::fromTheme(s_ghnsIcon));
auto changedSlot = static_cast<void (ConfigurationModule::*)()>(&ConfigurationModule::changed);
connect(m_ui->closeWindowsDoubleClick, &QCheckBox::stateChanged, this, changedSlot);
connect(m_ui->closeWindowsDoubleClick, &QCheckBox::toggled, this,
[this] (bool toggled) {
if (s_loading) {
return;
}
if (toggled)
m_ui->doubleClickMessage->animatedShow();
else
m_ui->doubleClickMessage->animatedHide();
}
);
connect(m_ui->borderSizesCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, [this] (int index) {
auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView");
if (listView) {
listView->setProperty("borderSizesIndex", index);
}
changed();
}
);
connect(m_model, &QAbstractItemModel::modelReset, this,
[this] {
const auto &kns = m_model->knsProviders();
m_ui->knsButton->setEnabled(!kns.isEmpty());
if (kns.isEmpty()) {
return;
}
if (kns.count() > 1) {
QMenu *menu = new QMenu(m_ui->knsButton);
for (auto it = kns.begin(); it != kns.end(); ++it) {
QAction *action = menu->addAction(QIcon::fromTheme(s_ghnsIcon), it.value());
action->setData(it.key());
connect(action, &QAction::triggered, this, [this, action] { showKNS(action->data().toString());});
}
m_ui->knsButton->setMenu(menu);
}
}
);
connect(m_ui->knsButton, &QPushButton::clicked, this,
[this] {
const auto &kns = m_model->knsProviders();
if (kns.isEmpty()) {
return;
}
showKNS(kns.firstKey());
}
);
connect(m_leftButtons, &QAbstractItemModel::rowsInserted, this, changedSlot);
connect(m_leftButtons, &QAbstractItemModel::rowsMoved, this, changedSlot);
connect(m_leftButtons, &QAbstractItemModel::rowsRemoved, this, changedSlot);
connect(m_rightButtons, &QAbstractItemModel::rowsInserted, this, changedSlot);
connect(m_rightButtons, &QAbstractItemModel::rowsMoved, this, changedSlot);
connect(m_rightButtons, &QAbstractItemModel::rowsRemoved, this, changedSlot);
QVBoxLayout *l = new QVBoxLayout(this);
l->addWidget(m_ui);
QMetaObject::invokeMethod(m_model, "init", Qt::QueuedConnection);
m_ui->installEventFilter(this);
}
ConfigurationModule::~ConfigurationModule() = default;
void ConfigurationModule::showEvent(QShowEvent *ev)
{
KCModule::showEvent(ev);
}
static const QMap<QString, KDecoration2::BorderSize> s_sizes = QMap<QString, KDecoration2::BorderSize>({
{QStringLiteral("None"), BorderSize::None},
{QStringLiteral("NoSides"), BorderSize::NoSides},
{QStringLiteral("Tiny"), BorderSize::Tiny},
{s_borderSizeNormal, BorderSize::Normal},
{QStringLiteral("Large"), BorderSize::Large},
{QStringLiteral("VeryLarge"), BorderSize::VeryLarge},
{QStringLiteral("Huge"), BorderSize::Huge},
{QStringLiteral("VeryHuge"), BorderSize::VeryHuge},
{QStringLiteral("Oversized"), BorderSize::Oversized}
});
static BorderSize stringToSize(const QString &name)
{
auto it = s_sizes.constFind(name);
if (it == s_sizes.constEnd()) {
// non sense values are interpreted just like normal
return BorderSize::Normal;
}
return it.value();
}
static QString sizeToString(BorderSize size)
{
return s_sizes.key(size, s_borderSizeNormal);
}
static QHash<KDecoration2::DecorationButtonType, QChar> s_buttonNames;
static void initButtons()
{
if (!s_buttonNames.isEmpty()) {
return;
}
s_buttonNames[KDecoration2::DecorationButtonType::Menu] = QChar('M');
s_buttonNames[KDecoration2::DecorationButtonType::ApplicationMenu] = QChar('N');
s_buttonNames[KDecoration2::DecorationButtonType::OnAllDesktops] = QChar('S');
s_buttonNames[KDecoration2::DecorationButtonType::ContextHelp] = QChar('H');
s_buttonNames[KDecoration2::DecorationButtonType::Minimize] = QChar('I');
s_buttonNames[KDecoration2::DecorationButtonType::Maximize] = QChar('A');
s_buttonNames[KDecoration2::DecorationButtonType::Close] = QChar('X');
s_buttonNames[KDecoration2::DecorationButtonType::KeepAbove] = QChar('F');
s_buttonNames[KDecoration2::DecorationButtonType::KeepBelow] = QChar('B');
s_buttonNames[KDecoration2::DecorationButtonType::Shade] = QChar('L');
}
static QString buttonsToString(const QVector<KDecoration2::DecorationButtonType> &buttons)
{
auto buttonToString = [](KDecoration2::DecorationButtonType button) -> QChar {
const auto it = s_buttonNames.constFind(button);
if (it != s_buttonNames.constEnd()) {
return it.value();
}
return QChar();
};
QString ret;
for (auto button : buttons) {
ret.append(buttonToString(button));
}
return ret;
}
static
QVector< KDecoration2::DecorationButtonType > readDecorationButtons(const KConfigGroup &config,
const char *key,
const QVector< KDecoration2::DecorationButtonType > &defaultValue)
{
initButtons();
auto buttonsFromString = [](const QString &buttons) -> QVector<KDecoration2::DecorationButtonType> {
QVector<KDecoration2::DecorationButtonType> ret;
for (auto it = buttons.begin(); it != buttons.end(); ++it) {
for (auto it2 = s_buttonNames.constBegin(); it2 != s_buttonNames.constEnd(); ++it2) {
if (it2.value() == (*it)) {
ret << it2.key();
}
}
}
return ret;
};
return buttonsFromString(config.readEntry(key, buttonsToString(defaultValue)));
}
void ConfigurationModule::load()
{
s_loading = true;
const KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName);
const QString plugin = config.readEntry("library", s_defaultPlugin);
const QString theme = config.readEntry("theme", s_defaultTheme);
m_ui->closeWindowsDoubleClick->setChecked(config.readEntry("CloseOnDoubleClickOnMenu", false));
const QVariant border = QVariant::fromValue(stringToSize(config.readEntry("BorderSize", s_borderSizeNormal)));
m_ui->borderSizesCombo->setCurrentIndex(m_ui->borderSizesCombo->findData(border));
int themeIndex = m_proxyModel->mapFromSource(m_model->findDecoration(plugin, theme)).row();
m_quickView->rootContext()->setContextProperty("initialThemeIndex", themeIndex);
// buttons
const auto &left = readDecorationButtons(config, "ButtonsOnLeft", QVector<KDecoration2::DecorationButtonType >{
KDecoration2::DecorationButtonType::Menu,
KDecoration2::DecorationButtonType::OnAllDesktops
});
while (m_leftButtons->rowCount() > 0) {
m_leftButtons->remove(0);
}
for (auto it = left.begin(); it != left.end(); ++it) {
m_leftButtons->add(*it);
}
const auto &right = readDecorationButtons(config, "ButtonsOnRight", QVector<KDecoration2::DecorationButtonType >{
KDecoration2::DecorationButtonType::ContextHelp,
KDecoration2::DecorationButtonType::Minimize,
KDecoration2::DecorationButtonType::Maximize,
KDecoration2::DecorationButtonType::Close
});
while (m_rightButtons->rowCount() > 0) {
m_rightButtons->remove(0);
}
for (auto it = right.begin(); it != right.end(); ++it) {
m_rightButtons->add(*it);
}
KCModule::load();
s_loading = false;
}
void ConfigurationModule::save()
{
KConfigGroup config = KSharedConfig::openConfig("kwinrc")->group(s_pluginName);
config.writeEntry("CloseOnDoubleClickOnMenu", m_ui->closeWindowsDoubleClick->isChecked());
config.writeEntry("BorderSize", sizeToString(m_ui->borderSizesCombo->currentData().value<BorderSize>()));
if (auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView")) {
const int currentIndex = listView->property("currentIndex").toInt();
if (currentIndex != -1) {
const QModelIndex index = m_proxyModel->index(currentIndex, 0);
if (index.isValid()) {
config.writeEntry("library", index.data(Qt::UserRole + 4).toString());
const QString theme = index.data(Qt::UserRole +5).toString();
config.writeEntry("theme", theme);
}
}
}
config.writeEntry("ButtonsOnLeft", buttonsToString(m_leftButtons->buttons()));
config.writeEntry("ButtonsOnRight", buttonsToString(m_rightButtons->buttons()));
config.writeEntry(s_configCloseOnDoubleClickOnMenu, m_currentSettings.closeOnDoubleClickOnMenu);
config.writeEntry(s_configBorderSize, Utils::borderSizeToString(m_currentSettings.borderSize));
config.writeEntry(s_configDecoButtonsOnLeft, Utils::buttonsToString(m_currentSettings.buttonsOnLeft));
config.writeEntry(s_configDecoButtonsOnRight, Utils::buttonsToString(m_currentSettings.buttonsOnRight));
config.sync();
KCModule::save();
// Send signal to all kwin instances
m_savedSettings = m_currentSettings;
// Send a signal to all kwin instances
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"),
QStringLiteral("org.kde.KWin"),
QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
updateNeedsSave();
}
void ConfigurationModule::defaults()
void KCMKWinDecoration::defaults()
{
if (auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView")) {
const QModelIndex index = m_proxyModel->mapFromSource(m_model->findDecoration(s_defaultPlugin));
listView->setProperty("currentIndex", index.isValid() ? index.row() : -1);
int themeIndex = m_proxyThemesModel->mapFromSource(m_themesModel->findDecoration(s_defaultPlugin, s_defaultTheme)).row();
if (themeIndex < 0) {
qWarning() << "Default plugin" << s_defaultPlugin << "and theme" << s_defaultTheme << "not found";
}
m_ui->borderSizesCombo->setCurrentIndex(m_ui->borderSizesCombo->findData(QVariant::fromValue(stringToSize(s_borderSizeNormal))));
m_ui->closeWindowsDoubleClick->setChecked(false);
KCModule::defaults();
setTheme(themeIndex);
setBorderSize(s_defaultBorderSize);
setCloseOnDoubleClickOnMenu(s_defaultCloseOnDoubleClickOnMenu);
m_leftButtonsModel->replace(s_defaultDecoButtonsOnLeft);
m_rightButtonsModel->replace(s_defaultDecoButtonsOnRight);
updateNeedsSave();
}
void ConfigurationModule::showKNS(const QString &config)
void KCMKWinDecoration::updateNeedsSave()
{
QPointer<KNS3::DownloadDialog> downloadDialog = new KNS3::DownloadDialog(config, this);
if (downloadDialog->exec() == QDialog::Accepted && !downloadDialog->changedEntries().isEmpty()) {
auto listView = m_quickView->rootObject()->findChild<QQuickItem*>("listView");
QString selectedPluginName;
QString selectedThemeName;
if (listView) {
const QModelIndex index = m_proxyModel->index(listView->property("currentIndex").toInt(), 0);
if (index.isValid()) {
selectedPluginName = index.data(Qt::UserRole + 4).toString();
selectedThemeName = index.data(Qt::UserRole + 5).toString();
}
}
m_model->init();
if (!selectedPluginName.isEmpty()) {
const QModelIndex index = m_model->findDecoration(selectedPluginName, selectedThemeName);
const QModelIndex proxyIndex = m_proxyModel->mapFromSource(index);
if (listView) {
listView->setProperty("currentIndex", proxyIndex.isValid() ? proxyIndex.row() : -1);
}
}
}
delete downloadDialog;
m_currentSettings.buttonsOnLeft = m_leftButtonsModel->buttons();
m_currentSettings.buttonsOnRight = m_rightButtonsModel->buttons();
setNeedsSave(m_savedSettings.closeOnDoubleClickOnMenu != m_currentSettings.closeOnDoubleClickOnMenu
|| m_savedSettings.borderSize != m_currentSettings.borderSize
|| m_savedSettings.themeIndex != m_currentSettings.themeIndex
|| m_savedSettings.buttonsOnLeft != m_currentSettings.buttonsOnLeft
|| m_savedSettings.buttonsOnRight != m_currentSettings.buttonsOnRight);
}
bool ConfigurationModule::eventFilter(QObject *watched, QEvent *e)
QSortFilterProxyModel *KCMKWinDecoration::themesModel() const
{
if (watched != m_ui) {
return false;
}
if (e->type() == QEvent::PaletteChange) {
updateColors();
}
return false;
return m_proxyThemesModel;
}
void ConfigurationModule::updateColors()
QAbstractListModel *KCMKWinDecoration::leftButtonsModel()
{
m_quickView->rootContext()->setContextProperty("backgroundColor", m_ui->palette().color(QPalette::Active, QPalette::Window));
m_quickView->rootContext()->setContextProperty("highlightColor", m_ui->palette().color(QPalette::Active, QPalette::Shadow));
m_quickView->rootContext()->setContextProperty("baseColor", m_ui->palette().color(QPalette::Active, QPalette::Base));
return m_leftButtonsModel;
}
QAbstractListModel *KCMKWinDecoration::rightButtonsModel()
{
return m_rightButtonsModel;
}
QAbstractListModel *KCMKWinDecoration::availableButtonsModel() const
{
return m_availableButtonsModel;
}
QStringList KCMKWinDecoration::borderSizesModel() const
{
return Utils::getBorderSizeNames().values();
}
int KCMKWinDecoration::borderSize() const
{
return Utils::getBorderSizeNames().keys().indexOf(m_currentSettings.borderSize);
}
int KCMKWinDecoration::theme() const
{
return m_currentSettings.themeIndex;
}
bool KCMKWinDecoration::closeOnDoubleClickOnMenu() const
{
return m_currentSettings.closeOnDoubleClickOnMenu;
}
void KCMKWinDecoration::setBorderSize(int index)
{
setBorderSize(Utils::getBorderSizeNames().keys().at(index));
}
void KCMKWinDecoration::setBorderSize(KDecoration2::BorderSize size)
{
if (m_currentSettings.borderSize == size) {
return;
}
m_currentSettings.borderSize = size;
emit borderSizeChanged();
updateNeedsSave();
}
void KCMKWinDecoration::setTheme(int index)
{
// The initial themeIndex is set to -2 to always initially apply a theme, any theme
if (m_currentSettings.themeIndex == index) {
return;
}
m_currentSettings.themeIndex = index;
emit themeChanged();
updateNeedsSave();
}
void KCMKWinDecoration::setCloseOnDoubleClickOnMenu(bool enable)
{
if (m_currentSettings.closeOnDoubleClickOnMenu == enable) {
return;
}
m_currentSettings.closeOnDoubleClickOnMenu = enable;
emit closeOnDoubleClickOnMenuChanged();
updateNeedsSave();
}
#include "kcm.moc"

View file

@ -1,80 +1,116 @@
/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
* Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KDECORATIONS_KCM_H
#define KDECORATIONS_KCM_H
#include <kcmodule.h>
#include <ui_kcm.h>
#include <QAbstractItemModel>
#pragma once
#include "utils.h"
#include <KQuickAddons/ConfigModule>
class QAbstractItemModel;
class QSortFilterProxyModel;
class QQuickView;
class QQuickItem;
namespace KNS3
{
class DownloadDialog;
}
namespace KDecoration2
{
enum class BorderSize;
namespace Preview
{
class PreviewBridge;
class ButtonsModel;
}
namespace Configuration
{
class DecorationsModel;
}
}
class ConfigurationForm : public QWidget, public Ui::KCMForm
{
public:
explicit ConfigurationForm(QWidget* parent);
};
class ConfigurationModule : public KCModule
class KCMKWinDecoration : public KQuickAddons::ConfigModule
{
Q_OBJECT
public:
explicit ConfigurationModule(QWidget *parent = nullptr, const QVariantList &args = QVariantList());
virtual ~ConfigurationModule();
Q_PROPERTY(QSortFilterProxyModel *themesModel READ themesModel CONSTANT)
Q_PROPERTY(QStringList borderSizesModel READ borderSizesModel CONSTANT)
Q_PROPERTY(int borderSize READ borderSize WRITE setBorderSize NOTIFY borderSizeChanged)
Q_PROPERTY(int theme READ theme WRITE setTheme NOTIFY themeChanged)
Q_PROPERTY(QAbstractListModel *leftButtonsModel READ leftButtonsModel NOTIFY buttonsChanged)
Q_PROPERTY(QAbstractListModel *rightButtonsModel READ rightButtonsModel NOTIFY buttonsChanged)
Q_PROPERTY(QAbstractListModel *availableButtonsModel READ availableButtonsModel CONSTANT)
Q_PROPERTY(bool closeOnDoubleClickOnMenu READ closeOnDoubleClickOnMenu WRITE setCloseOnDoubleClickOnMenu NOTIFY closeOnDoubleClickOnMenuChanged)
bool eventFilter(QObject *watched, QEvent *e) override;
public:
KCMKWinDecoration(QObject *parent, const QVariantList &arguments);
QSortFilterProxyModel *themesModel() const;
QAbstractListModel *leftButtonsModel();
QAbstractListModel *rightButtonsModel();
QAbstractListModel *availableButtonsModel() const;
QStringList borderSizesModel() const;
int borderSize() const;
int theme() const;
bool closeOnDoubleClickOnMenu() const;
void setBorderSize(int index);
void setBorderSize(KDecoration2::BorderSize size);
void setTheme(int index);
void setCloseOnDoubleClickOnMenu(bool enable);
Q_INVOKABLE void getNewStuff(QQuickItem *context);
Q_SIGNALS:
void themeChanged();
void buttonsChanged();
void borderSizeChanged();
void closeOnDoubleClickOnMenuChanged();
public Q_SLOTS:
void defaults() override;
void load() override;
void save() override;
void defaults() override;
protected:
void showEvent(QShowEvent *ev) override;
private Q_SLOTS:
void updateNeedsSave();
void reloadKWinSettings();
private:
void showKNS(const QString &config);
void updateColors();
DecorationsModel *m_model;
QSortFilterProxyModel *m_proxyModel;
ConfigurationForm *m_ui;
QQuickView *m_quickView;
Preview::ButtonsModel *m_leftButtons;
Preview::ButtonsModel *m_rightButtons;
Preview::ButtonsModel *m_availableButtons;
KDecoration2::Configuration::DecorationsModel *m_themesModel;
QSortFilterProxyModel *m_proxyThemesModel;
KDecoration2::Preview::ButtonsModel *m_leftButtonsModel;
KDecoration2::Preview::ButtonsModel *m_rightButtonsModel;
KDecoration2::Preview::ButtonsModel *m_availableButtonsModel;
QPointer<KNS3::DownloadDialog> m_newStuffDialog;
struct Settings
{
KDecoration2::BorderSize borderSize;
int themeIndex;
bool closeOnDoubleClickOnMenu;
DecorationButtonsList buttonsOnLeft;
DecorationButtonsList buttonsOnRight;
};
Settings m_savedSettings;
Settings m_currentSettings;
};
}
}
#endif

View file

@ -1,182 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KCMForm</class>
<widget class="QWidget" name="KCMForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>386</width>
<height>272</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="theTab">
<attribute name="title">
<string>Theme</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="filter">
<property name="placeholderText">
<string>Search...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="closeWindowsDoubleClick">
<property name="toolTip">
<string extracomment="Check this option if you want windows to be closed when you double click the menu button."/>
</property>
<property name="text">
<string>Close windows by double clicking &amp;the menu button</string>
</property>
</widget>
</item>
<item>
<widget class="KMessageWidget" name="doubleClickMessage"/>
</item>
<item>
<widget class="QWidget" name="view">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="borderSizesLabel">
<property name="text">
<string>Border si&amp;ze:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>borderSizesCombo</cstring>
</property>
</widget>
</item>
<item>
<widget class="KComboBox" name="borderSizesCombo">
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">No Borders</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">No Side Borders</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Tiny</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Normal</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Large</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Very Large</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Huge</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Very Huge</string>
</property>
</item>
<item>
<property name="text">
<string comment="@item:inlistbox Border size:">Oversized</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="borderSizesSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="knsButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Get New Window Decorations...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Buttons</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KComboBox</class>
<extends>QComboBox</extends>
<header>kcombobox.h</header>
</customwidget>
<customwidget>
<class>KMessageWidget</class>
<extends>QFrame</extends>
<header location="global">kmessagewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -1,15 +1,15 @@
[Desktop Entry]
Exec=kcmshell5 kwindecoration
Icon=preferences-system-windows-action
Exec=kcmshell5 kwindecoration
Categories=Qt;KDE;X-KDE-settings-looknfeel;
Type=Service
X-KDE-ServiceTypes=KCModule
X-DocPath=kcontrol/kwindecoration/index.html
X-KDE-Library=kcm_kwindecoration
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=applicationstyle
X-KDE-Weight=40
X-DocPath=kcontrol/kwindecoration/index.html
Name=Window Decorations
Name[ar]=زخارف النوافذ
@ -72,50 +72,7 @@ Name[x-test]=xxWindow Decorationsxx
Name[zh_CN]=
Name[zh_TW]=
Comment=Look and Feel of Window Titles
Comment[bs]=Izgled i osjećaj naslova prozora
Comment[ca]=Aspecte i comportament dels títols de les finestres
Comment[ca@valencia]=Aspecte i comportament dels títols de les finestres
Comment[cs]=Vzhled a dekorace titulků oken
Comment[da]=Udseendet af vinduestitler
Comment[de]=Erscheinungsbild von Fenstertiteln
Comment[el]=Διαμόρφωση της εμφάνισης και αίσθησης του τίτλου των παραθύρων
Comment[en_GB]=Look and Feel of Window Titles
Comment[es]=Aspecto visual de los títulos de las ventanas
Comment[et]=Akna tiitliribade välimus ja tunnetus
Comment[eu]=Leiho-tituluen itxura eta izaera
Comment[fi]=Ikkunoiden kehysten ulkoasu
Comment[fr]=Apparence des titres de fenêtre
Comment[gl]=Aparencia e o comportamento dos títulos das xanelas
Comment[he]=הגדרת המראה והתחושה של מסגרות החלונות
Comment[hu]=Az ablakok címsorának megjelenése
Comment[ia]=Semblantia de titulos de fenestra
Comment[id]=Look and Feel pada Judul Window
Comment[it]=Aspetto dei titoli delle finestre
Comment[ja]=
Comment[ko]=
Comment[lt]=Langų antraščių išvaizda ir elgsena
Comment[nb]=Utseende og virkemåte for vindustitlene
Comment[nds]=Dat Utsehn vun de Finstertiteln instellen
Comment[nl]=Uiterlijk en gedrag van venstertitels
Comment[nn]=Utsjånad og åtferd for vindaugstitlar
Comment[pa]=ਿ -
Comment[pl]=Tytuły okien - wrażenia wzrokowe i dotykowe
Comment[pt]=Aparência e Comportamento dos Títulos das Janelas
Comment[pt_BR]=Aparência do título das janelas
Comment[ru]=Настройка внешнего вида заголовков окон
Comment[sk]=Nastavenie vzhľadu titulkov okien
Comment[sl]=Videz in občutek naslovnih vrstic okna
Comment[sr]=Изглед и осећај за наслове прозора
Comment[sr@ijekavian]=Изглед и осјећај за наслове прозора
Comment[sr@ijekavianlatin]=Izgled i osjećaj za naslove prozora
Comment[sr@latin]=Izgled i osećaj za naslove prozora
Comment[sv]=Namnlisternas utseende och känsla
Comment[tr]=Pencere Başlıklarının Görünüm ve Davranışlarını Yapılandır
Comment[uk]=Вигляд і поведінка смужок заголовків вікон
Comment[x-test]=xxLook and Feel of Window Titlesxx
Comment[zh_CN]=
Comment[zh_TW]=
Comment=Configure window titlebars and borders
X-KDE-Keywords=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration
X-KDE-Keywords[bs]=kwin,prozor,upravitelj,granica,stil,tema,izgled,osjećati,izgled,dugme,držati,ivica,kwm,dekoracija
@ -160,6 +117,3 @@ X-KDE-Keywords[uk]=kwin,window,manager,border,style,theme,look,feel,layout,butto
X-KDE-Keywords[x-test]=xxkwinxx,xxwindowxx,xxmanagerxx,xxborderxx,xxstylexx,xxthemexx,xxlookxx,xxfeelxx,xxlayoutxx,xxbuttonxx,xxhandlexx,xxedgexx,xxkwmxx,xxdecorationxx
X-KDE-Keywords[zh_CN]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration,,,,,,,,,,
X-KDE-Keywords[zh_TW]=kwin,window,manager,border,style,theme,look,feel,layout,button,handle,edge,kwm,decoration
Categories=Qt;KDE;X-KDE-settings-looknfeel;

View file

@ -17,24 +17,22 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.1
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick 2.7
import org.kde.kwin.private.kdecoration 1.0 as KDecoration
import org.kde.plasma.core 2.0 as PlasmaCore;
ListView {
id: view
property string key
property bool dragging: false
property int iconSize: units.iconSizes.small
orientation: ListView.Horizontal
interactive: false
spacing: units.smallSpacing
implicitHeight: units.iconSizes.small
implicitWidth: count * (units.iconSizes.small + units.smallSpacing) - Math.min(1, count) * units.smallSpacing
implicitHeight: iconSize
implicitWidth: count * (iconSize + units.smallSpacing) - Math.min(1, count) * units.smallSpacing
delegate: Item {
width: units.iconSizes.small
height: units.iconSizes.small
width: iconSize
height: iconSize
KDecoration.Button {
id: button
property int itemIndex: index
@ -42,10 +40,13 @@ ListView {
bridge: bridgeItem.bridge
settings: settingsItem
type: model["button"]
width: iconSize
height: iconSize
anchors.fill: Drag.active ? undefined : parent
Drag.keys: [ "decoButtonRemove", view.key ]
Drag.active: dragArea.drag.active
Drag.onActiveChanged: view.dragging = Drag.active
color: palette.windowText
}
MouseArea {
id: dragArea

View file

@ -1,5 +1,6 @@
/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
* Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@ -17,16 +18,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls 2.0 as QQC2
import QtQuick.Layouts 1.1
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.4 as Controls
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kwin.private.kdecoration 1.0 as KDecoration
import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons;
Item {
objectName: "buttonLayout"
Layout.preferredHeight: layout.height
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
property int buttonIconSize: units.iconSizes.medium
property int titleBarSpacing: units.iconSizes.smallMedium
KDecoration.Bridge {
id: bridgeItem
plugin: "org.kde.breeze"
@ -35,58 +38,62 @@ Item {
id: settingsItem
bridge: bridgeItem.bridge
}
Rectangle {
anchors.fill: parent
border.width: Math.ceil(units.gridUnit / 16.0)
border.color: highlightColor
color: baseColor
Layout.fillWidth: true
color: palette.base
radius: units.smallSpacing
height: fakeWindow.height
Layout.margins: units.smallSpacing
ColumnLayout {
id: layout
id: fakeWindow
width: parent.width
height: titlebarRect.height + availableGrid.height + dragHint.height + 5*layout.spacing
Rectangle {
id: titlebarRect
height: buttonPreviewRow.height + units.smallSpacing
border.width: Math.ceil(units.gridUnit / 16.0)
border.color: highlightColor
color: backgroundColor
id: titleBar
Layout.fillWidth: true
Layout.topMargin: -border.width // prevent a double top border
height: buttonPreviewRow.height + 2 * titleBarSpacing
radius: units.smallSpacing
gradient: Gradient {
GradientStop { position: 0.0; color: palette.midlight }
GradientStop { position: 1.0; color: palette.window }
}
RowLayout {
id: buttonPreviewRow
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.margins: units.smallSpacing / 2
height: Math.max(units.iconSizes.small, titlebar.implicitHeight) + units.smallSpacing/2
ButtonGroup {
id: leftButtonsView
height: buttonPreviewRow.height
model: leftButtons
key: "decoButtonLeft"
}
QQC2.Label {
id: titlebar
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
font: titleFont
text: i18n("Titlebar")
anchors {
margins: titleBarSpacing
left: parent.left
right: parent.right
top: parent.top
}
ButtonGroup {
id: leftButtonsView
iconSize: buttonIconSize
model: kcm.leftButtonsModel
key: "decoButtonLeft"
}
Controls.Label {
id: titleBarLabel
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
font.bold: true
text: i18n("Titlebar")
}
ButtonGroup {
id: rightButtonsView
height: buttonPreviewRow.height
model: rightButtons
iconSize: buttonIconSize
model: kcm.rightButtonsModel
key: "decoButtonRight"
}
}
DropArea {
id: titleBarDropArea
anchors {
fill: titlebarRect
leftMargin: -units.iconSizes.small
topMargin: -units.iconSizes.small
rightMargin: +units.iconSizes.small
bottomMargin: +units.iconSizes.small
fill: parent
margins: -titleBarSpacing
}
keys: [ "decoButtonAdd", "decoButtonRight", "decoButtonLeft" ]
onEntered: {
@ -105,9 +112,13 @@ Item {
return;
}
var point = mapToItem(view, drag.x, drag.y);
var index = view.indexAt(point.x, point.y);
if (index == -1 && (view.x + view.width <= drag.x)) {
index = view.count - 1;
var index = 0
for(var childIndex = 0 ; childIndex < (view.count - 1) ; childIndex++) {
var child = view.contentItem.children[childIndex]
if (child.x > point.x) {
break
}
index = childIndex + 1
}
if (drop.keys.indexOf("decoButtonAdd") != -1) {
view.model.add(index, drag.source.type);
@ -138,52 +149,64 @@ Item {
}
}
GridView {
id: availableGrid
id: availableButtonsGrid
Layout.fillWidth: true
model: availableButtons
Layout.minimumHeight: availableButtonsGrid.cellHeight * 2
Layout.margins: units.largeSpacing
model: kcm.availableButtonsModel
interactive: false
height: Math.ceil(cellHeight * 3)
delegate: Item {
id: availableDelegate
Layout.margins: units.gridUnit
width: availableGrid.cellWidth
height: availableGrid.cellHeight
Layout.margins: units.largeSpacing
width: availableButtonsGrid.cellWidth
height: availableButtonsGrid.cellHeight
opacity: (leftButtonsView.dragging || rightButtonsView.dragging) ? 0.25 : 1.0
KDecoration.Button {
id: availableButton
anchors.centerIn: Drag.active ? undefined : parent
bridge: bridgeItem.bridge
settings: settingsItem
type: model["button"]
width: units.iconSizes.small
height: units.iconSizes.small
Drag.keys: [ "decoButtonAdd" ]
Drag.active: dragArea.drag.active
Rectangle {
id: availableButtonFrame
anchors.horizontalCenter: parent.horizontalCenter
color: palette.window
radius: units.smallSpacing
width: buttonIconSize + units.largeSpacing
height: buttonIconSize + units.largeSpacing
KDecoration.Button {
id: availableButton
anchors.centerIn: Drag.active ? undefined : availableButtonFrame
bridge: bridgeItem.bridge
settings: settingsItem
type: model["button"]
width: buttonIconSize
height: buttonIconSize
Drag.keys: [ "decoButtonAdd" ]
Drag.active: dragArea.drag.active
color: palette.windowText
}
MouseArea {
id: dragArea
anchors.fill: availableButton
drag.target: availableButton
cursorShape: Qt.SizeAllCursor
onReleased: {
if (availableButton.Drag.target) {
availableButton.Drag.drop();
} else {
availableButton.Drag.cancel();
}
}
}
}
QQC2.Label {
Controls.Label {
id: iconLabel
text: model["display"]
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
anchors.bottom: parent.bottom
anchors.top: availableButtonFrame.bottom
anchors.topMargin: units.smallSpacing
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
wrapMode: Text.Wrap
height: 2 * dragHint.implicitHeight + lineHeight
}
MouseArea {
id: dragArea
anchors.fill: availableButton
drag.target: availableButton
cursorShape: Qt.SizeAllCursor
onReleased: {
if (availableButton.Drag.target) {
availableButton.Drag.drop();
} else {
availableButton.Drag.cancel();
}
}
height: 2 * implicitHeight + lineHeight
}
}
DropArea {
@ -198,17 +221,17 @@ Item {
ColumnLayout {
anchors.centerIn: parent
opacity: (leftButtonsView.dragging || rightButtonsView.dragging) ? 1.0 : 0.0
QQC2.Label {
Controls.Label {
text: i18n("Drop here to remove button")
font.weight: Font.Bold
Layout.bottomMargin: units.smallSpacing
}
KQuickControlsAddons.QIconItem {
PlasmaCore.IconItem {
id: icon
width: units.iconSizes.smallMedium
height: units.iconSizes.smallMedium
icon: "emblem-remove"
Layout.alignment: Qt.AlignHCenter
source: "emblem-remove"
}
Item {
Layout.fillHeight: true
@ -218,11 +241,12 @@ Item {
}
Text {
id: dragHint
opacity: (leftButtonsView.dragging || rightButtonsView.dragging || availableGrid.dragging) ? 0.0 : 0.50
color: palette.windowText
opacity: (leftButtonsView.dragging || rightButtonsView.dragging || availableButtonsGrid.dragging) ? 0.0 : 1.0
Layout.fillWidth: true
Layout.bottomMargin: units.smallSpacing
Layout.topMargin: titleBarSpacing
Layout.bottomMargin: titleBarSpacing
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
wrapMode: Text.NoWrap
text: i18n("Drag buttons between here and the titlebar")
}

View file

@ -0,0 +1,126 @@
/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
* Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.7
import org.kde.kcm 1.1 as KCM
import org.kde.kirigami 2.2 as Kirigami
import org.kde.kwin.private.kdecoration 1.0 as KDecoration
KCM.GridView {
property int cellSpacing: 2 * units.largeSpacing
property int previewWidth: 18 * units.gridUnit
property int previewHeight: previewWidth / 1.7
function updateDecoration(item, marginTopLeft, marginBottomRight) {
var mainMargin = units.largeSpacing
var shd = item.shadow
item.anchors.leftMargin = mainMargin + marginTopLeft - (shd ? shd.paddingLeft : 0)
item.anchors.rightMargin = mainMargin + marginBottomRight - (shd ? shd.paddingRight : 0)
item.anchors.topMargin = mainMargin + marginTopLeft - (shd ? shd.paddingTop : 0)
item.anchors.bottomMargin = mainMargin + marginBottomRight - (shd ? shd.paddingBottom : 0)
}
view.model: kcm.themesModel
view.currentIndex: kcm.theme
view.onCurrentIndexChanged: kcm.theme = view.currentIndex
view.onContentHeightChanged: view.positionViewAtIndex(view.currentIndex, GridView.Visible)
view.cellWidth: previewWidth + cellSpacing
view.cellHeight: previewHeight + cellSpacing
view.delegate: KCM.GridDelegate {
text: model.display
thumbnailAvailable: true
thumbnail: Rectangle {
anchors.fill: parent
width: previewWidth
height: previewHeight
color: palette.base
clip: true
// Workaround: the thumbnail size can't be otherwise changed
Component.onCompleted: {
parent.parent.width = previewWidth
parent.parent.height = previewHeight
}
KDecoration.Bridge {
id: bridgeItem
plugin: model.plugin
theme: model.theme
}
KDecoration.Settings {
id: settingsItem
bridge: bridgeItem.bridge
}
KDecoration.Decoration {
id: inactivePreview
bridge: bridgeItem.bridge
settings: settingsItem
anchors.fill: parent
onShadowChanged: updateDecoration(inactivePreview, 0, client.decoration.titleBar.height)
Component.onCompleted: {
client.active = false
client.caption = model.display
updateDecoration(inactivePreview, 0, client.decoration.titleBar.height)
}
}
KDecoration.Decoration {
id: activePreview
bridge: bridgeItem.bridge
settings: settingsItem
anchors.fill: parent
onShadowChanged: updateDecoration(activePreview, client.decoration.titleBar.height, 0)
Component.onCompleted: {
client.active = true
client.caption = model.display
updateDecoration(activePreview, client.decoration.titleBar.height, 0)
}
}
MouseArea {
anchors.fill: parent
onClicked: view.currentIndex = index
}
Connections {
target: kcm
onBorderSizeChanged: settingsItem.borderSizesIndex = kcm.borderSize
}
}
actions: [
Kirigami.Action {
iconName: "edit-entry"
tooltip: i18n("Edit %1 Theme", model.display)
enabled: model.configureable
onTriggered: {
view.currentIndex = index
bridgeItem.bridge.configure()
}
}
]
onClicked: view.currentIndex = index
}
Connections {
target: kcm
onThemeChanged: view.currentIndex = kcm.theme
}
}

View file

@ -0,0 +1,127 @@
/*
Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.4 as Controls
import org.kde.kcm 1.1 as KCM
import org.kde.kconfig 1.0 // for KAuthorized
import org.kde.kirigami 2.4 as Kirigami
Kirigami.Page {
KCM.ConfigModule.quickHelp: i18n("This module lets you configure the window decorations.")
KCM.ConfigModule.buttons: KCM.ConfigModule.Help | KCM.ConfigModule.Default | KCM.ConfigModule.Apply
title: kcm.name
SystemPalette {
id: palette
colorGroup: SystemPalette.Active
}
// To match SimpleKCM's borders of Page + headerParent/footerParent (also specified in raw pixels)
leftPadding: Kirigami.Settings.isMobile ? 0 : 8
topPadding: leftPadding
rightPadding: leftPadding
bottomPadding: leftPadding
ColumnLayout {
id: tabLayout
anchors.fill: parent
spacing: 0
Controls.TabBar {
id: tabBar
Layout.fillWidth: true
Controls.TabButton {
text: i18nc("tab label", "Theme")
}
Controls.TabButton {
text: i18nc("tab label", "Titlebar Buttons")
}
}
Controls.Frame {
Layout.fillWidth: true
Layout.fillHeight: true
StackLayout {
anchors.fill: parent
currentIndex: tabBar.currentIndex
ColumnLayout {
Themes {
Layout.fillWidth: true
Layout.fillHeight: true
}
RowLayout {
Controls.Label {
Layout.alignment: Qt.AlignRight
text: i18nc("combobox label", "Window border size:")
}
Controls.ComboBox {
id: borderSizeComboBox
model: kcm.borderSizesModel
currentIndex: kcm.borderSize
onActivated: {
kcm.borderSize = currentIndex
}
}
Item {
Layout.fillWidth: true
}
Controls.Button {
text: i18nc("button text", "Get New Window Decorations...")
icon.name: "get-hot-new-stuff"
onClicked: kcm.getNewStuff(this)
visible: KAuthorized.authorize("ghns")
}
}
}
ColumnLayout {
Buttons {
Layout.fillWidth: true
Layout.fillHeight: true
}
Controls.CheckBox {
id: closeOnDoubleClickOnMenuCheckBox
text: i18nc("checkbox label", "Close windows by double clicking the menu button")
checked: kcm.closeOnDoubleClickOnMenu
onToggled: {
kcm.closeOnDoubleClickOnMenu = checked
infoLabel.visible = checked
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
id: infoLabel
type: Kirigami.MessageType.Information
text: i18nc("popup tip", "Close by double clicking: Keep the window's Menu button pressed until it appears.")
showCloseButton: true
visible: false
}
}
}
}
}
}

View file

@ -0,0 +1,77 @@
[Desktop Entry]
Icon=preferences-system-windows-action
Type=Service
Keywords=
X-KDE-ParentApp=
X-KDE-System-Settings-Parent-Category=applicationstyle
X-KDE-PluginInfo-Author=Valerio Pilo
X-KDE-PluginInfo-Email=vpilo@coldshock.net
X-KDE-PluginInfo-License=GPL-2.0+
X-KDE-PluginInfo-Name=kcm_kwindecoration
X-KDE-PluginInfo-Version=
X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop
X-KDE-ServiceTypes=Plasma/Generic
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml
Name=Window Decorations
Name[ar]=زخارف النوافذ
Name[bg]=Декорации на прозорците
Name[bs]=Dekoracije prozora
Name[ca]=Decoració de les finestres
Name[ca@valencia]=Decoració de les finestres
Name[cs]=Dekorace oken
Name[da]=Vinduesdekorationer
Name[de]=Fensterdekoration
Name[el]=Διακοσμήσεις παραθύρου
Name[en_GB]=Window Decorations
Name[es]=Decoración de ventanas
Name[et]=Akna dekoratsioonid
Name[eu]=Leiho-apaindurak
Name[fi]=Ikkunakehykset
Name[fr]=Décorations de fenêtres
Name[ga]=Maisiúcháin Fhuinneog
Name[gl]=Decoración da xanela
Name[he]=מסגרת חלון
Name[hi]=ि
Name[hr]=Ukrasi prozora
Name[hu]=Ablakdekorációk
Name[ia]=Decorationes de fenestra
Name[id]=Dekorasi Window
Name[is]=Gluggaskreytingar
Name[it]=Decorazioni delle finestre
Name[ja]=
Name[kk]=Терезенің безендірулері
Name[km]=
Name[kn]=ಿ
Name[ko]=
Name[lt]=Lango dekoracijos
Name[lv]=Logu dekorācijas
Name[mr]=
Name[nb]=Vinduspynt
Name[nds]=Finstern opfladusen
Name[nl]=Vensterdecoraties
Name[nn]=Vindaugspynt
Name[pa]=ਿ
Name[pl]=Wygląd okien
Name[pt]=Decorações das Janelas
Name[pt_BR]=Decorações da janela
Name[ro]=Decorații fereastră
Name[ru]=Оформление окон
Name[si]=
Name[sk]=Dekorácie okien
Name[sl]=Okraski oken
Name[sr]=Декорације прозора
Name[sr@ijekavian]=Декорације прозора
Name[sr@ijekavianlatin]=Dekoracije prozora
Name[sr@latin]=Dekoracije prozora
Name[sv]=Fönsterdekorationer
Name[th]=
Name[tr]=Pencere Dekorasyonları
Name[ug]=كۆزنەك بېزەكلىرى
Name[uk]=Обрамлення вікон
Name[wa]=Gåyotaedjes des fniesses
Name[x-test]=xxWindow Decorationsxx
Name[zh_CN]=
Name[zh_TW]=
Comment=Configure window titlebars and borders

View file

@ -1,160 +0,0 @@
/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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 {
objectName: "themeList"
frameVisible: true
GridView {
id: gridView
objectName: "listView"
signal userChangedSelection()
model: decorationsModel
cellWidth: 20 * units.gridUnit
cellHeight: cellWidth / 1.6
highlightFollowsCurrentItem: true
onContentHeightChanged: {
if (gridView.currentIndex == -1 && initialThemeIndex != -1) {
gridView.currentIndex = initialThemeIndex
}
gridView.positionViewAtIndex(gridView.currentIndex, GridView.Visible)
}
Rectangle {
z: -1
anchors.fill: parent
color: baseColor
}
highlight: Rectangle {
color: highlightColor
opacity: 0.6
}
highlightMoveDuration: units.longDuration
boundsBehavior: Flickable.StopAtBounds
property int borderSizesIndex: 3 // 3 == Normal
delegate: Item {
width: gridView.cellWidth
height: gridView.cellHeight
KDecoration.Bridge {
id: bridgeItem
plugin: model["plugin"]
theme: model["theme"]
}
KDecoration.Settings {
id: settingsItem
bridge: bridgeItem.bridge
borderSizesIndex: gridView.borderSizesIndex
}
Component.onCompleted: {
if (gridView.currentIndex == -1 && initialThemeIndex != -1) {
gridView.currentIndex = initialThemeIndex
}
}
MouseArea {
hoverEnabled: false
anchors.fill: parent
onClicked: {
gridView.currentIndex = index
gridView.userChangedSelection()
}
}
ColumnLayout {
id: decorationPreviews
property string themeName: model["display"]
anchors.fill: parent
Item {
KDecoration.Decoration {
id: inactivePreview
bridge: bridgeItem.bridge
settings: settingsItem
anchors.fill: parent
Component.onCompleted: {
client.caption = decorationPreviews.themeName
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.bridge
settings: settingsItem
anchors.fill: parent
Component.onCompleted: {
client.caption = decorationPreviews.themeName
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: {
gridView.currentIndex = index
gridView.userChangedSelection()
}
}
Layout.fillWidth: true
Layout.fillHeight: true
/* Create an invisible rectangle that's the same size as the inner content rect
of the foreground window preview so that we can center the button within it.
We have to center rather than using anchors because the button width varies
with different localizations */
Item {
anchors {
left: parent.left
leftMargin: 10
right: parent.right
rightMargin: 40
top: parent.top
topMargin: 68
bottom: parent.bottom
bottomMargin: 10
}
Button {
id: configureButton
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
enabled: model["configureable"]
iconName: "configure"
text: i18n("Configure %1...", decorationPreviews.themeName)
onClicked: {
gridView.currentIndex = index
gridView.userChangedSelection()
bridgeItem.bridge.configure()
}
}
}
}
}
}
}
Layout.preferredHeight: 20 * units.gridUnit
}

View file

@ -1,37 +0,0 @@
/*
* Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.1
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
Rectangle {
color: backgroundColor
ColumnLayout {
anchors.fill: parent
Previews {
Layout.fillWidth: true
Layout.fillHeight: true
}
Buttons {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
}
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "utils.h"
#include <KConfigGroup>
#include <KLocalizedString>
namespace
{
const QMap<QString, KDecoration2::BorderSize> s_borderSizes {
{ QStringLiteral("None"), KDecoration2::BorderSize::None },
{ QStringLiteral("NoSides"), KDecoration2::BorderSize::NoSides },
{ QStringLiteral("Tiny"), KDecoration2::BorderSize::Tiny },
{ QStringLiteral("Normal"), KDecoration2::BorderSize::Normal },
{ QStringLiteral("Large"), KDecoration2::BorderSize::Large },
{ QStringLiteral("VeryLarge"), KDecoration2::BorderSize::VeryLarge },
{ QStringLiteral("Huge"), KDecoration2::BorderSize::Huge },
{ QStringLiteral("VeryHuge"), KDecoration2::BorderSize::VeryHuge },
{ QStringLiteral("Oversized"), KDecoration2::BorderSize::Oversized }
};
const QMap<KDecoration2::BorderSize, QString> s_borderSizeNames {
{ KDecoration2::BorderSize::None, i18n("No Borders") },
{ KDecoration2::BorderSize::NoSides, i18n("No Side Borders") },
{ KDecoration2::BorderSize::Tiny, i18n("Tiny") },
{ KDecoration2::BorderSize::Normal, i18n("Normal") },
{ KDecoration2::BorderSize::Large, i18n("Large") },
{ KDecoration2::BorderSize::VeryLarge, i18n("Very Large") },
{ KDecoration2::BorderSize::Huge, i18n("Huge") },
{ KDecoration2::BorderSize::VeryHuge, i18n("Very Huge") },
{ KDecoration2::BorderSize::Oversized, i18n("Oversized") }
};
const QHash<KDecoration2::DecorationButtonType, QChar> s_buttonNames {
{KDecoration2::DecorationButtonType::Menu, QChar('M') },
{KDecoration2::DecorationButtonType::ApplicationMenu, QChar('N') },
{KDecoration2::DecorationButtonType::OnAllDesktops, QChar('S') },
{KDecoration2::DecorationButtonType::ContextHelp, QChar('H') },
{KDecoration2::DecorationButtonType::Minimize, QChar('I') },
{KDecoration2::DecorationButtonType::Maximize, QChar('A') },
{KDecoration2::DecorationButtonType::Close, QChar('X') },
{KDecoration2::DecorationButtonType::KeepAbove, QChar('F') },
{KDecoration2::DecorationButtonType::KeepBelow, QChar('B') },
{KDecoration2::DecorationButtonType::Shade, QChar('L') }
};
}
namespace Utils
{
QString buttonsToString(const DecorationButtonsList &buttons)
{
auto buttonToString = [](KDecoration2::DecorationButtonType button) -> QChar {
const auto it = s_buttonNames.constFind(button);
if (it != s_buttonNames.constEnd()) {
return it.value();
}
return QChar();
};
QString ret;
for (auto button : buttons) {
ret.append(buttonToString(button));
}
return ret;
}
DecorationButtonsList readDecorationButtons(const KConfigGroup &config, const QString &key, const DecorationButtonsList &defaultValue)
{
auto buttonsFromString = [](const QString &buttons) -> DecorationButtonsList {
DecorationButtonsList ret;
for (auto it = buttons.begin(); it != buttons.end(); ++it) {
for (auto it2 = s_buttonNames.constBegin(); it2 != s_buttonNames.constEnd(); ++it2) {
if (it2.value() == (*it)) {
ret << it2.key();
}
}
}
return ret;
};
return buttonsFromString(config.readEntry(key, buttonsToString(defaultValue)));
}
KDecoration2::BorderSize stringToBorderSize(const QString &name)
{
auto it = s_borderSizes.constFind(name);
if (it == s_borderSizes.constEnd()) {
// non sense values are interpreted just like normal
return KDecoration2::BorderSize::Normal;
}
return it.value();
}
QString borderSizeToString(KDecoration2::BorderSize size)
{
return s_borderSizes.key(size);
}
const QMap<KDecoration2::BorderSize, QString> &getBorderSizeNames()
{
return s_borderSizeNames;
}
} // namespace Utils

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2019 Valerio Pilo <vpilo@coldshock.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <KDecoration2/DecorationButton>
#include <KSharedConfig>
#include <QVector>
using DecorationButtonsList = QVector<KDecoration2::DecorationButtonType>;
namespace Utils
{
QString buttonsToString(const DecorationButtonsList &buttons);
DecorationButtonsList readDecorationButtons(const KConfigGroup &config, const QString &key, const DecorationButtonsList &defaultValue);
KDecoration2::BorderSize stringToBorderSize(const QString &name);
QString borderSizeToString(KDecoration2::BorderSize size);
const QMap<KDecoration2::BorderSize, QString> &getBorderSizeNames();
}

View file

@ -0,0 +1,67 @@
[KNewStuff3]
Name=Window Decorations
Name[ar]=زخارف النوافذ
Name[bg]=Декорации на прозорците
Name[bs]=Dekoracije prozora
Name[ca]=Decoració de les finestres
Name[ca@valencia]=Decoració de les finestres
Name[cs]=Dekorace oken
Name[da]=Vinduesdekorationer
Name[de]=Fensterdekoration
Name[el]=Διακοσμήσεις παραθύρου
Name[en_GB]=Window Decorations
Name[es]=Decoración de ventanas
Name[et]=Akna dekoratsioonid
Name[eu]=Leiho-apaindurak
Name[fi]=Ikkunakehykset
Name[fr]=Décorations de fenêtres
Name[ga]=Maisiúcháin Fhuinneog
Name[gl]=Decoración da xanela
Name[he]=מסגרת חלון
Name[hi]=विंडो सजावट
Name[hr]=Ukrasi prozora
Name[hu]=Ablakdekorációk
Name[ia]=Decorationes de fenestra
Name[id]=Dekorasi Window
Name[is]=Gluggaskreytingar
Name[it]=Decorazioni delle finestre
Name[ja]=ウィンドウの飾り
Name[kk]=Терезенің безендірулері
Name[km]=ការ​តុបតែង​បង្អួច
Name[kn]=ವಿಂಡೋ ಅಲಂಕಾರಗಳು
Name[ko]=창 장식
Name[lt]=Lango dekoracijos
Name[lv]=Logu dekorācijas
Name[mr]=चौकट सजावट
Name[nb]=Vinduspynt
Name[nds]=Finstern opfladusen
Name[nl]=Vensterdecoraties
Name[nn]=Vindaugspynt
Name[pa]=ਵਿੰਡੋ ਸਜਾਵਟ
Name[pl]=Wygląd okien
Name[pt]=Decorações das Janelas
Name[pt_BR]=Decorações da janela
Name[ro]=Decorații fereastră
Name[ru]=Оформление окон
Name[si]=කවුළු සැරසිලි
Name[sk]=Dekorácie okien
Name[sl]=Okraski oken
Name[sr]=Декорације прозора
Name[sr@ijekavian]=Декорације прозора
Name[sr@ijekavianlatin]=Dekoracije prozora
Name[sr@latin]=Dekoracije prozora
Name[sv]=Fönsterdekorationer
Name[th]=ส่วนตกแต่งหน้าต่าง
Name[tr]=Pencere Dekorasyonları
Name[ug]=كۆزنەك بېزەكلىرى
Name[uk]=Обрамлення вікон
Name[wa]=Gåyotaedjes des fniesses
Name[x-test]=xxWindow Decorationsxx
Name[zh_CN]=窗口装饰
Name[zh_TW]=視窗裝飾
ProvidersUrl=https://autoconfig.kde.org/ocs/providers.xml
Categories=Various Plasma Styles,Aurorae Themes
TargetDir=themes
StandardResource=tmp
Uncompress=archive