""" sn_basis/functions/dialog_wrapper.py – Benutzer-Dialoge (Qt5/6/Mock-kompatibel) """ from typing import Any from typing import Literal, Optional from sn_basis.functions.qt_wrapper import ( QMessageBox, YES, NO, CANCEL, QT_VERSION, exec_dialog, ICON_QUESTION, QProgressDialog, QCoreApplication, Qt, QInputDialog, QLineEdit, ) def ask_yes_no( title: str, message: str, default: bool = True, parent: Any = None, ) -> bool: """ Stellt Ja/Nein-Frage. Funktioniert in PyQt5/6 UND Mock-Modus. """ try: if QT_VERSION == 0: # Mock-Modus print(f"🔍 Mock-Modus: ask_yes_no('{title}') → {default}") return default # ✅ KORREKT: Verwende YES/NO-Aliase aus qt_wrapper! buttons = YES | NO default_button = YES if default else NO result = QMessageBox.question( parent, title, message, buttons, default_button ) # ✅ int(result) == int(YES) funktioniert Qt5/6/Mock print(f"DEBUG ask_yes_no: result={result}, YES={YES}, match={int(result) == int(YES)}") return int(result) == int(YES) except Exception as e: print(f"⚠️ ask_yes_no Fehler: {e}") return default def show_info_dialog(title: str, message: str, parent: Any = None) -> None: """ Zeigt einen modalen Info-Dialog mit OK-Button. Blockiert bis der Nutzer bestätigt. von Test abgesehen, weil vernachlässigbar; HD 18.09.2026 """ try: if QT_VERSION == 0: # Mock-Modus print(f"Mock-Modus: show_info_dialog('{title}')") return QMessageBox.information(parent, title, message) except Exception as e: print(f"⚠️ show_info_dialog Fehler: {e}") def ask_text( title: str, label: str, default_text: str = "", parent: Any = None, ) -> tuple[str, bool]: """Zeigt einen modalen Texteingabe-Dialog und gibt Text + OK-Status zurück. von Test abgesehen, weil vernachlässigbar; HD 18.09.2026 """ try: if QT_VERSION == 0: # Mock-Modus print(f"Mock-Modus: ask_text('{title}') -> '{default_text}'") return default_text, True # PyQt6: QLineEdit.EchoMode.Normal / PyQt5: QLineEdit.Normal echo_mode = ( getattr(QLineEdit, "Normal", None) or getattr(getattr(QLineEdit, "EchoMode", None), "Normal", None) or 0 ) text, accepted = QInputDialog.getText( parent, title, label, echo_mode, default_text, ) return str(text or ""), bool(accepted) except Exception as e: print(f"⚠️ ask_text Fehler: {e}") return default_text, False OverwriteDecision = Optional[Literal["overwrite", "append", "cancel"]] def ask_overwrite_append_cancel_custom( parent, title: str, message: str, ) -> Literal["overwrite", "append", "cancel"]: """Zeigt Dialog mit benutzerdefinierten Buttons: Überschreiben/Anhängen/Abbrechen. Parameters ---------- parent : Eltern-Widget oder None. title : str Dialog-Titel. message : str Hauptmeldung mit Erklärung. Returns ------- Literal["overwrite", "append", "cancel"] Genaue Entscheidung des Nutzers. """ msg = QMessageBox(parent) msg.setIcon(ICON_QUESTION) msg.setWindowTitle(title) msg.setText(message) # Eigene Buttons mit exakten Texten overwrite_btn = msg.addButton("Überschreiben", QMessageBox.ButtonRole.AcceptRole) append_btn = msg.addButton("Anhängen", QMessageBox.ButtonRole.ActionRole) cancel_btn = msg.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole) exec_dialog(msg) clicked = msg.clickedButton() if clicked == overwrite_btn: return "overwrite" elif clicked == append_btn: return "append" else: # cancel_btn return "cancel" def ask_auswahl( title: str, message: str, optionen: list[tuple[str, str]], default: str, abbrechen: Optional[str] = None, parent: Any = None, ) -> str: """Stellt eine Frage mit frei benannten Knöpfen und gibt den Schlüssel der Wahl zurück. Parameters ---------- optionen : ``(schluessel, beschriftung)`` in Anzeigereihenfolge. default : Vorbelegter Knopf; im Mock-Modus die Antwort. abbrechen : Schlüssel, der beim Schließen des Fensters (Esc, X) und bei einem Fehler gilt; ohne Angabe ``default``. """ rueckfall = abbrechen if abbrechen is not None else default if QT_VERSION == 0: # Mock-Modus return default try: from sn_basis.functions import qt_wrapper msg = QMessageBox(parent) msg.setIcon(ICON_QUESTION) msg.setWindowTitle(title) msg.setText(message) knoepfe = {} for schluessel, beschriftung in optionen: rolle = qt_wrapper.RejectRole if schluessel == abbrechen else qt_wrapper.ActionRole knoepfe[schluessel] = msg.addButton(beschriftung, rolle) if default in knoepfe: msg.setDefaultButton(knoepfe[default]) if abbrechen in knoepfe: msg.setEscapeButton(knoepfe[abbrechen]) exec_dialog(msg) geklickt = msg.clickedButton() for schluessel, knopf in knoepfe.items(): if geklickt == knopf: return schluessel return rueckfall except Exception as e: print(f"⚠️ ask_auswahl Fehler: {e}") return rueckfall def ask_ergaenzen_entfernen_ersetzen_abbrechen( parent: Any, title: str, message: str, ) -> Literal["ergaenzen", "entfernen", "ersetzen", "abbrechen"]: """Zeigt Dialog mit vier Optionen für Verfahrensgebiet-Editoperationen. Optionen: - **Ergänzen**: ausgewählte Flurstücke zum Verfahrensgebiet hinzufügen - **Entfernen**: ausgewählte Flurstücke aus dem Verfahrensgebiet entfernen - **Ersetzen**: Verfahrensgebiet vollständig durch Auswahl ersetzen - **Abbrechen**: Vorgang beenden ohne Änderung Parameters ---------- parent : Eltern-Widget oder None. title : str Dialog-Titel. message : str Hauptmeldung mit Erklärung. Returns ------- Literal["ergaenzen", "entfernen", "ersetzen", "abbrechen"] Genaue Entscheidung des Nutzers. von Test abgesehen, weil vernachlässigbar; HD 18.09.2026 """ if QT_VERSION == 0: # Mock-Modus print(f"Mock-Modus: ask_ergaenzen_entfernen_ersetzen_abbrechen('{title}') → abbrechen") return "abbrechen" msg = QMessageBox(parent) msg.setIcon(ICON_QUESTION) msg.setWindowTitle(title) msg.setText(message) ergaenzen_btn = msg.addButton("Ergänzen", QMessageBox.ButtonRole.ActionRole) entfernen_btn = msg.addButton("Entfernen", QMessageBox.ButtonRole.ActionRole) ersetzen_btn = msg.addButton("Ersetzen", QMessageBox.ButtonRole.AcceptRole) msg.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole) exec_dialog(msg) clicked = msg.clickedButton() if clicked == ergaenzen_btn: return "ergaenzen" elif clicked == entfernen_btn: return "entfernen" elif clicked == ersetzen_btn: return "ersetzen" else: return "abbrechen" def ask_detailpruefung_oder_vg_laden( parent: Any, title: str, message: str, ) -> Literal["detailpruefung", "als_vg_laden", "abbrechen"]: """Zeigt Dialog mit drei Optionen nach erkannter Flächenabweichung. Optionen: - **Detailprüfung starten**: Knickpunktbasierte Prüfung des BROR-Umrings - **Bodenordnungsobjekt als VG laden**: BROR direkt als Verfahrensgebiet übernehmen - **Abbrechen**: Vorgang beenden ohne weitere Aktion Parameters ---------- parent : Eltern-Widget oder None. title : str Dialog-Titel. message : str Hauptmeldung mit Flächen-Informationen. Returns ------- Literal["detailpruefung", "als_vg_laden", "abbrechen"] Genaue Entscheidung des Nutzers. von Test abgesehen, weil vernachlässigbar; HD 18.09.2026 """ if QT_VERSION == 0: # Mock-Modus print(f"Mock-Modus: ask_detailpruefung_oder_vg_laden('{title}') → abbrechen") return "abbrechen" msg = QMessageBox(parent) msg.setIcon(ICON_QUESTION) msg.setWindowTitle(title) msg.setText(message) detail_btn = msg.addButton("Detailprüfung starten", QMessageBox.ButtonRole.AcceptRole) vg_btn = msg.addButton("Bodenordnungsobjekt als Verfahrensgebiet laden", QMessageBox.ButtonRole.ActionRole) msg.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole) exec_dialog(msg) clicked = msg.clickedButton() if clicked == detail_btn: return "detailpruefung" elif clicked == vg_btn: return "als_vg_laden" else: return "abbrechen" def ask_detailpruefung_oder_abbrechen( parent: Any, title: str, message: str, ) -> Literal["detailpruefung", "abbrechen"]: """Zeigt Dialog mit zwei Optionen nach erkannter Flächenabweichung (Shape-Workflow). Optionen: - **Detailprüfung starten**: Knickpunktbasierte Prüfung des Shape-VG-Umrings - **Abbrechen**: Vorgang beenden, Shape-Verfahrensgebiet unverändert behalten Parameters ---------- parent : Eltern-Widget oder None. title : str Dialog-Titel. message : str Hauptmeldung mit Flächen-Informationen. Returns ------- Literal["detailpruefung", "abbrechen"] Genaue Entscheidung des Nutzers. von Test abgesehen, weil vernachlässigbar; HD 18.09.2026 """ if QT_VERSION == 0: # Mock-Modus print(f"Mock-Modus: ask_detailpruefung_oder_abbrechen('{title}') → abbrechen") return "abbrechen" msg = QMessageBox(parent) msg.setIcon(ICON_QUESTION) msg.setWindowTitle(title) msg.setText(message) detail_btn = msg.addButton("Detailprüfung starten", QMessageBox.ButtonRole.AcceptRole) msg.addButton("Abbrechen", QMessageBox.ButtonRole.RejectRole) exec_dialog(msg) clicked = msg.clickedButton() if clicked == detail_btn: return "detailpruefung" else: return "abbrechen" class ProgressDialog: def __init__(self, total: int, title: str = "Fortschritt", label: str = "Verarbeite..."): self.total = max(total, 1) self._canceled = False if QT_VERSION == 0: self.value = 0 self.label = label self.title = title return self._dlg = QProgressDialog(label, "Abbrechen", 0, self.total) self._dlg.setWindowTitle(title) # Qt5 vs Qt6: WindowModality-Enum unterschiedlich verfügbar modality = None if hasattr(Qt, "WindowModality"): try: modality = Qt.WindowModality.WindowModal except Exception: modality = None if modality is None and hasattr(Qt, "WindowModal"): modality = Qt.WindowModal if modality is not None: try: self._dlg.setWindowModality(modality) except Exception: pass self._dlg.setMinimumDuration(0) self._dlg.setAutoClose(False) self._dlg.setAutoReset(False) self._dlg.setValue(0) def on_cancel(): if self._dlg and self._dlg.value() >= self.total: # OK-Button am Ende self._dlg.close() return self._canceled = True self._dlg.close() try: self._dlg.canceled.connect(on_cancel) except Exception: pass def set_total(self, total: int) -> None: """von Test abgesehen, weil vernachlässigbar; HD 18.09.2026""" self.total = max(total, 1) if QT_VERSION == 0: return if self._dlg is not None: self._dlg.setMaximum(self.total) def set_value(self, value: int) -> None: """von Test abgesehen, weil vernachlässigbar; HD 18.09.2026""" if QT_VERSION == 0: self.value = value return if self._dlg is not None: self._dlg.setValue(min(value, self.total)) if value >= self.total: self._dlg.setLabelText("Fertig. Klicken Sie auf OK, um das Fenster zu schließen.") self._dlg.setCancelButtonText("OK") QCoreApplication.processEvents() def set_label(self, text: str) -> None: """von Test abgesehen, weil vernachlässigbar; HD 18.09.2026""" if QT_VERSION == 0: self.label = text return if self._dlg is not None: self._dlg.setLabelText(text) QCoreApplication.processEvents() def is_canceled(self) -> bool: """von Test abgesehen, weil vernachlässigbar; HD 18.09.2026""" if QT_VERSION == 0: return self._canceled if self._dlg is not None: return self._canceled or self._dlg.wasCanceled() return self._canceled def close(self) -> None: """von Test abgesehen, weil vernachlässigbar; HD 18.09.2026""" if QT_VERSION == 0: return if self._dlg is not None: self._dlg.close() def raise_to_front(self) -> None: """Bringt den Fortschrittsdialog in den Vordergrund. Wird aufgerufen, nachdem ein modaler Subdialog (z. B. Auswahlbox) geschlossen wurde und der Fortschrittsdialog wieder sichtbar sein soll. von Test abgesehen, weil vernachlässigbar; HD 18.09.2026 """ if QT_VERSION == 0: return if self._dlg is not None: try: self._dlg.raise_() self._dlg.activateWindow() QCoreApplication.processEvents() except Exception: pass def create_progress_dialog(total: int, title: str = "Fortschritt", label: str = "Verarbeite...") -> ProgressDialog: """von Test abgesehen, weil vernachlässigbar; HD 18.09.2026""" return ProgressDialog(total, title, label) def ask_mehrfachauswahl( titel: str, hinweis: str, eintraege: list[dict], vorauswahl: set[str] | list[str], parent: Any = None, ) -> Optional[list[str]]: """Zeigt eine Liste mit Häkchen, in der Einträge an- und abgewählt werden. Parameters ---------- eintraege : Dicts mit ``schluessel`` und ``text``, optional ``gruppe`` (Zwischenüberschrift; Einträge einer Gruppe müssen aufeinander folgen) und ``icon`` (z. B. Symbolvorschau). vorauswahl : Schlüssel der anfangs angehakten Einträge. Returns ------- list[str] | None Schlüssel der angehakten Einträge in Listenreihenfolge, ``None`` bei Abbruch. Im Mock-Modus die Vorauswahl. """ vorauswahl = set(vorauswahl) if QT_VERSION == 0: # Mock-Modus return [e["schluessel"] for e in eintraege if e["schluessel"] in vorauswahl] from sn_basis.functions.qt_wrapper import ( QDialog, QDialogButtonBox, QFont, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout, DialogAccepted, ItemIsEnabled, ItemIsUserCheckable, CheckStateChecked, CheckStateUnchecked, UserRole, ) dialog = QDialog(parent) dialog.setWindowTitle(titel) layout = QVBoxLayout(dialog) beschriftung = QLabel(hinweis) beschriftung.setWordWrap(True) layout.addWidget(beschriftung) suche = QLineEdit() suche.setPlaceholderText("Suche …") layout.addWidget(suche) liste = QListWidget() kopf_schrift = QFont() kopf_schrift.setBold(True) zeilen: list[tuple[Any, dict]] = [] koepfe: dict[str, Any] = {} letzte_gruppe = None for eintrag in eintraege: gruppe = eintrag.get("gruppe") if gruppe and gruppe != letzte_gruppe: kopf = QListWidgetItem(gruppe) kopf.setFlags(ItemIsEnabled) kopf.setFont(kopf_schrift) liste.addItem(kopf) koepfe[gruppe] = kopf letzte_gruppe = gruppe zeile = QListWidgetItem(eintrag["text"]) if eintrag.get("icon") is not None: zeile.setIcon(eintrag["icon"]) zeile.setFlags(ItemIsUserCheckable | ItemIsEnabled) zeile.setCheckState(CheckStateChecked if eintrag["schluessel"] in vorauswahl else CheckStateUnchecked) zeile.setData(UserRole, eintrag["schluessel"]) liste.addItem(zeile) zeilen.append((zeile, eintrag)) layout.addWidget(liste, 1) def _filtern(text: str) -> None: suchtext = text.strip().lower() sichtbare_gruppen = set() for zeile, eintrag in zeilen: treffer = not suchtext or suchtext in eintrag["text"].lower() \ or suchtext in (eintrag.get("gruppe") or "").lower() zeile.setHidden(not treffer) if treffer: sichtbare_gruppen.add(eintrag.get("gruppe")) for gruppe, kopf in koepfe.items(): kopf.setHidden(gruppe not in sichtbare_gruppen) def _alle_setzen(zustand: Any) -> None: for zeile, _ in zeilen: if not zeile.isHidden(): zeile.setCheckState(zustand) suche.textChanged.connect(_filtern) knopfzeile = QHBoxLayout() alle = QPushButton("Alle auswählen") alle.clicked.connect(lambda *_: _alle_setzen(CheckStateChecked)) keine = QPushButton("Keine auswählen") keine.clicked.connect(lambda *_: _alle_setzen(CheckStateUnchecked)) knopfzeile.addWidget(alle) knopfzeile.addWidget(keine) knopfzeile.addStretch(1) layout.addLayout(knopfzeile) knoepfe = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) knoepfe.accepted.connect(dialog.accept) knoepfe.rejected.connect(dialog.reject) layout.addWidget(knoepfe) dialog.resize(460, 560) if exec_dialog(dialog) != DialogAccepted: return None return [eintrag["schluessel"] for zeile, eintrag in zeilen if zeile.checkState() == CheckStateChecked]