#include "thememanager.h" #include #include #include #include #include #include #include #include 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::dialogOutlineColor() const { const QColor window = windowColor(); return window.valueF() < 0.5 ? window.lighter(180) : window.darker(180); } 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); }