510 lines
17 KiB
C++
510 lines
17 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::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();
|
|
}
|
|
|
|
QString ThemeManager::approveBypassKey() const
|
|
{
|
|
return m_approveBypassKey;
|
|
}
|
|
|
|
void ThemeManager::setApproveBypassKey(const QString &key)
|
|
{
|
|
if (key == m_approveBypassKey)
|
|
return;
|
|
m_approveBypassKey = key;
|
|
saveSettings();
|
|
emit shortcutsChanged();
|
|
}
|
|
|
|
QString ThemeManager::denyBypassKey() const
|
|
{
|
|
return m_denyBypassKey;
|
|
}
|
|
|
|
void ThemeManager::setDenyBypassKey(const QString &key)
|
|
{
|
|
if (key == m_denyBypassKey)
|
|
return;
|
|
m_denyBypassKey = key;
|
|
saveSettings();
|
|
emit shortcutsChanged();
|
|
}
|
|
|
|
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();
|
|
|
|
m_approveBypassKey = settings.value("Shortcuts/approveBypass", "Ctrl+Y").toString();
|
|
m_denyBypassKey = settings.value("Shortcuts/denyBypass", "Ctrl+N").toString();
|
|
} else {
|
|
// Defaults when no config file exists
|
|
m_approveBypassKey = "Ctrl+Y";
|
|
m_denyBypassKey = "Ctrl+N";
|
|
}
|
|
}
|
|
|
|
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);
|
|
settings.setValue("Shortcuts/approveBypass", m_approveBypassKey);
|
|
settings.setValue("Shortcuts/denyBypass", m_denyBypassKey);
|
|
}
|