Files
Plugin_SN_Plan41/ui/tab_a_ui.py

356 lines
13 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_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()