Files
Plugin_SN_Verfahrensgebiet/functions/verfahrensgebiet_dialoge.py
T
Michael OttoandClaude Sonnet 5 5816b2af22 Landkreis-Auswahldialog bei fehlenden Projekteigenschaften, Style-Fix "Nur laden"
- Ist weder Verfahrensnummer noch Landkreis in den Projekteigenschaften
  gesetzt, wird jetzt per Dialog ein Landkreis abgefragt (inkl. "Alle
  Landkreise") statt direkt abzubrechen.
- Verfahrensgebiet-spezifische ALKIS-Dialoge aus sn_basis hierher verschoben:
  neue Datei functions/verfahrensgebiet_dialoge.py
  (ask_bror_kombinationen, ask_landkreis_objekte, ask_landkreis_auswahl).
- Bugfix: BROR-Layer bekommt seinen Style (BauRaumOderBodenordnungsrecht.qml)
  jetzt direkt beim Laden in verfahrensgebiet_alkis_komplett(), unabhängig
  vom weiteren Ablauf. working_tab.py überspringt bei "Nur Objekte laden"
  jetzt den gesamten VG-Nachbehandlungsblock (Style/Extent/Haken/Combobox/
  Erfolgsmeldung), da dort kein echter VG-Layer entsteht. Detailprüfung/
  Verifikation im echten QGIS (nicht nur per Unittest) steht noch aus.
- Bugfix: Erfolgsmeldung "Layer wurde ... geladen (inkl.
  Flurstücksverschneidung)" erscheint nicht mehr bei "Nur Objekte laden".

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

