110 lines
3.8 KiB
Python
110 lines
3.8 KiB
Python
"""
|
|
Settings dialog.
|
|
|
|
Built dynamically from app.config.SETTINGS_SCHEMA, grouped by section
|
|
(Database, JIRA, ...). When a new service adds settings to that
|
|
schema, they show up here automatically - no UI changes needed.
|
|
|
|
The number of settings has grown a lot, so the group content lives in
|
|
a QScrollArea and the dialog's height is capped to the actual screen's
|
|
available space - Save/Cancel stay outside the scroll area, pinned at
|
|
the bottom, so they're always reachable no matter how tall the content
|
|
gets or how small the screen is.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from collections import defaultdict
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QDialog,
|
|
QVBoxLayout,
|
|
QFormLayout,
|
|
QLineEdit,
|
|
QPushButton,
|
|
QHBoxLayout,
|
|
QGroupBox,
|
|
QLabel,
|
|
QMessageBox,
|
|
QScrollArea,
|
|
QWidget,
|
|
QApplication,
|
|
)
|
|
|
|
from app import config
|
|
|
|
|
|
class SettingsDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Settings")
|
|
|
|
self._fields: dict[str, QLineEdit] = {}
|
|
current_values = config.load_settings()
|
|
|
|
# Group schema entries by their group label, preserving order
|
|
groups: dict[str, list[tuple[str, str, bool]]] = defaultdict(list)
|
|
for key, (label, group, is_secret) in config.SETTINGS_SCHEMA.items():
|
|
groups[group].append((key, label, is_secret))
|
|
|
|
outer_layout = QVBoxLayout(self)
|
|
outer_layout.addWidget(
|
|
QLabel("Changes are saved to your .env file and applied immediately.")
|
|
)
|
|
|
|
# All the group boxes live inside a scrollable area, since the
|
|
# full list no longer reliably fits on smaller screens.
|
|
scroll_content = QWidget()
|
|
content_layout = QVBoxLayout(scroll_content)
|
|
for group_name, entries in groups.items():
|
|
box = QGroupBox(group_name)
|
|
form = QFormLayout(box)
|
|
for key, label, is_secret in entries:
|
|
field = QLineEdit(current_values.get(key, ""))
|
|
if is_secret:
|
|
field.setEchoMode(QLineEdit.EchoMode.Password)
|
|
form.addRow(label, field)
|
|
self._fields[key] = field
|
|
content_layout.addWidget(box)
|
|
content_layout.addStretch()
|
|
|
|
scroll_area = QScrollArea()
|
|
scroll_area.setWidget(scroll_content)
|
|
scroll_area.setWidgetResizable(True)
|
|
outer_layout.addWidget(scroll_area, stretch=1)
|
|
|
|
button_row = QHBoxLayout()
|
|
save_button = QPushButton("Save")
|
|
cancel_button = QPushButton("Cancel")
|
|
save_button.clicked.connect(self._on_save)
|
|
cancel_button.clicked.connect(self.reject)
|
|
button_row.addStretch()
|
|
button_row.addWidget(cancel_button)
|
|
button_row.addWidget(save_button)
|
|
outer_layout.addLayout(button_row)
|
|
|
|
self._size_to_fit_screen()
|
|
|
|
def _size_to_fit_screen(self) -> None:
|
|
"""Start at a comfortable size, but never taller/wider than the
|
|
actual screen has room for - whatever's left just scrolls."""
|
|
width, height = 560, 720
|
|
|
|
screen = self.screen() or QApplication.primaryScreen()
|
|
if screen is not None:
|
|
available = screen.availableGeometry()
|
|
# Leave a little breathing room so window chrome/taskbars don't
|
|
# push Save off-screen either.
|
|
height = min(height, available.height() - 80)
|
|
width = min(width, available.width() - 80)
|
|
|
|
self.resize(max(width, 400), max(height, 300))
|
|
|
|
def _on_save(self) -> None:
|
|
values = {key: field.text().strip() for key, field in self._fields.items()}
|
|
try:
|
|
config.save_settings(values)
|
|
except OSError as exc:
|
|
QMessageBox.critical(self, "Couldn't save settings", str(exc))
|
|
return
|
|
self.accept()
|