Add KDE color scheme support
ThemeManager reads .colors files from /usr/share/color-schemes and ~/.local/share/color-schemes (KDE standard locations). Supports: - 'system' mode: follows kdeglobals ColorScheme setting - explicit scheme name: forces specific scheme (BreezeDark, etc.) QML now uses theme.* colors throughout instead of hardcoded Nord values. Org-mode semantic colors (todoColor, doneColor, priorityAColor, etc.) adjust automatically for dark/light themes. Settings saved to ~/.config/orkmode/theme.conf
This commit is contained in:
parent
bc2130d5b1
commit
64c7272f70
|
|
@ -18,6 +18,8 @@ qt_add_executable(orkmode
|
|||
src/orkclient.h
|
||||
src/documentmodel.cpp
|
||||
src/documentmodel.h
|
||||
src/thememanager.cpp
|
||||
src/thememanager.h
|
||||
)
|
||||
|
||||
qt_add_qml_module(orkmode
|
||||
|
|
|
|||
|
|
@ -9,14 +9,55 @@ ApplicationWindow {
|
|||
visible: true
|
||||
title: qsTr("orkmode")
|
||||
|
||||
// orkmode palette — muted, org-like.
|
||||
readonly property color bgColor: "#1e2127"
|
||||
readonly property color panelColor: "#252932"
|
||||
readonly property color textColor: "#d8dee9"
|
||||
readonly property color dimColor: "#7a8290"
|
||||
readonly property color accentColor: "#5e81ac"
|
||||
// Apply theme palette to Qt Quick Controls
|
||||
palette.active.window: theme.windowColor
|
||||
palette.active.windowText: theme.windowTextColor
|
||||
palette.active.base: theme.baseColor
|
||||
palette.active.text: theme.textColor
|
||||
palette.active.button: theme.buttonColor
|
||||
palette.active.buttonText: theme.buttonTextColor
|
||||
palette.active.highlight: theme.highlightColor
|
||||
palette.active.highlightedText: theme.highlightedTextColor
|
||||
palette.active.mid: theme.midColor
|
||||
palette.active.dark: theme.darkColor
|
||||
palette.active.light: theme.lightColor
|
||||
palette.active.shadow: theme.shadowColor
|
||||
palette.active.link: theme.linkColor
|
||||
palette.active.toolTipBase: theme.tooltipBaseColor
|
||||
palette.active.toolTipText: theme.tooltipTextColor
|
||||
palette.active.placeholderText: theme.placeholderTextColor
|
||||
palette.active.alternateBase: theme.alternateBaseColor
|
||||
palette.inactive.window: theme.windowColor
|
||||
palette.inactive.windowText: theme.windowTextColor
|
||||
palette.inactive.base: theme.baseColor
|
||||
palette.inactive.text: theme.textColor
|
||||
palette.inactive.button: theme.buttonColor
|
||||
palette.inactive.buttonText: theme.buttonTextColor
|
||||
palette.inactive.highlight: theme.highlightColor
|
||||
palette.inactive.highlightedText: theme.highlightedTextColor
|
||||
palette.inactive.mid: theme.midColor
|
||||
palette.inactive.dark: theme.darkColor
|
||||
palette.inactive.light: theme.lightColor
|
||||
palette.inactive.shadow: theme.shadowColor
|
||||
palette.inactive.link: theme.linkColor
|
||||
palette.inactive.toolTipBase: theme.tooltipBaseColor
|
||||
palette.inactive.toolTipText: theme.tooltipTextColor
|
||||
palette.inactive.placeholderText: theme.placeholderTextColor
|
||||
palette.inactive.alternateBase: theme.alternateBaseColor
|
||||
palette.disabled.window: theme.windowColor
|
||||
palette.disabled.windowText: theme.midColor
|
||||
palette.disabled.base: theme.baseColor
|
||||
palette.disabled.text: theme.midColor
|
||||
palette.disabled.button: theme.buttonColor
|
||||
palette.disabled.buttonText: theme.midColor
|
||||
palette.disabled.highlight: theme.highlightColor
|
||||
palette.disabled.highlightedText: theme.highlightedTextColor
|
||||
palette.disabled.mid: theme.midColor
|
||||
palette.disabled.dark: theme.darkColor
|
||||
palette.disabled.light: theme.lightColor
|
||||
palette.disabled.shadow: theme.shadowColor
|
||||
|
||||
color: bgColor
|
||||
color: theme.windowColor
|
||||
|
||||
// Currently selected document name (namespace directory).
|
||||
property string currentDoc: ""
|
||||
|
|
@ -40,14 +81,14 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
header: ToolBar {
|
||||
background: Rectangle { color: root.panelColor }
|
||||
background: Rectangle { color: theme.buttonColor }
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
Label {
|
||||
text: "orkmode"
|
||||
color: root.textColor
|
||||
color: theme.buttonTextColor
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
|
|
@ -56,7 +97,7 @@ ApplicationWindow {
|
|||
text: orkClient.connected
|
||||
? "● " + orkClient.address
|
||||
: "○ disconnected"
|
||||
color: orkClient.connected ? "#a3be8c" : "#bf616a"
|
||||
color: orkClient.connected ? theme.doneColor : theme.deadlineColor
|
||||
font.pixelSize: 12
|
||||
}
|
||||
ToolButton {
|
||||
|
|
@ -76,7 +117,7 @@ ApplicationWindow {
|
|||
Rectangle {
|
||||
Layout.preferredWidth: 220
|
||||
Layout.fillHeight: true
|
||||
color: root.panelColor
|
||||
color: theme.baseColor
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -84,7 +125,7 @@ ApplicationWindow {
|
|||
|
||||
Label {
|
||||
text: qsTr("Documents")
|
||||
color: root.dimColor
|
||||
color: theme.placeholderTextColor
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
Layout.margins: 10
|
||||
|
|
@ -102,21 +143,21 @@ ApplicationWindow {
|
|||
highlighted: root.currentDoc === modelData.name
|
||||
onClicked: root.selectDoc(modelData.name)
|
||||
background: Rectangle {
|
||||
color: highlighted ? root.accentColor : "transparent"
|
||||
color: highlighted ? theme.highlightColor : "transparent"
|
||||
opacity: highlighted ? 0.3 : 1.0
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 1
|
||||
Label {
|
||||
text: modelData.title
|
||||
color: root.textColor
|
||||
color: theme.textColor
|
||||
font.pixelSize: 13
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Label {
|
||||
text: modelData.name
|
||||
color: root.dimColor
|
||||
color: theme.placeholderTextColor
|
||||
font.pixelSize: 10
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -127,7 +168,7 @@ ApplicationWindow {
|
|||
}
|
||||
}
|
||||
|
||||
Rectangle { Layout.preferredWidth: 1; Layout.fillHeight: true; color: "#11141a" }
|
||||
Rectangle { Layout.preferredWidth: 1; Layout.fillHeight: true; color: theme.darkColor }
|
||||
|
||||
// Outline view.
|
||||
Item {
|
||||
|
|
@ -146,7 +187,7 @@ ApplicationWindow {
|
|||
text: orkClient.connected
|
||||
? qsTr("Select a document")
|
||||
: qsTr("Not connected: %1").arg(orkClient.error)
|
||||
color: root.dimColor
|
||||
color: theme.placeholderTextColor
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,27 @@ TreeView {
|
|||
id: tree
|
||||
clip: true
|
||||
|
||||
readonly property color textColor: "#d8dee9"
|
||||
readonly property color dimColor: "#7a8290"
|
||||
readonly property int rowPad: 6
|
||||
|
||||
// Palette for TODO keywords by state.
|
||||
function keywordColor(kw) {
|
||||
switch (kw) {
|
||||
case "TODO": return "#bf616a"
|
||||
case "NEXT": return "#d08770"
|
||||
case "WAITING": return "#ebcb8b"
|
||||
case "DONE": return "#a3be8c"
|
||||
case "CANCELLED": return "#7a8290"
|
||||
default: return "#88c0d0"
|
||||
case "TODO": return theme.todoColor
|
||||
case "NEXT": return theme.todoColor
|
||||
case "WAITING": return theme.waitingColor
|
||||
case "DONE": return theme.doneColor
|
||||
case "CANCELLED": return theme.cancelledColor
|
||||
default: return theme.linkColor
|
||||
}
|
||||
}
|
||||
|
||||
// Priority color by level.
|
||||
function priorityColor(pri) {
|
||||
switch (pri) {
|
||||
case "A": return theme.priorityAColor
|
||||
case "B": return theme.priorityBColor
|
||||
case "C": return theme.priorityCColor
|
||||
default: return theme.textColor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +51,8 @@ TreeView {
|
|||
implicitWidth: tree.width
|
||||
|
||||
background: Rectangle {
|
||||
color: del.current ? "#2f3540" : "transparent"
|
||||
color: del.current ? theme.highlightColor : "transparent"
|
||||
opacity: del.current ? 0.2 : 1.0
|
||||
radius: 4
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +88,7 @@ TreeView {
|
|||
Label {
|
||||
visible: (model.priority ?? "") !== ""
|
||||
text: "[#" + (model.priority ?? "") + "]"
|
||||
color: "#b48ead"
|
||||
color: tree.priorityColor(model.priority ?? "")
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
}
|
||||
|
|
@ -87,7 +96,7 @@ TreeView {
|
|||
// Title.
|
||||
Label {
|
||||
text: (model.title ?? "")
|
||||
color: (model.isDone ?? false) ? tree.dimColor : tree.textColor
|
||||
color: (model.isDone ?? false) ? theme.placeholderTextColor : theme.textColor
|
||||
font.pixelSize: tree.levelFontSize(model.level ?? 1)
|
||||
font.strikeout: (model.isDone ?? false)
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -98,13 +107,13 @@ TreeView {
|
|||
Label {
|
||||
visible: (model.scheduled ?? "") !== ""
|
||||
text: "⏱ " + (model.scheduled ?? "")
|
||||
color: "#81a1c1"
|
||||
color: theme.timestampColor
|
||||
font.pixelSize: 11
|
||||
}
|
||||
Label {
|
||||
visible: (model.deadline ?? "") !== ""
|
||||
text: "⚑ " + (model.deadline ?? "")
|
||||
color: "#bf616a"
|
||||
color: theme.deadlineColor
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
|
|
@ -114,14 +123,14 @@ TreeView {
|
|||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
radius: 3
|
||||
color: "#3b4252"
|
||||
color: theme.alternateBaseColor
|
||||
implicitWidth: tagLabel.implicitWidth + 8
|
||||
implicitHeight: tagLabel.implicitHeight + 3
|
||||
Label {
|
||||
id: tagLabel
|
||||
anchors.centerIn: parent
|
||||
text: parent.modelData
|
||||
color: "#8fbcbb"
|
||||
color: theme.tagColor
|
||||
font.pixelSize: 10
|
||||
}
|
||||
}
|
||||
|
|
@ -140,7 +149,7 @@ TreeView {
|
|||
width: 2
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 2
|
||||
color: "#3b4252"
|
||||
color: theme.midColor
|
||||
}
|
||||
|
||||
// Section body text.
|
||||
|
|
@ -149,7 +158,7 @@ TreeView {
|
|||
Layout.fillWidth: true
|
||||
Layout.leftMargin: del.bodyIndent - 10
|
||||
text: del.sectionBody
|
||||
color: tree.dimColor
|
||||
color: theme.placeholderTextColor
|
||||
font.pixelSize: 12
|
||||
lineHeight: 1.15
|
||||
wrapMode: Text.WordWrap
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
// the GUI walks the server's namespace and renders it.
|
||||
#include "documentmodel.h"
|
||||
#include "orkclient.h"
|
||||
#include "thememanager.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QImage>
|
||||
|
|
@ -78,9 +79,10 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
// Expose the client and the document model to QML.
|
||||
// Expose the client, document model, and theme manager to QML.
|
||||
OrkClient client;
|
||||
DocumentModel docModel;
|
||||
ThemeManager theme;
|
||||
docModel.setClient(&client);
|
||||
|
||||
const bool connected = client.connectTo(addr);
|
||||
|
|
@ -119,6 +121,7 @@ int main(int argc, char *argv[]) {
|
|||
QQmlApplicationEngine engine;
|
||||
engine.rootContext()->setContextProperty("orkClient", &client);
|
||||
engine.rootContext()->setContextProperty("docModel", &docModel);
|
||||
engine.rootContext()->setContextProperty("theme", &theme);
|
||||
|
||||
QObject::connect(
|
||||
&engine, &QQmlApplicationEngine::objectCreationFailed, &app,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,355 @@
|
|||
#include "thememanager.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
|
||||
ThemeManager::ThemeManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_mode("system")
|
||||
{
|
||||
loadSettings();
|
||||
applyPalette();
|
||||
}
|
||||
|
||||
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;
|
||||
schemes << "system";
|
||||
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();
|
||||
}
|
||||
}
|
||||
return schemes;
|
||||
}
|
||||
|
||||
bool ThemeManager::isDark() const
|
||||
{
|
||||
QColor bg = m_palette.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 {
|
||||
path = schemePath(m_mode);
|
||||
}
|
||||
|
||||
if (path.isEmpty() || !QFile::exists(path)) {
|
||||
// Fallback to application default palette
|
||||
m_palette = QGuiApplication::palette();
|
||||
return;
|
||||
}
|
||||
|
||||
m_palette = paletteFromScheme(path);
|
||||
QGuiApplication::setPalette(m_palette);
|
||||
}
|
||||
|
||||
// Palette color accessors
|
||||
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); }
|
||||
|
||||
// Org-mode semantic colors — adjust brightness based on dark/light theme
|
||||
QColor ThemeManager::todoColor() const
|
||||
{
|
||||
// Orange/amber for active TODO
|
||||
return isDark() ? QColor("#ebcb8b") : QColor("#bf8b00");
|
||||
}
|
||||
|
||||
QColor ThemeManager::doneColor() const
|
||||
{
|
||||
// Green for completed
|
||||
return isDark() ? QColor("#a3be8c") : QColor("#4c8c4a");
|
||||
}
|
||||
|
||||
QColor ThemeManager::waitingColor() const
|
||||
{
|
||||
// Cyan/teal for waiting/blocked
|
||||
return isDark() ? QColor("#88c0d0") : QColor("#2e7d8a");
|
||||
}
|
||||
|
||||
QColor ThemeManager::cancelledColor() const
|
||||
{
|
||||
// Dimmed/grey for cancelled
|
||||
return midColor();
|
||||
}
|
||||
|
||||
QColor ThemeManager::priorityAColor() const
|
||||
{
|
||||
// Red for high priority
|
||||
return isDark() ? QColor("#bf616a") : QColor("#c0392b");
|
||||
}
|
||||
|
||||
QColor ThemeManager::priorityBColor() const
|
||||
{
|
||||
// Yellow/orange for medium priority
|
||||
return isDark() ? QColor("#ebcb8b") : QColor("#d68910");
|
||||
}
|
||||
|
||||
QColor ThemeManager::priorityCColor() const
|
||||
{
|
||||
// Blue for low priority
|
||||
return isDark() ? QColor("#81a1c1") : QColor("#2874a6");
|
||||
}
|
||||
|
||||
QColor ThemeManager::tagColor() const
|
||||
{
|
||||
// Teal/cyan for tags
|
||||
return isDark() ? QColor("#8fbcbb") : QColor("#117a65");
|
||||
}
|
||||
|
||||
QColor ThemeManager::timestampColor() const
|
||||
{
|
||||
// Blue for scheduled timestamps
|
||||
return isDark() ? QColor("#81a1c1") : QColor("#2874a6");
|
||||
}
|
||||
|
||||
QColor ThemeManager::deadlineColor() const
|
||||
{
|
||||
// Red for deadlines
|
||||
return isDark() ? QColor("#bf616a") : QColor("#c0392b");
|
||||
}
|
||||
|
||||
QPalette ThemeManager::paletteFromScheme(const QString &path) const
|
||||
{
|
||||
QSettings scheme(path, QSettings::IniFormat);
|
||||
QPalette pal;
|
||||
|
||||
// Window colors
|
||||
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 colors
|
||||
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 colors
|
||||
scheme.beginGroup("Colors:Button");
|
||||
QColor buttonBg = parseColor(scheme.value("BackgroundNormal"));
|
||||
QColor buttonFg = parseColor(scheme.value("ForegroundNormal"));
|
||||
scheme.endGroup();
|
||||
|
||||
// Selection colors
|
||||
scheme.beginGroup("Colors:Selection");
|
||||
QColor selBg = parseColor(scheme.value("BackgroundNormal"));
|
||||
QColor selFg = parseColor(scheme.value("ForegroundNormal"));
|
||||
scheme.endGroup();
|
||||
|
||||
// Tooltip colors
|
||||
scheme.beginGroup("Colors:Tooltip");
|
||||
QColor tipBg = parseColor(scheme.value("BackgroundNormal"));
|
||||
QColor tipFg = parseColor(scheme.value("ForegroundNormal"));
|
||||
scheme.endGroup();
|
||||
|
||||
// Computed colors
|
||||
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 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
|
||||
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
|
||||
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 (value.typeId() == QMetaType::QStringList) {
|
||||
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
|
||||
{
|
||||
// Try to read from kdeglobals
|
||||
QString kdeglobals = QDir::homePath() + "/.config/kdeglobals";
|
||||
if (QFile::exists(kdeglobals)) {
|
||||
QSettings settings(kdeglobals, QSettings::IniFormat);
|
||||
settings.beginGroup("General");
|
||||
QString schemeName = settings.value("ColorScheme").toString();
|
||||
settings.endGroup();
|
||||
|
||||
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;
|
||||
}
|
||||
return QString("%1/%2.colors").arg(SCHEMES_DIR, name);
|
||||
}
|
||||
|
||||
QStringList ThemeManager::schemeDirs() const
|
||||
{
|
||||
QStringList dirs;
|
||||
// User dir first
|
||||
QString userDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
|
||||
+ "/color-schemes";
|
||||
if (QDir(userDir).exists())
|
||||
dirs << userDir;
|
||||
dirs << SCHEMES_DIR;
|
||||
return dirs;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// ThemeManager — KDE color scheme integration for QML.
|
||||
//
|
||||
// Reads .colors files from /usr/share/color-schemes and ~/.local/share/color-schemes.
|
||||
// Exposes palette colors as Q_PROPERTYs for QML binding. Supports:
|
||||
// - "system" mode: follows KDE's kdeglobals ColorScheme setting
|
||||
// - explicit scheme name: forces a specific scheme (e.g., "BreezeDark")
|
||||
//
|
||||
// The QML layer applies theme.* colors to the window palette and custom styling.
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
#include <QSettings>
|
||||
#include <QStringList>
|
||||
|
||||
class ThemeManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
// Mode: "system" or a scheme name (e.g., "BreezeLight", "BreezeDark")
|
||||
Q_PROPERTY(QString mode READ mode WRITE setMode NOTIFY modeChanged)
|
||||
Q_PROPERTY(QStringList availableSchemes READ availableSchemes CONSTANT)
|
||||
Q_PROPERTY(bool isDark READ isDark NOTIFY paletteChanged)
|
||||
|
||||
// Palette colors for QML
|
||||
Q_PROPERTY(QColor windowColor READ windowColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor windowTextColor READ windowTextColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor baseColor READ baseColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor alternateBaseColor READ alternateBaseColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor textColor READ textColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor buttonColor READ buttonColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor buttonTextColor READ buttonTextColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor highlightColor READ highlightColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor highlightedTextColor READ highlightedTextColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor tooltipBaseColor READ tooltipBaseColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor tooltipTextColor READ tooltipTextColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor linkColor READ linkColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor midColor READ midColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor darkColor READ darkColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor lightColor READ lightColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor shadowColor READ shadowColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor placeholderTextColor READ placeholderTextColor NOTIFY paletteChanged)
|
||||
|
||||
// Org-mode semantic colors (derived from palette or hardcoded for readability)
|
||||
Q_PROPERTY(QColor todoColor READ todoColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor doneColor READ doneColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor waitingColor READ waitingColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor cancelledColor READ cancelledColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor priorityAColor READ priorityAColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor priorityBColor READ priorityBColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor priorityCColor READ priorityCColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor tagColor READ tagColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor timestampColor READ timestampColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor deadlineColor READ deadlineColor NOTIFY paletteChanged)
|
||||
|
||||
public:
|
||||
explicit ThemeManager(QObject *parent = nullptr);
|
||||
|
||||
QString mode() const;
|
||||
void setMode(const QString &mode);
|
||||
QStringList availableSchemes() const;
|
||||
bool isDark() const;
|
||||
|
||||
// Palette colors
|
||||
QColor windowColor() const;
|
||||
QColor windowTextColor() const;
|
||||
QColor baseColor() const;
|
||||
QColor alternateBaseColor() const;
|
||||
QColor textColor() const;
|
||||
QColor buttonColor() const;
|
||||
QColor buttonTextColor() const;
|
||||
QColor highlightColor() const;
|
||||
QColor highlightedTextColor() const;
|
||||
QColor tooltipBaseColor() const;
|
||||
QColor tooltipTextColor() const;
|
||||
QColor linkColor() const;
|
||||
QColor midColor() const;
|
||||
QColor darkColor() const;
|
||||
QColor lightColor() const;
|
||||
QColor shadowColor() const;
|
||||
QColor placeholderTextColor() const;
|
||||
|
||||
// Org-mode semantic colors
|
||||
QColor todoColor() const;
|
||||
QColor doneColor() const;
|
||||
QColor waitingColor() const;
|
||||
QColor cancelledColor() const;
|
||||
QColor priorityAColor() const;
|
||||
QColor priorityBColor() const;
|
||||
QColor priorityCColor() const;
|
||||
QColor tagColor() const;
|
||||
QColor timestampColor() const;
|
||||
QColor deadlineColor() const;
|
||||
|
||||
void applyPalette();
|
||||
|
||||
signals:
|
||||
void modeChanged();
|
||||
void paletteChanged();
|
||||
|
||||
private:
|
||||
QPalette paletteFromScheme(const QString &path) const;
|
||||
QColor parseColor(const QVariant &value) const;
|
||||
QString systemSchemePath() const;
|
||||
QString schemePath(const QString &name) const;
|
||||
QStringList schemeDirs() const;
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
QString m_mode;
|
||||
QPalette m_palette;
|
||||
static constexpr const char *SCHEMES_DIR = "/usr/share/color-schemes";
|
||||
};
|
||||
Loading…
Reference in New Issue