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
+599
View File
@@ -0,0 +1,599 @@
/* ═══════════════════════════════════════════════════════════════════════════
DBClient — Light Theme (Catppuccin Latte palette)
═══════════════════════════════════════════════════════════════════════════ */
/* ── Variables via flat tokens ─────────────────────────────────────────────
base #eff1f5 crust #dce0e8
surface0 #ccd0da surface1 #bcc0cc surface2 #acb0be
overlay #9ca0b0 subtext #6c6f85 text #4c4f69
blue #1e66f5 lavender #7287fd sapphire #209fb5
green #40a02b teal #179299 sky #04a5e5
mauve #8839ef pink #ea76cb red #d20f39
peach #fe640b yellow #df8e1d
─────────────────────────────────────────────────────────────────────── */
/* ── Global reset ───────────────────────────────────────────────────────── */
* {
outline: none;
}
QMainWindow, QDialog {
background-color: #eff1f5;
color: #4c4f69;
}
QWidget {
background-color: #eff1f5;
color: #4c4f69;
font-family: "Segoe UI", "Inter", sans-serif;
font-size: 10pt;
}
/* ── Menu bar ───────────────────────────────────────────────────────────── */
QMenuBar {
background-color: #dce0e8;
color: #4c4f69;
border-bottom: 1px solid #ccd0da;
padding: 2px 0;
}
QMenuBar::item {
padding: 4px 12px;
border-radius: 4px;
}
QMenuBar::item:selected {
background-color: #ccd0da;
}
QMenu {
background-color: #e6e9ef;
border: 1px solid #bcc0cc;
border-radius: 6px;
padding: 4px;
}
QMenu::item {
padding: 6px 24px 6px 12px;
border-radius: 4px;
color: #4c4f69;
}
QMenu::item:selected {
background-color: #ccd0da;
color: #4c4f69;
}
QMenu::separator {
height: 1px;
background: #bcc0cc;
margin: 4px 8px;
}
/* ── Status bar ─────────────────────────────────────────────────────────── */
QStatusBar {
background-color: #dce0e8;
border-top: 1px solid #ccd0da;
color: #6c6f85;
font-size: 9pt;
padding: 2px 8px;
}
/* ── Scrollbars ─────────────────────────────────────────────────────────── */
QScrollBar:vertical {
background: #eff1f5;
width: 10px;
margin: 0;
}
QScrollBar::handle:vertical {
background: #bcc0cc;
border-radius: 5px;
min-height: 24px;
}
QScrollBar::handle:vertical:hover {
background: #acb0be;
}
QScrollBar:horizontal {
background: #eff1f5;
height: 10px;
}
QScrollBar::handle:horizontal {
background: #bcc0cc;
border-radius: 5px;
min-width: 24px;
}
QScrollBar::handle:horizontal:hover {
background: #acb0be;
}
QScrollBar::add-line, QScrollBar::sub-line { height: 0; width: 0; }
QScrollBar::add-page, QScrollBar::sub-page { background: transparent; }
/* ── Splitter ────────────────────────────────────────────────────────────── */
QSplitter::handle {
background-color: #ccd0da;
}
QSplitter::handle:horizontal { width: 2px; }
QSplitter::handle:vertical { height: 2px; }
QSplitter::handle:hover {
background-color: #1e66f5;
}
/* ── Sidebar ─────────────────────────────────────────────────────────────── */
#sidebarHeader {
background-color: #e6e9ef;
border-bottom: 1px solid #ccd0da;
}
#sidebarTitle {
color: #1e66f5;
font-size: 11pt;
font-weight: 600;
}
#newConnBtn {
background-color: #ccd0da;
color: #1e66f5;
border: 1px solid #bcc0cc;
border-radius: 6px;
font-size: 14pt;
padding: 0;
}
#newConnBtn:hover {
background-color: #bcc0cc;
color: #7287fd;
}
/* ── Tree widget ─────────────────────────────────────────────────────────── */
QTreeWidget {
background-color: #e6e9ef;
border: none;
color: #4c4f69;
font-size: 10pt;
show-decoration-selected: 1;
}
QTreeWidget::item {
padding: 3px 4px;
border-radius: 4px;
}
QTreeWidget::item:hover {
background-color: #d8dce8;
}
QTreeWidget::item:selected {
background-color: #ccd0da;
color: #4c4f69;
}
QTreeWidget::branch {
background: transparent;
}
/* ── Tab widget ──────────────────────────────────────────────────────────── */
QTabWidget::pane {
border: none;
border-top: 1px solid #ccd0da;
background-color: #eff1f5;
}
QTabBar {
background-color: #e6e9ef;
}
QTabBar::tab {
background-color: #e6e9ef;
color: #6c6f85;
padding: 7px 16px;
border: none;
border-right: 1px solid #ccd0da;
font-size: 10pt;
}
QTabBar::tab:selected {
background-color: #eff1f5;
color: #4c4f69;
border-bottom: 2px solid #1e66f5;
}
QTabBar::tab:hover:!selected {
background-color: #d0d4e0;
color: #4c4f69;
}
QTabBar::close-button {
subcontrol-position: right;
padding: 2px;
}
/* ── Table view ──────────────────────────────────────────────────────────── */
QTableView, QTableWidget {
background-color: #eff1f5;
alternate-background-color: #e6e9ef;
gridline-color: #ccd0da;
color: #4c4f69;
border: none;
selection-background-color: #ccd0da;
selection-color: #4c4f69;
font-size: 10pt;
}
QTableView::item, QTableWidget::item {
padding: 2px 6px;
}
QHeaderView {
background-color: #e6e9ef;
}
QHeaderView::section {
background-color: #e6e9ef;
color: #1e66f5;
border: none;
border-right: 1px solid #ccd0da;
border-bottom: 1px solid #ccd0da;
padding: 4px 8px;
font-weight: 600;
}
QHeaderView::section:hover {
background-color: #d0d4e0;
}
/* ── Plain text edit (SQL editor body) ───────────────────────────────────── */
QPlainTextEdit {
background-color: #eff1f5;
color: #4c4f69;
border: none;
selection-background-color: #bcc0cc;
font-family: "Consolas", "JetBrains Mono", "Courier New", monospace;
font-size: 13pt;
line-height: 1.5;
}
/* ── Line number gutter ──────────────────────────────────────────────────── */
LineNumberArea {
background-color: #dce0e8;
}
/* ── Buttons ─────────────────────────────────────────────────────────────── */
QPushButton {
background-color: #ccd0da;
color: #4c4f69;
border: 1px solid #bcc0cc;
border-radius: 6px;
padding: 5px 14px;
font-size: 10pt;
}
QPushButton:hover {
background-color: #bcc0cc;
border-color: #acb0be;
}
QPushButton:pressed {
background-color: #d0d4de;
}
QPushButton:disabled {
color: #acb0be;
background-color: #d0d4de;
border-color: #ccd0da;
}
#runBtn {
background-color: #40a02b;
color: #eff1f5;
font-weight: 700;
border: none;
}
#runBtn:hover {
background-color: #179299;
}
#stopBtn {
background-color: #d20f39;
color: #eff1f5;
font-weight: 700;
border: none;
}
#stopBtn:hover {
background-color: #e5394b;
}
#commitBtn {
background-color: #40a02b;
color: #eff1f5;
font-weight: 700;
border: none;
}
#rollbackBtn {
background-color: #fe640b;
color: #eff1f5;
font-weight: 700;
border: none;
}
#crudAddBtn {
background-color: #ccd0da;
color: #40a02b;
border: 1px solid #40a02b;
font-weight: 600;
}
#crudAddBtn:hover {
background-color: #40a02b;
color: #eff1f5;
}
#crudDeleteBtn {
background-color: #ccd0da;
color: #d20f39;
border: 1px solid #d20f39;
font-weight: 600;
}
#crudDeleteBtn:hover {
background-color: #d20f39;
color: #eff1f5;
}
/* ── Line edit ───────────────────────────────────────────────────────────── */
QLineEdit {
background-color: #e6e9ef;
color: #4c4f69;
border: 1px solid #bcc0cc;
border-radius: 6px;
padding: 5px 10px;
font-size: 10pt;
selection-background-color: #1e66f5;
selection-color: #eff1f5;
}
QLineEdit:focus {
border-color: #1e66f5;
}
QLineEdit::placeholder {
color: #acb0be;
}
/* ── Combo box ───────────────────────────────────────────────────────────── */
QComboBox {
background-color: #e6e9ef;
color: #4c4f69;
border: 1px solid #bcc0cc;
border-radius: 6px;
padding: 5px 10px;
font-size: 10pt;
}
QComboBox:focus {
border-color: #1e66f5;
}
QComboBox::drop-down {
border: none;
width: 24px;
}
QComboBox QAbstractItemView {
background-color: #e6e9ef;
color: #4c4f69;
border: 1px solid #bcc0cc;
selection-background-color: #ccd0da;
}
/* ── Spin box ────────────────────────────────────────────────────────────── */
QSpinBox {
background-color: #e6e9ef;
color: #4c4f69;
border: 1px solid #bcc0cc;
border-radius: 6px;
padding: 4px 8px;
}
QSpinBox:focus { border-color: #1e66f5; }
QSpinBox::up-button, QSpinBox::down-button {
background-color: #bcc0cc;
border: none;
border-radius: 3px;
width: 16px;
}
/* ── Check box ───────────────────────────────────────────────────────────── */
QCheckBox {
color: #4c4f69;
spacing: 8px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
border: 2px solid #bcc0cc;
border-radius: 4px;
background: #e6e9ef;
}
QCheckBox::indicator:checked {
background-color: #1e66f5;
border-color: #1e66f5;
}
/* ── Dialog ──────────────────────────────────────────────────────────────── */
QDialog {
background-color: #eff1f5;
}
QDialogButtonBox QPushButton {
min-width: 80px;
}
/* ── Form layout labels ──────────────────────────────────────────────────── */
QFormLayout QLabel {
color: #6c6f85;
}
/* ── Tab widget in dialogs ───────────────────────────────────────────────── */
QTabWidget#dialogTabs::pane {
border: 1px solid #ccd0da;
border-radius: 6px;
margin-top: -1px;
}
/* ── Group box ───────────────────────────────────────────────────────────── */
QGroupBox {
border: 1px solid #bcc0cc;
border-radius: 6px;
margin-top: 1em;
color: #6c6f85;
font-weight: 600;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 4px;
}
/* ── Progress bar ────────────────────────────────────────────────────────── */
QProgressBar {
background-color: #ccd0da;
border: 1px solid #bcc0cc;
border-radius: 6px;
height: 10px;
text-align: center;
}
QProgressBar::chunk {
background-color: #1e66f5;
border-radius: 5px;
}
/* ── Dock widget ─────────────────────────────────────────────────────────── */
QDockWidget {
color: #4c4f69;
titlebar-close-icon: none;
font-weight: 600;
}
QDockWidget::title {
background-color: #e6e9ef;
padding: 6px;
border-bottom: 1px solid #ccd0da;
}
/* ── Tool button ─────────────────────────────────────────────────────────── */
QToolButton {
background-color: #ccd0da;
color: #4c4f69;
border: 1px solid #bcc0cc;
border-radius: 5px;
padding: 4px 8px;
font-size: 13pt;
}
QToolButton:hover {
background-color: #bcc0cc;
}
/* ── Message box ─────────────────────────────────────────────────────────── */
QMessageBox {
background-color: #eff1f5;
}
QMessageBox QLabel {
color: #4c4f69;
font-size: 10pt;
}
/* ── Empty workspace label ───────────────────────────────────────────────── */
#emptyLabel {
color: #acb0be;
font-size: 14pt;
line-height: 2;
}
/* ── Status label in results toolbar ────────────────────────────────────── */
#statusLabel {
color: #6c6f85;
font-size: 9pt;
padding-left: 4px;
}
/* ── DB label in SQL editor toolbar ─────────────────────────────────────── */
#dbLabel {
color: #9ca0b0;
font-size: 9pt;
padding-right: 4px;
}
/* ── Workspace tab widget ────────────────────────────────────────────────── */
#workspace > QTabBar::tab {
min-width: 120px;
}
/* ── Horizontal separator line in dialog ────────────────────────────────── */
QFrame[frameShape="4"] { /* HLine */
color: #ccd0da;
margin: 4px 0;
}
/* ── Structure view title ────────────────────────────────────────────────── */
#structureTitle {
color: #7287fd;
}
/* ── Pagination bar ──────────────────────────────────────────────────────── */
#paginationBar {
background-color: #e6e9ef;
border-top: 1px solid #bcc0cc;
min-height: 38px;
}
#pgBtn {
background-color: #d0d4de;
color: #4c4f69;
border: 1px solid #bcc0cc;
border-radius: 5px;
padding: 4px 10px;
font-size: 9pt;
min-width: 52px;
}
#pgBtn:hover {
background-color: #ccd0da;
border-color: #acb0be;
}
#pgBtn:disabled {
color: #bcc0cc;
background-color: #eff1f5;
border-color: #ccd0da;
}
#pageLbl {
color: #1e66f5;
font-size: 9pt;
font-weight: 600;
padding: 0 6px;
}
#rowCountLbl {
color: #6c6f85;
font-size: 9pt;
padding-left: 8px;
}