""" 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()