300 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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_verfahrensgebiet/functions/verfahrensgebiet_dialoge.py – Dialoge zum
Laden eines Verfahrensgebiets aus ALKIS (Qt5/6/Mock-kompatibel).
"""
from typing import Any, Optional
from sn_basis.functions.qt_wrapper import (
QT_VERSION, exec_dialog, QInputDialog, QLineEdit,
QDialog, QDialogButtonBox, QListWidget, QListWidgetItem, QAbstractItemView,
QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange,
QIcon, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox,
DialogAccepted, ExtendedSelection, SelectRows, NoEditTriggers, AscendingOrder,
)
from sn_basis.functions.landkreise import ALLE_LANDKREISE
import os
# .../python/plugins (drei Ebenen über dieser Datei)
_PLUGINS_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
_WAPPEN_ICON = os.path.join(_PLUGINS_DIR, "sn_basis", "favicon.ico")
def ask_bror_kombinationen(
parent: Any,
kombinationen: list[tuple[str, str]],
) -> list[tuple[str, str]]:
"""Zeigt Dialog zur Auswahl einer oder mehrerer BROR-Kombinationen
(adv_name + bezeichnung).
Wird aufgerufen, wenn der WFS mehrere verschiedene Kombinationen aus
``adv_name`` und ``bezeichnung`` zurückliefert. Es können ein oder
mehrere Objekte gemeinsam ausgewählt und geladen (verschmolzen) werden.
Parameters
----------
parent :
Eltern-Widget oder None.
kombinationen :
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
Returns
-------
list[tuple[str, str]]
Die gewählten ``(adv_name, bezeichnung)``-Tupel. Leere Liste bei
Abbruch oder wenn nichts ausgewählt wurde.
"""
if not kombinationen:
return []
if QT_VERSION == 0: # Mock-Modus
print(f"Mock-Modus: ask_bror_kombinationen → {kombinationen[:1]}")
return kombinationen[:1]
dialog = QDialog(parent)
dialog.setWindowTitle("Verfahrensgebiet aus ALKIS laden")
if os.path.exists(_WAPPEN_ICON):
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
layout = QVBoxLayout(dialog)
hinweis = QLabel(
f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n"
"Bitte wählen Sie eines oder mehrere Objekte aus:"
)
hinweis.setWordWrap(True)
layout.addWidget(hinweis)
list_widget = QListWidget()
list_widget.setSelectionMode(ExtendedSelection)
for adv, bez in kombinationen:
item = QListWidgetItem(f"{adv} | {bez}")
list_widget.addItem(item)
list_widget.item(0).setSelected(True)
layout.addWidget(list_widget, 1) # Stretch-Faktor: wächst mit Fenstergröße
mehrfachauswahl_hinweis = QLabel("<small>Mehrfachauswahl mit Strg + Umschalt</small>")
layout.addWidget(mehrfachauswahl_hinweis)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addWidget(buttons)
dialog.adjustSize()
breite = dialog.sizeHint().width()
# Liste auf max. 5 sichtbare Zeilen begrenzen, Rest per Scrollbar
sichtbare_zeilen = min(len(kombinationen), 5)
zeilenhoehe = list_widget.sizeHintForRow(0) if list_widget.count() else 20
# + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen
# Einträgen), damit diese nicht die letzte Zeile abschneidet.
scrollbar_puffer = list_widget.horizontalScrollBar().sizeHint().height()
list_hoehe = zeilenhoehe * sichtbare_zeilen + 2 * list_widget.frameWidth() + 4 + scrollbar_puffer
list_widget.setMinimumHeight(list_hoehe)
dialog.resize(breite, dialog.sizeHint().height())
if exec_dialog(dialog) != DialogAccepted:
return []
ausgewaehlte_indizes = sorted(list_widget.row(item) for item in list_widget.selectedItems())
return [kombinationen[i] for i in ausgewaehlte_indizes]
def ask_landkreis_objekte(
parent: Any,
kombinationen: list[tuple[str, str]],
) -> tuple[list[tuple[str, str]], bool]:
"""Zeigt Dialog zur Auswahl eines, mehrerer oder aller BROR-Objekte eines
Landkreises (Fallback, wenn keine Verfahrensnummer gesetzt ist).
Wie :func:`ask_bror_kombinationen`, zusätzlich mit einer "Alle
auswählen"-Checkbox (da hier potenziell deutlich mehr Treffer als bei der
VKZ-Suche auftreten) und einer "Nur laden"-Checkbox, mit der gesteuert
wird, ob nach dem Laden der weitere Workflow (Gemarkung/Flurstücke/
Flächenvergleich) fortgesetzt werden soll.
Parameters
----------
parent :
Eltern-Widget oder None.
kombinationen :
Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln.
Returns
-------
tuple[list[tuple[str, str]], bool]
``(ausgewaehlte_kombinationen, nur_laden)``. ``([], False)`` bei
Abbruch oder wenn nichts ausgewählt wurde.
"""
if not kombinationen:
return [], False
if QT_VERSION == 0: # Mock-Modus
print(f"Mock-Modus: ask_landkreis_objekte → {kombinationen[:1]}")
return kombinationen[:1], False
dialog = QDialog(parent)
dialog.setWindowTitle("Verfahrensgebiete aus Landkreis laden")
if os.path.exists(_WAPPEN_ICON):
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
layout = QVBoxLayout(dialog)
hinweis = QLabel(
f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n"
"Bitte wählen Sie eines oder mehrere Objekte aus:"
)
hinweis.setWordWrap(True)
layout.addWidget(hinweis)
suche_zeile = QHBoxLayout()
suche_zeile.addWidget(QLabel("Suche:"))
suche_feld = QLineEdit()
suche_feld.setPlaceholderText("Nummer oder Name …")
suche_zeile.addWidget(suche_feld)
layout.addLayout(suche_zeile)
alle_auswaehlen = QCheckBox("Alle auswählen")
layout.addWidget(alle_auswaehlen)
# Standardsortierung nach Bezeichnung; per Klick auf Spaltenkopf änderbar
# (setSortingEnabled aktiviert die eingebaute Tabellensortierung).
sortierte_kombinationen = sorted(kombinationen, key=lambda kb: kb[1])
table = QTableWidget(len(sortierte_kombinationen), 2)
table.setHorizontalHeaderLabels(["Nummer", "Name"])
table.setSelectionMode(ExtendedSelection)
table.setSelectionBehavior(SelectRows)
table.setEditTriggers(NoEditTriggers)
table.verticalHeader().setVisible(False)
table.verticalHeader().setDefaultSectionSize(22) # flachere Zeilen
table.horizontalHeader().setStretchLastSection(True)
table.setSortingEnabled(False)
for row, (adv, bez) in enumerate(sortierte_kombinationen):
table.setItem(row, 0, QTableWidgetItem(bez))
table.setItem(row, 1, QTableWidgetItem(adv))
table.setSortingEnabled(True)
table.sortByColumn(0, AscendingOrder) # Standard: aufsteigend nach Nummer
table.resizeColumnToContents(0)
table.setColumnWidth(0, max(110, table.columnWidth(0)))
table.selectRow(0)
layout.addWidget(table, 1) # Stretch-Faktor: wächst mit Fenstergröße
def _suche_geaendert(text):
text_lower = text.strip().lower()
for row in range(table.rowCount()):
bez_text = table.item(row, 0).text().lower()
adv_text = table.item(row, 1).text().lower()
treffer = not text_lower or text_lower in bez_text or text_lower in adv_text
table.setRowHidden(row, not treffer)
suche_feld.textChanged.connect(_suche_geaendert)
def _alle_auswaehlen_umschalten(checked):
table.clearSelection()
if checked:
letzte_spalte = table.columnCount() - 1
for row in range(table.rowCount()):
if not table.isRowHidden(row):
# setRangeSelected (statt selectRow) fügt der Selektion
# hinzu, ohne vorherige Zeilen wieder abzuwählen.
table.setRangeSelected(
QTableWidgetSelectionRange(row, 0, row, letzte_spalte), True
)
alle_auswaehlen.stateChanged.connect(_alle_auswaehlen_umschalten)
mehrfachauswahl_hinweis = QLabel("<small>Mehrfachauswahl mit Strg + Umschalt</small>")
layout.addWidget(mehrfachauswahl_hinweis)
nur_laden_checkbox = QCheckBox("Nur Objekte laden (ohne weitere Prüfungen)")
layout.addWidget(nur_laden_checkbox)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addWidget(buttons)
dialog.adjustSize()
# Bezeichnungs-Spalte + großzügige Breite für den ADV-Namen (lange Namen
# sollen möglichst ohne Abschneiden sichtbar sein) + Rahmen/Scrollbar-Puffer.
breite = max(dialog.sizeHint().width(), table.columnWidth(0) + 480)
# Tabelle auf max. 15 sichtbare Zeilen begrenzen, Rest per Scrollbar – hier
# potenziell deutlich mehr Treffer als bei der VKZ-Suche, daher großzügiger
# bemessen als bei ask_bror_kombinationen (dort 5).
sichtbare_zeilen = min(len(sortierte_kombinationen), 15)
zeilenhoehe = table.verticalHeader().defaultSectionSize() if table.rowCount() else 20
kopfzeile_hoehe = table.horizontalHeader().height()
# + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen
# Einträgen), damit diese nicht die letzte Zeile abschneidet.
scrollbar_puffer = table.horizontalScrollBar().sizeHint().height()
table_hoehe = (
zeilenhoehe * sichtbare_zeilen + kopfzeile_hoehe
+ 2 * table.frameWidth() + 4 + scrollbar_puffer
)
table.setMinimumHeight(table_hoehe)
dialog.resize(breite, dialog.sizeHint().height())
if exec_dialog(dialog) != DialogAccepted:
return [], False
ausgewaehlte_zeilen = sorted(set(item.row() for item in table.selectedItems()))
ausgewaehlt = [(table.item(row, 1).text(), table.item(row, 0).text()) for row in ausgewaehlte_zeilen]
return ausgewaehlt, nur_laden_checkbox.isChecked()
def ask_landkreis_auswahl(
parent: Any,
landkreise: list[tuple[str, str]],
) -> Optional[str]:
"""Fragt per Dropdown-Dialog einen Landkreis ab (Fallback, wenn weder
Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt ist).
Parameters
----------
parent :
Eltern-Widget oder None.
landkreise :
Liste von ``(kennzahl, name)``-Tupeln, z.B. ``LANDKREISE`` aus
:mod:`sn_basis.functions.landkreise`.
Die Auswahlliste beginnt mit einem leeren Platzhalter (der Nutzer muss
aktiv einen Eintrag wählen) und enthält zusätzlich "Alle Landkreise"
(Sentinel :data:`~sn_basis.functions.landkreise.ALLE_LANDKREISE`).
Returns
-------
Optional[str]
Gewählte Landkreiskennzahl, :data:`ALLE_LANDKREISE` bei Auswahl aller
Landkreise, oder ``None`` bei Abbruch, leerem Platzhalter oder leerer
Landkreisliste.
"""
if not landkreise:
return None
if QT_VERSION == 0: # Mock-Modus
print("Mock-Modus: ask_landkreis_auswahl → None (keine aktive Auswahl)")
return None
ALLE_LABEL = "Alle Landkreise"
namen = [""] + [ALLE_LABEL] + [name for _, name in landkreise]
dialog = QInputDialog(parent)
dialog.setWindowTitle("Landkreis auswählen")
if os.path.exists(_WAPPEN_ICON):
dialog.setWindowIcon(QIcon(_WAPPEN_ICON))
dialog.setLabelText("Landkreis:")
dialog.setComboBoxItems(namen)
dialog.setComboBoxEditable(False)
if exec_dialog(dialog) != DialogAccepted:
return None
name = dialog.textValue()
if not name:
return None
if name == ALLE_LABEL:
return ALLE_LANDKREISE
name_zu_kennzahl = {n: k for k, n in landkreise}
return name_zu_kennzahl.get(str(name))