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>
This commit is contained in:
2026-05-22 09:11:05 -04:00
co-authored by Claude Sonnet 4.6
parent 6cff9557b3
commit 06b951daf7
11 changed files with 857 additions and 76 deletions
+23 -14
View File
@@ -12,6 +12,7 @@ from PyQt6.QtWidgets import QApplication, QListWidget, QListWidgetItem
from app.ui.syntax_highlighter import _KEYWORDS, _TYPES, _FUNCTIONS
from app.utils.worker import SchemaWorker
from app.config.theme import get_palette
# Combined, deduplicated keyword list used as the static completion pool.
SQL_KEYWORDS: list[str] = list(dict.fromkeys(_KEYWORDS + _TYPES + _FUNCTIONS))
@@ -51,24 +52,28 @@ class _CompletionPopup(QListWidget):
self.setFont(font)
self._row_h = QFontMetrics(font).height() + 6
self.setStyleSheet("""
QListWidget {
background: #292c3c;
color: #c6d0f5;
border: 1px solid #51576d;
self._apply_popup_style()
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
def _apply_popup_style(self):
p = get_palette()
self.setStyleSheet(f"""
QListWidget {{
background: {p['popup_bg']};
color: {p['text']};
border: 1px solid {p['popup_border']};
border-radius: 4px;
padding: 2px;
outline: none;
}
QListWidget::item { padding: 2px 8px; }
QListWidget::item:selected {
background: #414559;
color: #c6d0f5;
}
QListWidget::item:hover { background: #363a4f; }
}}
QListWidget::item {{ padding: 2px 8px; }}
QListWidget::item:selected {{
background: {p['popup_selected']};
color: {p['text']};
}}
QListWidget::item:hover {{ background: {p['popup_hover']}; }}
""")
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.itemClicked.connect(
lambda item: self._editor._completer._accept_completion(item.text())
@@ -142,6 +147,10 @@ class SqlCompleter(QObject):
# ── Public API ────────────────────────────────────────────────────────────
def update_theme(self) -> None:
"""Reapply popup colors after a theme change."""
self._popup._apply_popup_style()
def set_context(self, driver, database: str) -> None:
"""Update the active driver/database and refresh schema cache."""
self._driver = driver