Files
LinedanceAfspiller/linedance-app/ui/settings_dialog.py
2026-04-13 07:23:37 +02:00

385 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
settings_dialog.py — Indstillinger for LineDance Player.
Gemmes via QSettings og læses ved opstart.
"""
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QComboBox, QSpinBox, QCheckBox, QFrame,
QTabWidget, QWidget, QFileDialog, QGroupBox, QFormLayout,
)
from PyQt6.QtCore import Qt, QSettings
SETTINGS_KEY_THEME = "appearance/dark_theme"
SETTINGS_KEY_DEMO_SEC = "playback/demo_seconds"
SETTINGS_KEY_DEMO_FADE = "playback/demo_fade_seconds"
SETTINGS_KEY_VOLUME = "playback/volume"
SETTINGS_KEY_MAIL_CLIENT = "mail/client"
SETTINGS_KEY_MAIL_PATH = "mail/custom_path"
SETTINGS_KEY_AUTO_LOGIN = "online/auto_login"
SETTINGS_KEY_USERNAME = "online/username"
SETTINGS_KEY_PASSWORD = "online/password"
SETTINGS_KEY_SERVER_URL = "online/server_url"
SETTINGS_KEY_LANGUAGE = "appearance/language"
SETTINGS_KEY_BETWEEN_SEC = "playback/between_seconds"
SETTINGS_KEY_WORKSHOP_MIN = "playback/workshop_minutes"
def load_settings() -> dict:
s = QSettings("LineDance", "Player")
return {
"dark_theme": s.value(SETTINGS_KEY_THEME, True, type=bool),
"demo_seconds": s.value(SETTINGS_KEY_DEMO_SEC, 10, type=int),
"demo_fade_seconds": s.value(SETTINGS_KEY_DEMO_FADE, 5, type=int),
"volume": s.value(SETTINGS_KEY_VOLUME, 78, type=int),
"mail_client": s.value(SETTINGS_KEY_MAIL_CLIENT, "auto"),
"mail_path": s.value(SETTINGS_KEY_MAIL_PATH, ""),
"auto_login": s.value(SETTINGS_KEY_AUTO_LOGIN, False, type=bool),
"username": s.value(SETTINGS_KEY_USERNAME, ""),
"password": s.value(SETTINGS_KEY_PASSWORD, ""),
"server_url": s.value(SETTINGS_KEY_SERVER_URL, "http://localhost:8000"),
"language": s.value(SETTINGS_KEY_LANGUAGE, "da"),
"between_seconds": s.value(SETTINGS_KEY_BETWEEN_SEC, 60, type=int),
"workshop_minutes": s.value(SETTINGS_KEY_WORKSHOP_MIN, 10, type=int),
}
def save_settings(values: dict):
s = QSettings("LineDance", "Player")
s.setValue(SETTINGS_KEY_THEME, values.get("dark_theme", True))
s.setValue(SETTINGS_KEY_DEMO_SEC, values.get("demo_seconds", 10))
s.setValue(SETTINGS_KEY_DEMO_FADE, values.get("demo_fade_seconds", 5))
s.setValue(SETTINGS_KEY_VOLUME, values.get("volume", 78))
s.setValue(SETTINGS_KEY_MAIL_CLIENT, values.get("mail_client", "auto"))
s.setValue(SETTINGS_KEY_MAIL_PATH, values.get("mail_path", ""))
s.setValue(SETTINGS_KEY_AUTO_LOGIN, values.get("auto_login", False))
s.setValue(SETTINGS_KEY_USERNAME, values.get("username", ""))
s.setValue(SETTINGS_KEY_PASSWORD, values.get("password", ""))
s.setValue(SETTINGS_KEY_SERVER_URL, values.get("server_url", "http://localhost:8000"))
s.setValue(SETTINGS_KEY_LANGUAGE, values.get("language", "da"))
s.setValue(SETTINGS_KEY_BETWEEN_SEC, values.get("between_seconds", 60))
s.setValue(SETTINGS_KEY_WORKSHOP_MIN,values.get("workshop_minutes", 10))
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Indstillinger")
self.setMinimumWidth(480)
self.setModal(True)
self._values = load_settings()
self._build_ui()
self._populate()
def _build_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(12)
tabs = QTabWidget()
tabs.setStyleSheet("""
QTabBar::tab {
padding: 6px 14px;
font-size: 13px;
color: #9aa0b0;
background: #1e2128;
border: none;
min-width: 80px;
}
QTabBar::tab:selected {
color: #e0e4f0;
background: #2a2d36;
border-bottom: 2px solid #e8a020;
}
QTabBar::tab:hover {
color: #e0e4f0;
background: #252830;
}
""")
tabs.addTab(self._build_appearance_tab(), "Udseende")
tabs.addTab(self._build_playback_tab(), "Afspilning")
tabs.addTab(self._build_mail_tab(), "Mail")
tabs.addTab(self._build_online_tab(), "Online")
tabs.addTab(self._build_language_tab(), "Sprog")
layout.addWidget(tabs)
# Knapper
btn_row = QHBoxLayout()
btn_row.addStretch()
btn_cancel = QPushButton("Annuller")
btn_cancel.clicked.connect(self.reject)
btn_row.addWidget(btn_cancel)
btn_save = QPushButton("💾 Gem indstillinger")
btn_save.setObjectName("btn_play")
btn_save.setDefault(True)
btn_save.clicked.connect(self._save_and_close)
btn_row.addWidget(btn_save)
layout.addLayout(btn_row)
# ── Fane: Udseende ────────────────────────────────────────────────────────
def _build_appearance_tab(self) -> QWidget:
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(12)
grp = QGroupBox("Standard tema")
grp_layout = QVBoxLayout(grp)
self._chk_dark = QCheckBox("Start med mørkt tema")
grp_layout.addWidget(self._chk_dark)
note = QLabel("Du kan altid skifte tema mens programmet kører via topbar-knappen.")
note.setObjectName("result_count")
note.setWordWrap(True)
grp_layout.addWidget(note)
layout.addWidget(grp)
layout.addStretch()
return tab
# ── Fane: Afspilning ──────────────────────────────────────────────────────
def _build_playback_tab(self) -> QWidget:
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(12)
grp = QGroupBox("Forspil (▶ N SEK knappen)")
grp_layout = QFormLayout(grp)
self._spin_demo = QSpinBox()
self._spin_demo.setRange(3, 60)
self._spin_demo.setSuffix(" sekunder")
self._spin_demo.setFixedWidth(140)
grp_layout.addRow("Forspil-længde:", self._spin_demo)
self._spin_fade = QSpinBox()
self._spin_fade.setRange(0, 15)
self._spin_fade.setSuffix(" sekunder (0 = ingen fade)")
self._spin_fade.setFixedWidth(220)
self._spin_fade.setToolTip(
"Fade-out tilføjes til forspillets længde.\n"
"F.eks. 10 sek forspil + 5 sek fade = 15 sek total.\n"
"Sæt til 0 for ingen fade."
)
grp_layout.addRow("Fade-ud:", self._spin_fade)
note = QLabel(
"Forspillet afspiller begyndelsen af sangen så arrangøren kan bekræfte\n"
"at det er den rigtige sang og dans inden eventet starter.\n"
"Fade-ud tilføjes oven i forspillets længde og fades logaritmisk."
)
note.setObjectName("result_count")
note.setWordWrap(True)
grp_layout.addRow(note)
layout.addWidget(grp)
grp2 = QGroupBox("Danseliste-tider ( info-vinduet)")
grp2_layout = QFormLayout(grp2)
self._spin_between = QSpinBox()
self._spin_between.setRange(0, 600)
self._spin_between.setSuffix(" sekunder")
self._spin_between.setFixedWidth(140)
grp2_layout.addRow("Tid mellem musikstykker:", self._spin_between)
self._spin_workshop = QSpinBox()
self._spin_workshop.setRange(0, 120)
self._spin_workshop.setSuffix(" minutter")
self._spin_workshop.setFixedWidth(140)
grp2_layout.addRow("Tid per workshop:", self._spin_workshop)
layout.addWidget(grp2)
layout.addStretch()
return tab
# ── Fane: Mail ────────────────────────────────────────────────────────────
def _build_mail_tab(self) -> QWidget:
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(12)
grp = QGroupBox("Mailklient")
grp_layout = QFormLayout(grp)
self._mail_combo = QComboBox()
self._mail_combo.addItem("Auto-detekter (Thunderbird → Outlook → mailto:)", "auto")
self._mail_combo.addItem("Thunderbird", "thunderbird")
self._mail_combo.addItem("Outlook (Windows)", "outlook")
self._mail_combo.addItem("Brugerdefineret sti", "custom")
self._mail_combo.addItem("Kun mailto: (ingen vedhæftning)", "mailto")
self._mail_combo.currentIndexChanged.connect(self._on_mail_combo_changed)
grp_layout.addRow("Klient:", self._mail_combo)
path_row = QHBoxLayout()
self._mail_path = QLineEdit()
self._mail_path.setPlaceholderText("/usr/bin/thunderbird eller C:\\...\\thunderbird.exe")
path_row.addWidget(self._mail_path)
btn_browse = QPushButton("...")
btn_browse.setFixedWidth(32)
btn_browse.clicked.connect(self._browse_mail_path)
path_row.addWidget(btn_browse)
self._mail_path_row_widget = QWidget()
self._mail_path_row_widget.setLayout(path_row)
grp_layout.addRow("Sti:", self._mail_path_row_widget)
note = QLabel(
"Med Thunderbird og Outlook åbnes et nyt compose-vindue med filen vedhæftet.\n"
"mailto: åbner standard-mailprogrammet men uden automatisk vedhæftning."
)
note.setObjectName("result_count")
note.setWordWrap(True)
grp_layout.addRow(note)
layout.addWidget(grp)
layout.addStretch()
return tab
def _on_mail_combo_changed(self, idx: int):
is_custom = self._mail_combo.currentData() == "custom"
self._mail_path_row_widget.setVisible(is_custom)
def _browse_mail_path(self):
path, _ = QFileDialog.getOpenFileName(self, "Vælg mailklient")
if path:
self._mail_path.setText(path)
# ── Fane: Online ──────────────────────────────────────────────────────────
def _build_online_tab(self) -> QWidget:
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(12)
# Server URL
grp_server = QGroupBox("Server")
grp_server_layout = QFormLayout(grp_server)
self._server_url = QLineEdit()
self._server_url.setPlaceholderText("http://localhost:8000")
grp_server_layout.addRow("API-adresse:", self._server_url)
note_server = QLabel("Adressen på LineDance API-serveren.")
note_server.setObjectName("result_count")
grp_server_layout.addRow(note_server)
layout.addWidget(grp_server)
# Login
grp = QGroupBox("Konto")
grp_layout = QFormLayout(grp)
btn_register = QPushButton("✚ Opret ny konto...")
btn_register.clicked.connect(self._open_register)
grp_layout.addRow(btn_register)
self._chk_auto_login = QCheckBox("Log automatisk ind når programmet starter")
self._chk_auto_login.stateChanged.connect(self._on_auto_login_changed)
grp_layout.addRow(self._chk_auto_login)
self._user_input = QLineEdit()
self._user_input.setPlaceholderText("brugernavn eller e-mail")
grp_layout.addRow("Brugernavn:", self._user_input)
self._pass_input = QLineEdit()
self._pass_input.setEchoMode(QLineEdit.EchoMode.Password)
self._pass_input.setPlaceholderText("••••••••")
grp_layout.addRow("Kodeord:", self._pass_input)
note = QLabel(
"⚠ Kodeordet gemmes lokalt på denne computer.\n"
"Brug kun dette på en personlig maskine."
)
note.setObjectName("result_count")
note.setWordWrap(True)
grp_layout.addRow(note)
layout.addWidget(grp)
layout.addStretch()
return tab
def _open_register(self):
from ui.register_dialog import RegisterDialog
server_url = self._server_url.text().strip() or "http://localhost:8000"
dlg = RegisterDialog(server_url=server_url, parent=self)
dlg.exec()
def _build_language_tab(self) -> QWidget:
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(12)
grp = QGroupBox("Sprog")
grp_layout = QFormLayout(grp)
self._lang_combo = QComboBox()
self._lang_combo.addItem("Dansk", "da")
self._lang_combo.addItem("English", "en")
grp_layout.addRow("Programsprog:", self._lang_combo)
note = QLabel("Sproget anvendes næste gang programmet startes.")
note.setObjectName("result_count")
note.setWordWrap(True)
grp_layout.addRow(note)
layout.addWidget(grp)
layout.addStretch()
return tab
def _on_auto_login_changed(self, state: int):
enabled = state == Qt.CheckState.Checked.value
self._user_input.setEnabled(enabled)
self._pass_input.setEnabled(enabled)
# ── Populer fra gemte værdier ─────────────────────────────────────────────
def _populate(self):
v = self._values
self._chk_dark.setChecked(v.get("dark_theme", True))
self._spin_demo.setValue(v.get("demo_seconds", 10))
self._spin_fade.setValue(v.get("demo_fade_seconds", 5))
self._spin_between.setValue(v.get("between_seconds", 60))
self._spin_workshop.setValue(v.get("workshop_minutes", 10))
# Sprog
lang = v.get("language", "da")
for i in range(self._lang_combo.count()):
if self._lang_combo.itemData(i) == lang:
self._lang_combo.setCurrentIndex(i)
break
# Mail
client = v.get("mail_client", "auto")
for i in range(self._mail_combo.count()):
if self._mail_combo.itemData(i) == client:
self._mail_combo.setCurrentIndex(i)
break
self._mail_path.setText(v.get("mail_path", ""))
self._on_mail_combo_changed(self._mail_combo.currentIndex())
# Online
auto = v.get("auto_login", False)
self._chk_auto_login.setChecked(auto)
self._user_input.setText(v.get("username", ""))
self._pass_input.setText(v.get("password", ""))
self._server_url.setText(v.get("server_url", "http://localhost:8000"))
self._user_input.setEnabled(auto)
self._pass_input.setEnabled(auto)
# ── Gem ───────────────────────────────────────────────────────────────────
def _save_and_close(self):
values = {
"dark_theme": self._chk_dark.isChecked(),
"demo_seconds": self._spin_demo.value(),
"demo_fade_seconds": self._spin_fade.value(),
"between_seconds": self._spin_between.value(),
"workshop_minutes": self._spin_workshop.value(),
"mail_client": self._mail_combo.currentData(),
"mail_path": self._mail_path.text().strip(),
"auto_login": self._chk_auto_login.isChecked(),
"username": self._user_input.text().strip(),
"password": self._pass_input.text(),
"server_url": self._server_url.text().strip() or "http://localhost:8000",
"language": self._lang_combo.currentData(),
}
save_settings(values)
self._values = values
self.accept()
def get_values(self) -> dict:
return self._values