7 Commits
main ... dev

13 changed files with 894 additions and 30 deletions

BIN
assets/Linkliste.xlsx Normal file

Binary file not shown.

58
doc/Datenbank_ERD.md Normal file
View File

@@ -0,0 +1,58 @@
```mermaid
erDiagram
tbl_akteure{
Int4 fid PK
varchar Bezeichnung
varchar(6) vkz "Verfahren, für das der Akteur relevant ist"
}
tbl_konten{
varchar(3) kontonr
varchar bezeichnung
}
tbl_ausbauart{
int4 id PK
varchar Ausbauart_text
int4 preis
varchar(3) tbe_nr
varchar(3) Ausbauart_nr "Nr. der Ausbauart zur einfacheren Referenz"
}
tbl_Massnahme{
int4 MnNr
int4 Abschnitte FK
}
p41_Massnahmen_linie{
int4 id PK
geom Geometrie
varchar mnnr "Berechnet aus mn_konto und lfd_nr"
varchar mnname
int4 ausbauart FK "ref:tbl_ausbauart.id"
varchar(3) mn_konto FK "ref: tbl_konten.kontonr"
varchar(2) lfd_nr
varchar(1) tbe
bool umsetzung
int4 unterhalt_bisher FK "ref: tbl_akteure.id"
int4 unterhalt_zukuenftig FK "ref: tbl_akteure.id"
int4 bautraeger FK "ref: tbl_akteure.id"
int4 kostentraeger FK "ref: tbl_akteure.id"
int4 Planungsjahr
int4 baujahr
int4 Pflege_Anfang
int4 Pflege_Ende
float8 fahrbahnbreite
float8 gesamtbreite
varchar vorgesehene_regelungen
varchar bemerkungen
int4 laenge
int4 flaeche
varchar foerdersatz
bool ingenieurbauwerk
varchar bildpfad
bool fertiggestellt
int4 ausbauart_nr
bool plangenehmigt
}
tbl_konten ||--o{ p41_Massnahmen_linie : verwendet
tbl_akteure ||--o{ p41_Massnahmen_linie : verwendet
tbl_ausbauart ||--o{ p41_Massnahmen_linie : verwendet
```

21
main.py
View File

@@ -1,6 +1,8 @@
# sn_plan41/main.py
from qgis.utils import plugins
from sn_basis.ui.dockmanager import DockManager
from .ui.dockwidget import DockWidget
from sn_plan41.ui.dockwidget import DockWidget
class Plan41:
@@ -15,14 +17,17 @@ class Plan41:
def initGui(self):
basis = plugins.get("sn_basis")
if basis and basis.ui:
if not basis or not getattr(basis, "ui", None):
return
self.action = basis.ui.add_action(
self.plugin_name,
self.run,
tooltip=f"Öffnet {self.plugin_name}",
priority=20
priority=20,
)
basis.ui.finalize_menu_and_toolbar()
print("Plan41/sn_Basis:initGui called")
def unload(self):
if self.dockwidget:
@@ -32,13 +37,15 @@ class Plan41:
if self.action:
basis = plugins.get("sn_basis")
if basis and basis.ui:
# Action aus Menü und Toolbar entfernen
if basis and getattr(basis, "ui", None):
basis.ui.remove_action(self.action)
self.action = None
def run(self):
self.dockwidget = DockWidget(self.iface.mainWindow(), subtitle=self.plugin_name)
self.dockwidget = DockWidget(
self.iface.mainWindow(),
subtitle=self.plugin_name,
)
self.dockwidget.setObjectName(self.dock_name)
# Action-Referenz im Dock speichern
@@ -48,5 +55,5 @@ class Plan41:
# Toolbar-Button als aktiv markieren
basis = plugins.get("sn_basis")
if basis and basis.ui:
if basis and getattr(basis, "ui", None):
basis.ui.set_active_plugin(self.action)

3
pyrightconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extraPaths": ["."]
}

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
#Testordner

