"""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) # 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.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", "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() self.einstellungen_geaendert.emit() self.accept()