397 lines
12 KiB
Python
397 lines
12 KiB
Python
"""
|
||
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.
|
||
"""
|
||
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."""
|
||
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_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.
|
||
"""
|
||
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.
|
||
"""
|
||
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"
|
||
|
||
|
||
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:
|
||
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:
|
||
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:
|
||
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:
|
||
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:
|
||
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.
|
||
"""
|
||
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:
|
||
return ProgressDialog(total, title, label)
|
||
|
||
|
||
def ask_bror_kombination(
|
||
parent: Any,
|
||
kombinationen: list[tuple[str, str]],
|
||
) -> Optional[tuple[str, str]]:
|
||
"""Zeigt Dialog zur Auswahl einer BROR-Kombination (adv_name + bezeichnung).
|
||
|
||
Wird aufgerufen, wenn der WFS mehrere verschiedene Kombinationen aus
|
||
``adv_name`` und ``bezeichnung`` zurückliefert, sodass der Nutzer
|
||
entscheiden muss, welche Objekte in die Pipeline einfließen sollen.
|
||
|
||
Parameters
|
||
----------
|
||
parent :
|
||
Eltern-Widget oder None.
|
||
kombinationen :
|
||
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
|
||
|
||
Returns
|
||
-------
|
||
Optional[tuple[str, str]]
|
||
Das gewählte ``(adv_name, bezeichnung)``-Tupel oder ``None`` bei
|
||
Abbruch bzw. leerer Eingabe.
|
||
"""
|
||
if not kombinationen:
|
||
return None
|
||
|
||
if QT_VERSION == 0: # Mock-Modus
|
||
print(f"Mock-Modus: ask_bror_kombination → {kombinationen[0]}")
|
||
return kombinationen[0]
|
||
|
||
items = [f"{adv} | {bez}" for adv, bez in kombinationen]
|
||
item, ok = QInputDialog.getItem(
|
||
parent,
|
||
"BROR-Objekt auswählen",
|
||
"Mehrere BauRaumOderBodenordnungsrecht-Objekte gefunden.\n"
|
||
"Bitte wählen Sie die gewünschte Kombination\n"
|
||
"aus ADV-Name und Bezeichnung:",
|
||
items,
|
||
0,
|
||
False,
|
||
)
|
||
if not ok:
|
||
return None
|
||
try:
|
||
idx = items.index(item)
|
||
return kombinationen[idx]
|
||
except ValueError:
|
||
return None
|
||
|