Files
Plugin_SN_Basis/functions/dialog_wrapper.py
T
Michael OttoandClaude Sonnet 5 1b5a715c70 Landkreisliste als JSON auslagern; Verfahrensgebiet-Dialoge nach sn_verfahrensgebiet verschoben
- Landkreisliste liegt jetzt in data/landkreise.json statt hartcodiert in
  landkreise.py, damit sie ohne Codeänderung pflegbar und auch aus anderen
  Modulen nutzbar ist (Sentinel ALLE_LANDKREISE für "Alle Landkreise").
- ask_bror_kombinationen, ask_landkreis_objekte und ask_landkreis_auswahl aus
  dialog_wrapper.py entfernt: sie sind sn_verfahrensgebiet-spezifisches
  Vokabular (BROR/VKZ/Landkreis-Fallback) und gehören nicht in den
  domänenfreien sn_basis-Hub. Sie leben jetzt in
  sn_verfahrensgebiet/functions/verfahrensgebiet_dialoge.py.
- qt_wrapper.py: QTableWidget/QTableWidgetItem/QTableWidgetSelectionRange,
  SelectRows, NoEditTriggers, AscendingOrder ergänzt (für die neue
  Tabellen-Auswahl im Landkreis-Objektdialog).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 13:54:43 +02:00

393 lines
12 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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"
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.
"""
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:
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)