2026-03-04 15:31:36 +01:00
|
|
|
|
"""
|
|
|
|
|
|
sn_plan41/ui/tab_a_ui.py – UI für Tab A (Daten)
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-02-13 21:38:25 +01:00
|
|
|
|
from __future__ import annotations
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
2026-02-13 21:38:25 +01:00
|
|
|
|
from sn_basis.functions.qt_wrapper import (
|
2026-01-08 17:13:43 +01:00
|
|
|
|
QWidget,
|
|
|
|
|
|
QVBoxLayout,
|
2026-06-19 11:47:15 +02:00
|
|
|
|
QHBoxLayout,
|
2026-01-08 17:13:43 +01:00
|
|
|
|
QLabel,
|
2026-06-19 11:47:15 +02:00
|
|
|
|
QLineEdit,
|
2026-01-08 17:13:43 +01:00
|
|
|
|
QPushButton,
|
|
|
|
|
|
QToolButton,
|
|
|
|
|
|
QFileDialog,
|
|
|
|
|
|
QMessageBox,
|
2026-02-13 21:38:25 +01:00
|
|
|
|
ToolButtonTextBesideIcon,
|
|
|
|
|
|
ArrowDown,
|
2026-01-08 17:13:43 +01:00
|
|
|
|
ArrowRight,
|
|
|
|
|
|
SizePolicyPreferred,
|
|
|
|
|
|
SizePolicyMaximum,
|
2026-02-14 22:15:58 +01:00
|
|
|
|
QComboBox,
|
2026-02-13 21:38:25 +01:00
|
|
|
|
)
|
|
|
|
|
|
from sn_basis.functions.qgisui_wrapper import QgsFileWidget, QgsMapLayerComboBox
|
|
|
|
|
|
from sn_basis.functions.qgiscore_wrapper import QgsProject, QgsMapLayerProxyModel
|
|
|
|
|
|
from sn_basis.functions.variable_wrapper import get_variable, set_variable
|
2026-03-04 15:31:36 +01:00
|
|
|
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# Services (werden von DockWidget injiziert)
|
2026-02-13 21:38:25 +01:00
|
|
|
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
|
|
|
|
|
from sn_basis.modules.DataGrabber import DataGrabber
|
2026-03-04 15:31:36 +01:00
|
|
|
|
from sn_basis.modules.Dateipruefer import Dateipruefer
|
|
|
|
|
|
from sn_plan41.ui.tab_a_logic import TabALogic
|
2026-03-11 13:27:16 +01:00
|
|
|
|
from sn_basis.modules.linkpruefer import Linkpruefer
|
2026-06-19 11:47:15 +02:00
|
|
|
|
from sn_basis.modules.stilpruefer import Stilpruefer
|
|
|
|
|
|
from sn_plan41.modules.vln_api_logic import VlnApiLogic
|
2026-02-14 22:15:58 +01:00
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# Konstanten
|
2026-02-13 21:38:25 +01:00
|
|
|
|
RAUMFILTER_VAR = "Raumfilter"
|
|
|
|
|
|
RAUMFILTER_OPTIONS = ("Verfahrensgebiet", "Pufferlayer", "ohne")
|
|
|
|
|
|
RAUMFILTER_DEFAULT = "Pufferlayer"
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TabA(QWidget):
|
|
|
|
|
|
"""
|
2026-03-04 15:31:36 +01:00
|
|
|
|
UI-Klasse für Tab A (Daten) des Plan41-Plugins.
|
|
|
|
|
|
|
|
|
|
|
|
Zuständig für:
|
|
|
|
|
|
- Anzeige und Auswahl von Verfahrens-DB, Linklisten und Layern
|
|
|
|
|
|
- Steuerung der Pipeline über "Fachdaten laden"
|
|
|
|
|
|
- Persistierung von UI-States via Projektvariablen
|
|
|
|
|
|
|
|
|
|
|
|
Services (Pruefmanager, DataGrabber) werden zur Laufzeit vom DockWidget injiziert.
|
|
|
|
|
|
Alle fachlichen Prüfungen laufen über den zentralen Pruefmanager.
|
2026-01-08 17:13:43 +01:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
tab_title = "Daten" #: Tab-Titel für BaseDockWidget
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent: Optional[QWidget] = None):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Initialisiert die UI-Struktur.
|
|
|
|
|
|
|
|
|
|
|
|
Services werden später über :meth:`set_services` injiziert.
|
|
|
|
|
|
|
|
|
|
|
|
:param parent: Parent-Widget (typischerweise DockWidget)
|
|
|
|
|
|
"""
|
2026-01-08 17:13:43 +01:00
|
|
|
|
super().__init__(parent)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
|
|
|
|
|
# Services (werden von DockWidget gesetzt)
|
|
|
|
|
|
self.pruefmanager: Optional[Pruefmanager] = None
|
2026-03-11 13:27:16 +01:00
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
self.logic: Optional[TabALogic] = None
|
|
|
|
|
|
|
|
|
|
|
|
# UI-State
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.verfahrens_db: Optional[str] = None
|
|
|
|
|
|
self.lokale_linkliste: Optional[str] = None
|
2026-03-04 15:31:36 +01:00
|
|
|
|
self._pufferlayer = None
|
|
|
|
|
|
self._attributes_list = []
|
|
|
|
|
|
|
|
|
|
|
|
# UI-Referenzen
|
2026-02-14 22:15:58 +01:00
|
|
|
|
self._raumfilter_combo: Optional[QComboBox] = None
|
2026-06-19 11:47:15 +02:00
|
|
|
|
|
|
|
|
|
|
# VLN-API
|
|
|
|
|
|
self.vln_logic: Optional[VlnApiLogic] = None
|
|
|
|
|
|
self._vln_restoring: bool = False
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
self._build_ui()
|
|
|
|
|
|
self._restore_state()
|
2026-02-13 21:38:25 +01:00
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
def set_services(self, pruefmanager: Pruefmanager, data_grabber: DataGrabber) -> None:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Injiziert Services vom übergeordneten DockWidget.
|
|
|
|
|
|
|
|
|
|
|
|
:param pruefmanager: Zentrale Prüfmanager-Instanz
|
|
|
|
|
|
:param data_grabber: DataGrabber für Quellenprüfung/Abruf
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.pruefmanager = pruefmanager
|
|
|
|
|
|
self.data_grabber = data_grabber
|
2026-03-11 13:27:16 +01:00
|
|
|
|
self.logic = TabALogic(
|
2026-06-19 11:47:15 +02:00
|
|
|
|
pruefmanager=self.pruefmanager,
|
|
|
|
|
|
link_pruefer=Linkpruefer(),
|
|
|
|
|
|
stil_pruefer=Stilpruefer(),
|
|
|
|
|
|
)
|
2026-03-11 13:27:16 +01:00
|
|
|
|
self.logic.data_grabber = self.data_grabber
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
2026-06-19 11:47:15 +02:00
|
|
|
|
# VLN-API-Logik initialisieren
|
|
|
|
|
|
self.vln_logic = VlnApiLogic(self.pruefmanager)
|
|
|
|
|
|
self._update_vln_ui_state()
|
|
|
|
|
|
self._restore_vln_state()
|
|
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
def _build_ui(self) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Erstellt die komplette UI-Hierarchie mit allen Gruppen."""
|
2026-01-08 17:13:43 +01:00
|
|
|
|
main_layout = QVBoxLayout()
|
|
|
|
|
|
main_layout.setSpacing(4)
|
|
|
|
|
|
main_layout.setContentsMargins(4, 4, 4, 4)
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# === VERFAHRENS-DATENBANK ===
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.group_button = QToolButton()
|
|
|
|
|
|
self.group_button.setText("Verfahrens-Datenbank")
|
|
|
|
|
|
self.group_button.setCheckable(True)
|
|
|
|
|
|
self.group_button.setChecked(True)
|
|
|
|
|
|
self.group_button.setToolButtonStyle(ToolButtonTextBesideIcon)
|
|
|
|
|
|
self.group_button.setArrowType(ArrowDown)
|
|
|
|
|
|
self.group_button.setStyleSheet("font-weight: bold;")
|
|
|
|
|
|
self.group_button.toggled.connect(self._toggle_group)
|
|
|
|
|
|
main_layout.addWidget(self.group_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.group_content = QWidget()
|
|
|
|
|
|
self.group_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
|
|
|
|
|
|
group_layout = QVBoxLayout()
|
|
|
|
|
|
group_layout.setSpacing(2)
|
|
|
|
|
|
group_layout.setContentsMargins(10, 4, 4, 4)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
group_layout.addWidget(QLabel("bestehende Datei auswählen"))
|
|
|
|
|
|
self.file_widget = QgsFileWidget()
|
|
|
|
|
|
self.file_widget.setStorageMode(QgsFileWidget.GetFile)
|
|
|
|
|
|
self.file_widget.setFilter("Geopackage (*.gpkg)")
|
|
|
|
|
|
self.file_widget.fileChanged.connect(self._on_verfahrens_db_changed)
|
|
|
|
|
|
group_layout.addWidget(self.file_widget)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
group_layout.addWidget(QLabel("-oder-"))
|
|
|
|
|
|
self.btn_new = QPushButton("Neue Verfahrens-DB anlegen")
|
|
|
|
|
|
self.btn_new.clicked.connect(self._create_new_gpkg)
|
|
|
|
|
|
group_layout.addWidget(self.btn_new)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.group_content.setLayout(group_layout)
|
|
|
|
|
|
main_layout.addWidget(self.group_content)
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# === OPTIONALE LINKLISTE ===
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.optional_button = QToolButton()
|
|
|
|
|
|
self.optional_button.setText("Optional: Lokale Linkliste")
|
|
|
|
|
|
self.optional_button.setCheckable(True)
|
|
|
|
|
|
self.optional_button.setChecked(False)
|
|
|
|
|
|
self.optional_button.setToolButtonStyle(ToolButtonTextBesideIcon)
|
|
|
|
|
|
self.optional_button.setArrowType(ArrowRight)
|
|
|
|
|
|
self.optional_button.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
|
|
|
|
|
self.optional_button.toggled.connect(self._toggle_optional)
|
|
|
|
|
|
main_layout.addWidget(self.optional_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.optional_content = QWidget()
|
|
|
|
|
|
self.optional_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
|
|
|
|
|
|
optional_layout = QVBoxLayout()
|
|
|
|
|
|
optional_layout.setSpacing(2)
|
|
|
|
|
|
optional_layout.setContentsMargins(10, 4, 4, 20)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
optional_layout.addWidget(QLabel("(frei lassen für globale Linkliste)"))
|
|
|
|
|
|
self.linkliste_widget = QgsFileWidget()
|
|
|
|
|
|
self.linkliste_widget.setStorageMode(QgsFileWidget.GetFile)
|
|
|
|
|
|
self.linkliste_widget.setFilter("Excelliste (*.xlsx)")
|
|
|
|
|
|
self.linkliste_widget.fileChanged.connect(self._on_linkliste_changed)
|
|
|
|
|
|
optional_layout.addWidget(self.linkliste_widget)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.optional_content.setLayout(optional_layout)
|
|
|
|
|
|
self.optional_content.setVisible(False)
|
|
|
|
|
|
main_layout.addWidget(self.optional_content)
|
|
|
|
|
|
|
2026-03-27 11:19:37 +01:00
|
|
|
|
# === RAUMFILTER ===
|
2026-02-13 21:38:25 +01:00
|
|
|
|
main_layout.addWidget(QLabel("Raumfilter"))
|
2026-02-14 22:15:58 +01:00
|
|
|
|
self._raumfilter_combo = QComboBox(self)
|
2026-03-11 20:56:53 +01:00
|
|
|
|
self._raumfilter_combo.setToolTip("Wählt die räumliche Bezugsfläche für die Datenextraktion.")
|
2026-03-04 15:31:36 +01:00
|
|
|
|
self._raumfilter_combo.addItems(RAUMFILTER_OPTIONS)
|
|
|
|
|
|
self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed)
|
2026-02-13 21:38:25 +01:00
|
|
|
|
main_layout.addWidget(self._raumfilter_combo)
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# === PIPELINE-STEUERUNG ===
|
2026-02-14 22:15:58 +01:00
|
|
|
|
self.btn_pipeline = QPushButton("Fachdaten laden")
|
|
|
|
|
|
self.btn_pipeline.setToolTip("Starte Pipeline: Linkliste → DataGrabber → Datenschreiber → Log")
|
2026-03-11 13:27:16 +01:00
|
|
|
|
self.btn_pipeline.clicked.connect(self._on_load_fachdaten)
|
2026-02-14 22:15:58 +01:00
|
|
|
|
main_layout.addWidget(self.btn_pipeline)
|
|
|
|
|
|
|
2026-06-19 11:47:15 +02:00
|
|
|
|
# === VLN API / PLAN 41 ===
|
|
|
|
|
|
self._vln_group_button = QToolButton()
|
|
|
|
|
|
self._vln_group_button.setText("VLN API / Plan 41")
|
|
|
|
|
|
self._vln_group_button.setCheckable(True)
|
|
|
|
|
|
self._vln_group_button.setChecked(False)
|
|
|
|
|
|
self._vln_group_button.setToolButtonStyle(ToolButtonTextBesideIcon)
|
|
|
|
|
|
self._vln_group_button.setArrowType(ArrowRight)
|
|
|
|
|
|
self._vln_group_button.setStyleSheet("font-weight: bold; margin-top: 8px;")
|
|
|
|
|
|
self._vln_group_button.toggled.connect(self._toggle_vln_group)
|
|
|
|
|
|
main_layout.addWidget(self._vln_group_button)
|
|
|
|
|
|
|
|
|
|
|
|
self._vln_group_content = QWidget()
|
|
|
|
|
|
self._vln_group_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum)
|
|
|
|
|
|
vln_layout = QVBoxLayout()
|
|
|
|
|
|
vln_layout.setSpacing(4)
|
|
|
|
|
|
vln_layout.setContentsMargins(10, 4, 4, 8)
|
|
|
|
|
|
|
|
|
|
|
|
# Status
|
|
|
|
|
|
self._vln_status_label = QLabel("Nicht angemeldet")
|
|
|
|
|
|
self._vln_status_label.setStyleSheet("color: gray; font-style: italic;")
|
|
|
|
|
|
vln_layout.addWidget(self._vln_status_label)
|
|
|
|
|
|
|
|
|
|
|
|
# E-Mail und Passwort
|
|
|
|
|
|
vln_layout.addWidget(QLabel("E-Mail:"))
|
|
|
|
|
|
self._vln_mail_edit = QLineEdit()
|
|
|
|
|
|
self._vln_mail_edit.setPlaceholderText("mail@beispiel.de")
|
|
|
|
|
|
vln_layout.addWidget(self._vln_mail_edit)
|
|
|
|
|
|
|
|
|
|
|
|
vln_layout.addWidget(QLabel("Passwort:"))
|
|
|
|
|
|
self._vln_pass_edit = QLineEdit()
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._vln_pass_edit.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
|
except AttributeError:
|
|
|
|
|
|
self._vln_pass_edit.setEchoMode(QLineEdit.Password)
|
|
|
|
|
|
vln_layout.addWidget(self._vln_pass_edit)
|
|
|
|
|
|
|
|
|
|
|
|
# Login / Logout Buttons
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
|
self._btn_vln_login = QPushButton("Anmelden")
|
|
|
|
|
|
self._btn_vln_login.clicked.connect(self._on_vln_login)
|
|
|
|
|
|
self._btn_vln_logout = QPushButton("Abmelden")
|
|
|
|
|
|
self._btn_vln_logout.clicked.connect(self._on_vln_logout)
|
|
|
|
|
|
btn_row.addWidget(self._btn_vln_login)
|
|
|
|
|
|
btn_row.addWidget(self._btn_vln_logout)
|
|
|
|
|
|
vln_layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
|
|
# Verfahren
|
|
|
|
|
|
vln_layout.addWidget(QLabel("Verfahren:"))
|
|
|
|
|
|
self._vln_verfahren_combo = QComboBox()
|
|
|
|
|
|
self._vln_verfahren_combo.setToolTip("Verfahren (Teilnehmergemeinschaft) wählen")
|
|
|
|
|
|
self._vln_verfahren_combo.setEnabled(False)
|
|
|
|
|
|
self._vln_verfahren_combo.currentIndexChanged.connect(
|
|
|
|
|
|
self._on_vln_verfahren_changed
|
|
|
|
|
|
)
|
|
|
|
|
|
vln_layout.addWidget(self._vln_verfahren_combo)
|
|
|
|
|
|
|
|
|
|
|
|
# Plan 41 laden
|
|
|
|
|
|
self._btn_p41_laden = QPushButton("Plan 41 (Wege- und Gewässerplan) laden")
|
|
|
|
|
|
self._btn_p41_laden.setToolTip(
|
|
|
|
|
|
"Lädt Flächen- und Linienlayer für das gewählte Verfahren vom Server"
|
|
|
|
|
|
)
|
|
|
|
|
|
self._btn_p41_laden.setEnabled(False)
|
|
|
|
|
|
self._btn_p41_laden.clicked.connect(self._on_p41_laden)
|
|
|
|
|
|
vln_layout.addWidget(self._btn_p41_laden)
|
|
|
|
|
|
|
|
|
|
|
|
# Upload
|
|
|
|
|
|
self._btn_upload = QPushButton("Aktiven Layer hochladen")
|
|
|
|
|
|
self._btn_upload.setToolTip(
|
|
|
|
|
|
"Lädt alle Plan-41-Layer des aktiven Verfahrens zum Server hoch"
|
|
|
|
|
|
)
|
|
|
|
|
|
self._btn_upload.setEnabled(False)
|
|
|
|
|
|
self._btn_upload.clicked.connect(self._on_upload_active)
|
|
|
|
|
|
vln_layout.addWidget(self._btn_upload)
|
|
|
|
|
|
|
|
|
|
|
|
self._vln_group_content.setLayout(vln_layout)
|
|
|
|
|
|
self._vln_group_content.setVisible(False)
|
|
|
|
|
|
main_layout.addWidget(self._vln_group_content)
|
|
|
|
|
|
|
2026-01-08 17:13:43 +01:00
|
|
|
|
main_layout.addStretch(1)
|
|
|
|
|
|
self.setLayout(main_layout)
|
|
|
|
|
|
|
|
|
|
|
|
def _restore_state(self) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Stellt UI-State aus Projektvariablen/Persistenz wieder her."""
|
|
|
|
|
|
# Verfahrens-DB
|
2026-02-13 21:38:25 +01:00
|
|
|
|
try:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
db = get_variable("tab_a_verfahrens_db", scope="project")
|
|
|
|
|
|
if db and self.file_widget:
|
|
|
|
|
|
self.file_widget.setFilePath(db)
|
|
|
|
|
|
self.verfahrens_db = db
|
|
|
|
|
|
self._update_group_color()
|
2026-02-13 21:38:25 +01:00
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# Linkliste
|
|
|
|
|
|
try:
|
|
|
|
|
|
link = get_variable("tab_a_linkliste", scope="project")
|
|
|
|
|
|
if link and self.linkliste_widget:
|
|
|
|
|
|
self.linkliste_widget.setFilePath(link)
|
|
|
|
|
|
self.lokale_linkliste = link
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
# Raumfilter (schon im _build_ui behandelt)
|
|
|
|
|
|
|
2026-06-19 11:47:15 +02:00
|
|
|
|
def _restore_vln_state(self) -> None:
|
|
|
|
|
|
"""Stellt die zuletzt gewählte VKZ aus der Projektdatei wieder her."""
|
|
|
|
|
|
if self.vln_logic is None or not self.vln_logic.is_authenticated:
|
|
|
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._populate_verfahren_combo(restore_vkz=True)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
# === UI CALLBACKS ===
|
2026-06-19 11:47:15 +02:00
|
|
|
|
|
|
|
|
|
|
def _toggle_vln_group(self, checked: bool) -> None:
|
|
|
|
|
|
self._vln_group_button.setArrowType(ArrowDown if checked else ArrowRight)
|
|
|
|
|
|
self._vln_group_content.setVisible(checked)
|
|
|
|
|
|
|
|
|
|
|
|
def _update_vln_ui_state(self) -> None:
|
|
|
|
|
|
"""Passt alle VLN-Controls an den aktuellen Authentifizierungsstatus an."""
|
|
|
|
|
|
if self.vln_logic is None:
|
|
|
|
|
|
authenticated = False
|
|
|
|
|
|
available = False
|
|
|
|
|
|
else:
|
|
|
|
|
|
authenticated = self.vln_logic.is_authenticated
|
|
|
|
|
|
available = self.vln_logic.is_available()
|
|
|
|
|
|
|
|
|
|
|
|
if not available:
|
|
|
|
|
|
self._vln_status_label.setText(
|
|
|
|
|
|
"Plugin 'vln_karten' nicht installiert"
|
|
|
|
|
|
)
|
|
|
|
|
|
self._vln_status_label.setStyleSheet("color: red; font-style: italic;")
|
|
|
|
|
|
self._btn_vln_login.setEnabled(False)
|
|
|
|
|
|
elif authenticated:
|
|
|
|
|
|
mail = (self.vln_logic.mail or "API-Key") if self.vln_logic else ""
|
|
|
|
|
|
self._vln_status_label.setText("Angemeldet: %s" % mail)
|
|
|
|
|
|
self._vln_status_label.setStyleSheet(
|
|
|
|
|
|
"color: green; font-style: italic;"
|
|
|
|
|
|
)
|
|
|
|
|
|
self._btn_vln_login.setEnabled(False)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._vln_status_label.setText("Nicht angemeldet")
|
|
|
|
|
|
self._vln_status_label.setStyleSheet("color: gray; font-style: italic;")
|
|
|
|
|
|
self._btn_vln_login.setEnabled(True)
|
|
|
|
|
|
|
|
|
|
|
|
self._btn_vln_logout.setEnabled(authenticated)
|
|
|
|
|
|
self._vln_verfahren_combo.setEnabled(authenticated)
|
|
|
|
|
|
vkz_chosen = (
|
|
|
|
|
|
authenticated
|
|
|
|
|
|
and self._vln_verfahren_combo.currentData() is not None
|
|
|
|
|
|
)
|
|
|
|
|
|
self._btn_p41_laden.setEnabled(vkz_chosen)
|
|
|
|
|
|
self._btn_upload.setEnabled(authenticated)
|
|
|
|
|
|
|
|
|
|
|
|
def _populate_verfahren_combo(self, restore_vkz: bool = False) -> None:
|
|
|
|
|
|
"""Füllt die Verfahren-Combo und wählt ggf. die gespeicherte VKZ."""
|
|
|
|
|
|
if self.vln_logic is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
verfahren = self.vln_logic.get_verfahren()
|
|
|
|
|
|
self._vln_restoring = True
|
|
|
|
|
|
self._vln_verfahren_combo.clear()
|
|
|
|
|
|
self._vln_verfahren_combo.addItem("— Verfahren wählen —", None)
|
|
|
|
|
|
for v in verfahren:
|
|
|
|
|
|
label = (
|
|
|
|
|
|
"%s — %s" % (v["vkz"], v["name"])
|
|
|
|
|
|
if v["name"]
|
|
|
|
|
|
else v["vkz"]
|
|
|
|
|
|
)
|
|
|
|
|
|
self._vln_verfahren_combo.addItem(label, v["vkz"])
|
|
|
|
|
|
self._vln_restoring = False
|
|
|
|
|
|
|
|
|
|
|
|
if restore_vkz:
|
|
|
|
|
|
stored_vkz = self.vln_logic.load_stored_vkz()
|
|
|
|
|
|
if stored_vkz:
|
|
|
|
|
|
idx = self._vln_verfahren_combo.findData(stored_vkz)
|
|
|
|
|
|
if idx >= 0:
|
|
|
|
|
|
self._vln_verfahren_combo.setCurrentIndex(idx)
|
|
|
|
|
|
|
|
|
|
|
|
self._vln_verfahren_combo.setEnabled(True)
|
|
|
|
|
|
self._update_vln_ui_state()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_vln_login(self) -> None:
|
|
|
|
|
|
"""Führt den API-Login durch."""
|
|
|
|
|
|
if self.vln_logic is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
mail = self._vln_mail_edit.text().strip()
|
|
|
|
|
|
password = self._vln_pass_edit.text()
|
|
|
|
|
|
if not mail or not password:
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Anmeldung",
|
|
|
|
|
|
"Bitte E-Mail-Adresse und Passwort eingeben.",
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.vln_logic.login(mail, password)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
QMessageBox.warning(self, "Anmeldung fehlgeschlagen", str(exc))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self._vln_pass_edit.clear()
|
|
|
|
|
|
try:
|
|
|
|
|
|
self._populate_verfahren_combo(restore_vkz=True)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Verfahrensliste",
|
|
|
|
|
|
"Anmeldung erfolgreich, aber Verfahrensliste konnte nicht "
|
|
|
|
|
|
"geladen werden: %s" % exc,
|
|
|
|
|
|
)
|
|
|
|
|
|
self._update_vln_ui_state()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_vln_logout(self) -> None:
|
|
|
|
|
|
"""Meldet den Benutzer lokal ab."""
|
|
|
|
|
|
if self.vln_logic is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
self.vln_logic.logout()
|
|
|
|
|
|
self._vln_restoring = True
|
|
|
|
|
|
self._vln_verfahren_combo.clear()
|
|
|
|
|
|
self._vln_verfahren_combo.setEnabled(False)
|
|
|
|
|
|
self._vln_restoring = False
|
|
|
|
|
|
self._update_vln_ui_state()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_vln_verfahren_changed(self, _index: int) -> None:
|
|
|
|
|
|
"""Persistiert die gewählte VKZ."""
|
|
|
|
|
|
if self._vln_restoring or self.vln_logic is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
vkz = self._vln_verfahren_combo.currentData()
|
|
|
|
|
|
self.vln_logic.save_vkz(vkz)
|
|
|
|
|
|
self._update_vln_ui_state()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_p41_laden(self) -> None:
|
|
|
|
|
|
"""Lädt den Plan-41-Datensatz für die gewählte VKZ."""
|
|
|
|
|
|
if self.vln_logic is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
vkz = self._vln_verfahren_combo.currentData()
|
|
|
|
|
|
if not vkz:
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self, "Plan 41 laden", "Bitte zuerst ein Verfahren wählen."
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Bereits geladene Layer prüfen
|
|
|
|
|
|
existing = self.vln_logic.get_p41_layers(vkz)
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
try:
|
|
|
|
|
|
answer = QMessageBox.question(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Layer ersetzen",
|
|
|
|
|
|
"Plan 41 für VKZ %s ist bereits geladen.\n"
|
|
|
|
|
|
"Beim Neuladen werden die bestehenden Layer ersetzt. Fortfahren?"
|
|
|
|
|
|
% vkz,
|
|
|
|
|
|
)
|
|
|
|
|
|
# Qt6: StandardButton.Yes, Qt5: QMessageBox.Yes
|
|
|
|
|
|
yes = getattr(
|
|
|
|
|
|
getattr(QMessageBox, "StandardButton", QMessageBox),
|
|
|
|
|
|
"Yes",
|
|
|
|
|
|
QMessageBox.Yes if hasattr(QMessageBox, "Yes") else None,
|
|
|
|
|
|
)
|
|
|
|
|
|
if answer != yes:
|
|
|
|
|
|
return
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
self.vln_logic.remove_p41_layers(vkz)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
layers = self.vln_logic.load_p41(vkz)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
# AuthError: Sitzung abgelaufen
|
|
|
|
|
|
try:
|
|
|
|
|
|
from sn_plan41.modules.vln_api_client import AuthError as _AuthError
|
|
|
|
|
|
if isinstance(exc, _AuthError):
|
|
|
|
|
|
self.vln_logic.handle_session_expired()
|
|
|
|
|
|
self._on_vln_logout()
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Sitzung abgelaufen",
|
|
|
|
|
|
"Der API-Key ist nicht mehr gültig — bitte neu anmelden.",
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
QMessageBox.warning(self, "Laden fehlgeschlagen", str(exc))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
total = sum(l.featureCount() for l in layers)
|
|
|
|
|
|
detail = ", ".join(
|
|
|
|
|
|
"%s: %d" % (l.name(), l.featureCount()) for l in layers
|
|
|
|
|
|
)
|
|
|
|
|
|
if total == 0:
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Plan 41 laden",
|
|
|
|
|
|
"Keine Objekte vorhanden — ein leerer Layer wurde angelegt.",
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
try:
|
|
|
|
|
|
from sn_basis.functions.message_wrapper import info
|
|
|
|
|
|
info(
|
|
|
|
|
|
"Plan 41 geladen",
|
|
|
|
|
|
"%d Layer geladen (%s)." % (len(layers), detail),
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def _on_upload_active(self) -> None:
|
|
|
|
|
|
"""Lädt den aktiven Layer (und Geschwister-Layer) zum Server hoch."""
|
|
|
|
|
|
if self.vln_logic is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-09-14 12:20:31 +02:00
|
|
|
|
# Zusammenfassung für Bestätigungs-Dialog ermitteln
|
2026-06-19 11:47:15 +02:00
|
|
|
|
try:
|
|
|
|
|
|
from sn_basis.functions.qgisui_wrapper import iface as _iface
|
|
|
|
|
|
active_layer = _iface.activeLayer() if _iface else None
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
active_layer = None
|
|
|
|
|
|
|
|
|
|
|
|
summary = self.vln_logic.upload_summary(active_layer)
|
|
|
|
|
|
if summary is None:
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Hochladen nicht möglich",
|
|
|
|
|
|
"Der aktive Layer wurde nicht über 'vln_karten' geladen.",
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
answer = QMessageBox.question(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Zum Server hochladen",
|
|
|
|
|
|
"%s für VKZ %s übertragen?\n\n%s\n\n"
|
|
|
|
|
|
"%d Objekte; der bisherige Bestand dieser VKZ wird ersetzt."
|
|
|
|
|
|
% (
|
|
|
|
|
|
summary["dataset_label"],
|
|
|
|
|
|
summary["vkz"],
|
|
|
|
|
|
summary["listing"],
|
|
|
|
|
|
summary["total"],
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
yes = getattr(
|
|
|
|
|
|
getattr(QMessageBox, "StandardButton", QMessageBox),
|
|
|
|
|
|
"Yes",
|
|
|
|
|
|
QMessageBox.Yes if hasattr(QMessageBox, "Yes") else None,
|
|
|
|
|
|
)
|
|
|
|
|
|
if answer != yes:
|
|
|
|
|
|
return
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
success, message = self.vln_logic.upload_active_layer(active_layer)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
try:
|
|
|
|
|
|
from sn_plan41.modules.vln_api_client import AuthError as _AuthError
|
|
|
|
|
|
if isinstance(exc, _AuthError):
|
|
|
|
|
|
self.vln_logic.handle_session_expired()
|
|
|
|
|
|
self._on_vln_logout()
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Sitzung abgelaufen",
|
|
|
|
|
|
"Der API-Key ist nicht mehr gültig — bitte neu anmelden.",
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
QMessageBox.warning(self, "Hochladen fehlgeschlagen", str(exc))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if success:
|
|
|
|
|
|
try:
|
|
|
|
|
|
from sn_basis.functions.message_wrapper import info
|
|
|
|
|
|
info("Upload erfolgreich", message)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
else:
|
|
|
|
|
|
QMessageBox.warning(self, "Hochladen fehlgeschlagen", message)
|
2026-02-13 21:38:25 +01:00
|
|
|
|
def _toggle_group(self, checked: bool) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Zeigt/verbirgt Verfahrens-DB-Gruppe."""
|
2026-02-13 21:38:25 +01:00
|
|
|
|
self.group_button.setArrowType(ArrowDown if checked else ArrowRight)
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.group_content.setVisible(checked)
|
|
|
|
|
|
|
2026-02-13 21:38:25 +01:00
|
|
|
|
def _toggle_optional(self, checked: bool) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Zeigt/verbirgt optionale Linkliste."""
|
2026-02-13 21:38:25 +01:00
|
|
|
|
self.optional_button.setArrowType(ArrowDown if checked else ArrowRight)
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.optional_content.setVisible(checked)
|
|
|
|
|
|
|
|
|
|
|
|
def _on_verfahrens_db_changed(self, path: str) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Persistieret Verfahrens-DB-Pfad."""
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.verfahrens_db = path
|
2026-03-04 15:31:36 +01:00
|
|
|
|
set_variable("tab_a_verfahrens_db", path, scope="project")
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self._update_group_color()
|
|
|
|
|
|
|
|
|
|
|
|
def _on_linkliste_changed(self, path: str) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Persistieret lokale Linkliste."""
|
2026-01-08 17:13:43 +01:00
|
|
|
|
self.lokale_linkliste = path
|
2026-03-04 15:31:36 +01:00
|
|
|
|
set_variable("tab_a_linkliste", path, scope="project")
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-13 21:38:25 +01:00
|
|
|
|
def _on_raumfilter_changed(self, value: str) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Persistieret Raumfilter-Auswahl."""
|
2026-02-13 21:38:25 +01:00
|
|
|
|
set_variable(RAUMFILTER_VAR, value, scope="project")
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
def _create_new_gpkg(self) -> None:
|
|
|
|
|
|
"""Delegiert GPKG-Erstellung (Prüfungen über Services)."""
|
|
|
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
|
|
|
self, "Neue Verfahrens-Datenbank", "", "Geopackage (*.gpkg)"
|
2026-02-13 21:38:25 +01:00
|
|
|
|
)
|
2026-03-04 15:31:36 +01:00
|
|
|
|
if file_path:
|
|
|
|
|
|
if not file_path.lower().endswith(".gpkg"):
|
|
|
|
|
|
file_path += ".gpkg"
|
|
|
|
|
|
self.verfahrens_db = file_path
|
|
|
|
|
|
self.file_widget.setFilePath(file_path)
|
|
|
|
|
|
set_variable("tab_a_verfahrens_db", file_path, scope="project")
|
2026-02-13 21:38:25 +01:00
|
|
|
|
self._update_group_color()
|
2026-01-08 17:13:43 +01:00
|
|
|
|
|
2026-02-13 21:38:25 +01:00
|
|
|
|
def _update_group_color(self) -> None:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
"""Visuelles Feedback für Verfahrens-DB-Status."""
|
2026-01-08 17:13:43 +01:00
|
|
|
|
if self.verfahrens_db:
|
2026-03-11 13:27:16 +01:00
|
|
|
|
self.group_button.setStyleSheet("font-weight: bold; background-color: ##d7a8ff;")
|
2026-01-08 17:13:43 +01:00
|
|
|
|
else:
|
2026-03-04 15:31:36 +01:00
|
|
|
|
self.group_button.setStyleSheet("font-weight: bold;")
|
|
|
|
|
|
|
2026-03-11 13:27:16 +01:00
|
|
|
|
|
2026-03-04 15:31:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _on_load_fachdaten(self) -> None:
|
|
|
|
|
|
"""Kompatibilitäts-Handler → neue Pipeline."""
|
2026-03-12 16:14:24 +01:00
|
|
|
|
source = self.file_widget.filePath()
|
|
|
|
|
|
raumfilter = self._raumfilter_combo.currentText()
|
|
|
|
|
|
linkliste = self.linkliste_widget.filePath()
|
|
|
|
|
|
if self.logic:
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.logic._on_run_pipeline(source, linkliste, raumfilter)
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
QMessageBox.warning(
|
|
|
|
|
|
self,
|
|
|
|
|
|
"Fehler beim Laden",
|
|
|
|
|
|
f"Fehler beim Ausführen der Pipeline: {exc}",
|
|
|
|
|
|
QMessageBox.StandardButton.Ok,
|
|
|
|
|
|
)
|