148
tests/run_tests.py Normal file
View File

@@ -0,0 +1,148 @@
"""
sn_plan41/test/run_tests.py
Zentraler Test-Runner für sn_plan41.
Wrapper-konform, QGIS-unabhängig, CI- und IDE-fähig.
"""
import unittest
import datetime
import inspect
import os
import sys
from pathlib import Path
# ---------------------------------------------------------
# Plugin-Roots bestimmen
# ---------------------------------------------------------
THIS_FILE = Path(__file__).resolve()
# .../plugins/sn_plan41
SN_PLAN41_ROOT = THIS_FILE.parents[1]
# .../plugins/sn_basis
SN_BASIS_ROOT = SN_PLAN41_ROOT.parent / "sn_basis"
# ---------------------------------------------------------
# sys.path Bootstrap
# ---------------------------------------------------------
for path in (SN_PLAN41_ROOT, SN_BASIS_ROOT):
path_str = str(path)
if path_str not in sys.path:
sys.path.insert(0, path_str)
# ---------------------------------------------------------
# Farben
# ---------------------------------------------------------
RED = "\033[91m"
YELLOW = "\033[93m"
GREEN = "\033[92m"
CYAN = "\033[96m"
MAGENTA = "\033[95m"
RESET = "\033[0m"
GLOBAL_TEST_COUNTER = 0
# ---------------------------------------------------------
# Farbige TestResult-Klasse
# ---------------------------------------------------------
class ColoredTestResult(unittest.TextTestResult):
_last_test_class = None
def startTest(self, test):
global GLOBAL_TEST_COUNTER
GLOBAL_TEST_COUNTER += 1
self.stream.write(f"{CYAN}[Test {GLOBAL_TEST_COUNTER}]{RESET}\n")
super().startTest(test)
def startTestClass(self, test):
cls = test.__class__
file = inspect.getfile(cls)
filename = os.path.basename(file)
self.stream.write(
f"\n{MAGENTA}{'=' * 70}\n"
f"Starte Testklasse: {filename}{cls.__name__}\n"
f"{'=' * 70}{RESET}\n"
)
def addError(self, test, err):
super().addError(test, err)
self.stream.write(f"{RED}ERROR{RESET}\n")
def addFailure(self, test, err):
super().addFailure(test, err)
self.stream.write(f"{RED}FAILURE{RESET}\n")
def addSkip(self, test, reason):
super().addSkip(test, reason)
self.stream.write(f"{YELLOW}SKIPPED{RESET}: {reason}\n")
def addSuccess(self, test):
super().addSuccess(test)
self.stream.write(f"{GREEN}OK{RESET}\n")
# ---------------------------------------------------------
# Farbiger TestRunner
# ---------------------------------------------------------
class ColoredTestRunner(unittest.TextTestRunner):
def _makeResult(self):
result = ColoredTestResult(
self.stream,
self.descriptions,
self.verbosity,
)
original_start_test = result.startTest
def patched_start_test(test):
if result._last_test_class != test.__class__:
result.startTestClass(test)
result._last_test_class = test.__class__
original_start_test(test)
result.startTest = patched_start_test
return result
# ---------------------------------------------------------
# Testlauf starten
# ---------------------------------------------------------
def main():
print("\n" + "=" * 70)
print(
f"{CYAN}Testlauf gestartet am: "
f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S}{RESET}"
)
print("=" * 70 + "\n")
loader = unittest.TestLoader()
TEST_ROOT = SN_PLAN41_ROOT / "tests"
suite = loader.discover(
start_dir=str(TEST_ROOT),
pattern="test_*.py",
top_level_dir=str(SN_PLAN41_ROOT.parent),
)
runner = ColoredTestRunner(verbosity=2)
result = runner.run(suite)
return 0 if result.wasSuccessful() else 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -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"

153
tests/test_tab_a_logic.py Normal file
View File

