2026-03-19 16:32:01 +01:00
|
|
|
|
"""
|
|
|
|
|
|
sn_plan41/ui/tab_b_ui.py – UI für Tab B (Druck)
|
|
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
|
|
from sn_basis.functions.qt_wrapper import (
|
|
|
|
|
|
QWidget,
|
|
|
|
|
|
QVBoxLayout,
|
|
|
|
|
|
QLabel,
|
|
|
|
|
|
QComboBox,
|
|
|
|
|
|
)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
from sn_basis.functions.qgiscore_wrapper import QgsProject
|
2026-03-20 10:11:42 +01:00
|
|
|
|
from sn_basis.functions.qgisui_wrapper import iface
|
2026-03-19 16:32:01 +01:00
|
|
|
|
from sn_basis.functions.variable_wrapper import get_variable, set_variable
|
|
|
|
|
|
|
|
|
|
|
|
# Services (werden von DockWidget injiziert)
|
|
|
|
|
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
|
|
|
|
|
from sn_basis.modules.DataGrabber import DataGrabber
|
2026-03-20 10:11:42 +01:00
|
|
|
|
from sn_plan41.ui.tab_b_logic import (
|
|
|
|
|
|
TabBLogic,
|
|
|
|
|
|
MASSSTAB_WIE_KARTENFENSTER,
|
|
|
|
|
|
PLOTMASSSTAB_BY_AUSWAHL,
|
2026-03-20 11:45:36 +01:00
|
|
|
|
THEMA_WIE_KARTENFENSTER,
|
2026-03-20 10:11:42 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
KARTENNAME_VAR = "tab_b_kartenname"
|
|
|
|
|
|
KARTENNAME_PLACEHOLDER = "Kartenname wählen"
|
|
|
|
|
|
KARTENNAME_38 = "§38"
|
|
|
|
|
|
KARTENNAME_41 = "§41"
|
2026-03-20 10:11:42 +01:00
|
|
|
|
MASSSTAB_VAR = "tab_b_massstab"
|
2026-03-20 11:45:36 +01:00
|
|
|
|
THEMA_VAR = "tab_b_thema"
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
|
|
|
|
|
class TabB(QWidget):
|
|
|
|
|
|
"""
|
|
|
|
|
|
UI-Klasse für Tab B (Druck) des Plan41-Plugins.
|
|
|
|
|
|
|
|
|
|
|
|
Zuständig für:
|
|
|
|
|
|
- Auswahl des Druckthemas
|
|
|
|
|
|
- Auswahl der Druckparameter
|
|
|
|
|
|
- Start der Vorlagenanlage (Druck über QGIS-Druckfunktion)
|
|
|
|
|
|
|
|
|
|
|
|
Services (Pruefmanager, DataGrabber) werden zur Laufzeit vom DockWidget injiziert.
|
|
|
|
|
|
Alle fachlichen Prüfungen laufen über den zentralen Pruefmanager.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
tab_title = "Druck" #: 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.logic: Optional[TabBLogic] = None
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._kartenname_combo: Optional[QComboBox] = None
|
2026-03-20 10:11:42 +01:00
|
|
|
|
self._massstab_combo: Optional[QComboBox] = None
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._thema_combo: Optional[QComboBox] = None
|
2026-03-20 10:37:08 +01:00
|
|
|
|
self._theme_signal_connected = False
|
2026-03-20 11:31:44 +01:00
|
|
|
|
self._connected_theme_collection: object = None # Referenz für sauberes Trennen
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._build_ui()
|
|
|
|
|
|
self._restore_state()
|
2026-03-20 10:37:08 +01:00
|
|
|
|
self._connect_theme_collection_signals()
|
2026-03-20 11:31:44 +01:00
|
|
|
|
self._connect_project_signals()
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
2026-03-20 10:11:42 +01:00
|
|
|
|
def set_services(self, pruefmanager: Pruefmanager, data_grabber: DataGrabber) -> None:
|
|
|
|
|
|
"""Injiziert Services vom übergeordneten DockWidget."""
|
|
|
|
|
|
_ = data_grabber
|
|
|
|
|
|
self.pruefmanager = pruefmanager
|
|
|
|
|
|
self.logic = TabBLogic(pruefmanager=self.pruefmanager)
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
if self._kartenname_combo:
|
|
|
|
|
|
self.logic.set_kartenname_for_auswahl(self._kartenname_combo.currentText())
|
2026-03-20 10:11:42 +01:00
|
|
|
|
if self._massstab_combo:
|
|
|
|
|
|
self.logic.set_plotmassstab_for_auswahl(
|
|
|
|
|
|
self._massstab_combo.currentText(),
|
|
|
|
|
|
self._get_current_canvas_scale(),
|
|
|
|
|
|
)
|
2026-03-20 11:45:36 +01:00
|
|
|
|
if self._thema_combo:
|
|
|
|
|
|
self.logic.set_view_for_auswahl(self._thema_combo.currentText())
|
2026-03-20 10:11:42 +01:00
|
|
|
|
|
2026-03-19 16:32:01 +01:00
|
|
|
|
def _build_ui(self) -> None:
|
2026-03-20 10:11:42 +01:00
|
|
|
|
"""Erstellt die reduzierte UI für die Themenauswahl."""
|
2026-03-19 16:32:01 +01:00
|
|
|
|
main_layout = QVBoxLayout()
|
|
|
|
|
|
main_layout.setSpacing(4)
|
|
|
|
|
|
main_layout.setContentsMargins(4, 4, 4, 4)
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
kartenname_label = QLabel("Kartenname")
|
|
|
|
|
|
kartenname_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
|
|
|
|
|
main_layout.addWidget(kartenname_label)
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._kartenname_combo = QComboBox(self)
|
|
|
|
|
|
self._kartenname_combo.addItem(KARTENNAME_PLACEHOLDER)
|
|
|
|
|
|
self._kartenname_combo.addItem(KARTENNAME_38)
|
|
|
|
|
|
self._kartenname_combo.addItem(KARTENNAME_41)
|
|
|
|
|
|
self._kartenname_combo.currentTextChanged.connect(self._on_kartenname_changed)
|
|
|
|
|
|
main_layout.addWidget(self._kartenname_combo)
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
2026-03-20 10:37:08 +01:00
|
|
|
|
massstab_label = QLabel("Maßstab")
|
2026-03-20 10:11:42 +01:00
|
|
|
|
massstab_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
|
|
|
|
|
main_layout.addWidget(massstab_label)
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
2026-03-20 10:11:42 +01:00
|
|
|
|
self._massstab_combo = QComboBox(self)
|
|
|
|
|
|
self._massstab_combo.addItem(MASSSTAB_WIE_KARTENFENSTER)
|
|
|
|
|
|
self._massstab_combo.addItems(list(PLOTMASSSTAB_BY_AUSWAHL.keys()))
|
|
|
|
|
|
self._massstab_combo.currentTextChanged.connect(self._on_massstab_changed)
|
|
|
|
|
|
main_layout.addWidget(self._massstab_combo)
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
thema_label = QLabel("Thema")
|
|
|
|
|
|
thema_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
|
|
|
|
|
main_layout.addWidget(thema_label)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._thema_combo = QComboBox(self)
|
|
|
|
|
|
self._thema_combo.addItem(THEMA_WIE_KARTENFENSTER)
|
|
|
|
|
|
self._thema_combo.addItems(self._get_gespeicherte_themen())
|
|
|
|
|
|
self._thema_combo.currentTextChanged.connect(self._on_thema_changed)
|
|
|
|
|
|
main_layout.addWidget(self._thema_combo)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 10:11:42 +01:00
|
|
|
|
main_layout.addStretch(1)
|
|
|
|
|
|
self.setLayout(main_layout)
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
2026-03-20 10:11:42 +01:00
|
|
|
|
def _restore_state(self) -> None:
|
|
|
|
|
|
"""Stellt die gespeicherten Combobox-Zustände wieder her."""
|
2026-03-20 11:45:36 +01:00
|
|
|
|
if not self._kartenname_combo or not self._massstab_combo or not self._thema_combo:
|
2026-03-19 16:32:01 +01:00
|
|
|
|
return
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
saved_kartenname = get_variable(KARTENNAME_VAR, scope="project")
|
|
|
|
|
|
if saved_kartenname in (KARTENNAME_38, KARTENNAME_41):
|
|
|
|
|
|
self._kartenname_combo.setCurrentText(saved_kartenname)
|
2026-03-20 10:11:42 +01:00
|
|
|
|
else:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._kartenname_combo.setCurrentText(KARTENNAME_PLACEHOLDER)
|
2026-03-20 10:11:42 +01:00
|
|
|
|
|
|
|
|
|
|
saved_massstab = get_variable(MASSSTAB_VAR, scope="project")
|
|
|
|
|
|
valid_massstaebe = [MASSSTAB_WIE_KARTENFENSTER, *PLOTMASSSTAB_BY_AUSWAHL.keys()]
|
|
|
|
|
|
if saved_massstab in valid_massstaebe:
|
|
|
|
|
|
self._massstab_combo.setCurrentText(saved_massstab)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._massstab_combo.setCurrentText(MASSSTAB_WIE_KARTENFENSTER)
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
aktuelle_themen = [THEMA_WIE_KARTENFENSTER, *self._get_gespeicherte_themen()]
|
|
|
|
|
|
self._thema_combo.clear()
|
|
|
|
|
|
self._thema_combo.addItems(aktuelle_themen)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
saved_thema = get_variable(THEMA_VAR, scope="project")
|
|
|
|
|
|
if saved_thema in aktuelle_themen:
|
|
|
|
|
|
self._thema_combo.setCurrentText(saved_thema)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
else:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
def _on_kartenname_changed(self, value: str) -> None:
|
|
|
|
|
|
"""Persistiert die Kartennamen-Auswahl und setzt ``sn_kartenname``."""
|
|
|
|
|
|
if value in (KARTENNAME_38, KARTENNAME_41):
|
|
|
|
|
|
set_variable(KARTENNAME_VAR, value, scope="project")
|
2026-03-20 10:11:42 +01:00
|
|
|
|
else:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
set_variable(KARTENNAME_VAR, "", scope="project")
|
2026-03-20 10:11:42 +01:00
|
|
|
|
|
|
|
|
|
|
if self.logic:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self.logic.set_kartenname_for_auswahl(value)
|
2026-03-20 10:11:42 +01:00
|
|
|
|
|
|
|
|
|
|
def _on_massstab_changed(self, value: str) -> None:
|
|
|
|
|
|
"""Persistiert Maßstabsauswahl und setzt ``sn_plotmassstab``."""
|
|
|
|
|
|
set_variable(MASSSTAB_VAR, value, scope="project")
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|
|
|
|
|
|
if self.logic:
|
2026-03-20 10:11:42 +01:00
|
|
|
|
self.logic.set_plotmassstab_for_auswahl(value, self._get_current_canvas_scale())
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
def _on_thema_changed(self, value: str) -> None:
|
|
|
|
|
|
"""Persistiert die Thema-Auswahl und setzt ``sn_view``."""
|
|
|
|
|
|
set_variable(THEMA_VAR, value, scope="project")
|
2026-03-20 10:37:08 +01:00
|
|
|
|
if self.logic:
|
|
|
|
|
|
self.logic.set_view_for_auswahl(value)
|
|
|
|
|
|
|
2026-03-20 11:31:44 +01:00
|
|
|
|
def _connect_project_signals(self) -> None:
|
|
|
|
|
|
"""Verbindet QgsProject-Signale für Projektwechsel/-neuladen."""
|
|
|
|
|
|
project = QgsProject.instance()
|
|
|
|
|
|
for signal_name in ("readProject", "newProjectCreated", "cleared"):
|
|
|
|
|
|
signal = getattr(project, signal_name, None)
|
|
|
|
|
|
if signal is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
signal.connect(self._on_project_changed)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def _on_project_changed(self, *args) -> None:
|
|
|
|
|
|
"""Reagiert auf Projektwechsel: Signale neu binden, Combobox und State auffrischen."""
|
|
|
|
|
|
_ = args
|
|
|
|
|
|
# Alte Theme-Collection-Signals zuerst trennen
|
|
|
|
|
|
self._disconnect_theme_collection_signals()
|
|
|
|
|
|
# Neu verbinden für das jetzt geladene Projekt
|
|
|
|
|
|
self._theme_signal_connected = False
|
|
|
|
|
|
self._connect_theme_collection_signals()
|
|
|
|
|
|
# Ansicht-Liste + gespeicherten State wiederherstellen
|
|
|
|
|
|
self._restore_state()
|
|
|
|
|
|
|
|
|
|
|
|
def _disconnect_theme_collection_signals(self) -> None:
|
|
|
|
|
|
"""Trennt Signale der alten Theme-Collection sauber."""
|
|
|
|
|
|
collection = self._connected_theme_collection
|
|
|
|
|
|
if collection is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
for signal_name in ("mapThemesChanged", "changed", "themeChanged"):
|
|
|
|
|
|
signal = getattr(collection, signal_name, None)
|
|
|
|
|
|
if signal is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
signal.disconnect(self._refresh_thema_combo_live)
|
2026-03-20 11:31:44 +01:00
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
self._connected_theme_collection = None
|
|
|
|
|
|
|
2026-03-20 10:37:08 +01:00
|
|
|
|
def _connect_theme_collection_signals(self) -> None:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
"""Verbindet Signale der Theme-Collection für Live-Aktualisierung der Themenliste."""
|
2026-03-20 10:37:08 +01:00
|
|
|
|
if self._theme_signal_connected:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
theme_collection = QgsProject.instance().mapThemeCollection()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if theme_collection is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
connected_any = False
|
|
|
|
|
|
for signal_name in ("mapThemesChanged", "changed", "themeChanged"):
|
|
|
|
|
|
signal = getattr(theme_collection, signal_name, None)
|
|
|
|
|
|
if signal is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
signal.connect(self._refresh_thema_combo_live)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
connected_any = True
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-03-20 11:31:44 +01:00
|
|
|
|
if connected_any:
|
|
|
|
|
|
self._connected_theme_collection = theme_collection
|
2026-03-20 10:37:08 +01:00
|
|
|
|
self._theme_signal_connected = connected_any
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
def _refresh_thema_combo_live(self, *args) -> None:
|
|
|
|
|
|
"""Aktualisiert die Thema-Combobox bei Änderungen gespeicherter Layerthemen."""
|
2026-03-20 10:37:08 +01:00
|
|
|
|
_ = args
|
2026-03-20 11:45:36 +01:00
|
|
|
|
if not self._thema_combo:
|
2026-03-20 10:37:08 +01:00
|
|
|
|
return
|
|
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
vorherige_auswahl = self._thema_combo.currentText() or THEMA_WIE_KARTENFENSTER
|
|
|
|
|
|
eintraege = [THEMA_WIE_KARTENFENSTER, *self._get_gespeicherte_themen()]
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._thema_combo.blockSignals(True)
|
|
|
|
|
|
self._thema_combo.clear()
|
|
|
|
|
|
self._thema_combo.addItems(eintraege)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
|
|
|
|
|
if vorherige_auswahl in eintraege:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._thema_combo.setCurrentText(vorherige_auswahl)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
else:
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
|
|
|
|
|
|
self._thema_combo.blockSignals(False)
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
self._on_thema_changed(self._thema_combo.currentText())
|
2026-03-20 10:37:08 +01:00
|
|
|
|
|
2026-03-20 11:45:36 +01:00
|
|
|
|
def _get_gespeicherte_themen(self) -> list[str]:
|
|
|
|
|
|
"""Liefert die Namen der im Projekt gespeicherten Layerthemen (QgsMapThemeCollection)."""
|
2026-03-20 10:37:08 +01:00
|
|
|
|
try:
|
|
|
|
|
|
theme_collection = QgsProject.instance().mapThemeCollection()
|
|
|
|
|
|
if theme_collection is None:
|
|
|
|
|
|
return []
|
|
|
|
|
|
themes = theme_collection.mapThemes()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
namen: list[str] = []
|
|
|
|
|
|
for theme_name in themes:
|
|
|
|
|
|
name = str(theme_name or "").strip()
|
|
|
|
|
|
if name and name not in namen:
|
|
|
|
|
|
namen.append(name)
|
|
|
|
|
|
|
|
|
|
|
|
return namen
|
|
|
|
|
|
|
2026-03-20 10:11:42 +01:00
|
|
|
|
def _get_current_canvas_scale(self) -> float | None:
|
|
|
|
|
|
"""Liest den aktuellen Maßstab aus der Kartensicht."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
canvas = iface.mapCanvas()
|
|
|
|
|
|
if canvas is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
scale = canvas.scale()
|
|
|
|
|
|
return float(scale) if scale else None
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return None
|
2026-03-19 16:32:01 +01:00
|
|
|
|
|