forked from AG_QGIS/Plugin_SN_Plan41
356 lines
13 KiB
Python
356 lines
13 KiB
Python
"""
|
||
sn_plan41/ui/tab_a_ui.py – UI für Tab A (Daten)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Optional
|
||
|
||
from sn_basis.functions.qt_wrapper import (
|
||
QWidget,
|
||
QVBoxLayout,
|
||
QLabel,
|
||
QPushButton,
|
||
QToolButton,
|
||
QFileDialog,
|
||
QMessageBox,
|
||
ToolButtonTextBesideIcon,
|
||
ArrowDown,
|
||
ArrowRight,
|
||
SizePolicyPreferred,
|
||
SizePolicyMaximum,
|
||
QComboBox,
|
||
)
|
||
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
|
||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||
|
||
# Services (werden von DockWidget injiziert)
|
||
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||
from sn_basis.modules.DataGrabber import DataGrabber
|
||
from sn_basis.modules.Dateipruefer import Dateipruefer
|
||
from sn_plan41.ui.tab_a_logic import TabALogic
|
||
|
||
# Konstanten
|
||
RAUMFILTER_VAR = "Raumfilter"
|
||
RAUMFILTER_OPTIONS = ("Verfahrensgebiet", "Pufferlayer", "ohne")
|
||
RAUMFILTER_DEFAULT = "Pufferlayer"
|
||
|
||
|
||
class TabA(QWidget):
|
||
"""
|
||
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.
|
||
"""
|
||
|
||
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)
|
||
"""
|
||
super().__init__(parent)
|
||
|
||
# Services (werden von DockWidget gesetzt)
|
||
self.pruefmanager: Optional[Pruefmanager] = None
|
||
self.data_grabber: Optional[DataGrabber] = None
|
||
self.logic: Optional[TabALogic] = None
|
||
|
||
# UI-State
|
||
self.verfahrens_db: Optional[str] = None
|
||
self.lokale_linkliste: Optional[str] = None
|
||
self._pufferlayer = None
|
||
self._attributes_list = []
|
||
|
||
# UI-Referenzen
|
||
self._raumfilter_combo: Optional[QComboBox] = None
|
||
|
||
self._build_ui()
|
||
self._restore_state()
|
||
|
||
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
|
||
|
||
def _build_ui(self) -> None:
|
||
"""Erstellt die komplette UI-Hierarchie mit allen Gruppen."""
|
||
main_layout = QVBoxLayout()
|
||
main_layout.setSpacing(4)
|
||
main_layout.setContentsMargins(4, 4, 4, 4)
|
||
|
||
# === VERFAHRENS-DATENBANK ===
|
||
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)
|
||
|
||
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)
|
||
|
||
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)
|
||
|
||
self.group_content.setLayout(group_layout)
|
||
main_layout.addWidget(self.group_content)
|
||
|
||
# === OPTIONALE LINKLISTE ===
|
||
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)
|
||
|
||
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)
|
||
|
||
self.optional_content.setLayout(optional_layout)
|
||
self.optional_content.setVisible(False)
|
||
main_layout.addWidget(self.optional_content)
|
||
|
||
# === LAYER-AUSWAHL + RAUMFILTER ===
|
||
layer_label = QLabel("Verfahrensgebiet-Layer auswählen")
|
||
layer_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||
main_layout.addWidget(layer_label)
|
||
|
||
self.layer_combo = QgsMapLayerComboBox()
|
||
self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)
|
||
self.layer_combo.layerChanged.connect(self._on_layer_changed)
|
||
main_layout.addWidget(self.layer_combo)
|
||
|
||
main_layout.addWidget(QLabel("Raumfilter"))
|
||
self._raumfilter_combo = QComboBox(self)
|
||
self._raumfilter_combo.addItems(RAUMFILTER_OPTIONS)
|
||
self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed)
|
||
main_layout.addWidget(self._raumfilter_combo)
|
||
|
||
# === PIPELINE-STEUERUNG ===
|
||
self.btn_pipeline = QPushButton("Fachdaten laden")
|
||
self.btn_pipeline.setToolTip("Starte Pipeline: Linkliste → DataGrabber → Datenschreiber → Log")
|
||
self.btn_pipeline.clicked.connect(self._on_run_pipeline)
|
||
main_layout.addWidget(self.btn_pipeline)
|
||
|
||
|
||
main_layout.addStretch(1)
|
||
self.setLayout(main_layout)
|
||
|
||
def _restore_state(self) -> None:
|
||
"""Stellt UI-State aus Projektvariablen/Persistenz wieder her."""
|
||
# Verfahrens-DB
|
||
try:
|
||
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()
|
||
except Exception:
|
||
pass
|
||
|
||
# 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
|
||
|
||
# Layer
|
||
try:
|
||
layer_id = get_variable("tab_a_layer_id", scope="project")
|
||
if layer_id:
|
||
layer = QgsProject.instance().mapLayer(layer_id)
|
||
if layer and self.layer_combo:
|
||
self.layer_combo.setLayer(layer)
|
||
self._pufferlayer = layer
|
||
except Exception:
|
||
pass
|
||
|
||
# Raumfilter (schon im _build_ui behandelt)
|
||
|
||
# === UI CALLBACKS ===
|
||
def _toggle_group(self, checked: bool) -> None:
|
||
"""Zeigt/verbirgt Verfahrens-DB-Gruppe."""
|
||
self.group_button.setArrowType(ArrowDown if checked else ArrowRight)
|
||
self.group_content.setVisible(checked)
|
||
|
||
def _toggle_optional(self, checked: bool) -> None:
|
||
"""Zeigt/verbirgt optionale Linkliste."""
|
||
self.optional_button.setArrowType(ArrowDown if checked else ArrowRight)
|
||
self.optional_content.setVisible(checked)
|
||
|
||
def _on_verfahrens_db_changed(self, path: str) -> None:
|
||
"""Persistieret Verfahrens-DB-Pfad."""
|
||
self.verfahrens_db = path
|
||
set_variable("tab_a_verfahrens_db", path, scope="project")
|
||
self._update_group_color()
|
||
|
||
def _on_linkliste_changed(self, path: str) -> None:
|
||
"""Persistieret lokale Linkliste."""
|
||
self.lokale_linkliste = path
|
||
set_variable("tab_a_linkliste", path, scope="project")
|
||
|
||
def _on_layer_changed(self, layer) -> None:
|
||
"""Persistieret Layer-Auswahl."""
|
||
self._pufferlayer = layer
|
||
if layer:
|
||
set_variable("tab_a_layer_id", layer.id(), scope="project")
|
||
|
||
def _on_raumfilter_changed(self, value: str) -> None:
|
||
"""Persistieret Raumfilter-Auswahl."""
|
||
set_variable(RAUMFILTER_VAR, value, scope="project")
|
||
|
||
def _create_new_gpkg(self) -> None:
|
||
"""Delegiert GPKG-Erstellung (Prüfungen über Services)."""
|
||
file_path, _ = QFileDialog.getSaveFileName(
|
||
self, "Neue Verfahrens-Datenbank", "", "Geopackage (*.gpkg)"
|
||
)
|
||
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")
|
||
self._update_group_color()
|
||
|
||
def _update_group_color(self) -> None:
|
||
"""Visuelles Feedback für Verfahrens-DB-Status."""
|
||
if self.verfahrens_db:
|
||
self.group_button.setStyleSheet("font-weight: bold; background-color: #e0f7e0;")
|
||
else:
|
||
self.group_button.setStyleSheet("font-weight: bold;")
|
||
|
||
# === PIPELINE ===
|
||
def _on_run_pipeline(self) -> None:
|
||
"""DEBUG: Pipeline mit maximaler Ausgabe."""
|
||
print("\n" + "="*60)
|
||
print("🚀 _on_run_pipeline GESTARTET")
|
||
print("="*60)
|
||
# 🔥 DEBUG QT STATUS
|
||
from sn_basis.functions import qt_wrapper
|
||
qt_wrapper.debug_qt_status() # ← Zeigt EXAKT was läuft!
|
||
|
||
|
||
|
||
# 1. Services prüfen
|
||
print(f"pruefmanager: {self.pruefmanager is not None}")
|
||
print(f"data_grabber: {self.data_grabber is not None}")
|
||
print(f"logic: {hasattr(self, 'logic')}")
|
||
|
||
if not self.pruefmanager:
|
||
print("❌ FEHLER: self.pruefmanager fehlt!")
|
||
return
|
||
if not self.data_grabber:
|
||
print("❌ FEHLER: self.data_grabber fehlt!")
|
||
return
|
||
|
||
print("✅ Services OK")
|
||
|
||
# 2. FileWidget
|
||
source = self.file_widget.filePath()
|
||
print(f"📁 Eingabe: '{source}' (len={len(source or '')})")
|
||
|
||
# 3. Dateipruefer
|
||
print("🔍 Dateipruefer starte...")
|
||
try:
|
||
pruefer = Dateipruefer(
|
||
source,
|
||
basis_pfad="",
|
||
leereingabe_erlaubt=False,
|
||
standarddatei=None,
|
||
temporaer_erlaubt=True, # ✅ Explizit True
|
||
verfahrens_db_modus=True # ✅ Keyword-only
|
||
)
|
||
ergebnis1 = pruefer.pruefe()
|
||
print(f" → ok={ergebnis1.ok}, aktion='{ergebnis1.aktion}', kontext={ergebnis1.kontext}")
|
||
except Exception as e:
|
||
print(f"💥 Dateipruefer FEHLER: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return
|
||
|
||
# 4. Pruefmanager
|
||
print("🤖 Pruefmanager starte...")
|
||
try:
|
||
ergebnis2 = self.pruefmanager.verarbeite(ergebnis1)
|
||
|
||
print(f" → ok={ergebnis2.ok}, aktion='{ergebnis2.aktion}', kontext={ergebnis2.kontext}")
|
||
except Exception as e:
|
||
print(f"💥 Pruefmanager FEHLER: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return
|
||
|
||
# 5. Entscheidung
|
||
weiter = ergebnis2.ok
|
||
print(f"➡️ Weiter? {weiter} (aktion='{ergebnis2.aktion}')")
|
||
|
||
if weiter:
|
||
final_pfad = ergebnis2.kontext if ergebnis2.kontext else source
|
||
print(f"🚀 DataGrabber mit: '{final_pfad}'")
|
||
try:
|
||
self.data_grabber.run(final_pfad)
|
||
print("✅ DataGrabber aufgerufen!")
|
||
except Exception as e:
|
||
print(f"💥 DataGrabber FEHLER: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
else:
|
||
print("⏹️ Pipeline gestoppt (erwartet bei leerem Pfad)")
|
||
|
||
print("="*60 + "\n")
|
||
|
||
|
||
|
||
def _on_load_fachdaten(self) -> None:
|
||
"""Kompatibilitäts-Handler → neue Pipeline."""
|
||
self._on_run_pipeline()
|