@@ -0,0 +1,153 @@
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from sn_plan41.ui.tab_a_logic import TabALogic # type: ignore
from sn_basis.functions.variable_wrapper import get_variable # type: ignore
from sn_basis.functions.sys_wrapper import file_exists # type: ignore
class TestTabALogic(unittest.TestCase):
# -----------------------------------------------------
# 1. Verfahrens-DB setzen und laden
# -----------------------------------------------------
def test_verfahrens_db_set_and_load(self):
logic = TabALogic()
with TemporaryDirectory() as tmp:
db_path = Path(tmp) / "test.gpkg"
db_path.write_text("")
logic.set_verfahrens_db(str(db_path))
stored = get_variable("verfahrens_db", scope="project")
self.assertEqual(stored, str(db_path))
loaded = logic.load_verfahrens_db()
self.assertEqual(loaded, str(db_path))
# -----------------------------------------------------
# 2. Verfahrens-DB löschen
# -----------------------------------------------------
def test_verfahrens_db_clear(self):
logic = TabALogic()
logic.set_verfahrens_db(None)
stored = get_variable("verfahrens_db", scope="project")
self.assertEqual(stored, "")
# -----------------------------------------------------
# 3. Neue Verfahrens-DB anlegen
# -----------------------------------------------------
def test_create_new_verfahrens_db(self):
logic = TabALogic()
with TemporaryDirectory() as tmp:
db_path = Path(tmp) / "neu.gpkg"
result = logic.create_new_verfahrens_db(str(db_path))
self.assertTrue(result)
self.assertTrue(file_exists(db_path))
stored = get_variable("verfahrens_db", scope="project")
self.assertEqual(stored, str(db_path))
def test_create_new_verfahrens_db_with_none_path(self):
logic = TabALogic()
result = logic.create_new_verfahrens_db(None)
self.assertFalse(result)
# -----------------------------------------------------
# 4. Linkliste setzen und laden
# -----------------------------------------------------
def test_linkliste_set_and_load(self):
logic = TabALogic()
with TemporaryDirectory() as tmp:
link_path = Path(tmp) / "links.xlsx"
link_path.write_text("dummy")
logic.set_linkliste(str(link_path))
stored = get_variable("linkliste", scope="project")
self.assertEqual(stored, str(link_path))
loaded = logic.load_linkliste()
self.assertEqual(loaded, str(link_path))
# -----------------------------------------------------
# 5. Linkliste löschen
# -----------------------------------------------------
def test_linkliste_clear(self):
logic = TabALogic()
logic.set_linkliste(None)
stored = get_variable("linkliste", scope="project")
self.assertEqual(stored, "")
# -----------------------------------------------------
# 6. Layer-ID speichern
# -----------------------------------------------------
def test_verfahrensgebiet_layer_id_storage(self):
logic = TabALogic()
class MockLayer:
def id(self):
return "layer-123"
logic.save_verfahrensgebiet_layer(MockLayer())
stored = get_variable("verfahrensgebiet_layer", scope="project")
self.assertEqual(stored, "layer-123")
# -----------------------------------------------------
# 7. Ungültiger Layer wird ignoriert
# -----------------------------------------------------
def test_invalid_layer_is_rejected(self):
logic = TabALogic()
class InvalidLayer:
pass
logic.save_verfahrensgebiet_layer(InvalidLayer())
stored = get_variable("verfahrensgebiet_layer", scope="project")
self.assertEqual(stored, "")
#-----------------------------------------------------
# 8. Layer-ID wirft Exception
#----------------------------------------------------
def test_layer_id_raises_exception(self):
logic = TabALogic()
class BadLayer:
def id(self):
raise RuntimeError("boom")
logic.save_verfahrensgebiet_layer(BadLayer())
stored = get_variable("verfahrensgebiet_layer", scope="project")
self.assertEqual(stored, "")
# -----------------------------------------------------
# 11. Layer ID wird leer zurückgegeben
# -----------------------------------------------------
def test_layer_id_returns_empty(self):
logic = TabALogic()
class EmptyLayer:
def id(self):
return ""
logic.save_verfahrensgebiet_layer(EmptyLayer())
stored = get_variable("verfahrensgebiet_layer", scope="project")
self.assertEqual(stored, "")
if __name__ == "__main__":
unittest.main()

57
tests/test_tab_a_ui.py Normal file
View File

@@ -0,0 +1,57 @@
"""
Smoke-Tests für TabA UI (sn_plan41/ui/tab_a_ui.py)
Ziel:
- UI kann erstellt werden
- Callbacks crashen nicht
- Keine Qt-Verhaltensprüfung
"""
import unittest
from sn_plan41.ui.tab_a_ui import TabA #type:ignore
class TestTabAUI(unittest.TestCase):
# -----------------------------------------------------
# 1. UI kann erstellt werden
# -----------------------------------------------------
def test_tab_a_ui_can_be_created(self):
tab = TabA(parent=None,build_ui=False)
self.assertIsNotNone(tab)
self.assertEqual(tab.tab_title, "Daten")
# -----------------------------------------------------
# 2. Toggle-Callbacks crashen nicht
# -----------------------------------------------------
def test_tab_a_toggle_callbacks_do_not_crash(self):
tab = TabA(parent=None,build_ui=False)
tab._toggle_group(True)
tab._toggle_group(False)
tab._toggle_optional(True)
tab._toggle_optional(False)
# -----------------------------------------------------
# 3. Datei-Callbacks akzeptieren leere Eingaben
# -----------------------------------------------------
def test_tab_a_file_callbacks_accept_empty_input(self):
tab = TabA(parent=None,build_ui=False)
tab._on_verfahrens_db_changed("")
tab._on_linkliste_changed("")
# -----------------------------------------------------
# 4. Layer-Callback akzeptiert None
# -----------------------------------------------------
def test_tab_a_layer_callback_accepts_none(self):
tab = TabA(parent=None,build_ui=False)
tab._on_layer_changed(None)
if __name__ == "__main__":
unittest.main()

View File

@@ -1,7 +1,7 @@
from sn_basis.ui.tabs.settings_tab import SettingsTab
from sn_plan41.ui.tabs.tab_a import TabA
from sn_plan41.ui.tabs.tab_b import TabB
from sn_plan41.ui.tab_a_ui import TabA
#from sn_plan41.ui.tabs.tab_b import TabB
from sn_basis.ui.base_dockwidget import BaseDockWidget
class DockWidget(BaseDockWidget):
tabs = [TabA, TabB, SettingsTab]
tabs = [TabA, SettingsTab]

132
ui/tab_a_logic.py Normal file
View File

