Files
Plugin_SN_Plan41/ui/tab_b_ui.py

428 lines
17 KiB
Python
Raw Permalink Normal View History

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,
QCheckBox,
QHBoxLayout,
QPushButton,
2026-03-19 16:32:01 +01:00
)
2026-03-20 10:37:08 +01:00
from sn_basis.functions.qgiscore_wrapper import QgsProject
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
from sn_plan41.ui.tab_b_logic import (
TabBLogic,
MASSSTAB_WIE_KARTENFENSTER,
PLOTMASSSTAB_BY_AUSWAHL,
THEMA_WIE_KARTENFENSTER,
DIN_GROESSEN,
DIN_STANDARD,
ZIELGROESSE_VAR,
FORMFAKTOR_VAR,
)
KARTENNAME_VAR = "tab_b_kartenname"
KARTENNAME_PLACEHOLDER = "Kartenname wählen"
KARTENNAME_38 = "§38"
KARTENNAME_41 = "§41"
MASSSTAB_VAR = "tab_b_massstab"
THEMA_VAR = "tab_b_thema"
ZIELGROESSE_UI_VAR = "tab_b_zielgroesse"
FORMFAKTOR_UI_VAR = "tab_b_formfaktor"
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
self._kartenname_combo: Optional[QComboBox] = None
self._massstab_combo: Optional[QComboBox] = None
self._thema_combo: Optional[QComboBox] = None
2026-03-20 10:37:08 +01:00
self._theme_signal_connected = False
self._connected_theme_collection: object = None # Referenz für sauberes Trennen
self._zielgroesse_combo: Optional[QComboBox] = None
self._endlosrolle_cb: Optional[QCheckBox] = None
self._btn_vorlage_erstellen: Optional[QPushButton] = None
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()
self._connect_project_signals()
2026-03-19 16:32:01 +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)
if self._kartenname_combo:
self.logic.set_kartenname_for_auswahl(self._kartenname_combo.currentText())
if self._massstab_combo:
self.logic.set_plotmassstab_for_auswahl(
self._massstab_combo.currentText(),
self._get_current_canvas_scale(),
)
if self._thema_combo:
self.logic.set_view_for_auswahl(self._thema_combo.currentText())
if self._zielgroesse_combo:
self.logic.set_zielgroesse_for_auswahl(self._zielgroesse_combo.currentText())
if self._endlosrolle_cb:
self.logic.set_formfaktor(self._endlosrolle_cb.isChecked())
2026-03-19 16:32:01 +01:00
def _build_ui(self) -> None:
"""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)
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
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")
massstab_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(massstab_label)
2026-03-19 16:32:01 +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
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
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
zielgroesse_label = QLabel("max. Blattgröße")
zielgroesse_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
main_layout.addWidget(zielgroesse_label)
zielgroesse_row = QHBoxLayout()
zielgroesse_row.setSpacing(6)
self._zielgroesse_combo = QComboBox(self)
self._zielgroesse_combo.addItems(list(DIN_GROESSEN.keys()))
self._zielgroesse_combo.setCurrentText(DIN_STANDARD)
self._zielgroesse_combo.currentTextChanged.connect(self._on_zielgroesse_changed)
zielgroesse_row.addWidget(self._zielgroesse_combo)
self._endlosrolle_cb = QCheckBox("Endlosrolle", self)
self._endlosrolle_cb.setChecked(False)
self._endlosrolle_cb.stateChanged.connect(self._on_formfaktor_changed)
zielgroesse_row.addWidget(self._endlosrolle_cb)
main_layout.addLayout(zielgroesse_row)
self._btn_vorlage_erstellen = QPushButton("Vorlage erstellen", self)
self._btn_vorlage_erstellen.clicked.connect(self._on_vorlage_erstellen)
main_layout.addWidget(self._btn_vorlage_erstellen)
main_layout.addStretch(1)
self.setLayout(main_layout)
2026-03-19 16:32:01 +01:00
def _restore_state(self) -> None:
"""Stellt die gespeicherten Combobox-Zustände wieder her."""
if not self._kartenname_combo or not self._massstab_combo or not self._thema_combo:
2026-03-19 16:32:01 +01:00
return
if not self._zielgroesse_combo or not self._endlosrolle_cb:
return
2026-03-19 16:32:01 +01:00
saved_kartenname = get_variable(KARTENNAME_VAR, scope="project")
if saved_kartenname in (KARTENNAME_38, KARTENNAME_41):
self._kartenname_combo.setCurrentText(saved_kartenname)
else:
self._kartenname_combo.setCurrentText(KARTENNAME_PLACEHOLDER)
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)
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
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:
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
2026-03-20 10:37:08 +01:00
saved_zielgroesse = get_variable(ZIELGROESSE_UI_VAR, scope="project")
if saved_zielgroesse in DIN_GROESSEN:
self._zielgroesse_combo.setCurrentText(saved_zielgroesse)
else:
self._zielgroesse_combo.setCurrentText(DIN_STANDARD)
saved_formfaktor = get_variable(FORMFAKTOR_UI_VAR, scope="project")
self._endlosrolle_cb.setChecked(saved_formfaktor == "Endlosrolle")
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")
else:
set_variable(KARTENNAME_VAR, "", scope="project")
if self.logic:
self.logic.set_kartenname_for_auswahl(value)
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:
self.logic.set_plotmassstab_for_auswahl(value, self._get_current_canvas_scale())
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)
def _on_zielgroesse_changed(self, value: str) -> None:
"""Persistiert Blattgröße und setzt ``sn_zielgroesse``."""
set_variable(ZIELGROESSE_UI_VAR, value, scope="project")
if self.logic:
self.logic.set_zielgroesse_for_auswahl(value)
def _on_formfaktor_changed(self, state: int) -> None:
"""Persistiert Endlosrolle-Zustand und setzt ``sn_formfaktor``."""
checked = bool(state)
set_variable(FORMFAKTOR_UI_VAR, "Endlosrolle" if checked else "Blatt", scope="project")
if self.logic:
self.logic.set_formfaktor(checked)
def _on_vorlage_erstellen(self) -> None:
"""Startet die Pipeline Druckvorlage_anlegen."""
print("[TabB] _on_vorlage_erstellen aufgerufen")
if not self.logic or not self.pruefmanager:
print(f"[TabB] Abbruch: logic={self.logic}, pruefmanager={self.pruefmanager}")
return
# Layer direkt aus Tab A lesen (unabhängig von Projektvariable)
layer = None
tab_a = self._get_tab_a_widget()
print(f"[TabB] tab_a Widget: {tab_a!r}")
if tab_a is not None:
layer_combo = getattr(tab_a, "layer_combo", None)
print(f"[TabB] layer_combo: {layer_combo!r}")
if layer_combo is not None:
layer = layer_combo.currentLayer()
print(f"[TabB] layer: {layer!r} (Name: {getattr(layer, 'name', lambda: '')()})")
print(f"[TabB] Rufe druckvorlage_anlegen auf mit layer={layer!r}, "
f"kartenname={self._kartenname_combo.currentText() if self._kartenname_combo else '?'}, "
f"massstab={self._massstab_combo.currentText() if self._massstab_combo else '?'}, "
f"zielgroesse={self._zielgroesse_combo.currentText() if self._zielgroesse_combo else '?'}, "
f"formfaktor={self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else '?'}")
result = self.logic.druckvorlage_anlegen(
layer=layer,
kartenname_auswahl=self._kartenname_combo.currentText() if self._kartenname_combo else "",
massstab_auswahl=self._massstab_combo.currentText() if self._massstab_combo else "",
zielgroesse=self._zielgroesse_combo.currentText() if self._zielgroesse_combo else DIN_STANDARD,
formfaktor=self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else False,
)
print(f"[TabB] druckvorlage_anlegen Ergebnis: {result}")
if result.get("switch_to_tab_a"):
self._aktiviere_tab_a()
def _get_tab_widget(self):
"""Findet das übergeordnete QTabWidget anhand des ``tabBar``-Attributs."""
try:
widget = self.parent()
while widget is not None:
if hasattr(widget, "tabBar") and hasattr(widget, "setCurrentIndex"):
return widget
parent_fn = getattr(widget, "parent", None)
widget = parent_fn() if callable(parent_fn) else None
except Exception:
pass
return None
def _get_tab_a_widget(self):
"""Gibt die Tab-A-Widget-Instanz zurück (Index 0 im übergeordneten QTabWidget)."""
tab_widget = self._get_tab_widget()
if tab_widget is None:
return None
try:
return tab_widget.widget(0)
except Exception:
return None
def _aktiviere_tab_a(self) -> None:
"""Wechselt den aktiven Reiter auf Tab A im übergeordneten QTabWidget."""
tab_widget = self._get_tab_widget()
if tab_widget is not None:
try:
tab_widget.setCurrentIndex(0)
except Exception:
pass
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:
signal.disconnect(self._refresh_thema_combo_live)
except Exception:
pass
self._connected_theme_collection = None
2026-03-20 10:37:08 +01:00
def _connect_theme_collection_signals(self) -> None:
"""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:
signal.connect(self._refresh_thema_combo_live)
2026-03-20 10:37:08 +01:00
connected_any = True
except Exception:
pass
if connected_any:
self._connected_theme_collection = theme_collection
2026-03-20 10:37:08 +01:00
self._theme_signal_connected = connected_any
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
if not self._thema_combo:
2026-03-20 10:37:08 +01:00
return
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
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:
self._thema_combo.setCurrentText(vorherige_auswahl)
2026-03-20 10:37:08 +01:00
else:
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
self._thema_combo.blockSignals(False)
2026-03-20 10:37:08 +01:00
self._on_thema_changed(self._thema_combo.currentText())
2026-03-20 10:37:08 +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
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