Add font customization and UI scaling
ThemeManager now supports: - fontFamily, fontSize, fontStyle with full QFontDatabase access - uiScale (0.5x to 4.0x) saved to theme.conf - resetFont() to return to system defaults SettingsDialog provides UI for: - Color scheme selection (all installed KDE schemes) - Font family/style/size with live preview - UI scale slider with percentage display OutlineView uses pointSize (scalable) instead of pixelSize (fixed). All font sizes derived from theme.fontSize or baseFontSize.
This commit is contained in:
parent
64c7272f70
commit
5677be6236
|
|
@ -28,6 +28,7 @@ qt_add_qml_module(orkmode
|
|||
QML_FILES
|
||||
qml/Main.qml
|
||||
qml/OutlineView.qml
|
||||
qml/SettingsDialog.qml
|
||||
)
|
||||
|
||||
target_link_libraries(orkmode PRIVATE
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ ApplicationWindow {
|
|||
visible: true
|
||||
title: qsTr("orkmode")
|
||||
|
||||
// Apply theme font globally
|
||||
font.family: theme.fontFamily || Qt.application.font.family
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||
|
||||
// Apply theme palette to Qt Quick Controls
|
||||
palette.active.window: theme.windowColor
|
||||
palette.active.windowText: theme.windowTextColor
|
||||
|
|
@ -80,6 +84,11 @@ ApplicationWindow {
|
|||
function onConnectedChanged() { root.refreshDocs() }
|
||||
}
|
||||
|
||||
// Settings dialog
|
||||
SettingsDialog {
|
||||
id: settingsDialog
|
||||
}
|
||||
|
||||
header: ToolBar {
|
||||
background: Rectangle { color: theme.buttonColor }
|
||||
RowLayout {
|
||||
|
|
@ -106,6 +115,12 @@ ApplicationWindow {
|
|||
ToolTip.text: qsTr("Reload")
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
ToolButton {
|
||||
text: "⚙"
|
||||
onClicked: settingsDialog.open()
|
||||
ToolTip.text: qsTr("Settings")
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ TreeView {
|
|||
clip: true
|
||||
|
||||
readonly property int rowPad: 6
|
||||
readonly property int baseFontSize: theme.fontSize > 0 ? theme.fontSize : 12
|
||||
|
||||
// Palette for TODO keywords by state.
|
||||
function keywordColor(kw) {
|
||||
|
|
@ -36,7 +37,7 @@ TreeView {
|
|||
|
||||
// Headline text scales down slightly with depth.
|
||||
function levelFontSize(level) {
|
||||
return Math.max(13, 19 - level)
|
||||
return Math.max(tree.baseFontSize - 1, tree.baseFontSize + 5 - level)
|
||||
}
|
||||
|
||||
selectionModel: ItemSelectionModel {}
|
||||
|
|
@ -79,7 +80,7 @@ TreeView {
|
|||
anchors.centerIn: parent
|
||||
text: model.keyword ?? ""
|
||||
color: tree.keywordColor(model.keyword ?? "")
|
||||
font.pixelSize: 11
|
||||
font.pointSize: tree.baseFontSize - 1
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +90,7 @@ TreeView {
|
|||
visible: (model.priority ?? "") !== ""
|
||||
text: "[#" + (model.priority ?? "") + "]"
|
||||
color: tree.priorityColor(model.priority ?? "")
|
||||
font.pixelSize: 12
|
||||
font.pointSize: tree.baseFontSize
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
|
|
@ -97,7 +98,7 @@ TreeView {
|
|||
Label {
|
||||
text: (model.title ?? "")
|
||||
color: (model.isDone ?? false) ? theme.placeholderTextColor : theme.textColor
|
||||
font.pixelSize: tree.levelFontSize(model.level ?? 1)
|
||||
font.pointSize: tree.levelFontSize(model.level ?? 1)
|
||||
font.strikeout: (model.isDone ?? false)
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
|
|
@ -108,13 +109,13 @@ TreeView {
|
|||
visible: (model.scheduled ?? "") !== ""
|
||||
text: "⏱ " + (model.scheduled ?? "")
|
||||
color: theme.timestampColor
|
||||
font.pixelSize: 11
|
||||
font.pointSize: tree.baseFontSize - 1
|
||||
}
|
||||
Label {
|
||||
visible: (model.deadline ?? "") !== ""
|
||||
text: "⚑ " + (model.deadline ?? "")
|
||||
color: theme.deadlineColor
|
||||
font.pixelSize: 11
|
||||
font.pointSize: tree.baseFontSize - 1
|
||||
}
|
||||
|
||||
// Tags.
|
||||
|
|
@ -131,7 +132,7 @@ TreeView {
|
|||
anchors.centerIn: parent
|
||||
text: parent.modelData
|
||||
color: theme.tagColor
|
||||
font.pixelSize: 10
|
||||
font.pointSize: tree.baseFontSize - 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +160,7 @@ TreeView {
|
|||
Layout.leftMargin: del.bodyIndent - 10
|
||||
text: del.sectionBody
|
||||
color: theme.placeholderTextColor
|
||||
font.pixelSize: 12
|
||||
font.pointSize: tree.baseFontSize
|
||||
lineHeight: 1.15
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.PlainText
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Dialog {
|
||||
id: settingsDialog
|
||||
title: qsTr("Settings")
|
||||
modal: true
|
||||
standardButtons: Dialog.Close
|
||||
width: 450
|
||||
height: 400
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
background: Rectangle {
|
||||
color: theme.windowColor
|
||||
border.color: theme.midColor
|
||||
border.width: 1
|
||||
radius: 6
|
||||
}
|
||||
|
||||
header: Rectangle {
|
||||
implicitHeight: 40
|
||||
color: theme.buttonColor
|
||||
radius: 6
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: settingsDialog.title
|
||||
color: theme.buttonTextColor
|
||||
font.bold: true
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize + 1 : 13
|
||||
}
|
||||
|
||||
// Square off bottom corners
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 6
|
||||
color: theme.buttonColor
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
// --- Color Scheme ---
|
||||
GroupBox {
|
||||
title: qsTr("Color Scheme")
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
border.color: theme.midColor
|
||||
border.width: 1
|
||||
radius: 4
|
||||
y: 12
|
||||
}
|
||||
|
||||
label: Label {
|
||||
x: 8
|
||||
text: parent.title
|
||||
color: theme.textColor
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 8
|
||||
|
||||
ComboBox {
|
||||
id: schemeCombo
|
||||
Layout.fillWidth: true
|
||||
model: theme.availableSchemes
|
||||
currentIndex: {
|
||||
let idx = model.indexOf(theme.mode)
|
||||
return idx >= 0 ? idx : 0
|
||||
}
|
||||
onActivated: theme.mode = currentText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Font ---
|
||||
GroupBox {
|
||||
title: qsTr("Font")
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
border.color: theme.midColor
|
||||
border.width: 1
|
||||
radius: 4
|
||||
y: 12
|
||||
}
|
||||
|
||||
label: Label {
|
||||
x: 8
|
||||
text: parent.title
|
||||
color: theme.textColor
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
anchors.fill: parent
|
||||
columnSpacing: 12
|
||||
rowSpacing: 8
|
||||
|
||||
Label { text: qsTr("Family:"); color: theme.textColor }
|
||||
ComboBox {
|
||||
id: fontFamilyCombo
|
||||
Layout.fillWidth: true
|
||||
model: theme.availableFamilies
|
||||
editable: true
|
||||
currentIndex: {
|
||||
let fam = theme.fontFamily || Qt.application.font.family
|
||||
let idx = model.indexOf(fam)
|
||||
return idx >= 0 ? idx : 0
|
||||
}
|
||||
onActivated: theme.fontFamily = currentText
|
||||
onAccepted: theme.fontFamily = editText
|
||||
}
|
||||
|
||||
Label { text: qsTr("Style:"); color: theme.textColor }
|
||||
ComboBox {
|
||||
id: fontStyleCombo
|
||||
Layout.fillWidth: true
|
||||
model: theme.availableStyles
|
||||
currentIndex: {
|
||||
let st = theme.fontStyle
|
||||
let idx = model.indexOf(st)
|
||||
return idx >= 0 ? idx : 0
|
||||
}
|
||||
onActivated: theme.fontStyle = currentText
|
||||
}
|
||||
|
||||
Label { text: qsTr("Size:"); color: theme.textColor }
|
||||
SpinBox {
|
||||
id: fontSizeSpin
|
||||
from: 6
|
||||
to: 72
|
||||
value: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
||||
onValueModified: theme.fontSize = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI Scale ---
|
||||
GroupBox {
|
||||
title: qsTr("UI Scale")
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
border.color: theme.midColor
|
||||
border.width: 1
|
||||
radius: 4
|
||||
y: 12
|
||||
}
|
||||
|
||||
label: Label {
|
||||
x: 8
|
||||
text: parent.title
|
||||
color: theme.textColor
|
||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 11
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
|
||||
Slider {
|
||||
id: scaleSlider
|
||||
Layout.fillWidth: true
|
||||
from: 0.5
|
||||
to: 3.0
|
||||
stepSize: 0.1
|
||||
value: theme.uiScale
|
||||
onMoved: theme.uiScale = value
|
||||
}
|
||||
|
||||
Label {
|
||||
text: (theme.uiScale * 100).toFixed(0) + "%"
|
||||
color: theme.textColor
|
||||
Layout.preferredWidth: 50
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Reset buttons ---
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Button {
|
||||
text: qsTr("Reset Font")
|
||||
onClicked: theme.resetFont()
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,19 @@
|
|||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QFontDatabase>
|
||||
#include <QScreen>
|
||||
#include <QDebug>
|
||||
|
||||
ThemeManager::ThemeManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_mode("system")
|
||||
, m_fontSize(0)
|
||||
, m_uiScale(1.0)
|
||||
{
|
||||
loadSettings();
|
||||
applyPalette();
|
||||
applyFont();
|
||||
}
|
||||
|
||||
QString ThemeManager::mode() const
|
||||
|
|
@ -341,6 +346,10 @@ void ThemeManager::loadSettings()
|
|||
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_uiScale = settings.value("General/uiScale", 1.0).toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,4 +361,119 @@ void ThemeManager::saveSettings()
|
|||
|
||||
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/uiScale", m_uiScale);
|
||||
}
|
||||
|
||||
// --- 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
|
||||
{
|
||||
return QFontDatabase::families();
|
||||
}
|
||||
|
||||
QStringList ThemeManager::availableStyles() const
|
||||
{
|
||||
QString family = m_fontFamily.isEmpty()
|
||||
? QGuiApplication::font().family()
|
||||
: m_fontFamily;
|
||||
return QFontDatabase::styles(family);
|
||||
}
|
||||
|
||||
void ThemeManager::resetFont()
|
||||
{
|
||||
m_fontFamily.clear();
|
||||
m_fontSize = 0;
|
||||
m_fontStyle.clear();
|
||||
saveSettings();
|
||||
applyFont();
|
||||
emit fontChanged();
|
||||
}
|
||||
|
||||
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()) {
|
||||
QString family = m_fontFamily.isEmpty() ? font.family() : m_fontFamily;
|
||||
QFont styled = QFontDatabase::font(family, m_fontStyle,
|
||||
m_fontSize > 0 ? m_fontSize : font.pointSize());
|
||||
font.setWeight(styled.weight());
|
||||
font.setItalic(styled.italic());
|
||||
}
|
||||
|
||||
QGuiApplication::setFont(font);
|
||||
}
|
||||
|
||||
// --- HiDPI scaling ---
|
||||
|
||||
qreal ThemeManager::uiScale() const
|
||||
{
|
||||
return m_uiScale;
|
||||
}
|
||||
|
||||
void ThemeManager::setUiScale(qreal scale)
|
||||
{
|
||||
if (scale < 0.5) scale = 0.5;
|
||||
if (scale > 4.0) scale = 4.0;
|
||||
if (qFuzzyCompare(scale, m_uiScale))
|
||||
return;
|
||||
m_uiScale = scale;
|
||||
saveSettings();
|
||||
emit uiScaleChanged();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@ class ThemeManager : public QObject
|
|||
Q_PROPERTY(QColor timestampColor READ timestampColor NOTIFY paletteChanged)
|
||||
Q_PROPERTY(QColor deadlineColor READ deadlineColor NOTIFY paletteChanged)
|
||||
|
||||
// Font properties
|
||||
Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontChanged)
|
||||
Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontChanged)
|
||||
Q_PROPERTY(QString fontStyle READ fontStyle WRITE setFontStyle NOTIFY fontChanged)
|
||||
Q_PROPERTY(QStringList availableFamilies READ availableFamilies CONSTANT)
|
||||
Q_PROPERTY(QStringList availableStyles READ availableStyles NOTIFY fontChanged)
|
||||
|
||||
// HiDPI scaling
|
||||
Q_PROPERTY(qreal uiScale READ uiScale WRITE setUiScale NOTIFY uiScaleChanged)
|
||||
|
||||
public:
|
||||
explicit ThemeManager(QObject *parent = nullptr);
|
||||
|
||||
|
|
@ -93,11 +103,29 @@ public:
|
|||
QColor timestampColor() const;
|
||||
QColor deadlineColor() const;
|
||||
|
||||
// Font accessors
|
||||
QString fontFamily() const;
|
||||
void setFontFamily(const QString &family);
|
||||
int fontSize() const;
|
||||
void setFontSize(int size);
|
||||
QString fontStyle() const;
|
||||
void setFontStyle(const QString &style);
|
||||
QStringList availableFamilies() const;
|
||||
QStringList availableStyles() const;
|
||||
Q_INVOKABLE void resetFont();
|
||||
|
||||
// HiDPI scaling
|
||||
qreal uiScale() const;
|
||||
void setUiScale(qreal scale);
|
||||
|
||||
void applyPalette();
|
||||
void applyFont();
|
||||
|
||||
signals:
|
||||
void modeChanged();
|
||||
void paletteChanged();
|
||||
void fontChanged();
|
||||
void uiScaleChanged();
|
||||
|
||||
private:
|
||||
QPalette paletteFromScheme(const QString &path) const;
|
||||
|
|
@ -110,5 +138,9 @@ private:
|
|||
|
||||
QString m_mode;
|
||||
QPalette m_palette;
|
||||
QString m_fontFamily;
|
||||
int m_fontSize;
|
||||
QString m_fontStyle;
|
||||
qreal m_uiScale;
|
||||
static constexpr const char *SCHEMES_DIR = "/usr/share/color-schemes";
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue