Skip to content

Commit

Permalink
Add KDE theming support
Browse files Browse the repository at this point in the history
KDE applications are a notable part of the popular Qt software in the Linux world.
Sadly, they have their own theming add-ons that are applied through QPlatformTheme.

This adds support for all aspects of the KDE applications theming present in the plasma-integration QPlatformTheme:
* KDE's QtQuick style bridging the currently set QtWidgets style (you may want to get QtQuick style choice in the settings UI in the future instead)
* KDE's color schemes (the most important part as KDE applications don't fallback to QtQuick style/QtWidgets style/QPalette for colors)
* KDE's icon engine applying KDE's color schemes to icons (to not to have black icons on black background)
  • Loading branch information
ilya-fedin committed Jul 7, 2024
1 parent 2230c00 commit aef8f90
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 13 deletions.
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)

add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000 -DUSE_WIDGETS)

find_package(Qt6 REQUIRED COMPONENTS BuildInternals Core Widgets OPTIONAL_COMPONENTS LinguistTools)
find_package(Qt6 REQUIRED COMPONENTS BuildInternals Core Widgets QuickControls2 OPTIONAL_COMPONENTS LinguistTools)
find_package(KF6Config REQUIRED)
find_package(KF6ColorScheme REQUIRED)
find_package(KF6IconThemes REQUIRED)

get_target_property(QT_QTPATHS_EXECUTABLE Qt6::qtpaths IMPORTED_LOCATION)

Expand Down
2 changes: 1 addition & 1 deletion src/qt6ct-common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../)

add_library(qt6ct-common SHARED ${app_SRCS})
set_target_properties(qt6ct-common PROPERTIES VERSION ${QT6CT_VERSION})
target_link_libraries(qt6ct-common PRIVATE Qt6::Gui)
target_link_libraries(qt6ct-common PRIVATE Qt6::Gui KF6::ConfigCore KF6::ColorScheme)
install(TARGETS qt6ct-common DESTINATION ${CMAKE_INSTALL_LIBDIR})
11 changes: 11 additions & 0 deletions src/qt6ct-common/qt6ct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#include <QFile>
#include <QSettings>
#include <QtDebug>
#include <KSharedConfig>
#include <KColorScheme>
#include "qt6ct.h"

#ifndef QT6CT_DATADIR
Expand Down Expand Up @@ -116,6 +118,7 @@ QStringList Qt6CT::sharedColorSchemePaths()
for(const QString &p : QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation))
{
paths << (p + QLatin1String("/qt6ct/colors"));
paths << (p + QLatin1String("/color-schemes"));
}
paths << QLatin1String(QT6CT_DATADIR"/qt6ct/colors");
paths.removeDuplicates();
Expand Down Expand Up @@ -146,8 +149,16 @@ QString Qt6CT::resolvePath(const QString &path)
return tmp;
}

bool Qt6CT::isKColorScheme(const QString &filePath)
{
return filePath.toLower().endsWith(".colors");
}

QPalette Qt6CT::loadColorScheme(const QString &filePath, const QPalette &fallback)
{
if(isKColorScheme(filePath))
return KColorScheme::createApplicationPalette(KSharedConfig::openConfig(filePath));

QPalette customPalette;
QSettings settings(filePath, QSettings::IniFormat);
settings.beginGroup("ColorScheme");
Expand Down
1 change: 1 addition & 0 deletions src/qt6ct-common/qt6ct.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class QT6CT_EXPORT Qt6CT
static QString userColorSchemePath();
static QStringList sharedColorSchemePaths();
static QString resolvePath(const QString &path);
static bool isKColorScheme(const QString &filePath);
static QPalette loadColorScheme(const QString &filePath, const QPalette &fallback);

static void registerStyleInstance(StyleInstance *instance);
Expand Down
2 changes: 1 addition & 1 deletion src/qt6ct-qtplugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../)

add_library(qt6ct-qtplugin MODULE ${app_SRCS})
set_target_properties(qt6ct-qtplugin PROPERTIES OUTPUT_NAME qt6ct)
target_link_libraries(qt6ct-qtplugin PRIVATE Qt6::Widgets Qt6::GuiPrivate qt6ct-common)
target_link_libraries(qt6ct-qtplugin PRIVATE Qt6::Widgets Qt6::GuiPrivate Qt6::QuickControls2 KF6::IconThemes qt6ct-common)
install(TARGETS qt6ct-qtplugin DESTINATION ${PLUGINDIR}/platformthemes)
36 changes: 33 additions & 3 deletions src/qt6ct-qtplugin/qt6ctplatformtheme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#endif
#include <QFile>
#include <QFileSystemWatcher>
#include <QQuickStyle>
#include <private/qiconloader_p.h>

#include "qt6ct.h"
Expand All @@ -52,6 +53,9 @@
#include <QStringList>
#include <qpa/qplatformthemefactory_p.h>

#include <KIconEngine>
#include <KIconLoader>

Q_LOGGING_CATEGORY(lqt6ct, "qt6ct", QtWarningMsg)

//QT_QPA_PLATFORMTHEME=qt6ct
Expand All @@ -67,12 +71,16 @@ Qt6CTPlatformTheme::Qt6CTPlatformTheme()
QMetaObject::invokeMethod(this, "createFSWatcher", Qt::QueuedConnection);
#endif
QGuiApplication::setFont(m_generalFont);
//don't override the value explicitly set by the user
if(QQuickStyle::name().isEmpty() || QQuickStyle::name() == QLatin1String("Fusion"))
QQuickStyle::setStyle(QLatin1String("org.kde.desktop"));
}
qCDebug(lqt6ct) << "using qt6ct plugin";
#ifdef QT_WIDGETS_LIB
if(!QStyleFactory::keys().contains("qt6ct-style"))
qCCritical(lqt6ct) << "unable to find qt6ct proxy style";
#endif
QCoreApplication::instance()->installEventFilter(this);
}

Qt6CTPlatformTheme::~Qt6CTPlatformTheme()
Expand Down Expand Up @@ -146,6 +154,11 @@ QIcon Qt6CTPlatformTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::Ic
return QIcon::fromTheme(type.iconName());
}

QIconEngine *Qt6CTPlatformTheme::createIconEngine(const QString &iconName) const
{
return new KIconEngine(iconName, KIconLoader::global());
}

