From f5a5ed167bff4d84aa287ca4de2f245e1c2a593e Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 5 Dec 2025 13:07:37 +0100 Subject: [PATCH] =?UTF-8?q?Dateieingabe=20Verfahrens-DB=20und=20Linkliste?= =?UTF-8?q?=20in=20Tab=20A=20eingef=C3=BCgt,=20Verfahrensgebiets-Layerausw?= =?UTF-8?q?ahl=20in=20Tab=20A=20eingef=C3=BCgt.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/__init__.py | 1 + test/run_tests.py | 27 +++ test/start_osgeo4w_qgis.bat | 9 + test/test_tab_a.py | 112 +++++++++++++ ui/tabs/tab_a.py | 318 +++++++++++++++++++++++++++++++++++- 5 files changed, 460 insertions(+), 7 deletions(-) create mode 100644 test/__init__.py create mode 100644 test/run_tests.py create mode 100644 test/start_osgeo4w_qgis.bat create mode 100644 test/test_tab_a.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..324c4b2 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ +#Testordner \ No newline at end of file diff --git a/test/run_tests.py b/test/run_tests.py new file mode 100644 index 0000000..f61f42f --- /dev/null +++ b/test/run_tests.py @@ -0,0 +1,27 @@ +import unittest +import os +import sys + +# Plugin-Hauptverzeichnis ermitteln +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + +# Plugin-Ordner in den Python-Pfad aufnehmen +sys.path.insert(0, BASE_DIR) + +def run(): + # Testverzeichnis + test_dir = os.path.join(BASE_DIR, "tests") + + # Test-Suite automatisch finden + suite = unittest.defaultTestLoader.discover(test_dir) + + # Runner starten + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + # Exit-Code setzen (für CI oder Skripte nützlich) + sys.exit(not result.wasSuccessful()) + + +if __name__ == "__main__": + run() diff --git a/test/start_osgeo4w_qgis.bat b/test/start_osgeo4w_qgis.bat new file mode 100644 index 0000000..a4b0c23 --- /dev/null +++ b/test/start_osgeo4w_qgis.bat @@ -0,0 +1,9 @@ +@echo off +SET OSGEO4W_ROOT=D:\QGISQT5 +call %OSGEO4W_ROOT%\bin\o4w_env.bat +set QGIS_PREFIX_PATH=%OSGEO4W_ROOT%\apps\qgis +set PYTHONPATH=%QGIS_PREFIX_PATH%\python;%PYTHONPATH% +set PATH=%OSGEO4W_ROOT%\bin;%QGIS_PREFIX_PATH%\bin;%PATH% + +REM Neue Eingabeaufforderung starten und Python-Skript ausführen +start cmd /k "python run_tests.py" diff --git a/test/test_tab_a.py b/test/test_tab_a.py new file mode 100644 index 0000000..a85fbc2 --- /dev/null +++ b/test/test_tab_a.py @@ -0,0 +1,112 @@ +import unittest +import os +import tempfile +import sys + +# Plugin-Ordner in den Python-Pfad aufnehmen +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from qgis.PyQt.QtWidgets import QApplication +from qgis.core import QgsProject, QgsVectorLayer + +from sn_plan41.ui.tabs import tab_a + + +class TestTabA(unittest.TestCase): + + @classmethod + def setUpClass(cls): + """Qt-Anwendung initialisieren.""" + cls.app = QApplication([]) + + def setUp(self): + """Vor jedem Test: Projektvariablen löschen und TabA neu erzeugen.""" + self.project = QgsProject.instance() + self.project.setCustomVariables({}) + + # TabA erzeugen + self.tab = TabA() + + # Temporäre Testdateien + self.tmp_dir = tempfile.gettempdir() + self.test_db = os.path.join(self.tmp_dir, "test_db.gpkg") + self.test_link = os.path.join(self.tmp_dir, "test_link.gpkg") + + # Dummy-Dateien anlegen + with open(self.test_db, "w") as f: + f.write("") + with open(self.test_link, "w") as f: + f.write("") + + # --------------------------------------------------------- + # Verfahrens-DB speichern & wiederherstellen + # --------------------------------------------------------- + def test_save_and_restore_verfahrens_db(self): + self.tab.on_file_changed(self.test_db) + + vars = self.project.customVariables() + self.assertEqual(vars.get("sn_verfahrens_db"), self.test_db) + + tab2 = TabA() + self.assertEqual(tab2.verfahrens_db, self.test_db) + self.assertEqual(tab2.file_widget.filePath(), self.test_db) + + # --------------------------------------------------------- + # Verfahrens-DB löschen + # --------------------------------------------------------- + def test_delete_verfahrens_db(self): + self.tab.on_file_changed(self.test_db) + self.tab.on_file_changed("") + + vars = self.project.customVariables() + self.assertNotIn("sn_verfahrens_db", vars) + self.assertIsNone(self.tab.verfahrens_db) + + # --------------------------------------------------------- + # Linkliste speichern & löschen + # --------------------------------------------------------- + def test_save_and_delete_linkliste(self): + self.tab.on_linkliste_changed(self.test_link) + + vars = self.project.customVariables() + self.assertEqual(vars.get("sn_linkliste"), self.test_link) + + self.tab.on_linkliste_changed("") + + vars = self.project.customVariables() + self.assertNotIn("sn_linkliste", vars) + + # --------------------------------------------------------- + # Layer-Vorauswahl + # --------------------------------------------------------- + def test_preselect_verfahrensgebiet_layer(self): + vg_layer = QgsVectorLayer("Polygon?crs=EPSG:4326", "Verfahrensgebiet", "memory") + other_layer = QgsVectorLayer("Polygon?crs=EPSG:4326", "AndereDaten", "memory") + + self.project.addMapLayer(other_layer) + self.project.addMapLayer(vg_layer) + + tab2 = TabA() + + selected = tab2.layer_combo.currentLayer() + self.assertIsNotNone(selected) + self.assertEqual(selected.name(), "Verfahrensgebiet") + + # --------------------------------------------------------- + # Gespeicherter Layer wird wiederhergestellt + # --------------------------------------------------------- + def test_restore_saved_layer(self): + vg_layer = QgsVectorLayer("Polygon?crs=EPSG:4326", "Verfahrensgebiet", "memory") + self.project.addMapLayer(vg_layer) + + vars = {"sn_verfahrensgebiet_layer": vg_layer.id()} + self.project.setCustomVariables(vars) + + tab2 = TabA() + + selected = tab2.layer_combo.currentLayer() + self.assertEqual(selected.id(), vg_layer.id()) + + +if __name__ == "__main__": + unittest.main() diff --git a/ui/tabs/tab_a.py b/ui/tabs/tab_a.py index db0f486..194c221 100644 --- a/ui/tabs/tab_a.py +++ b/ui/tabs/tab_a.py @@ -1,12 +1,316 @@ -from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit +import os + +from qgis.PyQt.QtWidgets import ( + QWidget, QVBoxLayout, QLabel, QMessageBox, QPushButton, + QFileDialog, QToolButton, QSizePolicy +) +from qgis.PyQt.QtCore import Qt +from qgis.gui import QgsFileWidget, QgsMapLayerComboBox +from qgis.core import QgsProject, QgsMapLayerProxyModel + class TabA(QWidget): - tab_title = "Tab A" + tab_title = "Daten" def __init__(self, parent=None): super().__init__(parent) - layout = QVBoxLayout() - layout.addWidget(QLabel("Plugin2 – Tab A")) - layout.addWidget(QLineEdit("Feld A1")) - layout.addWidget(QLineEdit("Feld A2")) - self.setLayout(layout) + + # Variablen initialisieren + self.verfahrens_db = None + self.lokale_linkliste = None + + # --------------------------------------------------------- + # Hauptlayout + # --------------------------------------------------------- + main_layout = QVBoxLayout() + main_layout.setSpacing(4) + main_layout.setContentsMargins(4, 4, 4, 4) + + # --------------------------------------------------------- + # COLLAPSIBLE GRUPPE: 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(Qt.ToolButtonTextBesideIcon) + self.group_button.setArrowType(Qt.DownArrow) + self.group_button.setStyleSheet("font-weight: bold;") + self.group_button.toggled.connect(self.toggle_group) + main_layout.addWidget(self.group_button, 0) + + # Inhalt der Gruppe + self.group_content = QWidget() + self.group_content.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) + + group_layout = QVBoxLayout() + group_layout.setSpacing(2) + group_layout.setContentsMargins(10, 4, 4, 4) + + # Hinweis + hinweis1 = QLabel("bestehende Datei auswählen") + group_layout.addWidget(hinweis1) + + # Datei-Auswahl + self.file_widget = QgsFileWidget() + self.file_widget.setStorageMode(QgsFileWidget.GetFile) + self.file_widget.setFilter("Geopackage (*.gpkg)") + self.file_widget.fileChanged.connect(self.on_file_changed) + group_layout.addWidget(self.file_widget) + + # Hinweis "-oder-" + hinweis2 = QLabel("-oder-") + group_layout.addWidget(hinweis2) + + # Button: Neue Datei + 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, 0) + + # --------------------------------------------------------- + # COLLAPSIBLE Optional-Bereich + # --------------------------------------------------------- + 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(Qt.ToolButtonTextBesideIcon) + self.optional_button.setArrowType(Qt.RightArrow) + self.optional_button.setStyleSheet("font-weight: bold; margin-top: 6px;") + self.optional_button.toggled.connect(self.toggle_optional) + main_layout.addWidget(self.optional_button, 0) + + # Inhalt optional + self.optional_content = QWidget() + self.optional_content.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) + + optional_layout = QVBoxLayout() + optional_layout.setSpacing(2) + optional_layout.setContentsMargins(10, 4, 4, 20) + + # Hinweistext + optional_hint = QLabel("(frei lassen für globale Linkliste)") + optional_layout.addWidget(optional_hint) + + # Datei-Auswahl für Linkliste + self.linkliste_widget = QgsFileWidget() + self.linkliste_widget.setStorageMode(QgsFileWidget.GetFile) + self.linkliste_widget.setFilter("Geopackage (*.gpkg)") + self.linkliste_widget.fileChanged.connect(self.on_linkliste_changed) + optional_layout.addWidget(self.linkliste_widget) + main_layout.addWidget(self.optional_content, 0) + + # --------------------------------------------------------- + # Layer-Auswahlfeld + # --------------------------------------------------------- + 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.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) + + # ✅ QGIS 3.22–3.46 kompatibel + self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer) + + # Layerwechsel speichern + self.layer_combo.layerChanged.connect(self.on_layer_changed) + + main_layout.addWidget(self.layer_combo) + + self.optional_content.setLayout(optional_layout) + self.optional_content.setVisible(False) + + + # Spacer + main_layout.addStretch(1) + + self.setLayout(main_layout) + + # ✅ gespeicherte Werte wiederherstellen (jetzt existieren die Widgets!) + self.restore_saved_values() + + # ✅ Layer-Vorauswahl durchführen + self.preselect_verfahrensgebiet_layer() + + # --------------------------------------------------------- + # Collapsible Gruppe ein-/ausblenden + # --------------------------------------------------------- + def toggle_group(self, checked): + self.group_button.setArrowType(Qt.DownArrow if checked else Qt.RightArrow) + self.group_content.setVisible(checked) + + def toggle_optional(self, checked): + self.optional_button.setArrowType(Qt.DownArrow if checked else Qt.RightArrow) + self.optional_content.setVisible(checked) + + # --------------------------------------------------------- + # Datei-Auswahl: Verfahrens-DB + # --------------------------------------------------------- + def on_file_changed(self, path: str): + if not path: + self.verfahrens_db = None + + # ✅ Projektvariable löschen + vars = QgsProject.instance().customVariables() + if "sn_verfahrens_db" in vars: + del vars["sn_verfahrens_db"] + QgsProject.instance().setCustomVariables(vars) + + self.update_group_button_color() + return + + if not path.lower().endswith(".gpkg"): + path += ".gpkg" + self.file_widget.setFilePath(path) + + if os.path.exists(path): + self.verfahrens_db = path + else: + self.verfahrens_db = None + QMessageBox.warning(self, "Datei nicht gefunden", f"Die Datei existiert nicht:\n{path}") + self.file_widget.setFilePath("") + + # ✅ speichern + vars = QgsProject.instance().customVariables() + vars["sn_verfahrens_db"] = self.verfahrens_db + QgsProject.instance().setCustomVariables(vars) + + self.update_group_button_color() + + # --------------------------------------------------------- + # Datei-Auswahl: Lokale Linkliste + # --------------------------------------------------------- + def on_linkliste_changed(self, path: str): + if not path: + self.lokale_linkliste = None + + vars = QgsProject.instance().customVariables() + if "sn_linkliste" in vars: + del vars["sn_linkliste"] + QgsProject.instance().setCustomVariables(vars) + + return + + + if not path.lower().endswith(".gpkg"): + path += ".gpkg" + self.linkliste_widget.setFilePath(path) + + if os.path.exists(path): + self.lokale_linkliste = path + else: + self.lokale_linkliste = None + QMessageBox.warning(self, "Datei nicht gefunden", f"Die Datei existiert nicht:\n{path}") + self.linkliste_widget.setFilePath("") + + # ✅ speichern + vars = QgsProject.instance().customVariables() + vars["sn_linkliste"] = self.lokale_linkliste + QgsProject.instance().setCustomVariables(vars) + + # --------------------------------------------------------- + # Layer-Auswahl speichern + # --------------------------------------------------------- + def on_layer_changed(self, layer): + if layer: + vars = QgsProject.instance().customVariables() + vars["sn_verfahrensgebiet_layer"] = layer.id() + QgsProject.instance().setCustomVariables(vars) + + # --------------------------------------------------------- + # Button-Farbe aktualisieren + # --------------------------------------------------------- + def update_group_button_color(self): + if self.verfahrens_db: + self.group_button.setStyleSheet("font-weight: bold; background-color: #c4f7c4;") + else: + self.group_button.setStyleSheet("font-weight: bold;") + + # --------------------------------------------------------- + # Vorauswahl des Layers "Verfahrensgebiet" + # --------------------------------------------------------- + def preselect_verfahrensgebiet_layer(self): + project = QgsProject.instance() + + # ✅ zuerst gespeicherten Layer wiederherstellen + saved_layer_id = project.customVariables().get("sn_verfahrensgebiet_layer", None) + if saved_layer_id: + layer = project.mapLayer(saved_layer_id) + if layer: + self.layer_combo.setLayer(layer) + return + + # ✅ sonst nach Namen suchen + for layer in project.mapLayers().values(): + if "verfahrensgebiet" in layer.name().lower(): + self.layer_combo.setLayer(layer) + return + + # ✅ Fallback: erster Layer + if self.layer_combo.count() > 0: + self.layer_combo.setCurrentIndex(0) + + # --------------------------------------------------------- + # Werte wiederherstellen + # --------------------------------------------------------- + def restore_saved_values(self): + project = QgsProject.instance() + vars = project.customVariables() + + # ✅ Verfahrens-DB + saved_db = vars.get("sn_verfahrens_db", None) + if saved_db and os.path.exists(saved_db): + self.verfahrens_db = saved_db + self.file_widget.setFilePath(saved_db) + self.update_group_button_color() + + # ✅ Linkliste + saved_link = vars.get("sn_linkliste", None) + if saved_link and os.path.exists(saved_link): + self.lokale_linkliste = saved_link + self.linkliste_widget.setFilePath(saved_link) + def create_new_gpkg(self): + """Öffnet einen Save-Dialog und legt eine neue GPKG-Datei an.""" + file_path, _ = QFileDialog.getSaveFileName( + self, + "Neue Verfahrens-Datenbank anlegen", + "", + "Geopackage (*.gpkg);;Alle Dateien (*)" + ) + + if not file_path: + return # Abbruch + + # Automatisch .gpkg anhängen + if not file_path.lower().endswith(".gpkg"): + file_path += ".gpkg" + + # Existiert Datei bereits? + if os.path.exists(file_path): + overwrite = QMessageBox.question( + self, + "Datei existiert bereits", + f"Die Datei existiert bereits:\n\n{file_path}\n\nSoll sie überschrieben werden?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + if overwrite != QMessageBox.Yes: + return + + # Datei anlegen + try: + open(file_path, "w").close() + except Exception as e: + QMessageBox.critical(self, "Fehler", f"Die Datei konnte nicht angelegt werden:\n{e}") + return + + # Datei übernehmen + self.verfahrens_db = file_path + self.file_widget.setFilePath(file_path) + self.update_group_button_color() + + QMessageBox.information(self, "Projekt-DB angelegt", f"Neue Projekt-Datenbank wurde angelegt:\n{file_path}")