ollie/kde/gui/thememanager.cpp

468 lines
16 KiB
C++

#include "thememanager.h"
#include <QGuiApplication>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include <QFontDatabase>
#include <QFileDialog>
#include <KConfigGroup>
#include <QDebug>
ThemeManager::ThemeManager(QObject *parent)
: QObject(parent)
, m_mode("system")
, m_fontSize(0)
, m_closeToTray(false)
, m_watcher(nullptr)
{
loadSettings();
// Watch KDE global settings for system theme changes (via KConfigWatcher)
auto config = KSharedConfig::openConfig("kdeglobals");
m_configWatcher = KConfigWatcher::create(config);
connect(m_configWatcher.data(), &KConfigWatcher::configChanged,
this, [this](const KConfigGroup &group, const QByteArrayList &names) {
Q_UNUSED(names);
if (group.name() == "General" && m_mode == "system") {
applyPalette();
emit paletteChanged();
}
});
applyPalette();
applyFont();
}
QString ThemeManager::mode() const
{
return m_mode;
}
void ThemeManager::setMode(const QString &mode)
{
if (mode == m_mode)
return;
m_mode = mode;
saveSettings();
applyPalette();
emit modeChanged();
emit paletteChanged();
}
QStringList ThemeManager::availableSchemes() const
{
QStringList schemes;
QStringList dirs = schemeDirs();
for (const QString &dirPath : dirs) {
QDir dir(dirPath);
const auto entries = dir.entryInfoList({"*.colors"}, QDir::Files, QDir::Name);
for (const auto &entry : entries) {
if (!schemes.contains(entry.baseName()))
schemes << entry.baseName();
}
}
schemes.sort(Qt::CaseInsensitive);
return schemes;
}
bool ThemeManager::isDark() const
{
QPalette pal = QGuiApplication::palette();
// Use window background luminance to determine dark/light
QColor bg = pal.color(QPalette::Window);
qreal luminance = 0.2126 * bg.redF() + 0.7152 * bg.greenF() + 0.0722 * bg.blueF();
return luminance < 0.5;
}
void ThemeManager::applyPalette()
{
QString path;
if (m_mode == "system") {
path = systemSchemePath();
} else {
// Mode is a scheme name (e.g. "BreezeLight", "BreezeDark",
// "CatppuccinMochaMauve", etc.)
path = schemePath(m_mode);
}
if (path.isEmpty() || !QFile::exists(path)) {
// Fallback: don't override, let platform theme handle it
return;
}
m_palette = paletteFromScheme(path);
QGuiApplication::setPalette(m_palette);
}
bool ThemeManager::overriding() const
{
// Always provide colors (even in system mode) to fix FolderDialog
return true;
}
QColor ThemeManager::windowColor() const { return m_palette.color(QPalette::Active, QPalette::Window); }
QColor ThemeManager::windowTextColor() const { return m_palette.color(QPalette::Active, QPalette::WindowText); }
QColor ThemeManager::baseColor() const { return m_palette.color(QPalette::Active, QPalette::Base); }
QColor ThemeManager::alternateBaseColor() const { return m_palette.color(QPalette::Active, QPalette::AlternateBase); }
QColor ThemeManager::textColor() const { return m_palette.color(QPalette::Active, QPalette::Text); }
QColor ThemeManager::buttonColor() const { return m_palette.color(QPalette::Active, QPalette::Button); }
QColor ThemeManager::buttonTextColor() const { return m_palette.color(QPalette::Active, QPalette::ButtonText); }
QColor ThemeManager::highlightColor() const { return m_palette.color(QPalette::Active, QPalette::Highlight); }
QColor ThemeManager::highlightedTextColor() const { return m_palette.color(QPalette::Active, QPalette::HighlightedText); }
QColor ThemeManager::tooltipBaseColor() const { return m_palette.color(QPalette::Active, QPalette::ToolTipBase); }
QColor ThemeManager::tooltipTextColor() const { return m_palette.color(QPalette::Active, QPalette::ToolTipText); }
QColor ThemeManager::linkColor() const { return m_palette.color(QPalette::Active, QPalette::Link); }
QColor ThemeManager::midColor() const { return m_palette.color(QPalette::Active, QPalette::Mid); }
QColor ThemeManager::darkColor() const { return m_palette.color(QPalette::Active, QPalette::Dark); }
QColor ThemeManager::lightColor() const { return m_palette.color(QPalette::Active, QPalette::Light); }
QColor ThemeManager::shadowColor() const { return m_palette.color(QPalette::Active, QPalette::Shadow); }
QColor ThemeManager::placeholderTextColor() const { return m_palette.color(QPalette::Active, QPalette::PlaceholderText); }
QPalette ThemeManager::paletteFromScheme(const QString &path) const
{
QSettings scheme(path, QSettings::IniFormat);
QPalette pal;
// Read active/normal colors
// Window
scheme.beginGroup("Colors:Window");
QColor windowBg = parseColor(scheme.value("BackgroundNormal"));
QColor windowFg = parseColor(scheme.value("ForegroundNormal"));
QColor windowInactive = parseColor(scheme.value("ForegroundInactive"));
scheme.endGroup();
// View
scheme.beginGroup("Colors:View");
QColor viewBg = parseColor(scheme.value("BackgroundNormal"));
QColor viewAltBg = parseColor(scheme.value("BackgroundAlternate"));
QColor viewFg = parseColor(scheme.value("ForegroundNormal"));
QColor viewLink = parseColor(scheme.value("ForegroundLink"));
QColor viewVisited = parseColor(scheme.value("ForegroundVisited"));
QColor viewInactive = parseColor(scheme.value("ForegroundInactive"));
scheme.endGroup();
// Button
scheme.beginGroup("Colors:Button");
QColor buttonBg = parseColor(scheme.value("BackgroundNormal"));
QColor buttonFg = parseColor(scheme.value("ForegroundNormal"));
scheme.endGroup();
// Selection
scheme.beginGroup("Colors:Selection");
QColor selBg = parseColor(scheme.value("BackgroundNormal"));
QColor selFg = parseColor(scheme.value("ForegroundNormal"));
scheme.endGroup();
// Tooltip
scheme.beginGroup("Colors:Tooltip");
QColor tipBg = parseColor(scheme.value("BackgroundNormal"));
QColor tipFg = parseColor(scheme.value("ForegroundNormal"));
scheme.endGroup();
// Compute mid/dark/light/shadow from window background
QColor mid = QColor(
(windowBg.red() + windowFg.red()) / 2,
(windowBg.green() + windowFg.green()) / 2,
(windowBg.blue() + windowFg.blue()) / 2
);
QColor dark = windowBg.darker(150);
QColor light = windowBg.lighter(150);
QColor shadow = windowBg.darker(200);
QColor midlight = windowBg.lighter(125);
// Apply to Active group
pal.setColor(QPalette::Active, QPalette::Window, windowBg);
pal.setColor(QPalette::Active, QPalette::WindowText, windowFg);
pal.setColor(QPalette::Active, QPalette::Base, viewBg);
pal.setColor(QPalette::Active, QPalette::AlternateBase, viewAltBg);
pal.setColor(QPalette::Active, QPalette::Text, viewFg);
pal.setColor(QPalette::Active, QPalette::Link, viewLink);
pal.setColor(QPalette::Active, QPalette::LinkVisited, viewVisited);
pal.setColor(QPalette::Active, QPalette::Button, buttonBg);
pal.setColor(QPalette::Active, QPalette::ButtonText, buttonFg);
pal.setColor(QPalette::Active, QPalette::Highlight, selBg);
pal.setColor(QPalette::Active, QPalette::HighlightedText, selFg);
pal.setColor(QPalette::Active, QPalette::ToolTipBase, tipBg);
pal.setColor(QPalette::Active, QPalette::ToolTipText, tipFg);
pal.setColor(QPalette::Active, QPalette::Mid, mid);
pal.setColor(QPalette::Active, QPalette::Dark, dark);
pal.setColor(QPalette::Active, QPalette::Light, light);
pal.setColor(QPalette::Active, QPalette::Shadow, shadow);
pal.setColor(QPalette::Active, QPalette::Midlight, midlight);
pal.setColor(QPalette::Active, QPalette::PlaceholderText, viewInactive);
pal.setColor(QPalette::Active, QPalette::BrightText, selFg);
// Inactive group — same as active but with dimmed foreground
pal.setColor(QPalette::Inactive, QPalette::Window, windowBg);
pal.setColor(QPalette::Inactive, QPalette::WindowText, windowInactive);
pal.setColor(QPalette::Inactive, QPalette::Base, viewBg);
pal.setColor(QPalette::Inactive, QPalette::AlternateBase, viewAltBg);
pal.setColor(QPalette::Inactive, QPalette::Text, viewFg);
pal.setColor(QPalette::Inactive, QPalette::Link, viewLink);
pal.setColor(QPalette::Inactive, QPalette::LinkVisited, viewVisited);
pal.setColor(QPalette::Inactive, QPalette::Button, buttonBg);
pal.setColor(QPalette::Inactive, QPalette::ButtonText, buttonFg);
pal.setColor(QPalette::Inactive, QPalette::Highlight, selBg);
pal.setColor(QPalette::Inactive, QPalette::HighlightedText, selFg);
pal.setColor(QPalette::Inactive, QPalette::ToolTipBase, tipBg);
pal.setColor(QPalette::Inactive, QPalette::ToolTipText, tipFg);
pal.setColor(QPalette::Inactive, QPalette::Mid, mid);
pal.setColor(QPalette::Inactive, QPalette::Dark, dark);
pal.setColor(QPalette::Inactive, QPalette::Light, light);
pal.setColor(QPalette::Inactive, QPalette::Shadow, shadow);
pal.setColor(QPalette::Inactive, QPalette::Midlight, midlight);
pal.setColor(QPalette::Inactive, QPalette::PlaceholderText, viewInactive);
pal.setColor(QPalette::Inactive, QPalette::BrightText, selFg);
// Disabled group — dimmed versions
QColor disabledFg = windowInactive;
pal.setColor(QPalette::Disabled, QPalette::Window, windowBg);
pal.setColor(QPalette::Disabled, QPalette::WindowText, disabledFg);
pal.setColor(QPalette::Disabled, QPalette::Base, viewBg);
pal.setColor(QPalette::Disabled, QPalette::AlternateBase, viewAltBg);
pal.setColor(QPalette::Disabled, QPalette::Text, disabledFg);
pal.setColor(QPalette::Disabled, QPalette::Link, viewLink);
pal.setColor(QPalette::Disabled, QPalette::LinkVisited, viewVisited);
pal.setColor(QPalette::Disabled, QPalette::Button, buttonBg);
pal.setColor(QPalette::Disabled, QPalette::ButtonText, disabledFg);
pal.setColor(QPalette::Disabled, QPalette::Highlight, selBg);
pal.setColor(QPalette::Disabled, QPalette::HighlightedText, selFg);
pal.setColor(QPalette::Disabled, QPalette::ToolTipBase, tipBg);
pal.setColor(QPalette::Disabled, QPalette::ToolTipText, tipFg);
pal.setColor(QPalette::Disabled, QPalette::Mid, mid);
pal.setColor(QPalette::Disabled, QPalette::Dark, dark);
pal.setColor(QPalette::Disabled, QPalette::Light, light);
pal.setColor(QPalette::Disabled, QPalette::Shadow, shadow);
pal.setColor(QPalette::Disabled, QPalette::Midlight, midlight);
pal.setColor(QPalette::Disabled, QPalette::PlaceholderText, disabledFg);
pal.setColor(QPalette::Disabled, QPalette::BrightText, selFg);
return pal;
}
QColor ThemeManager::parseColor(const QVariant &value) const
{
QStringList parts;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (value.typeId() == QMetaType::QStringList) {
#else
if (value.type() == QVariant::StringList) {
#endif
parts = value.toStringList();
} else {
parts = value.toString().split(',');
}
if (parts.size() >= 3) {
return QColor(parts[0].trimmed().toInt(),
parts[1].trimmed().toInt(),
parts[2].trimmed().toInt());
}
return QColor();
}
QString ThemeManager::systemSchemePath() const
{
// Read current scheme from kdeglobals via KConfig
auto config = KSharedConfig::openConfig("kdeglobals");
config->reparseConfiguration(); // Ensure fresh read
KConfigGroup general(config, "General");
QString schemeName = general.readEntry("ColorScheme", QString());
if (!schemeName.isEmpty()) {
QString path = schemePath(schemeName);
if (QFile::exists(path))
return path;
}
// Fallback to BreezeLight
return schemePath("BreezeLight");
}
QString ThemeManager::schemePath(const QString &name) const
{
for (const QString &dir : schemeDirs()) {
QString path = QString("%1/%2.colors").arg(dir, name);
if (QFile::exists(path))
return path;
}
// Fallback to system dir even if file doesn't exist
return QString("%1/%2.colors").arg(SCHEMES_DIR, name);
}
QStringList ThemeManager::schemeDirs() const
{
QStringList dirs;
// User dir first (higher priority)
QString userDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ "/color-schemes";
if (QDir(userDir).exists())
dirs << userDir;
dirs << SCHEMES_DIR;
return dirs;
}
// --- Font methods ---
QString ThemeManager::fontFamily() const
{
return m_fontFamily;
}
void ThemeManager::setFontFamily(const QString &family)
{
if (family == m_fontFamily)
return;
m_fontFamily = family;
saveSettings();
applyFont();
emit fontChanged();
}
int ThemeManager::fontSize() const
{
return m_fontSize;
}
void ThemeManager::setFontSize(int size)
{
if (size < 6) size = 6;
if (size > 72) size = 72;
if (size == m_fontSize)
return;
m_fontSize = size;
saveSettings();
applyFont();
emit fontChanged();
}
QString ThemeManager::fontStyle() const
{
return m_fontStyle;
}
void ThemeManager::setFontStyle(const QString &style)
{
if (style == m_fontStyle)
return;
m_fontStyle = style;
saveSettings();
applyFont();
emit fontChanged();
}
QStringList ThemeManager::availableFamilies() const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return QFontDatabase::families();
#else
return QFontDatabase().families();
#endif
}
QStringList ThemeManager::availableStyles() const
{
QString family = m_fontFamily.isEmpty()
? QGuiApplication::font().family()
: m_fontFamily;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return QFontDatabase::styles(family);
#else
return QFontDatabase().styles(family);
#endif
}
// --- Close-to-tray ---
bool ThemeManager::closeToTray() const
{
return m_closeToTray;
}
void ThemeManager::setCloseToTray(bool enabled)
{
if (enabled == m_closeToTray)
return;
m_closeToTray = enabled;
saveSettings();
emit closeToTrayChanged();
}
void ThemeManager::resetFont()
{
m_fontFamily.clear();
m_fontSize = 0;
m_fontStyle.clear();
saveSettings();
applyFont();
emit fontChanged();
}
QString ThemeManager::chooseFolder(const QString &title, const QString &startDir)
{
QString dir = QFileDialog::getExistingDirectory(
nullptr, title,
startDir.isEmpty() ? QDir::homePath() : startDir,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
return dir;
}
void ThemeManager::applyFont()
{
QFont font;
if (!m_fontFamily.isEmpty())
font.setFamily(m_fontFamily);
if (m_fontSize > 0)
font.setPointSize(m_fontSize);
if (!m_fontStyle.isEmpty()) {
// Use QFontDatabase to resolve style string to weight + italic
QString family = m_fontFamily.isEmpty() ? font.family() : m_fontFamily;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFont styled = QFontDatabase::font(family, m_fontStyle,
m_fontSize > 0 ? m_fontSize : font.pointSize());
#else
QFont styled = QFontDatabase().font(family, m_fontStyle,
m_fontSize > 0 ? m_fontSize : font.pointSize());
#endif
font.setWeight(styled.weight());
font.setItalic(styled.italic());
}
QGuiApplication::setFont(font);
}
void ThemeManager::loadSettings()
{
QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
QString configFile = configDir + "/theme.conf";
if (QFile::exists(configFile)) {
QSettings settings(configFile, QSettings::IniFormat);
m_mode = settings.value("General/mode", "system").toString();
m_fontFamily = settings.value("Font/family", "").toString();
m_fontSize = settings.value("Font/size", 0).toInt();
m_fontStyle = settings.value("Font/style", "").toString();
m_closeToTray = settings.value("General/closeToTray", false).toBool();
}
}
void ThemeManager::saveSettings()
{
QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
QDir().mkpath(configDir);
QString configFile = configDir + "/theme.conf";
QSettings settings(configFile, QSettings::IniFormat);
settings.setValue("General/mode", m_mode);
settings.setValue("Font/family", m_fontFamily);
settings.setValue("Font/size", m_fontSize);
settings.setValue("Font/style", m_fontStyle);
settings.setValue("General/closeToTray", m_closeToTray);
}