"""Kontakte-Widget - Tabelle mit lokalen + CardDAV-Kontakten und Detail-Dialog.""" from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QDialog, QFormLayout, QLineEdit, QLabel, QHeaderView, QAbstractItemView, QGroupBox, QGridLayout, QScrollArea, QPlainTextEdit, QMessageBox, ) from PySide6.QtCore import Signal, Qt from PySide6.QtGui import QColor, QFont class KontaktDetailDialog(QDialog): """Detail-Dialog - zeigt alle Kontaktdaten, Nummern anklickbar.""" # Signal: Nummer zum Anrufen anrufen = Signal(str) # Signal: Favorit hinzufügen/entfernen (name, nummer, hinzufuegen) favorit_toggle = Signal(str, str, bool) def __init__(self, kontakt, parent=None): super().__init__(parent) self._kontakt = kontakt self._bearbeiten_gewuenscht = False self.setWindowTitle(kontakt.get("name", "Kontakt")) self.setMinimumWidth(420) self.setMinimumHeight(350) self.setModal(True) self._ui_aufbauen() def _ui_aufbauen(self): dialog_layout = QVBoxLayout(self) # Scrollbereich für viele Daten scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QScrollArea.Shape.NoFrame) inhalt = QWidget() layout = QVBoxLayout(inhalt) # === Name === name_label = QLabel(self._kontakt.get("name", "")) name_font = QFont() name_font.setPointSize(16) name_font.setBold(True) name_label.setFont(name_font) name_label.setTextInteractionFlags( Qt.TextInteractionFlag.TextSelectableByMouse) layout.addWidget(name_label) # Firma + Titel firma = self._kontakt.get("firma", "") titel = self._kontakt.get("titel", "") if firma and titel: layout.addWidget(self._info_label(f"{titel} bei {firma}")) elif firma: layout.addWidget(self._info_label(firma)) elif titel: layout.addWidget(self._info_label(titel)) # Account-Info quelle = self._kontakt.get("quelle", "lokal") account = self._kontakt.get("account", "") if quelle == "carddav" and account: layout.addWidget(self._info_label( f"CardDAV: {account}", "#999", 11)) layout.addSpacing(8) # === Telefonnummern (klickbar) === nummern = self._kontakt.get("nummern", {}) if not nummern and self._kontakt.get("nummer"): nummern = {"telefon": self._kontakt["nummer"]} if nummern: nummern_box = QGroupBox("Telefonnummern") nummern_grid = QGridLayout(nummern_box) typ_labels = { "telefon": "Telefon:", "handy": "Handy:", "geschaeftlich": "Geschäftlich:", "sonstige": "Sonstige:", } zeile = 0 for typ, label_text in typ_labels.items(): nummer = nummern.get(typ, "") if not nummer: continue label = QLabel(label_text) label.setStyleSheet("font-weight: bold;") nummern_grid.addWidget(label, zeile, 0) nummer_btn = QPushButton(f" {nummer} ") nummer_btn.setStyleSheet( "text-align: left; font-size: 14px; " "padding: 6px 12px; " "color: #4CAF50; border: 1px solid #4CAF50; " "border-radius: 4px; background: transparent;" ) nummer_btn.setCursor(Qt.CursorShape.PointingHandCursor) nummer_btn.setToolTip(f"Anrufen: {nummer}") nummer_btn.clicked.connect( lambda checked, n=nummer: self._nummer_anrufen(n) ) nummern_grid.addWidget(nummer_btn, zeile, 1) zeile += 1 layout.addWidget(nummern_box) # === Details (E-Mail, Adresse, Geburtstag, etc.) === details_form = QFormLayout() details_form.setSpacing(6) details_vorhanden = False # E-Mails emails = self._kontakt.get("emails", []) if not emails and self._kontakt.get("email"): emails = [self._kontakt["email"]] for em in emails: details_form.addRow( self._bold_label("E-Mail:"), self._selectable_label(em, "#5599DD")) details_vorhanden = True # Adresse adresse = self._kontakt.get("adresse", "") if adresse: adr_label = self._selectable_label(adresse) adr_label.setWordWrap(True) details_form.addRow(self._bold_label("Adresse:"), adr_label) details_vorhanden = True # Geburtstag geburtstag = self._kontakt.get("geburtstag", "") if geburtstag: details_form.addRow( self._bold_label("Geburtstag:"), self._selectable_label(geburtstag)) details_vorhanden = True # Website url = self._kontakt.get("url", "") if url: details_form.addRow( self._bold_label("Website:"), self._selectable_label(url, "#5599DD")) details_vorhanden = True # Notiz notiz = self._kontakt.get("notiz", "") if notiz: notiz_label = self._selectable_label(notiz) notiz_label.setWordWrap(True) details_form.addRow(self._bold_label("Notiz:"), notiz_label) details_vorhanden = True if details_vorhanden: details_box = QGroupBox("Details") details_box.setLayout(details_form) layout.addWidget(details_box) layout.addStretch() scroll.setWidget(inhalt) dialog_layout.addWidget(scroll) # Buttons: Bearbeiten + Favorit + Schließen btn_layout = QHBoxLayout() bearbeiten_btn = QPushButton("Bearbeiten") bearbeiten_btn.clicked.connect(self._bearbeiten_klick) btn_layout.addWidget(bearbeiten_btn) # Favorit-Toggle (nur wenn Nummer vorhanden) self._haupt_nummer = self._haupt_nummer_bestimmen() if self._haupt_nummer: self.favorit_btn = QPushButton() self.favorit_btn.setCheckable(True) self._favorit_btn_aktualisieren(False) self.favorit_btn.clicked.connect(self._favorit_toggle_klick) btn_layout.addWidget(self.favorit_btn) btn_layout.addStretch() schliessen_btn = QPushButton("Schließen") schliessen_btn.clicked.connect(self.accept) btn_layout.addWidget(schliessen_btn) dialog_layout.addLayout(btn_layout) def _info_label(self, text, farbe="#888", groesse=12): """Info-Label mit Farbe und Größe.""" label = QLabel(text) label.setStyleSheet(f"color: {farbe}; font-size: {groesse}px;") label.setTextInteractionFlags( Qt.TextInteractionFlag.TextSelectableByMouse) return label def _bold_label(self, text): """Fettgedrucktes Label.""" label = QLabel(text) label.setStyleSheet("font-weight: bold;") return label def _selectable_label(self, text, farbe=None): """Selektierbares/kopierbares Label.""" label = QLabel(text) label.setTextInteractionFlags( Qt.TextInteractionFlag.TextSelectableByMouse) if farbe: label.setStyleSheet(f"color: {farbe};") return label def _haupt_nummer_bestimmen(self): """Erste verfügbare Nummer des Kontakts bestimmen.""" nummern = self._kontakt.get("nummern", {}) if not nummern and self._kontakt.get("nummer"): return self._kontakt["nummer"] for typ in ("handy", "telefon", "geschaeftlich", "sonstige"): if typ in nummern: return nummern[typ] return "" def favorit_status_setzen(self, ist_favorit): """Favorit-Button von außen aktualisieren.""" if hasattr(self, "favorit_btn"): self.favorit_btn.setChecked(ist_favorit) self._favorit_btn_aktualisieren(ist_favorit) def _favorit_btn_aktualisieren(self, aktiv): """Favorit-Button Aussehen je nach Status.""" if aktiv: self.favorit_btn.setText("Favorit entfernen") self.favorit_btn.setStyleSheet( "color: #FFD700; font-weight: bold;") else: self.favorit_btn.setText("Als Favorit") self.favorit_btn.setStyleSheet("") def _favorit_toggle_klick(self): """Favorit-Button geklickt.""" ist_aktiv = self.favorit_btn.isChecked() self._favorit_btn_aktualisieren(ist_aktiv) name = self._kontakt.get("name", "") self.favorit_toggle.emit(name, self._haupt_nummer, ist_aktiv) def _bearbeiten_klick(self): """Bearbeiten-Button geklickt → Flag setzen und schließen.""" self._bearbeiten_gewuenscht = True self.accept() @property def bearbeiten_gewuenscht(self): """True wenn der User 'Bearbeiten' geklickt hat.""" return self._bearbeiten_gewuenscht def _nummer_anrufen(self, nummer): """Nummer-Button geklickt → anrufen und Dialog schließen.""" self.anrufen.emit(nummer) self.accept() class KontaktBearbeitenDialog(QDialog): """Dialog zum Hinzufügen/Bearbeiten eines Kontakts (lokal + CardDAV).""" def __init__(self, kontakt=None, parent=None): super().__init__(parent) self._kontakt = kontakt or {} self.setWindowTitle( "Kontakt bearbeiten" if kontakt else "Neuer Kontakt" ) self.setMinimumWidth(420) self.setMinimumHeight(500) self.setModal(True) self._ui_aufbauen() def _ui_aufbauen(self): dialog_layout = QVBoxLayout(self) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QScrollArea.Shape.NoFrame) inhalt = QWidget() layout = QVBoxLayout(inhalt) # === Grunddaten === grund_form = QFormLayout() grund_form.setSpacing(6) self.name_eingabe = QLineEdit(self._kontakt.get("name", "")) self.name_eingabe.setPlaceholderText("Vor- und Nachname") grund_form.addRow("Name:", self.name_eingabe) self.firma_eingabe = QLineEdit(self._kontakt.get("firma", "")) self.firma_eingabe.setPlaceholderText("Firmenname") grund_form.addRow("Firma:", self.firma_eingabe) self.titel_eingabe = QLineEdit(self._kontakt.get("titel", "")) self.titel_eingabe.setPlaceholderText("Berufsbezeichnung") grund_form.addRow("Titel:", self.titel_eingabe) grund_box = QGroupBox("Grunddaten") grund_box.setLayout(grund_form) layout.addWidget(grund_box) # === Telefonnummern === nummern = self._kontakt.get("nummern", {}) if not nummern and self._kontakt.get("nummer"): nummern = {"telefon": self._kontakt["nummer"]} tel_form = QFormLayout() tel_form.setSpacing(6) self.telefon_eingabe = QLineEdit(nummern.get("telefon", "")) self.telefon_eingabe.setPlaceholderText("Festnetz") tel_form.addRow("Telefon:", self.telefon_eingabe) self.handy_eingabe = QLineEdit(nummern.get("handy", "")) self.handy_eingabe.setPlaceholderText("Mobilnummer") tel_form.addRow("Handy:", self.handy_eingabe) self.geschaeftlich_eingabe = QLineEdit( nummern.get("geschaeftlich", "")) self.geschaeftlich_eingabe.setPlaceholderText("Geschäftlich") tel_form.addRow("Geschäftlich:", self.geschaeftlich_eingabe) self.sonstige_eingabe = QLineEdit(nummern.get("sonstige", "")) self.sonstige_eingabe.setPlaceholderText("Weitere Nummer") tel_form.addRow("Sonstige:", self.sonstige_eingabe) tel_box = QGroupBox("Telefonnummern") tel_box.setLayout(tel_form) layout.addWidget(tel_box) # === Kontaktdaten === kontakt_form = QFormLayout() kontakt_form.setSpacing(6) # E-Mail (erste aus der Liste oder Einzelwert) emails = self._kontakt.get("emails", []) email_wert = emails[0] if emails else self._kontakt.get("email", "") self.email_eingabe = QLineEdit(email_wert) self.email_eingabe.setPlaceholderText("name@beispiel.de") kontakt_form.addRow("E-Mail:", self.email_eingabe) self.url_eingabe = QLineEdit(self._kontakt.get("url", "")) self.url_eingabe.setPlaceholderText("https://...") kontakt_form.addRow("Website:", self.url_eingabe) self.geburtstag_eingabe = QLineEdit( self._kontakt.get("geburtstag", "")) self.geburtstag_eingabe.setPlaceholderText("TT.MM.JJJJ") kontakt_form.addRow("Geburtstag:", self.geburtstag_eingabe) kontakt_box = QGroupBox("Kontaktdaten") kontakt_box.setLayout(kontakt_form) layout.addWidget(kontakt_box) # === Adresse + Notiz (mehrzeilig) === extra_form = QFormLayout() extra_form.setSpacing(6) self.adresse_eingabe = QPlainTextEdit( self._kontakt.get("adresse", "")) self.adresse_eingabe.setPlaceholderText( "Straße, PLZ Ort, Land") self.adresse_eingabe.setMaximumHeight(80) extra_form.addRow("Adresse:", self.adresse_eingabe) self.notiz_eingabe = QPlainTextEdit( self._kontakt.get("notiz", "")) self.notiz_eingabe.setPlaceholderText("Freitext-Notizen...") self.notiz_eingabe.setMaximumHeight(80) extra_form.addRow("Notiz:", self.notiz_eingabe) extra_box = QGroupBox("Sonstiges") extra_box.setLayout(extra_form) layout.addWidget(extra_box) layout.addStretch() scroll.setWidget(inhalt) dialog_layout.addWidget(scroll) # Buttons btn_layout = QHBoxLayout() ok_btn = QPushButton("Speichern") 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) dialog_layout.addLayout(btn_layout) @property def kontakt_daten(self): """Kontakt-Dict aus den Eingabefeldern erstellen.""" name = self.name_eingabe.text().strip() if not name: return None nummern = {} if self.telefon_eingabe.text().strip(): nummern["telefon"] = self.telefon_eingabe.text().strip() if self.handy_eingabe.text().strip(): nummern["handy"] = self.handy_eingabe.text().strip() if self.geschaeftlich_eingabe.text().strip(): nummern["geschaeftlich"] = ( self.geschaeftlich_eingabe.text().strip()) if self.sonstige_eingabe.text().strip(): nummern["sonstige"] = self.sonstige_eingabe.text().strip() # Mindestens Name muss vorhanden sein # (Nummer nicht zwingend, z.B. bei CardDAV nur Email) ergebnis = { "name": name, "nummern": nummern, "firma": self.firma_eingabe.text().strip(), "titel": self.titel_eingabe.text().strip(), "email": self.email_eingabe.text().strip(), "emails": ([self.email_eingabe.text().strip()] if self.email_eingabe.text().strip() else []), "adresse": self.adresse_eingabe.toPlainText().strip(), "geburtstag": self.geburtstag_eingabe.text().strip(), "url": self.url_eingabe.text().strip(), "notiz": self.notiz_eingabe.toPlainText().strip(), } # Metadaten vom Original übernehmen (CardDAV-Referenz) for key in ("_href", "_etag", "quelle", "account"): if key in self._kontakt: ergebnis[key] = self._kontakt[key] return ergebnis class KontakteWidget(QWidget): """Kontaktliste als Tabelle mit Spalten (Name, Telefon, Handy, Geschäftlich).""" # Signal: Nummer zum Anrufen anrufen = Signal(str) # Signal: Kontaktliste hat sich geändert (für Wählfeld-Suche) kontakte_geaendert = Signal() # Signal: Favorit hinzufügen/entfernen (name, nummer, hinzufuegen) favorit_toggle = Signal(str, str, bool) # Spalten-Index SPALTE_NAME = 0 SPALTE_TELEFON = 1 SPALTE_HANDY = 2 SPALTE_GESCHAEFTLICH = 3 def __init__(self, config_manager, parent=None): super().__init__(parent) self._config = config_manager self._kontakte = [] # Lokale Kontakte self._carddav_kontakte = [] # CardDAV-Kontakte (read-only) self._alle_kontakte = [] # Zusammengeführte, sortierte Liste self._ui_aufbauen() self._kontakte_laden() def _ui_aufbauen(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(6) # Suchfeld self._suchfeld = QLineEdit() self._suchfeld.setPlaceholderText("Kontakte durchsuchen...") self._suchfeld.setClearButtonEnabled(True) self._suchfeld.setStyleSheet( "QLineEdit { " " border: 1px solid #555; border-radius: 6px; " " padding: 6px 10px; " " background: rgba(255,255,255,0.05); " "}" "QLineEdit:focus { border-color: #5599DD; }" ) self._suchfeld.textChanged.connect(self._suche_filtern) layout.addWidget(self._suchfeld) # Tabelle self.tabelle = QTableWidget() self.tabelle.setColumnCount(4) self.tabelle.setHorizontalHeaderLabels( ["Name", "Telefon", "Handy", "Geschäftlich"] ) self.tabelle.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows ) self.tabelle.setSelectionMode( QAbstractItemView.SelectionMode.SingleSelection ) self.tabelle.setEditTriggers( QAbstractItemView.EditTrigger.NoEditTriggers ) self.tabelle.setAlternatingRowColors(True) self.tabelle.verticalHeader().setVisible(False) self.tabelle.setSortingEnabled(True) self.tabelle.setStyleSheet( "QTableWidget { " " border: 1px solid #444; border-radius: 4px; " " gridline-color: #3a3a3a; " "}" "QTableWidget::item { padding: 4px 6px; }" "QTableWidget::item:selected { " " background-color: rgba(85,153,221,0.3); " "}" "QHeaderView::section { " " background-color: rgba(255,255,255,0.06); " " border: none; border-bottom: 2px solid #555; " " padding: 6px 8px; font-weight: bold; " "}" ) # Spaltenbreiten header = self.tabelle.horizontalHeader() header.setSectionResizeMode( self.SPALTE_NAME, QHeaderView.ResizeMode.Stretch ) header.setSectionResizeMode( self.SPALTE_TELEFON, QHeaderView.ResizeMode.ResizeToContents ) header.setSectionResizeMode( self.SPALTE_HANDY, QHeaderView.ResizeMode.ResizeToContents ) header.setSectionResizeMode( self.SPALTE_GESCHAEFTLICH, QHeaderView.ResizeMode.ResizeToContents ) self.tabelle.doubleClicked.connect(self._doppelklick) layout.addWidget(self.tabelle) # Buttons btn_layout = QHBoxLayout() btn_layout.setSpacing(4) btn_style = ( "QPushButton { " " border: 1px solid #555; border-radius: 6px; " " padding: 6px 12px; " " background: rgba(255,255,255,0.06); " "}" "QPushButton:hover { " " background: rgba(85,153,221,0.15); " " border-color: #5599DD; " "}" "QPushButton:pressed { background: rgba(85,153,221,0.25); }" ) gruen_style = ( "QPushButton { " " border: 1px solid #4CAF50; border-radius: 6px; " " padding: 6px 12px; color: #4CAF50; " " background: rgba(76,175,80,0.08); " "}" "QPushButton:hover { background: rgba(76,175,80,0.2); }" "QPushButton:pressed { background: rgba(76,175,80,0.3); }" ) anrufen_btn = QPushButton("Anrufen") anrufen_btn.setStyleSheet(gruen_style) anrufen_btn.setCursor(Qt.CursorShape.PointingHandCursor) anrufen_btn.clicked.connect(self._anrufen_klick) btn_layout.addWidget(anrufen_btn) details_btn = QPushButton("Details") details_btn.setStyleSheet(btn_style) details_btn.setCursor(Qt.CursorShape.PointingHandCursor) details_btn.clicked.connect(self._details_zeigen) btn_layout.addWidget(details_btn) btn_layout.addStretch() hinzufuegen_btn = QPushButton("Neu") hinzufuegen_btn.setStyleSheet(btn_style) hinzufuegen_btn.setCursor(Qt.CursorShape.PointingHandCursor) hinzufuegen_btn.clicked.connect(self._kontakt_hinzufuegen) btn_layout.addWidget(hinzufuegen_btn) bearbeiten_btn = QPushButton("Bearbeiten") bearbeiten_btn.setStyleSheet(btn_style) bearbeiten_btn.setCursor(Qt.CursorShape.PointingHandCursor) bearbeiten_btn.clicked.connect(self._kontakt_bearbeiten) btn_layout.addWidget(bearbeiten_btn) loeschen_btn = QPushButton("Löschen") loeschen_btn.setStyleSheet(btn_style) loeschen_btn.setCursor(Qt.CursorShape.PointingHandCursor) loeschen_btn.clicked.connect(self._kontakt_loeschen) btn_layout.addWidget(loeschen_btn) sync_btn = QPushButton("Sync") sync_btn.setToolTip("CardDAV-Kontakte synchronisieren") sync_btn.setStyleSheet(btn_style) sync_btn.setCursor(Qt.CursorShape.PointingHandCursor) sync_btn.clicked.connect(self._carddav_sync) btn_layout.addWidget(sync_btn) layout.addLayout(btn_layout) def _tabelle_fuellen(self): """Tabelle mit allen Kontakten füllen.""" self.tabelle.setSortingEnabled(False) # Alle Kontakte zusammenführen self._alle_kontakte = [] for k in self._kontakte: eintrag = {**k, "quelle": "lokal"} # Abwärtskompatibel: einzelne "nummer" → nummern-Dict if "nummer" in eintrag and "nummern" not in eintrag: eintrag["nummern"] = {"telefon": eintrag["nummer"]} self._alle_kontakte.append(eintrag) for k in self._carddav_kontakte: self._alle_kontakte.append({**k, "quelle": "carddav"}) self._alle_kontakte.sort(key=lambda k: k.get("name", "").lower()) self.tabelle.setRowCount(len(self._alle_kontakte)) carddav_farbe = QColor("#5599DD") for zeile, kontakt in enumerate(self._alle_kontakte): quelle = kontakt.get("quelle", "lokal") nummern = kontakt.get("nummern", {}) account = kontakt.get("account", "") # Name (mit Account-Info für CardDAV) name = kontakt.get("name", "") if quelle == "carddav" and account: name_text = f"{name} [{account}]" else: name_text = name name_item = QTableWidgetItem(name_text) name_item.setData(Qt.ItemDataRole.UserRole, zeile) # Index # Nummern-Spalten telefon_item = QTableWidgetItem(nummern.get("telefon", "")) handy_item = QTableWidgetItem(nummern.get("handy", "")) geschaeft_item = QTableWidgetItem( nummern.get("geschaeftlich", "")) if quelle == "carddav": for item in (name_item, telefon_item, handy_item, geschaeft_item): item.setForeground(carddav_farbe) self.tabelle.setItem(zeile, self.SPALTE_NAME, name_item) self.tabelle.setItem(zeile, self.SPALTE_TELEFON, telefon_item) self.tabelle.setItem(zeile, self.SPALTE_HANDY, handy_item) self.tabelle.setItem( zeile, self.SPALTE_GESCHAEFTLICH, geschaeft_item) self.tabelle.setSortingEnabled(True) def _kontakt_am_index(self, zeile): """Kontakt-Dict für eine Tabellenzeile holen.""" name_item = self.tabelle.item(zeile, self.SPALTE_NAME) if not name_item: return None idx = name_item.data(Qt.ItemDataRole.UserRole) if idx is not None and 0 <= idx < len(self._alle_kontakte): return self._alle_kontakte[idx] return None def _aktuelle_zeile_kontakt(self): """Kontakt der aktuell ausgewählten Zeile.""" zeile = self.tabelle.currentRow() if zeile < 0: return None return self._kontakt_am_index(zeile) def _doppelklick(self, index): """Doppelklick → Detail-Dialog öffnen.""" kontakt = self._kontakt_am_index(index.row()) if kontakt: self._detail_dialog_oeffnen(kontakt) def _details_zeigen(self): """Details-Button → Detail-Dialog.""" kontakt = self._aktuelle_zeile_kontakt() if kontakt: self._detail_dialog_oeffnen(kontakt) def _detail_dialog_oeffnen(self, kontakt): """Detail-Dialog mit allen Kontaktdaten und klickbaren Nummern.""" dialog = KontaktDetailDialog(kontakt, parent=self) dialog.anrufen.connect(self.anrufen) dialog.favorit_toggle.connect( lambda n, nr, h: self.favorit_toggle.emit(n, nr, h)) dialog.exec() if dialog.bearbeiten_gewuenscht: self._kontakt_bearbeiten_dialog(kontakt) def _anrufen_klick(self): """Anrufen-Button → erste verfügbare Nummer anrufen.""" kontakt = self._aktuelle_zeile_kontakt() if not kontakt: return nummern = kontakt.get("nummern", {}) if not nummern and kontakt.get("nummer"): nummern = {"telefon": kontakt["nummer"]} # Erste verfügbare Nummer (Priorität: handy > telefon > geschäftlich > sonstige) for typ in ("handy", "telefon", "geschaeftlich", "sonstige"): if typ in nummern: self.anrufen.emit(nummern[typ]) return def _kontakt_hinzufuegen(self): """Neuen lokalen Kontakt hinzufügen.""" dialog = KontaktBearbeitenDialog(parent=self) if dialog.exec() == QDialog.DialogCode.Accepted: daten = dialog.kontakt_daten if daten: self._kontakte.append(daten) self._kontakte.sort( key=lambda k: k.get("name", "").lower()) self._tabelle_fuellen() self._kontakte_speichern() def _kontakt_bearbeiten(self): """Ausgewählten Kontakt bearbeiten (über Tabellen-Button).""" kontakt = self._aktuelle_zeile_kontakt() if not kontakt: return self._kontakt_bearbeiten_dialog(kontakt) def _kontakt_bearbeiten_dialog(self, kontakt): """Edit-Dialog öffnen und Änderungen speichern (lokal + CardDAV).""" dialog = KontaktBearbeitenDialog(kontakt=kontakt, parent=self) if dialog.exec() != QDialog.DialogCode.Accepted: return daten = dialog.kontakt_daten if not daten: return quelle = kontakt.get("quelle", "lokal") if quelle == "carddav": self._carddav_kontakt_speichern(kontakt, daten) else: self._lokalen_kontakt_speichern(kontakt, daten) def _lokalen_kontakt_speichern(self, kontakt_alt, kontakt_neu): """Lokalen Kontakt aktualisieren.""" alter_name = kontakt_alt.get("name", "") for i, k in enumerate(self._kontakte): if k.get("name") == alter_name: self._kontakte[i] = kontakt_neu break else: # Nicht gefunden → als neuen Kontakt anlegen self._kontakte.append(kontakt_neu) self._kontakte.sort(key=lambda k: k.get("name", "").lower()) self._tabelle_fuellen() self._kontakte_speichern() self.kontakte_geaendert.emit() def _carddav_kontakt_speichern(self, kontakt_alt, kontakt_neu): """CardDAV-Kontakt auf Server schreiben (PUT).""" account_name = kontakt_alt.get("account", "") accounts = self._config.get("carddav", "accounts") or [] # Account-Credentials suchen account = None for acc in accounts: if acc.get("name") == account_name: account = acc break if not account: QMessageBox.warning( self, "Fehler", f"CardDAV-Account '{account_name}' nicht gefunden.\n" "Kontakt kann nicht gespeichert werden.") return from utils.carddav import CardDavSync sync = CardDavSync() erfolg, fehler = sync.kontakt_aktualisieren( account["url"], account["benutzername"], account["passwort"], kontakt_neu, ) if erfolg: # Cache aktualisieren for i, k in enumerate(self._carddav_kontakte): if k.get("_href") == kontakt_alt.get("_href"): self._carddav_kontakte[i] = kontakt_neu break self._config.set("carddav", "kontakte_cache", self._carddav_kontakte) self._config.speichern() self._tabelle_fuellen() self.kontakte_geaendert.emit() QMessageBox.information( self, "Gespeichert", f"Kontakt '{kontakt_neu.get('name')}' wurde auf dem " f"Server aktualisiert.") else: QMessageBox.warning( self, "Fehler beim Speichern", f"Kontakt konnte nicht gespeichert werden:\n{fehler}") def _kontakt_loeschen(self): """Ausgewählten Kontakt löschen (nur lokale).""" kontakt = self._aktuelle_zeile_kontakt() if not kontakt or kontakt.get("quelle") == "carddav": return name = kontakt.get("name") self._kontakte = [k for k in self._kontakte if k.get("name") != name] self._tabelle_fuellen() self._kontakte_speichern() def _kontakte_laden(self): """Kontakte aus Config laden (lokal + CardDAV-Cache).""" self._kontakte = self._config.kontakte_laden() self._carddav_kontakte = ( self._config.get("carddav", "kontakte_cache") or []) self._tabelle_fuellen() self.kontakte_geaendert.emit() def _kontakte_speichern(self): """Lokale Kontakte in Config speichern.""" self._config.kontakte_speichern(self._kontakte) def _carddav_sync(self): """CardDAV-Kontakte synchronisieren (alle Accounts).""" accounts = self._config.get("carddav", "accounts") or [] if not accounts: return from utils.carddav import CardDavSync sync = CardDavSync() kontakte, fehler = sync.alle_accounts_abrufen(accounts) if not fehler or kontakte: self._carddav_kontakte = kontakte self._config.set("carddav", "kontakte_cache", kontakte) self._config.speichern() self._tabelle_fuellen() self.kontakte_geaendert.emit() def _suche_filtern(self, text): """Tabellenzeilen nach Suchbegriff filtern.""" text = text.lower().strip() for zeile in range(self.tabelle.rowCount()): sichtbar = True if text: # In allen Spalten suchen sichtbar = False for spalte in range(self.tabelle.columnCount()): item = self.tabelle.item(zeile, spalte) if item and text in item.text().lower(): sichtbar = True break self.tabelle.setRowHidden(zeile, not sichtbar) def aktualisieren(self): """Kontakte neu laden (extern aufrufbar).""" self._kontakte_laden() def alle_kontakte_fuer_suche(self): """Alle Kontakte für die Suche im Wählfeld bereitstellen.""" return self._alle_kontakte