Files
DBClient/app/ui/preferences_dialog.py
T
nngoandClaude Sonnet 4.6 06b951daf7 feat: dark/light theme toggle (Catppuccin Frappé ↔ Latte)
- app/config/theme.py: palette definitions for both themes + apply_qss()
  helper used by both main.py (startup) and MainWindow (live toggle)
- resources/style_light.qss: complete Catppuccin Latte QSS
- View menu: ☀️/🌙 Switch Theme action (Ctrl+Shift+T) toggles live
- Preferences → Appearance: Color theme dropdown persists choice
- SQLHighlighter: _build_rules() reads get_palette(); update_theme()
  rebuilds rules and rehighlights all open editors on switch
- SqlCompleter: popup stylesheet reads get_palette() via
  _apply_popup_style(); update_theme() called on switch
- EditableTableModel: dirty/delete cell colors read get_palette() live
- ResultTableModel: NULL_COLOR property reads get_palette() live
- TableViewer: apply_settings() refreshes frozen-view border + repaints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:11:05 -04:00

153 lines
5.9 KiB
Python

"""Application Preferences dialog."""
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QFormLayout, QGroupBox,
QComboBox, QSpinBox, QCheckBox, QDialogButtonBox, QLabel,
)
from PyQt6.QtCore import pyqtSignal
from app.config.settings import get_settings
_FONT_FAMILIES = ["Consolas", "JetBrains Mono", "Fira Code", "Cascadia Code", "Courier New"]
_PAGE_SIZES = [50, 100, 500, 1000, 0] # 0 → All
_PAGE_LABELS = ["50", "100", "500", "1 000", "All"]
_THEMES = [("dark", "🌙 Dark (Catppuccin Frappé)"),
("light", "☀️ Light (Catppuccin Latte)")]
class PreferencesDialog(QDialog):
settings_applied = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Preferences")
self.setMinimumWidth(400)
self.resize(420, 380)
self._build_ui()
self._load()
def _build_ui(self):
root = QVBoxLayout(self)
root.setSpacing(12)
root.setContentsMargins(16, 16, 16, 16)
# ── SQL Editor ────────────────────────────────────────────────────────
editor_box = QGroupBox("SQL Editor")
ef = QFormLayout(editor_box)
ef.setSpacing(8)
self._font_family = QComboBox()
self._font_family.addItems(_FONT_FAMILIES)
ef.addRow("Font family:", self._font_family)
self._font_size = QSpinBox()
self._font_size.setRange(8, 28)
self._font_size.setSuffix(" pt")
ef.addRow("Font size:", self._font_size)
self._word_wrap = QCheckBox("Word wrap")
ef.addRow("", self._word_wrap)
root.addWidget(editor_box)
# ── Results ───────────────────────────────────────────────────────────
results_box = QGroupBox("Results")
rf = QFormLayout(results_box)
rf.setSpacing(8)
self._page_size = QComboBox()
for lbl in _PAGE_LABELS:
self._page_size.addItem(lbl)
rf.addRow("Default page size:", self._page_size)
self._timeout = QSpinBox()
self._timeout.setRange(5, 600)
self._timeout.setSuffix(" s")
rf.addRow("Query timeout:", self._timeout)
root.addWidget(results_box)
# ── Appearance ────────────────────────────────────────────────────────
appear_box = QGroupBox("Appearance")
af = QFormLayout(appear_box)
af.setSpacing(8)
self._theme = QComboBox()
for _, label in _THEMES:
self._theme.addItem(label)
af.addRow("Color theme:", self._theme)
root.addWidget(appear_box)
# ── General ───────────────────────────────────────────────────────────
general_box = QGroupBox("General")
gf = QFormLayout(general_box)
gf.setSpacing(8)
self._max_history = QSpinBox()
self._max_history.setRange(50, 10_000)
self._max_history.setSingleStep(100)
gf.addRow("Max history entries:", self._max_history)
self._auto_commit = QCheckBox("Auto-commit queries")
gf.addRow("", self._auto_commit)
root.addWidget(general_box)
root.addStretch()
# ── Buttons ───────────────────────────────────────────────────────────
note = QLabel("Theme and font changes take effect immediately.")
note.setObjectName("statusLabel")
root.addWidget(note)
bb = QDialogButtonBox(
QDialogButtonBox.StandardButton.Cancel |
QDialogButtonBox.StandardButton.Apply |
QDialogButtonBox.StandardButton.Ok
)
bb.rejected.connect(self.reject)
bb.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(self._apply)
bb.accepted.connect(self._ok)
root.addWidget(bb)
# ── Load / save ───────────────────────────────────────────────────────────
def _load(self):
s = get_settings()
family = s.get("font_family", "Consolas")
idx = self._font_family.findText(family)
self._font_family.setCurrentIndex(max(idx, 0))
self._font_size.setValue(s.get("font_size", 13))
self._word_wrap.setChecked(s.get("word_wrap", False))
page = s.get("result_page_size", 100)
pi = _PAGE_SIZES.index(page) if page in _PAGE_SIZES else 1
self._page_size.setCurrentIndex(pi)
self._timeout.setValue(s.get("query_timeout", 60))
self._max_history.setValue(s.get("max_history", 500))
self._auto_commit.setChecked(s.get("auto_commit", True))
theme_name = s.get("theme", "dark")
ti = next((i for i, (k, _) in enumerate(_THEMES) if k == theme_name), 0)
self._theme.setCurrentIndex(ti)
def _apply(self):
s = get_settings()
s.set("font_family", self._font_family.currentText())
s.set("font_size", self._font_size.value())
s.set("word_wrap", self._word_wrap.isChecked())
s.set("result_page_size", _PAGE_SIZES[self._page_size.currentIndex()])
s.set("query_timeout", self._timeout.value())
s.set("max_history", self._max_history.value())
s.set("auto_commit", self._auto_commit.isChecked())
s.set("theme", _THEMES[self._theme.currentIndex()][0])
s.save()
self.settings_applied.emit()
def _ok(self):
self._apply()
self.accept()