@@ -0,0 +1,132 @@
"""
sn_plan41/ui/tab_a_logic.py Fachlogik für Tab A (Daten)
"""
from typing import Optional
from sn_basis.functions.variable_wrapper import (#type: ignore
get_variable,
set_variable,
)
from sn_basis.functions.sys_wrapper import (#type:ignore
file_exists,
write_text,
)
from sn_basis.functions.ly_existence_wrapper import layer_exists#type:ignore
from sn_basis.functions.ly_metadata_wrapper import get_layer_type#type:ignore
class TabALogic:
"""
Kapselt die komplette Logik von Tab A:
- Verfahrens-Datenbank
- optionale Linkliste
- Verfahrensgebiet-Layer
"""
# -------------------------------
# Verfahrens-Datenbank
# -------------------------------
def load_verfahrens_db(self) -> Optional[str]:
"""
Lädt die gespeicherte Verfahrens-Datenbank.
"""
path = get_variable("verfahrens_db", scope="project")
if path and file_exists(path):
return path
return None
def set_verfahrens_db(self, path: Optional[str]) -> None:
"""
Speichert oder löscht die Verfahrens-Datenbank.
"""
if path:
set_variable("verfahrens_db", path, scope="project")
else:
set_variable("verfahrens_db", "", scope="project")
def create_new_verfahrens_db(self, path: str) -> bool:
"""
Legt eine neue leere GPKG-Datei an.
"""
if not path:
return False
try:
write_text(path, "")
except Exception:
return False
self.set_verfahrens_db(path)
return True
# -------------------------------
# Lokale Linkliste
# -------------------------------
def load_linkliste(self) -> Optional[str]:
"""
Lädt die gespeicherte lokale Linkliste.
"""
path = get_variable("linkliste", scope="project")
if path and file_exists(path):
return path
return None
def set_linkliste(self, path: Optional[str]) -> None:
"""
Speichert oder löscht die lokale Linkliste.
"""
if path:
set_variable("linkliste", path, scope="project")
else:
set_variable("linkliste", "", scope="project")
# -------------------------------
# Verfahrensgebiet-Layer
# -------------------------------
def save_verfahrensgebiet_layer(self, layer) -> None:
"""
Speichert die ID des Verfahrensgebiet-Layers.
Ungültige Layer werden ignoriert.
"""
if layer is None:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
if not hasattr(layer, "id") or not callable(layer.id):
set_variable("verfahrensgebiet_layer", "", scope="project")
return
try:
layer_id = layer.id()
except Exception:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
if not layer_id:
set_variable("verfahrensgebiet_layer", "", scope="project")
return
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
def load_verfahrensgebiet_layer_id(self) -> Optional[str]:
"""
Lädt die gespeicherte Layer-ID.
"""
value = get_variable("verfahrensgebiet_layer", scope="project")
return value or None
def is_valid_verfahrensgebiet_layer(self, layer) -> bool:
"""
Prüft, ob ein Layer als Verfahrensgebiet geeignet ist.
"""
if not layer_exists(layer):
return False
layer_type = get_layer_type(layer)
return layer_type == "vector"

308
ui/tab_a_ui.py Normal file
View File