void Qt6CTPlatformTheme::applySettings()
{
if(!QGuiApplication::desktopSettingsAware() || m_isIgnored)
Expand Down Expand Up @@ -186,6 +199,9 @@ void Qt6CTPlatformTheme::applySettings()
if(m_update && m_usePalette)
qApp->setPalette(*m_palette);

if(Qt6CT::isKColorScheme(m_schemePath))
qApp->setProperty("KDE_COLOR_SCHEME_PATH", m_schemePath);

if(m_userStyleSheet != m_prevStyleSheet)
{
// prepend our stylesheet to that of the application
Expand Down Expand Up @@ -252,11 +268,11 @@ void Qt6CTPlatformTheme::readSettings()

settings.beginGroup("Appearance");
m_style = settings.value("style", "Fusion").toString();
QString schemePath = settings.value("custom_palette", false).toBool()
m_schemePath = settings.value("custom_palette", false).toBool()
? Qt6CT::resolvePath(settings.value("color_scheme_path").toString()) //replace environment variables
: QString();
m_palette = !schemePath.isEmpty()
? std::make_unique<QPalette>(Qt6CT::loadColorScheme(schemePath, *QPlatformTheme::palette(SystemPalette)))
m_palette = !m_schemePath.isEmpty()
? std::make_unique<QPalette>(Qt6CT::loadColorScheme(m_schemePath, *QPlatformTheme::palette(SystemPalette)))
: nullptr;
m_iconTheme = settings.value("icon_theme").toString();
//load dialogs
Expand Down Expand Up @@ -365,3 +381,17 @@ QString Qt6CTPlatformTheme::loadStyleSheets(const QStringList &paths)
content.replace(regExp, "\n");
return content;
}

//There's such a thing as KColorSchemeManager that lets the user to change the color scheme
//application-wide and we should re-apply the color scheme if KCSM resets it to the default
//which leads KColorScheme to get the color scheme from kdeglobals which won't help us.
bool Qt6CTPlatformTheme::eventFilter(QObject *obj, QEvent *e)
{
if(obj == qApp &&
e->type() == QEvent::DynamicPropertyChange &&
static_cast<QDynamicPropertyChangeEvent*>(e)->propertyName() == "KDE_COLOR_SCHEME_PATH" &&
qApp->property("KDE_COLOR_SCHEME_PATH").toString().isEmpty() &&
Qt6CT::isKColorScheme(m_schemePath))
applySettings();
return QObject::eventFilter(obj, e);
}
7 changes: 5 additions & 2 deletions src/qt6ct-qtplugin/qt6ctplatformtheme.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ class Qt6CTPlatformTheme : public QObject, public QGenericUnixTheme
//virtual QPixmap fileIconPixmap(const QFileInfo &fileInfo, const QSizeF &size,
// QPlatformTheme::IconOptions iconOptions = 0) const;

//virtual QIconEngine *createIconEngine(const QString &iconName) const;
virtual QIconEngine *createIconEngine(const QString &iconName) const;
//virtual QList<QKeySequence> keyBindings(QKeySequence::StandardKey key) const;
//virtual QString standardButtonText(int button) const;

protected:
bool eventFilter(QObject *obj, QEvent *e) override;

private slots:
void applySettings();
#ifdef QT_WIDGETS_LIB
Expand All @@ -79,7 +82,7 @@ private slots:
bool hasWidgets();
#endif
QString loadStyleSheets(const QStringList &paths);
QString m_style, m_iconTheme, m_userStyleSheet, m_prevStyleSheet;
QString m_style, m_schemePath, m_iconTheme, m_userStyleSheet, m_prevStyleSheet;
std::unique_ptr<QPalette> m_palette;
QFont m_generalFont, m_fixedFont;
int m_doubleClickInterval;
Expand Down
2 changes: 1 addition & 1 deletion src/qt6ct/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ if(Qt6LinguistTools_FOUND)
endif()

add_executable(qt6ct ${app_SRCS})
target_link_libraries(qt6ct PRIVATE Qt6::Widgets Qt6::WidgetsPrivate qt6ct-common)
target_link_libraries(qt6ct PRIVATE Qt6::Widgets Qt6::WidgetsPrivate KF6::ConfigCore qt6ct-common)
install(TARGETS qt6ct DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES qt6ct.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
21 changes: 18 additions & 3 deletions src/qt6ct/appearancepage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include <QMenu>
#include <QIcon>
#include <QStringList>
#include <KSharedConfig>
#include <KConfigGroup>
#include <qpa/qplatformthemefactory_p.h>
#include "qt6ct.h"
#include "appearancepage.h"
Expand Down Expand Up @@ -71,7 +73,7 @@ AppearancePage::AppearancePage(QWidget *parent) :
QMenu *menu = new QMenu(this);
menu->addAction(QIcon::fromTheme("document-new"), tr("Create"), this, SLOT(createColorScheme()));
m_changeColorSchemeAction = menu->addAction(QIcon::fromTheme("accessories-text-editor"), tr("Edit"), this, SLOT(changeColorScheme()));
menu->addAction(QIcon::fromTheme("edit-copy"), tr("Create a Copy"), this, SLOT(copyColorScheme()));
m_copyColorSchemeAction = menu->addAction(QIcon::fromTheme("edit-copy"), tr("Create a Copy"), this, SLOT(copyColorScheme()));
m_renameColorSchemeAction = menu->addAction(tr("Rename"), this, SLOT(renameColorScheme()));
menu->addSeparator();
m_removeColorSchemeAction = menu->addAction(QIcon::fromTheme("edit-delete"), tr("Remove"), this, SLOT(removeColorScheme()));
Expand Down Expand Up @@ -303,6 +305,7 @@ void AppearancePage::setPreviewPalette(const QPalette &p)

void AppearancePage::updateActions()
{
m_copyColorSchemeAction->setVisible(!Qt6CT::isKColorScheme(m_ui->colorSchemeComboBox->currentData().toString()));
if(m_ui->colorSchemeComboBox->count() == 0 ||
!QFileInfo(m_ui->colorSchemeComboBox->currentData().toString()).isWritable())
{
Expand Down Expand Up @@ -380,11 +383,23 @@ void AppearancePage::findColorSchemes(const QString &path)
{
QDir dir(path);
dir.setFilter(QDir::Files);
dir.setNameFilters(QStringList() << "*.conf");
dir.setNameFilters(QStringList() << "*.conf" << "*.colors");

for(const QFileInfo &info : dir.entryInfoList())
{
m_ui->colorSchemeComboBox->addItem(info.baseName(), info.filePath());
QString name;
QString path = info.filePath();
if(Qt6CT::isKColorScheme(path))
{
KSharedConfigPtr config = KSharedConfig::openConfig(path, KConfig::SimpleConfig);
KConfigGroup group(config, "General");
name = group.readEntry("Name", info.baseName()) + " (KColorScheme)";
}
else
{
name = info.baseName();
}
m_ui->colorSchemeComboBox->addItem(name, path);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/qt6ct/appearancepage.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ private slots:
QStyle *m_selectedStyle = nullptr;
QPalette m_customPalette;
QWidget *m_previewWidget;
QAction *m_changeColorSchemeAction, *m_renameColorSchemeAction, *m_removeColorSchemeAction;
QAction *m_changeColorSchemeAction, *m_copyColorSchemeAction, *m_renameColorSchemeAction, *m_removeColorSchemeAction;
Ui::PreviewForm *m_previewUi;
};

Expand Down

0 comments on commit aef8f90

Please sign in to comment.