Commit 4fcc01d5 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

[Style KCM] Port to new design

This ports the widget style KCM to use a grid view KCM with live preview thumbnails and inline configure button.

* Icon and tool bar settings have been moved into a popup, similar to Icons KCM, to leave more room for the rather large thumbnails
* Overall cleanup of unused/defunct features and modernized codebase
* Support for non-Linux is dropped, i.e. it assumes "Breeze" as the default, no "native" (e.g. Mac or Windows style) as we want
  our apps to follow whatever other platform they run in, rather than offering this KCM there

Differential Revision: https://phabricator.kde.org/D24361
parent 6c611826
......@@ -3,12 +3,12 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcmstyle\")
########### next target ###############
set(kcm_style_PART_SRCS ../krdb/krdb.cpp styleconfdialog.cpp kcmstyle.cpp)
set(kcm_style_PART_SRCS ../krdb/krdb.cpp styleconfdialog.cpp kcmstyle.cpp stylesmodel.cpp previewitem.cpp)
set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml)
qt5_add_dbus_interface(kcm_style_PART_SRCS ${klauncher_xml} klauncher_iface)
ki18n_wrap_ui(kcm_style_PART_SRCS stylepreview.ui styleconfig.ui)
ki18n_wrap_ui(kcm_style_PART_SRCS stylepreview.ui)
add_library(kcm_style MODULE ${kcm_style_PART_SRCS})
......@@ -23,13 +23,13 @@ target_link_libraries(kcm_style
${X11_LIBRARIES}
KF5::KDELibs4Support
KF5::GuiAddons
KF5::QuickAddons
KF5::WindowSystem
KF5::NewStuff
)
install(TARGETS kcm_style DESTINATION ${KDE_INSTALL_PLUGINDIR})
kcoreaddons_desktop_to_json(kcm_style "kcm_style.desktop")
install(FILES kcm_style.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
install(TARGETS kcm_style DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
########### install files ###############
install( FILES style.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
kpackage_install_package(package kcm_style kcms)
#! /usr/bin/env bash
$EXTRACTRC *.ui >> rc.cpp
$XGETTEXT *.cpp -o $podir/kcmstyle.pot
$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/kcmstyle.pot
rm -f rc.cpp
This diff is collapsed.
......@@ -3,6 +3,7 @@
* Copyright (C) 2002 Karol Szwed <gallium@kde.org>
* Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
* Copyright (C) 2007 Urs Wolfer <uwolfer @ kde.org>
* Copyright (C) 2019 Kai Uwe Broulik <kde@broulik.de>
*
* Portions Copyright (C) TrollTech AS.
*
......@@ -28,75 +29,81 @@
#ifndef KCMSTYLE_H
#define KCMSTYLE_H
#include <QHash>
#include <QLayout>
#include <QMap>
#include <QPointer>
#include <kcmodule.h>
#include <KQuickAddons/ConfigModule>
class KConfig;
class StylePreview;
class StyleConfig;
class QQuickItem;
struct StyleEntry {
QString name;
QString desc;
QString configPage;
bool hidden;
};
class StylesModel;
class StyleConfigDialog;
class KCMStyle : public KCModule
class KCMStyle : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(StylesModel *model READ model CONSTANT)
Q_PROPERTY(bool iconsOnButtons READ iconsOnButtons WRITE setIconsOnButtons NOTIFY iconsOnButtonsChanged)
Q_PROPERTY(bool iconsInMenus READ iconsInMenus WRITE setIconsInMenus NOTIFY iconsInMenusChanged)
Q_PROPERTY(ToolBarStyle mainToolBarStyle READ mainToolBarStyle WRITE setMainToolBarStyle NOTIFY mainToolBarStyleChanged)
Q_PROPERTY(ToolBarStyle otherToolBarStyle READ otherToolBarStyle WRITE setOtherToolBarStyle NOTIFY otherToolBarStyleChanged)
public:
KCMStyle( QWidget* parent, const QVariantList& );
KCMStyle(QObject *parent, const QVariantList &args);
~KCMStyle() override;
enum ToolBarStyle {
NoText,
TextOnly,
TextBesideIcon,
TextUnderIcon
};
Q_ENUM(ToolBarStyle)
StylesModel *model() const;
bool iconsOnButtons() const;
void setIconsOnButtons(bool enable);
Q_SIGNAL void iconsOnButtonsChanged();
bool iconsInMenus() const;
void setIconsInMenus(bool enable);
Q_SIGNAL void iconsInMenusChanged();
ToolBarStyle mainToolBarStyle() const;
void setMainToolBarStyle(ToolBarStyle style);
Q_SIGNAL void mainToolBarStyleChanged();
ToolBarStyle otherToolBarStyle() const;
void setOtherToolBarStyle(ToolBarStyle style);
Q_SIGNAL void otherToolBarStyleChanged();
Q_INVOKABLE void configure(const QString &styleName, QQuickItem *ctx = nullptr);
void load() override;
void save() override;
void defaults() override;
static QString defaultStyle();
protected:
bool findStyle( const QString& str, int& combobox_item );
void switchStyle(const QString& styleName, bool force = false);
void setStyleRecursive(QWidget* w, QStyle* s);
Q_SIGNALS:
void showErrorMessage(const QString &message);
void loadStyle( KConfig& config );
void loadEffects( KConfig& config );
void addWhatsThis();
void styleReconfigured(const QString &styleName);
void changeEvent( QEvent *event ) override;
protected Q_SLOTS:
void styleSpecificConfig();
void updateConfigButton();
private:
StylesModel *m_model;
void setStyleDirty();
void setEffectsDirty();
bool m_selectedStyleDirty = false;
bool m_effectsDirty = false;
void styleChanged();
bool m_iconsOnButtons = false;
bool m_iconsInMenus = false;
ToolBarStyle m_mainToolBarStyle = NoText;
ToolBarStyle m_otherToolBarStyle = NoText;
private:
QString currentStyle();
static QString toolbarButtonText(int index);
static int toolbarButtonIndex(const QString &text);
static QString menuBarStyleText(int index);
static int menuBarStyleIndex(const QString &text);
bool m_bStyleDirty, m_bEffectsDirty;
QHash <QString,StyleEntry*> styleEntries;
QMap <QString,QString> nameToStyleKey;
QVBoxLayout* mainLayout;
// Widgets
StylePreview* stylePreview;
StyleConfig* styleConfig;
QStyle* appliedStyle;
QPalette palette;
QPointer<StyleConfigDialog> m_styleConfigDialog;
};
#endif // __KCMSTYLE_H
......
/*
* Copyright 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* 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 QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QtControls
import org.kde.kirigami 2.4 as Kirigami
import org.kde.private.kcms.style 1.0 as Private
QtControls.Popup {
id: effectSettingsPopup
modal: true
onOpened: {
// can we do this automatically with "focus: true" somewhere?
iconsOnButtonsCheckBox.forceActiveFocus();
}
Kirigami.FormLayout {
// Popup's autosizing causes FormLayout to collapse when opening it a second time :(
wideMode: true
QtControls.CheckBox {
id: iconsOnButtonsCheckBox
Kirigami.FormData.label: i18n("Show icons:")
text: i18n("On buttons")
checked: kcm.iconsOnButtons
onClicked: kcm.iconsOnButtons = checked
}
QtControls.CheckBox {
text: i18n("In menus")
checked: kcm.iconsInMenus
onClicked: kcm.iconsInMenus = checked
}
QtControls.ComboBox {
id: mainToolBarStyleCombo
Kirigami.FormData.label: i18n("Main toolbar label:")
model: [
{text: i18n("None"), value: Private.KCM.NoText},
{text: i18n("Text only"), value: Private.KCM.TextOnly},
{text: i18n("Beside icons"), value: Private.KCM.TextBesideIcon},
{text: i18n("Below icon"), value: Private.KCM.TextUnderIcon}
]
textRole: "text"
currentIndex: model.findIndex(function (item) {
return item.value === kcm.mainToolBarStyle
})
onActivated: kcm.mainToolBarStyle = model[currentIndex].value
}
QtControls.ComboBox {
Kirigami.FormData.label: i18n("Secondary toolbar label:")
model: mainToolBarStyleCombo.model
textRole: "text"
currentIndex: model.findIndex(function (item) {
return item.value === kcm.otherToolBarStyle
})
onActivated: kcm.otherToolBarStyle = model[currentIndex].value
}
}
}
/*
* Copyright 2019 Kai Uwe Broulik <kde@privat.broulik.de>
*
* 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.6
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QtControls
import org.kde.kirigami 2.4 as Kirigami
import org.kde.kcm 1.1 as KCM
import org.kde.private.kcms.style 1.0 as Private
KCM.GridViewKCM {
id: root
KCM.ConfigModule.quickHelp: i18n("This module allows you to modify the visual appearance of applications' user interface elements.")
view.model: kcm.model
view.currentIndex: kcm.model.selectedStyleIndex
Component.onCompleted: {
// The widget thumbnails are a bit more elaborate and need more room, especially when translated
view.implicitCellWidth = Kirigami.Units.gridUnit * 20;
view.implicitCellHeight = Kirigami.Units.gridUnit * 14;
}
// putting the InlineMessage as header item causes it to show up initially despite visible false
header: ColumnLayout {
Kirigami.InlineMessage {
id: infoLabel
Layout.fillWidth: true
showCloseButton: true
visible: false
Connections {
target: kcm
onShowErrorMessage: {
infoLabel.type = Kirigami.MessageType.Error;
infoLabel.text = message;
infoLabel.visible = true;
}
}
}
}
view.delegate: KCM.GridDelegate {
id: delegate
text: model.display
toolTip: model.description
thumbnailAvailable: thumbnailItem.valid
thumbnail: Private.PreviewItem {
id: thumbnailItem
anchors.fill: parent
styleName: model.styleName
Connections {
target: kcm
onStyleReconfigured: {
if (styleName === model.styleName) {
thumbnailItem.reload();
}
}
}
}
actions: [
Kirigami.Action {
iconName: "document-edit"
tooltip: i18n("Configure Style...")
enabled: model.configurable
onTriggered: kcm.configure(model.styleName, delegate)
}
]
onClicked: {
kcm.model.selectedStyle = model.styleName;
view.forceActiveFocus();
}
}
footer: RowLayout {
Layout.fillWidth: true
QtControls.Button {
id: effectSettingsButton
text: i18n("Configure Icons and Toolbars")
icon.name: "configure-toolbars" // proper icon?
checkable: true
checked: effectSettingsPopupLoader.item && effectSettingsPopupLoader.item.opened
onClicked: {
effectSettingsPopupLoader.active = true;
effectSettingsPopupLoader.item.open();
}
}
}
Loader {
id: effectSettingsPopupLoader
active: false
sourceComponent: EffectSettingsPopup {
parent: effectSettingsButton
y: -height
}
}
}
[Desktop Entry]
Name=Application Style
Comment=Configure application style and behavior
Icon=preferences-desktop-theme-applications
Type=Service
X-KDE-PluginInfo-Author=Kai Uwe Broulik
X-KDE-PluginInfo-Email=kde@privat.broulik.de
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=kcm_style
X-KDE-PluginInfo-Version=
X-KDE-PluginInfo-Website=
X-KDE-ServiceTypes=Plasma/Generic
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml
/*
* Copyright © 2003-2007 Fredrik Höglund <fredrik@kde.org>
* Copyright 2019 Kai Uwe Broulik <kde@broulik.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License version 2 as published by the Free Software Foundation.
*
* 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "previewitem.h"
#include <QHoverEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QPixmapCache>
#include <QQuickWindow>
#include <QStyleFactory>
#include <QWidget>
#include <KColorScheme>
#include <KSharedConfig>
PreviewItem::PreviewItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
setAcceptHoverEvents(true);
// HACK QtCurve deadlocks on application teardown when the Q_GLOBAL_STATIC QFactoryLoader
// in QStyleFactory is destroyed which destroys all loaded styles prompting QtCurve
// to disconnect from DBus stalling the application.
// This also happens before any of the KCM objects are destroyed, so our only chance
// is cleaning up in response to aboutToQuit
connect(qApp, &QApplication::aboutToQuit, this, [this] {
m_style.reset();
});
}
PreviewItem::~PreviewItem() = default;
void PreviewItem::componentComplete()
{
QQuickPaintedItem::componentComplete();
reload();
}
bool PreviewItem::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_widget.data()) {
switch (event->type()) {
case QEvent::Show:
case QEvent::UpdateRequest:
update();
break;
default:
break;
}
}
return QQuickPaintedItem::eventFilter(watched, event);
}
QString PreviewItem::styleName() const
{
return m_styleName;
}
void PreviewItem::setStyleName(const QString &styleName)
{
if (m_styleName == styleName) {
return;
}
m_styleName = styleName;
reload();
emit styleNameChanged();
}
bool PreviewItem::isValid() const
{
return m_style && m_widget;
}
void setStyleRecursively(QWidget *widget, QStyle *style)
{
// Don't let styles kill the palette for other styles being previewed.
widget->setPalette(QPalette());
QPalette newPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig()));
style->polish(newPalette);
widget->setPalette(newPalette);
widget->setStyle(style);
const auto children = widget->children();
for (QObject *child : children) {
if (child->isWidgetType()) {
setStyleRecursively(static_cast<QWidget *>(child), style);
}
}
}
void PreviewItem::reload()
{
if (!isComponentComplete()) {
return;
}
const bool oldValid = isValid();
m_style.reset(QStyleFactory::create(m_styleName));
if (!m_style) {
qWarning() << "Failed to load style" << m_styleName;
if (oldValid != isValid()) {
emit validChanged();
}
return;
}
m_widget.reset(new QWidget);
// Don't actually show the widget as a separate window when calling show()
m_widget->setAttribute(Qt::WA_DontShowOnScreen);
// Do not wait for this widget to close before the app closes
m_widget->setAttribute(Qt::WA_QuitOnClose, false);
m_ui.setupUi(m_widget.data());
// Prevent Qt from wrongly caching radio button images
QPixmapCache::clear();
setStyleRecursively(m_widget.data(), m_style.data());
m_widget->ensurePolished();
const auto sizeHint = m_widget->sizeHint();
setImplicitSize(sizeHint.width(), sizeHint.height());
m_widget->resize(qRound(width()), qRound(height()));
m_widget->installEventFilter(this);
m_widget->show();
if (oldValid != isValid()) {
emit validChanged();
}
}
void PreviewItem::paint(QPainter *painter)
{
if (m_widget && m_widget->isVisible()) {
m_widget->render(painter);
}
}
void PreviewItem::hoverMoveEvent(QHoverEvent *event)
{
sendHoverEvent(event);
}
void PreviewItem::hoverLeaveEvent(QHoverEvent *event)
{
if (m_lastWidgetUnderMouse) {
dispatchEnterLeave(nullptr, m_lastWidgetUnderMouse, mapToGlobal(event->pos()));
m_lastWidgetUnderMouse = nullptr;
}
}
void PreviewItem::sendHoverEvent(QHoverEvent *event)
{
if (!m_widget || !m_widget->isVisible()) {
return;
}
QPointF pos = event->pos();
QWidget *child = m_widget->childAt(pos.toPoint());
QWidget *receiver = child ? child : m_widget.data();
dispatchEnterLeave(receiver, m_lastWidgetUnderMouse, mapToGlobal(event->pos()));
m_lastWidgetUnderMouse = receiver;
pos = receiver->mapFrom(m_widget.data(), pos.toPoint());
QMouseEvent mouseEvent(QEvent::MouseMove, pos, receiver->mapTo(receiver->topLevelWidget(), pos.toPoint()),
receiver->mapToGlobal(pos.toPoint()),
Qt::NoButton, {} /*buttons*/, event->modifiers());
qApp->sendEvent(receiver, &mouseEvent);
event->setAccepted(mouseEvent.isAccepted());
}
// Simplified copy of QApplicationPrivate::dispatchEnterLeave
void PreviewItem::dispatchEnterLeave(QWidget *enter, QWidget *leave, const QPointF &globalPosF)
{
if ((!enter && !leave) || (enter == leave)) {
return;
}