@@ -0,0 +1,308 @@
"""
sn_plan41/ui/tab_a_ui.py UI für Tab A (Daten)
"""
from typing import Optional
from sn_basis.functions.qt_wrapper import ( # type: ignore
QWidget,
QVBoxLayout,
QLabel,
QPushButton,
QToolButton,
QFileDialog,
QMessageBox,
QTabWidget,
ToolButtonTextBesideIcon,
ArrowDown,
ArrowRight,
SizePolicyPreferred,
SizePolicyMaximum,
)
from sn_basis.functions.qgisui_wrapper import ( # type: ignore
QgsFileWidget,
QgsMapLayerComboBox,
add_dock_widget,
)
from sn_basis.functions.qgiscore_wrapper import ( # type: ignore
QgsProject,
QgsMapLayerProxyModel,
)
from sn_basis.functions.message_wrapper import ( # type: ignore
info,
warning,
error,
)
from sn_basis.functions.dialog_wrapper import ask_yes_no # type: ignore
from sn_basis.functions.sys_wrapper import file_exists # type: ignore
from sn_plan41.ui.tab_a_logic import TabALogic # type: ignore
class TabA(QWidget):
"""
UI-Klasse für Tab A (Daten).
Enthält ausschließlich UI-Code und delegiert Logik an TabALogic.
"""
def __init__(self, parent=None, build_ui: bool=True):
super().__init__(parent)
self.parent=parent
self.tab_title="Daten"
self.logic = TabALogic()
self.verfahrens_db: Optional[str] = None
self.lokale_linkliste: Optional[str] = None
if build_ui:
self._build_ui()
self._restore_state()
# ---------------------------------------------------------
# UI-Aufbau
# ---------------------------------------------------------
def _build_ui(self) -> None:
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
# -------------------------------
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(SizePolicyPreferred, SizePolicyMaximum)
self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.layer_combo.layerChanged.connect(self._on_layer_changed)
main_layout.addWidget(self.layer_combo)
main_layout.addStretch(1)
self.setLayout(main_layout)
# ---------------------------------------------------------
# State Restore
# ---------------------------------------------------------
def _restore_state(self) -> None:
db = self.logic.load_verfahrens_db()
if db:
self.verfahrens_db = db
self.file_widget.setFilePath(db)
self._update_group_color()
link = self.logic.load_linkliste()
if link:
self.lokale_linkliste = link
self.linkliste_widget.setFilePath(link)
layer_id = self.logic.load_verfahrensgebiet_layer_id()
if layer_id:
layer = QgsProject.instance().mapLayer(layer_id)
if layer:
self.layer_combo.setLayer(layer)
# ---------------------------------------------------------
# UI-Callbacks
# ---------------------------------------------------------
def _toggle_group(self, checked: bool):
"""
Klappt den Gruppenbereich ein oder aus.
"""
if not hasattr(self, "group_button"):
return
self.group_button.setArrowType(
ArrowDown if checked else ArrowRight
)
self.group_content.setVisible(checked)
def _toggle_optional(self, checked: bool):
"""
Klappt den optionalen Bereich ein oder aus.
"""
if not hasattr(self, "optional_button"):
return
self.group_button.setArrowType(
ArrowDown if checked else ArrowRight
)
self.optional_content.setVisible(checked)
def _on_verfahrens_db_changed(self, path: str) -> None:
if not path:
self.verfahrens_db = None
self.logic.set_verfahrens_db(None)
self._update_group_color()
return
if not path.lower().endswith(".gpkg"):
path += ".gpkg"
self.file_widget.setFilePath(path)
if not file_exists(path):
warning("Datei nicht gefunden", f"Die Datei existiert nicht:\n{path}")
self.file_widget.setFilePath("")
return
self.verfahrens_db = path
self.logic.set_verfahrens_db(path)
self._update_group_color()
def _on_linkliste_changed(self, path: str) -> None:
if not path:
self.lokale_linkliste = None
self.logic.set_linkliste(None)
return
if not path.lower().endswith(".xlsx"):
path += ".xlsx"
self.linkliste_widget.setFilePath(path)
if not file_exists(path):
warning("Datei nicht gefunden", f"Die Datei existiert nicht:\n{path}")
self.linkliste_widget.setFilePath("")
return
self.lokale_linkliste = path
self.logic.set_linkliste(path)
def _on_layer_changed(self, layer) -> None:
self.logic.save_verfahrensgebiet_layer(layer)
def _create_new_gpkg(self) -> None:
file_path, _ = QFileDialog.getSaveFileName(
self,
"Neue Verfahrens-Datenbank anlegen",
"",
"Geopackage (*.gpkg)",
)
if not file_path:
return
if not file_path.lower().endswith(".gpkg"):
file_path += ".gpkg"
if file_exists(file_path):
overwrite = ask_yes_no(
"Datei existiert bereits",
f"Die Datei existiert bereits:\n\n{file_path}\n\nSoll sie überschrieben werden?",
default=False,
parent=self,
)
if not overwrite:
return
if not self.logic.create_new_verfahrens_db(file_path):
error("Fehler", "Die Datei konnte nicht angelegt werden.")
return
self.verfahrens_db = file_path
self.file_widget.setFilePath(file_path)
self._update_group_color()
info("Projekt-DB angelegt", f"Neue Projekt-Datenbank wurde angelegt:\n{file_path}")
# ---------------------------------------------------------
# UI-Helfer
# ---------------------------------------------------------
def _update_group_color(self):
"""
Aktualisiert die Darstellung der Gruppenüberschrift.
"""
if not hasattr(self, "group_button"):
return
if self.verfahrens_db:
self.group_button.setStyleSheet("font-weight: bold;")
else:
self.group_button.setStyleSheet("")

View File

@@ -1,12 +0,0 @@
from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit
class TabA(QWidget):
tab_title = "Tab A"
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)