Commit 669b2587 authored by Dominic Hayes's avatar Dominic Hayes Committed by Nate Graham
Browse files

kcms/lookandfeel: Allow themes to set titlebar button layout and BorderlessMaximizedWindows

This merge request adds support for additional Global Theme layout settings to
be set in KCM, if the selected theme supports them:

- (controlled by Desktop Layout switch) Borderless Maximized Windows
- (own switch) Titlebar Buttons Layout

Ones with their own switch also have new checkboxes for themselves in More Options in
the KCM, and the values for all the added options, in the new `layouts/defaults` file
of Global Themes, are made to match their KDE Plasma config files counterparts in names
and values.

Example of a `layouts/defaults` file in a Global Theme:

```
[kwinrc][org.kde.kdecoration2]
ButtonsOnLeft=M
ButtonsOnRight=IAX

[kwinrc][Windows]
BorderlessMaximizedWindows=false
```

NOTE: If a Global Theme doesn't supply `BorderlessMaximizedWindows`, it'll be turned off automatically when applying a Desktop Layout to prevent issues.
parent 7dc57878
Pipeline #214924 passed with stage
in 7 minutes and 34 seconds
......@@ -79,7 +79,9 @@ KCMLookandFeel::KCMLookandFeel(QObject *parent, const KPluginMetaData &data, con
roles[HasRunCommandRole] = "hasRunCommand";
roles[HasLogoutRole] = "hasLogout";
roles[HasGlobalThemeRole] = "hasGlobalTheme"; //For the Global Theme global checkbox
roles[HasDesktopLayoutRole] = "hasDesktopLayout"; //For the Desktop Layout checkbox in More Options
roles[HasLayoutSettingsRole] = "hasLayoutSettings"; //For the Desktop Layout checkbox in More Options
roles[HasDesktopLayoutRole] = "hasDesktopLayout";
roles[HasTitlebarLayoutRole] = "hasTitlebarLayout";
roles[HasColorsRole] = "hasColors";
roles[HasWidgetStyleRole] = "hasWidgetStyle";
roles[HasIconsRole] = "hasIcons";
......@@ -244,6 +246,7 @@ void KCMLookandFeel::addKPackageToModel(const KPackage::Package &pkg)
// What the package provides
row->setData(!pkg.filePath("defaults").isEmpty(), HasGlobalThemeRole);
row->setData(!pkg.filePath("layoutdefaults").isEmpty(), HasLayoutSettingsRole);
row->setData(!pkg.filePath("layouts").isEmpty(), HasDesktopLayoutRole);
row->setData(!pkg.filePath("splashmainscript").isEmpty(), HasSplashRole);
row->setData(!pkg.filePath("lockscreenmainscript").isEmpty(), HasLockScreenRole);
......@@ -307,6 +310,15 @@ void KCMLookandFeel::addKPackageToModel(const KPackage::Package &pkg)
row->setData(false, HasWindowDecorationRole);
row->setData(false, HasFontsRole);
}
if (!pkg.filePath("layoutdefaults").isEmpty()) {
KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("layoutdefaults"));
KConfigGroup cg(conf, "kwinrc");
cg = KConfigGroup(&cg, "org.kde.kdecoration2");
row->setData((!cg.readEntry("ButtonsOnLeft", QString()).isEmpty() || !cg.readEntry("ButtonsOnRight", QString()).isEmpty()), HasTitlebarLayoutRole);
} else {
//This fallback is needed since the sheet 'breaks' without it
row->setData(false, HasTitlebarLayoutRole);
}
m_model->appendRow(row);
}
......@@ -333,10 +345,25 @@ void KCMLookandFeel::save()
return;
}
const int index = pluginIndex(lookAndFeelSettings()->lookAndFeelPackage());
auto applyFlags = m_lnf->appearanceToApply();
// Disable unavailable flags to prevent unintentional applies
// TODO: Also do for LayoutSettings once layout options get added besides just Desktop Layout
const int index = pluginIndex(lookAndFeelSettings()->lookAndFeelPackage());
auto layoutApplyFlags = m_lnf->layoutToApply();
// Layout Options:
constexpr std::array layoutPairs {
std::make_pair(LookAndFeelManager::DesktopLayout, HasDesktopLayoutRole),
std::make_pair(LookAndFeelManager::TitlebarLayout, HasTitlebarLayoutRole),
std::make_pair(LookAndFeelManager::WindowPlacement, HasDesktopLayoutRole),
std::make_pair(LookAndFeelManager::ShellPackage, HasDesktopLayoutRole),
std::make_pair(LookAndFeelManager::DesktopSwitcher, HasDesktopLayoutRole),
};
for (const auto &pair : layoutPairs) {
if ( m_lnf->layoutToApply().testFlag(pair.first) ) {
layoutApplyFlags.setFlag(pair.first, m_model->data(m_model->index(index, 0), pair.second).toBool());
}
}
m_lnf->setLayoutToApply(layoutApplyFlags);
// Appearance Options:
auto appearanceApplyFlags = m_lnf->appearanceToApply();
constexpr std::array appearancePairs {
std::make_pair(LookAndFeelManager::Colors, HasColorsRole),
std::make_pair(LookAndFeelManager::WindowDecoration, HasWindowDecorationRole),
......@@ -350,7 +377,7 @@ void KCMLookandFeel::save()
};
for (const auto &pair : appearancePairs) {
if ( m_lnf->appearanceToApply().testFlag(pair.first) ) {
applyFlags.setFlag(pair.first, m_model->data(m_model->index(index, 0), pair.second).toBool());
appearanceApplyFlags.setFlag(pair.first, m_model->data(m_model->index(index, 0), pair.second).toBool());
}
}
if ( m_lnf->appearanceToApply().testFlag(LookAndFeelManager::WidgetStyle) ) {
......@@ -359,11 +386,11 @@ void KCMLookandFeel::save()
KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("defaults"));
KConfigGroup cg(conf, "kdeglobals");
std::unique_ptr<QStyle> newStyle(QStyleFactory::create(cg.readEntry("widgetStyle", QString())));
applyFlags.setFlag(LookAndFeelManager::WidgetStyle,
appearanceApplyFlags.setFlag(LookAndFeelManager::WidgetStyle,
(newStyle != nullptr && m_model->data(m_model->index(index, 0), HasWidgetStyleRole).toBool())); // Widget Style isn't in
// the loop above since it has all of this extra checking too for it
}
m_lnf->setAppearanceToApply(applyFlags);
m_lnf->setAppearanceToApply(appearanceApplyFlags);
ManagedConfigModule::save();
m_lnf->save(package, m_package);
......@@ -417,15 +444,7 @@ void KCMLookandFeel::resetLayoutToApply()
return;
}
constexpr std::array layoutPairs{
std::make_pair(LookAndFeelManager::DesktopLayout, HasDesktopLayoutRole),
std::make_pair(LookAndFeelManager::WindowPlacement, HasDesktopLayoutRole),
std::make_pair(LookAndFeelManager::ShellPackage, HasDesktopLayoutRole),
std::make_pair(LookAndFeelManager::DesktopSwitcher, HasDesktopLayoutRole),
}; // NOTE: Items that have their own Has...Role instead will be added soon
for (const auto &pair : layoutPairs) {
applyFlags.setFlag(pair.first, m_model->data(m_model->index(index, 0), pair.second).toBool());
}
applyFlags.setFlag(LookAndFeelManager::LayoutSettings, m_model->data(m_model->index(index, 0), HasLayoutSettingsRole).toBool());
m_lnf->setLayoutToApply(applyFlags); //emits over in lookandfeelmananager
}
......
......@@ -46,7 +46,9 @@ public:
HasRunCommandRole,
HasLogoutRole,
HasGlobalThemeRole,
HasLayoutSettingsRole,
HasDesktopLayoutRole,
HasTitlebarLayoutRole,
HasColorsRole,
HasWidgetStyleRole,
HasIconsRole,
......
......@@ -152,6 +152,26 @@ void LookAndFeelManager::setWindowDecoration(const QString &library, const QStri
writeNewDefaults(group, defaultGroup, QStringLiteral("NoPlugin"), noPlugin ? "true" : "false", KConfig::Notify);
}
void LookAndFeelManager::setTitlebarLayout(const QString &leftbtns, const QString &rightbtns)
{
if (leftbtns.isEmpty() && rightbtns.isEmpty()) {
return;
}
writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("org.kde.kdecoration2"), QStringLiteral("ButtonsOnLeft"), leftbtns, KConfig::Notify);
writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("org.kde.kdecoration2"), QStringLiteral("ButtonsOnRight"), rightbtns, KConfig::Notify);
}
void LookAndFeelManager::setBorderlessMaximized(const QString &value)
{
if (value.isEmpty()) { //Turn borderless off for unsupported LNFs to prevent issues
writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("Windows"), QStringLiteral("BorderlessMaximizedWindows"), "false", KConfig::Notify);
return;
}
writeNewDefaults(QStringLiteral("kwinrc"), QStringLiteral("Windows"), QStringLiteral("BorderlessMaximizedWindows"), value, KConfig::Notify);
}
void LookAndFeelManager::setWidgetStyle(const QString &style)
{
if (style.isEmpty()) {
......@@ -377,6 +397,19 @@ void LookAndFeelManager::save(const KPackage::Package &package, const KPackage::
}
}
if (!package.filePath("layoutdefaults").isEmpty()) {
KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("layoutdefaults"));
KConfigGroup group(conf, "kwinrc");
if (m_layoutToApply.testFlag(LookAndFeelManager::TitlebarLayout)) {
group = KConfigGroup(&group, "org.kde.kdecoration2");
setTitlebarLayout(group.readEntry("ButtonsOnLeft", QString()), group.readEntry("ButtonsOnRight", QString()));
}
if (m_layoutToApply.testFlag(LookAndFeelManager::DesktopLayout) && m_mode == Mode::Apply) {
group = KConfigGroup(conf, "kwinrc");
group = KConfigGroup(&group, "Windows");
setBorderlessMaximized(group.readEntry("BorderlessMaximizedWindows", QString()));
}
}
if (!package.filePath("defaults").isEmpty()) {
KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("defaults"));
KConfigGroup group(conf, "kdeglobals");
......@@ -507,12 +540,6 @@ void LookAndFeelManager::save(const KPackage::Package &package, const KPackage::
return;
}
// Reload KWin if something changed, but only once.
if (m_appearanceToApply.testFlag(LookAndFeelManager::WindowSwitcher) || m_layoutToApply.testFlag(LookAndFeelManager::DesktopSwitcher) || m_appearanceToApply.testFlag(LookAndFeelManager::WindowDecoration) || m_layoutToApply.testFlag(LookAndFeelManager::WindowPlacement)) {
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
}
if (m_plasmashellChanged) {
QDBusMessage message =
QDBusMessage::createSignal(QStringLiteral("/PlasmaShell"), QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("refreshCurrentShell"));
......@@ -559,6 +586,12 @@ void LookAndFeelManager::save(const KPackage::Package &package, const KPackage::
Q_EMIT refreshServices(toStop, toStart);
}
}
// Reload KWin if something changed, but only once.
if (m_appearanceToApply.testFlag(LookAndFeelManager::WindowSwitcher) || m_layoutToApply.testFlag(LookAndFeelManager::DesktopSwitcher) ||
m_appearanceToApply.testFlag(LookAndFeelManager::WindowDecoration) || m_layoutToApply.testFlag(LookAndFeelManager::WindowPlacement) || m_layoutToApply.testFlag(LookAndFeelManager::WindowPlacement) || m_layoutToApply.testFlag(LookAndFeelManager::TitlebarLayout)) {
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
}
}
void LookAndFeelManager::setCursorTheme(const QString themeName)
......
......@@ -49,11 +49,11 @@ public:
enum LayoutToApplyFlags {
DesktopLayout = 1 << 0,
WindowPlacement = 1 << 1, // FIXME: Do we still want these three?
ShellPackage = 1 << 2,
DesktopSwitcher = 1 << 3,
LayoutSettings = (1 << 4) - 1, // NOTE: This will be used once TitlebarLayout, etc. is added, it's just here for now
// to simplify lnftool and lookandfeelmanager
TitlebarLayout = 1 << 1,
WindowPlacement = 1 << 2, //FIXME: Do we still want these three?
ShellPackage = 1 << 3,
DesktopSwitcher = 1 << 4,
LayoutSettings = (1<< 5) -1,
};
Q_DECLARE_FLAGS(LayoutToApply, LayoutToApplyFlags)
Q_FLAG(LayoutToApply)
......@@ -86,6 +86,8 @@ public:
void setWindowSwitcher(const QString &theme);
void setDesktopSwitcher(const QString &theme);
void setWindowDecoration(const QString &library, const QString &theme, bool noPlugin);
void setTitlebarLayout(const QString &leftbtns, const QString &rightbtns);
void setBorderlessMaximized(const QString &value);
void setWindowPlacement(const QString &value);
void setShellPackage(const QString &name);
......
......@@ -20,33 +20,50 @@ ColumnLayout {
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
QtControls.CheckBox { //FIXME: Once we have decided the fate of these checkboxes
//(make them GUI selectable, or absorb them into other values like DesktopLayout)
//we can then convert this into a checkbox of a repeater of its own:
// DesktopSwitcher, WindowPlacement, ShellPackage
id: resetCheckbox
ColumnLayout {
Kirigami.FormData.label: i18n("Layout settings:")
text: i18n("Desktop layout")
checked: kcm.layoutToApply & Private.LookandFeelManager.DesktopLayout
onCheckedChanged: { //NOTE: onCheckedChanged is used here to make sure the other checkboxes
//it sets as well do not get inconsistently set - see fixme above
if (checked) {
kcm.layoutToApply |= Private.LookandFeelManager.DesktopLayout
kcm.layoutToApply |= Private.LookandFeelManager.DesktopSwitcher
kcm.layoutToApply |= Private.LookandFeelManager.WindowPlacement
kcm.layoutToApply |= Private.LookandFeelManager.ShellPackage
} else {
kcm.layoutToApply &= ~ Private.LookandFeelManager.DesktopLayout
kcm.layoutToApply &= ~ Private.LookandFeelManager.DesktopSwitcher
kcm.layoutToApply &= ~ Private.LookandFeelManager.WindowPlacement
kcm.layoutToApply &= ~ Private.LookandFeelManager.ShellPackage
QtControls.CheckBox { //FIXME: Once we have decided the fate of these checkboxes
//(make them GUI selectable, or absorb them into other values like DesktopLayout)
//we can then move this checkbox into the below repeater:
// DesktopSwitcher, WindowPlacement, ShellPackage
id: resetCheckbox
Kirigami.FormData.label: i18n("Layout settings:")
text: i18n("Desktop layout")
checked: kcm.layoutToApply & Private.LookandFeelManager.DesktopLayout
onCheckedChanged: { //NOTE: onCheckedChanged is used here to make sure the other checkboxes
//it sets as well do not get inconsistently set - see fixme above
if (checked) {
kcm.layoutToApply |= Private.LookandFeelManager.DesktopLayout
kcm.layoutToApply |= Private.LookandFeelManager.DesktopSwitcher
kcm.layoutToApply |= Private.LookandFeelManager.WindowPlacement
kcm.layoutToApply |= Private.LookandFeelManager.ShellPackage
} else {
kcm.layoutToApply &= ~ Private.LookandFeelManager.DesktopLayout
kcm.layoutToApply &= ~ Private.LookandFeelManager.DesktopSwitcher
kcm.layoutToApply &= ~ Private.LookandFeelManager.WindowPlacement
kcm.layoutToApply &= ~ Private.LookandFeelManager.ShellPackage
}
}
visible: view.model.data(view.model.index(view.currentIndex, 0), Private.KCMLookandFeel.HasDesktopLayoutRole)
}
Repeater {
model: [
{ text: i18n("Titlebar Buttons layout"), role: Private.KCMLookandFeel.HasTitlebarLayoutRole, flag: Private.LookandFeelManager.TitlebarLayout },
]
delegate: QtControls.CheckBox {
required property var modelData
text: modelData.text
checked: kcm.layoutToApply & modelData.flag
visible: view.model.data(view.model.index(view.currentIndex, 0), modelData.role)
onToggled: kcm.layoutToApply ^= modelData.flag
}
}
visible: view.model.data(view.model.index(view.currentIndex, 0), Private.KCMLookandFeel.HasDesktopLayoutRole)
}
QtControls.Label {
Layout.fillWidth: true
text: i18n("Applying a Desktop layout replaces your current configuration of desktops, panels, and widgets")
text: i18n("Applying a Desktop layout replaces your current configuration of desktops, panels, docks, and widgets")
elide: Text.ElideRight
wrapMode: Text.WordWrap
font: Kirigami.Theme.smallFont
......
......@@ -44,35 +44,23 @@ ColumnLayout {
}
QtControls.CheckBox {
id: resetCheckbox
text: i18n("Desktop layout")
checked: kcm.layoutToApply & Private.LookandFeelManager.DesktopLayout
onCheckedChanged: { //NOTE: onCheckedChanged is used here to make sure the other checkboxes
//it sets as well do not get inconsistent values - see FIXME right below
if (checked) {
kcm.layoutToApply |= Private.LookandFeelManager.DesktopLayout
kcm.layoutToApply |= Private.LookandFeelManager.DesktopSwitcher
kcm.layoutToApply |= Private.LookandFeelManager.WindowPlacement
kcm.layoutToApply |= Private.LookandFeelManager.ShellPackage
} else {
kcm.layoutToApply &= ~ Private.LookandFeelManager.DesktopLayout
kcm.layoutToApply &= ~ Private.LookandFeelManager.DesktopSwitcher
kcm.layoutToApply &= ~ Private.LookandFeelManager.WindowPlacement
kcm.layoutToApply &= ~ Private.LookandFeelManager.ShellPackage
}
}
enabled: view.model.data(view.model.index(view.currentIndex, 0), Private.KCMLookandFeel.HasDesktopLayoutRole)
text: i18n("Desktop and window layout")
checked: kcm.layoutToApply & Private.LookandFeelManager.LayoutSettings
onToggled: kcm.layoutToApply ^= Private.LookandFeelManager.LayoutSettings
enabled: view.model.data(view.model.index(view.currentIndex, 0), Private.KCMLookandFeel.HasLayoutSettingsRole) ||
view.model.data(view.model.index(view.currentIndex, 0), Private.KCMLookandFeel.HasDesktopLayoutRole)
visible: enabled && !resetCheckboxLblSub.visible
}
QtControls.Label {
id: resetCheckboxLblSub
visible: resetCheckbox.enabled && !appearanceSettingsCheckbox.enabled
Layout.fillWidth: true
text: i18nc("List item", "• Desktop layout")
text: i18nc("List item", "• Desktop and window layout")
wrapMode: Text.WordWrap
}
QtControls.Label {
Layout.fillWidth: true
text: i18n("Applying a Desktop layout replaces your current configuration of desktops, panels, and widgets")
text: i18n("Applying a Desktop layout replaces your current configuration of desktops, panels, docks, and widgets")
elide: Text.ElideRight
wrapMode: Text.WordWrap
font: Kirigami.Theme.smallFont
......
......@@ -123,6 +123,7 @@ KCM.GridViewKCM {
view.forceActiveFocus() //Prevent further button presses via keyboard
}
enabled: kcm.appearanceToApply & Private.LookandFeelManager.AppearanceSettings ||
kcm.layoutToApply & Private.LookandFeelManager.LayoutSettings ||
kcm.layoutToApply & Private.LookandFeelManager.DesktopLayout
}
QtControls.Button {
......
......@@ -19,6 +19,7 @@ void LookAndFeelPackage::initPackage(KPackage::Package *package)
// Defaults
package->addFileDefinition("defaults", QStringLiteral("defaults"), i18n("Default settings for theme, etc."));
package->addFileDefinition("layoutdefaults", QStringLiteral("layouts/defaults"), i18n("Default layout-related settings for titlebars, etc."));
package->addDirectoryDefinition("plasmoidsetupscripts", QStringLiteral("plasmoidsetupscripts"), i18n("Script to tweak default configs of plasmoids"));
// Colors
package->addFileDefinition("colors", QStringLiteral("colors"), i18n("Color scheme to use for applications."));
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment