- Autostart: Desktop-Entry wird in ~/.config/autostart/ erstellt - Minimiert starten: App startet direkt im System-Tray - Einstellungen lesen Config direkt, kein CLI-Flag nötig - README.md aktualisiert Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
492 lines
18 KiB
Python
492 lines
18 KiB
Python
"""Einstellungen-Dialog - SIP, Audio, CardDAV und allgemeine Konfiguration."""
|
|
|
|
from PySide6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
|
|
QLineEdit, QPushButton, QComboBox, QCheckBox,
|
|
QTabWidget, QWidget, QSpinBox, QLabel, QFileDialog,
|
|
QListWidget, QListWidgetItem, QGroupBox, QGridLayout,
|
|
)
|
|
from PySide6.QtCore import Signal, Qt
|
|
|
|
|
|
class CardDavAccountDialog(QDialog):
|
|
"""Dialog zum Hinzufügen/Bearbeiten eines CardDAV-Accounts."""
|
|
|
|
def __init__(self, account=None, parent=None):
|
|
super().__init__(parent)
|
|
self._account = account or {}
|
|
self.setWindowTitle(
|
|
"Account bearbeiten" if account else "Neuer CardDAV-Account"
|
|
)
|
|
self.setMinimumWidth(450)
|
|
self.setModal(True)
|
|
self._ui_aufbauen()
|
|
|
|
def _ui_aufbauen(self):
|
|
layout = QVBoxLayout(self)
|
|
form = QFormLayout()
|
|
|
|
self.name_eingabe = QLineEdit(self._account.get("name", ""))
|
|
self.name_eingabe.setPlaceholderText("z.B. Privat, Geschäftlich")
|
|
form.addRow("Name:", self.name_eingabe)
|
|
|
|
self.url_eingabe = QLineEdit(self._account.get("url", ""))
|
|
self.url_eingabe.setPlaceholderText(
|
|
"https://nextcloud.example.com/remote.php/dav/addressbooks/"
|
|
"users/name/contacts/"
|
|
)
|
|
form.addRow("URL:", self.url_eingabe)
|
|
|
|
self.benutzer_eingabe = QLineEdit(
|
|
self._account.get("benutzername", ""))
|
|
self.benutzer_eingabe.setPlaceholderText("Benutzername")
|
|
form.addRow("Benutzer:", self.benutzer_eingabe)
|
|
|
|
self.passwort_eingabe = QLineEdit(self._account.get("passwort", ""))
|
|
self.passwort_eingabe.setEchoMode(QLineEdit.EchoMode.Password)
|
|
self.passwort_eingabe.setPlaceholderText("Passwort oder App-Token")
|
|
form.addRow("Passwort:", self.passwort_eingabe)
|
|
|
|
layout.addLayout(form)
|
|
|
|
btn_layout = QHBoxLayout()
|
|
ok_btn = QPushButton("OK")
|
|
ok_btn.setDefault(True)
|
|
ok_btn.clicked.connect(self.accept)
|
|
abbrechen_btn = QPushButton("Abbrechen")
|
|
abbrechen_btn.clicked.connect(self.reject)
|
|
btn_layout.addStretch()
|
|
btn_layout.addWidget(abbrechen_btn)
|
|
btn_layout.addWidget(ok_btn)
|
|
layout.addLayout(btn_layout)
|
|
|
|
@property
|
|
def account_daten(self):
|
|
"""Account-Dict aus den Eingabefeldern."""
|
|
return {
|
|
"name": self.name_eingabe.text().strip(),
|
|
"url": self.url_eingabe.text().strip(),
|
|
"benutzername": self.benutzer_eingabe.text().strip(),
|
|
"passwort": self.passwort_eingabe.text(),
|
|
}
|
|
|
|
|
|
class EinstellungenDialog(QDialog):
|
|
"""Dialog für Anwendungseinstellungen mit Tabs."""
|
|
|
|
einstellungen_geaendert = Signal()
|
|
|
|
def __init__(self, config_manager, audio_manager=None,
|
|
klingelton_player=None, parent=None):
|
|
super().__init__(parent)
|
|
self._config = config_manager
|
|
self._audio = audio_manager
|
|
self._klingelton = klingelton_player
|
|
self.setWindowTitle("Einstellungen")
|
|
self.setMinimumWidth(500)
|
|
self.setModal(True)
|
|
self._ui_aufbauen()
|
|
self._werte_laden()
|
|
|
|
def _ui_aufbauen(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
self.tabs = QTabWidget()
|
|
layout.addWidget(self.tabs)
|
|
|
|
self._sip_tab_erstellen()
|
|
self._audio_tab_erstellen()
|
|
self._allgemein_tab_erstellen()
|
|
self._carddav_tab_erstellen()
|
|
self._blf_tab_erstellen()
|
|
|
|
# Buttons
|
|
btn_layout = QHBoxLayout()
|
|
self.speichern_btn = QPushButton("Speichern")
|
|
self.abbrechen_btn = QPushButton("Abbrechen")
|
|
self.speichern_btn.clicked.connect(self._speichern)
|
|
self.abbrechen_btn.clicked.connect(self.reject)
|
|
btn_layout.addStretch()
|
|
btn_layout.addWidget(self.abbrechen_btn)
|
|
btn_layout.addWidget(self.speichern_btn)
|
|
layout.addLayout(btn_layout)
|
|
|
|
def _sip_tab_erstellen(self):
|
|
"""Tab für SIP-Einstellungen."""
|
|
widget = QWidget()
|
|
form = QFormLayout(widget)
|
|
|
|
self.sip_server = QLineEdit()
|
|
self.sip_server.setPlaceholderText("192.168.154.242")
|
|
form.addRow("Server:", self.sip_server)
|
|
|
|
self.sip_port = QSpinBox()
|
|
self.sip_port.setRange(1, 65535)
|
|
self.sip_port.setValue(5060)
|
|
form.addRow("Port:", self.sip_port)
|
|
|
|
self.sip_extension = QLineEdit()
|
|
self.sip_extension.setPlaceholderText("200")
|
|
form.addRow("Extension:", self.sip_extension)
|
|
|
|
self.sip_passwort = QLineEdit()
|
|
self.sip_passwort.setEchoMode(QLineEdit.EchoMode.Password)
|
|
form.addRow("Passwort:", self.sip_passwort)
|
|
|
|
self.sip_transport = QComboBox()
|
|
self.sip_transport.addItems(["UDP", "TCP"])
|
|
form.addRow("Transport:", self.sip_transport)
|
|
|
|
self.tabs.addTab(widget, "SIP")
|
|
|
|
def _audio_tab_erstellen(self):
|
|
"""Tab für Audio-Einstellungen (PipeWire/KDE-Geräte)."""
|
|
widget = QWidget()
|
|
form = QFormLayout(widget)
|
|
|
|
hinweis = QLabel(
|
|
"Geräte werden wie in KDE-Audioeinstellungen angezeigt.")
|
|
hinweis.setStyleSheet(
|
|
"color: #888; font-size: 11px; margin-bottom: 4px;")
|
|
form.addRow(hinweis)
|
|
|
|
self.aufnahme_combo = QComboBox()
|
|
form.addRow("Mikrofon:", self.aufnahme_combo)
|
|
|
|
self.wiedergabe_combo = QComboBox()
|
|
form.addRow("Lautsprecher:", self.wiedergabe_combo)
|
|
|
|
self.klingelton_geraet_combo = QComboBox()
|
|
form.addRow("Klingelton-Gerät:", self.klingelton_geraet_combo)
|
|
|
|
aktualisieren_btn = QPushButton("Geräte aktualisieren")
|
|
aktualisieren_btn.clicked.connect(self._audiogeraete_aktualisieren)
|
|
form.addRow("", aktualisieren_btn)
|
|
|
|
self.tabs.addTab(widget, "Audio")
|
|
|
|
def _allgemein_tab_erstellen(self):
|
|
"""Tab für allgemeine Einstellungen."""
|
|
widget = QWidget()
|
|
form = QFormLayout(widget)
|
|
|
|
self.tray_check = QCheckBox(
|
|
"Beim Schließen in System-Tray minimieren")
|
|
form.addRow(self.tray_check)
|
|
|
|
self.autostart_check = QCheckBox(
|
|
"Beim Systemstart automatisch starten")
|
|
form.addRow(self.autostart_check)
|
|
|
|
self.minimiert_starten_check = QCheckBox(
|
|
"Minimiert starten (direkt in System-Tray)")
|
|
form.addRow(self.minimiert_starten_check)
|
|
|
|
# Klingelton-Datei
|
|
klingelton_layout = QHBoxLayout()
|
|
self.klingelton_eingabe = QLineEdit()
|
|
self.klingelton_eingabe.setPlaceholderText("Standard-Klingelton")
|
|
self.klingelton_eingabe.setReadOnly(True)
|
|
klingelton_btn = QPushButton("Durchsuchen...")
|
|
klingelton_btn.clicked.connect(self._klingelton_waehlen)
|
|
klingelton_test_btn = QPushButton("Test")
|
|
klingelton_test_btn.clicked.connect(self._klingelton_testen)
|
|
klingelton_layout.addWidget(self.klingelton_eingabe)
|
|
klingelton_layout.addWidget(klingelton_btn)
|
|
klingelton_layout.addWidget(klingelton_test_btn)
|
|
form.addRow("Klingelton:", klingelton_layout)
|
|
|
|
self.tabs.addTab(widget, "Allgemein")
|
|
|
|
def _carddav_tab_erstellen(self):
|
|
"""Tab für CardDAV-Konfiguration (Multi-Account)."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
hinweis = QLabel(
|
|
"Kontakte von Nextcloud/CardDAV-Servern synchronisieren.\n"
|
|
"Mehrere Accounts möglich (z.B. Privat + Geschäftlich)."
|
|
)
|
|
hinweis.setStyleSheet(
|
|
"color: #888; font-size: 11px; margin-bottom: 4px;")
|
|
layout.addWidget(hinweis)
|
|
|
|
# Account-Liste
|
|
self.carddav_account_liste = QListWidget()
|
|
self.carddav_account_liste.setMaximumHeight(120)
|
|
layout.addWidget(self.carddav_account_liste)
|
|
|
|
# Account-Buttons
|
|
acc_btn_layout = QHBoxLayout()
|
|
acc_hinzu_btn = QPushButton("Account hinzufügen")
|
|
acc_hinzu_btn.clicked.connect(self._carddav_account_hinzufuegen)
|
|
acc_btn_layout.addWidget(acc_hinzu_btn)
|
|
|
|
acc_bearbeiten_btn = QPushButton("Bearbeiten")
|
|
acc_bearbeiten_btn.clicked.connect(self._carddav_account_bearbeiten)
|
|
acc_btn_layout.addWidget(acc_bearbeiten_btn)
|
|
|
|
acc_loeschen_btn = QPushButton("Entfernen")
|
|
acc_loeschen_btn.clicked.connect(self._carddav_account_loeschen)
|
|
acc_btn_layout.addWidget(acc_loeschen_btn)
|
|
|
|
layout.addLayout(acc_btn_layout)
|
|
|
|
# Sync-Optionen
|
|
form = QFormLayout()
|
|
self.carddav_auto_sync = QCheckBox("Automatisch synchronisieren")
|
|
form.addRow(self.carddav_auto_sync)
|
|
|
|
self.carddav_intervall = QSpinBox()
|
|
self.carddav_intervall.setRange(60, 86400)
|
|
self.carddav_intervall.setValue(3600)
|
|
self.carddav_intervall.setSuffix(" Sekunden")
|
|
form.addRow("Intervall:", self.carddav_intervall)
|
|
|
|
layout.addLayout(form)
|
|
|
|
# Sync-Button + Status
|
|
sync_layout = QHBoxLayout()
|
|
self.carddav_sync_btn = QPushButton("Jetzt synchronisieren")
|
|
self.carddav_sync_btn.clicked.connect(self._carddav_sync)
|
|
sync_layout.addWidget(self.carddav_sync_btn)
|
|
layout.addLayout(sync_layout)
|
|
|
|
self.carddav_status = QLabel("")
|
|
self.carddav_status.setStyleSheet("color: #888; font-size: 11px;")
|
|
layout.addWidget(self.carddav_status)
|
|
|
|
layout.addStretch()
|
|
self.tabs.addTab(widget, "CardDAV")
|
|
|
|
def _blf_tab_erstellen(self):
|
|
"""Tab für BLF-Konfiguration."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
hinweis = QLabel(
|
|
"BLF-Extensions (eine pro Zeile):\n"
|
|
"Diese Extensions werden auf Besetzt-Status überwacht."
|
|
)
|
|
layout.addWidget(hinweis)
|
|
|
|
from PySide6.QtWidgets import QTextEdit
|
|
self.blf_eingabe = QTextEdit()
|
|
self.blf_eingabe.setPlaceholderText("200\n201\n202")
|
|
layout.addWidget(self.blf_eingabe)
|
|
|
|
self.tabs.addTab(widget, "BLF")
|
|
|
|
def _werte_laden(self):
|
|
"""Gespeicherte Werte in die Felder laden."""
|
|
sip = self._config.sip
|
|
self.sip_server.setText(sip.get("server", ""))
|
|
self.sip_port.setValue(sip.get("port", 5060))
|
|
self.sip_extension.setText(sip.get("extension", ""))
|
|
self.sip_passwort.setText(sip.get("passwort", ""))
|
|
|
|
transport = sip.get("transport", "udp").upper()
|
|
idx = self.sip_transport.findText(transport)
|
|
if idx >= 0:
|
|
self.sip_transport.setCurrentIndex(idx)
|
|
|
|
allg = self._config.allgemein
|
|
self.tray_check.setChecked(allg.get("minimieren_in_tray", True))
|
|
self.autostart_check.setChecked(allg.get("autostart", False))
|
|
self.minimiert_starten_check.setChecked(
|
|
allg.get("minimiert_starten", False))
|
|
self.klingelton_eingabe.setText(allg.get("klingelton", ""))
|
|
|
|
# CardDAV-Accounts laden
|
|
self._carddav_accounts = list(
|
|
self._config.get("carddav", "accounts") or [])
|
|
self._carddav_account_liste_aktualisieren()
|
|
|
|
cdav = self._config.carddav
|
|
self.carddav_auto_sync.setChecked(cdav.get("auto_sync", True))
|
|
self.carddav_intervall.setValue(cdav.get("sync_intervall", 3600))
|
|
|
|
blf = self._config.blf
|
|
extensions = blf.get("extensions", [])
|
|
self.blf_eingabe.setPlainText("\n".join(extensions))
|
|
|
|
self._audiogeraete_aktualisieren()
|
|
|
|
def _carddav_account_liste_aktualisieren(self):
|
|
"""Account-Liste im UI aktualisieren."""
|
|
self.carddav_account_liste.clear()
|
|
for acc in self._carddav_accounts:
|
|
name = acc.get("name", "Unbenannt")
|
|
url = acc.get("url", "")
|
|
# Kurze URL-Anzeige
|
|
url_kurz = url
|
|
if len(url_kurz) > 50:
|
|
url_kurz = url_kurz[:47] + "..."
|
|
text = f"{name} - {url_kurz}"
|
|
item = QListWidgetItem(text)
|
|
self.carddav_account_liste.addItem(item)
|
|
|
|
def _carddav_account_hinzufuegen(self):
|
|
"""Neuen CardDAV-Account hinzufügen."""
|
|
dialog = CardDavAccountDialog(parent=self)
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
acc = dialog.account_daten
|
|
if acc.get("url") and acc.get("benutzername"):
|
|
self._carddav_accounts.append(acc)
|
|
self._carddav_account_liste_aktualisieren()
|
|
|
|
def _carddav_account_bearbeiten(self):
|
|
"""Ausgewählten Account bearbeiten."""
|
|
zeile = self.carddav_account_liste.currentRow()
|
|
if zeile < 0 or zeile >= len(self._carddav_accounts):
|
|
return
|
|
dialog = CardDavAccountDialog(
|
|
account=self._carddav_accounts[zeile], parent=self)
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
self._carddav_accounts[zeile] = dialog.account_daten
|
|
self._carddav_account_liste_aktualisieren()
|
|
|
|
def _carddav_account_loeschen(self):
|
|
"""Ausgewählten Account entfernen."""
|
|
zeile = self.carddav_account_liste.currentRow()
|
|
if zeile < 0 or zeile >= len(self._carddav_accounts):
|
|
return
|
|
del self._carddav_accounts[zeile]
|
|
self._carddav_account_liste_aktualisieren()
|
|
|
|
def _audiogeraete_aktualisieren(self):
|
|
"""Audiogeräte-Dropdowns mit PipeWire-Geräten aktualisieren."""
|
|
self.aufnahme_combo.clear()
|
|
self.wiedergabe_combo.clear()
|
|
self.klingelton_geraet_combo.clear()
|
|
|
|
self.aufnahme_combo.addItem("Standard (KDE-Einstellung)", "")
|
|
self.wiedergabe_combo.addItem("Standard (KDE-Einstellung)", "")
|
|
self.klingelton_geraet_combo.addItem("Standard (KDE-Einstellung)", "")
|
|
|
|
if not self._audio:
|
|
return
|
|
|
|
for geraet in self._audio.aufnahme_geraete():
|
|
self.aufnahme_combo.addItem(
|
|
geraet["name"], geraet["pw_name"])
|
|
|
|
for geraet in self._audio.wiedergabe_geraete():
|
|
self.wiedergabe_combo.addItem(
|
|
geraet["name"], geraet["pw_name"])
|
|
self.klingelton_geraet_combo.addItem(
|
|
geraet["name"], geraet["pw_name"])
|
|
|
|
audio_cfg = self._config.audio
|
|
self._combo_auswahl_setzen(
|
|
self.aufnahme_combo, audio_cfg.get("aufnahme_geraet", ""))
|
|
self._combo_auswahl_setzen(
|
|
self.wiedergabe_combo, audio_cfg.get("wiedergabe_geraet", ""))
|
|
self._combo_auswahl_setzen(
|
|
self.klingelton_geraet_combo,
|
|
audio_cfg.get("klingelton_geraet", ""))
|
|
|
|
def _combo_auswahl_setzen(self, combo, wert):
|
|
"""ComboBox auf den Eintrag mit passendem Data-Wert setzen."""
|
|
for i in range(combo.count()):
|
|
if combo.itemData(i) == wert:
|
|
combo.setCurrentIndex(i)
|
|
return
|
|
|
|
def _klingelton_waehlen(self):
|
|
"""Dateidialog für Klingelton-Auswahl."""
|
|
datei, _ = QFileDialog.getOpenFileName(
|
|
self, "Klingelton wählen", "",
|
|
"Audio-Dateien (*.wav *.mp3 *.ogg *.oga);;Alle Dateien (*)"
|
|
)
|
|
if datei:
|
|
self.klingelton_eingabe.setText(datei)
|
|
|
|
def _klingelton_testen(self):
|
|
"""Klingelton einmal zum Testen abspielen."""
|
|
if not self._klingelton:
|
|
return
|
|
device = self.klingelton_geraet_combo.currentData() or ""
|
|
datei = self.klingelton_eingabe.text()
|
|
self._klingelton.test_abspielen(datei, device)
|
|
|
|
def _carddav_sync(self):
|
|
"""CardDAV-Kontakte von allen Accounts synchronisieren."""
|
|
if not self._carddav_accounts:
|
|
self.carddav_status.setText("Keine Accounts konfiguriert")
|
|
self.carddav_status.setStyleSheet(
|
|
"color: orange; font-size: 11px;")
|
|
return
|
|
|
|
self.carddav_status.setText("Synchronisiere...")
|
|
self.carddav_status.setStyleSheet("color: #888; font-size: 11px;")
|
|
self.carddav_sync_btn.setEnabled(False)
|
|
|
|
from utils.carddav import CardDavSync
|
|
sync = CardDavSync()
|
|
kontakte, fehler = sync.alle_accounts_abrufen(
|
|
self._carddav_accounts)
|
|
|
|
self.carddav_sync_btn.setEnabled(True)
|
|
|
|
if fehler and not kontakte:
|
|
self.carddav_status.setText(
|
|
f"Fehler: {'; '.join(fehler)}")
|
|
self.carddav_status.setStyleSheet(
|
|
"color: red; font-size: 11px;")
|
|
else:
|
|
text = f"{len(kontakte)} Kontakte synchronisiert"
|
|
if fehler:
|
|
text += f" (Warnungen: {'; '.join(fehler)})"
|
|
self.carddav_status.setText(text)
|
|
self.carddav_status.setStyleSheet(
|
|
"color: green; font-size: 11px;")
|
|
self._config.set("carddav", "kontakte_cache", kontakte)
|
|
|
|
def _speichern(self):
|
|
"""Einstellungen speichern und Dialog schließen."""
|
|
# SIP
|
|
self._config.set("sip", "server", self.sip_server.text().strip())
|
|
self._config.set("sip", "port", self.sip_port.value())
|
|
self._config.set("sip", "extension",
|
|
self.sip_extension.text().strip())
|
|
self._config.set("sip", "passwort", self.sip_passwort.text())
|
|
self._config.set("sip", "transport",
|
|
self.sip_transport.currentText().lower())
|
|
|
|
# Audio
|
|
aufnahme = self.aufnahme_combo.currentData() or ""
|
|
wiedergabe = self.wiedergabe_combo.currentData() or ""
|
|
klingelton_geraet = (
|
|
self.klingelton_geraet_combo.currentData() or "")
|
|
self._config.set("audio", "aufnahme_geraet", aufnahme)
|
|
self._config.set("audio", "wiedergabe_geraet", wiedergabe)
|
|
self._config.set("audio", "klingelton_geraet", klingelton_geraet)
|
|
|
|
# Allgemein
|
|
self._config.set("allgemein", "minimieren_in_tray",
|
|
self.tray_check.isChecked())
|
|
self._config.set("allgemein", "autostart",
|
|
self.autostart_check.isChecked())
|
|
self._config.set("allgemein", "minimiert_starten",
|
|
self.minimiert_starten_check.isChecked())
|
|
self._config.set("allgemein", "klingelton",
|
|
self.klingelton_eingabe.text())
|
|
|
|
# CardDAV (Multi-Account)
|
|
self._config.set("carddav", "accounts", self._carddav_accounts)
|
|
self._config.set("carddav", "auto_sync",
|
|
self.carddav_auto_sync.isChecked())
|
|
self._config.set("carddav", "sync_intervall",
|
|
self.carddav_intervall.value())
|
|
|
|
# BLF
|
|
blf_text = self.blf_eingabe.toPlainText().strip()
|
|
extensions = [e.strip() for e in blf_text.split("\n") if e.strip()]
|
|
self._config.set("blf", "extensions", extensions)
|
|
|
|
self._config.speichern()
|
|
# Autostart Desktop-Entry erstellen/entfernen
|
|
self._config.autostart_aktualisieren()
|
|
self.einstellungen_geaendert.emit()
|
|
self.accept()
|