""" sn_basis/ui/widgets/basemap_bereich.py – einklappbarer Bereich „Basemap“ im Druck-Tab Einstellungen der Basemap-Legende (Auswertebereich, Erzeugen beim Anlegen der Vorlage), „Basemap-Legende aktualisieren“ für bestehende Vorlagen und lokale Stilvorlagen des Basemap-Layers (laden, speichern, als Standard markieren, löschen). Basis #88. Die Handler enthalten nur Ablauf und Rückfragen; die Logik liegt in ``modules/basemap_legende`` und ``modules/basemap_stilvorlagen``. """ from __future__ import annotations from typing import Any, Optional from sn_basis.functions.dialog_wrapper import ask_text, ask_yes_no from sn_basis.functions.message_wrapper import info, success, warning from sn_basis.functions.qgiscore_wrapper import QgsProject from sn_basis.functions.qt_wrapper import ( ArrowDown, ArrowRight, QCheckBox, QComboBox, QHBoxLayout, QLabel, QPushButton, QToolButton, QVBoxLayout, QWidget, ToolButtonTextBesideIcon, ) from sn_basis.modules import basemap_legende as legende from sn_basis.modules.basemap_legende_layout import layouts_mit_basemap_legende from sn_basis.modules.basemap_stilvorlagen import BasemapStilvorlagen TITEL = "Basemap" KEIN_LAYER_TEXT = "Kein Basemap-Layer im Projekt" class BasemapBereich(QWidget): """Einklappbarer Basemap-Bereich; standardmäßig eingeklappt.""" def __init__(self, parent: Optional[Any] = None, project: Any | None = None, stilvorlagen: BasemapStilvorlagen | None = None) -> None: super().__init__(parent) self._project = project self._stilvorlagen = stilvorlagen self._build_ui() self.aktualisieren() # --- Aufbau --------------------------------------------------------- def _build_ui(self) -> None: """von Test abgesehen, weil reiner Widget-Aufbau; im QGIS-Test geprüft""" haupt = QVBoxLayout() haupt.setSpacing(4) haupt.setContentsMargins(0, 0, 0, 0) knopf = QToolButton() knopf.setText(TITEL) knopf.setCheckable(True) knopf.setChecked(False) knopf.setToolButtonStyle(ToolButtonTextBesideIcon) knopf.setArrowType(ArrowRight) knopf.setStyleSheet("font-weight: bold; margin-top: 6px;") knopf.toggled.connect(self._umschalten) self._knopf = knopf haupt.addWidget(knopf) inhalt = QWidget() spalte = QVBoxLayout() spalte.setSpacing(4) spalte.setContentsMargins(10, 4, 4, 4) self._status = QLabel(KEIN_LAYER_TEXT) self._status.setWordWrap(True) spalte.addWidget(self._status) spalte.addWidget(self._ueberschrift("Legende")) spalte.addWidget(QLabel("Auswertebereich")) self._bereich_combo = QComboBox(self) for schluessel, text in legende.AUSWERTEBEREICHE.items(): self._bereich_combo.addItem(text, schluessel) self._bereich_combo.currentIndexChanged.connect(self._on_bereich_geaendert) spalte.addWidget(self._bereich_combo) self._erzeugen_cb = QCheckBox("Basemap-Legende beim Erstellen der Vorlage erzeugen (§ 38, § 41)", self) self._erzeugen_cb.toggled.connect(self._on_erzeugen_geaendert) spalte.addWidget(self._erzeugen_cb) spalte.addWidget(QLabel("Vorlage")) self._layout_combo = QComboBox(self) spalte.addWidget(self._layout_combo) self._aktualisieren_btn = QPushButton("Basemap-Legende aktualisieren", self) self._aktualisieren_btn.clicked.connect(self._on_legende_aktualisieren) spalte.addWidget(self._aktualisieren_btn) spalte.addWidget(self._ueberschrift("Stilvorlage")) self._stil_combo = QComboBox(self) spalte.addWidget(self._stil_combo) zeile = QHBoxLayout() for text, handler in ( ("Laden", self._on_stil_laden), ("Speichern unter …", self._on_stil_speichern), ("Als Standard", self._on_stil_standard), ("Löschen", self._on_stil_loeschen), ): knopf_stil = QPushButton(text, self) knopf_stil.clicked.connect(lambda *_, h=handler: h()) zeile.addWidget(knopf_stil) spalte.addLayout(zeile) inhalt.setLayout(spalte) inhalt.setVisible(False) self._inhalt = inhalt haupt.addWidget(inhalt) self.setLayout(haupt) @staticmethod def _ueberschrift(text: str) -> Any: beschriftung = QLabel(text) beschriftung.setStyleSheet("font-weight: bold; margin-top: 4px;") return beschriftung # --- Zustand -------------------------------------------------------- def _projekt(self) -> Any: return getattr(self, "_project", None) or QgsProject.instance() def _vorlagen(self) -> BasemapStilvorlagen: if getattr(self, "_stilvorlagen", None) is None: self._stilvorlagen = BasemapStilvorlagen() return self._stilvorlagen def basemap_layer(self) -> Any | None: """Erster Basemap-Layer des Projekts (sichtbar oder nicht).""" try: layer = list(self._projekt().mapLayers().values()) except Exception: return None gefunden = legende.finde_basemap_layer(layer) return gefunden[0] if gefunden else None def _umschalten(self, offen: bool) -> None: self._knopf.setArrowType(ArrowDown if offen else ArrowRight) self._inhalt.setVisible(offen) if offen: self.aktualisieren() def aktualisieren(self) -> None: """Liest Projektvariablen, Layouts und Stilvorlagen neu ein (z. B. nach Projektwechsel).""" layer = self.basemap_layer() self._status.setText(f"Basemap-Layer: {layer.name()}" if layer is not None else KEIN_LAYER_TEXT) bereich = legende.lies_auswertebereich() self._bereich_combo.blockSignals(True) self._bereich_combo.setCurrentIndex(max(0, self._bereich_combo.findData(bereich))) self._bereich_combo.blockSignals(False) self._erzeugen_cb.blockSignals(True) self._erzeugen_cb.setChecked(legende.legende_erzeugen_aktiv()) self._erzeugen_cb.blockSignals(False) self._layout_combo.clear() self._layout_combo.addItems(layouts_mit_basemap_legende(self._projekt())) self._aktualisieren_btn.setEnabled(layer is not None and self._layout_combo.count() > 0) self._stil_combo.clear() for vorlage in self._vorlagen().liste(): self._stil_combo.addItem(f"{vorlage.anzeigename}{' ★' if vorlage.standard else ''}", vorlage.name) standard = self._vorlagen().standard() if standard: self._stil_combo.setCurrentIndex(max(0, self._stil_combo.findData(standard))) # --- Handler: Legende ----------------------------------------------- def _on_bereich_geaendert(self, *_: Any) -> None: bereich = self._bereich_combo.currentData() if bereich in legende.AUSWERTEBEREICHE: legende.setze_auswertebereich(bereich) def _on_erzeugen_geaendert(self, aktiv: bool) -> None: legende.setze_legende_erzeugen(bool(aktiv)) def _on_legende_aktualisieren(self) -> None: name = self._layout_combo.currentText() layout = self._projekt().layoutManager().layoutByName(name) if name else None if layout is None: warning(TITEL, "Bitte eine Vorlage mit Basemap-Legende wählen.") return try: ergebnis = legende.BasemapLegende(self._projekt()).aktualisiere(layout) except Exception as fehler: warning(TITEL, f"Die Basemap-Legende konnte nicht aktualisiert werden: {fehler}") return if ergebnis["status"] == "ok": success(TITEL, ergebnis["meldung"]) elif ergebnis["status"] != "abgebrochen": info(TITEL, ergebnis["meldung"]) # --- Handler: Stilvorlagen ------------------------------------------ def _gewaehlte_vorlage(self) -> str: return str(self._stil_combo.currentData() or "") def _on_stil_laden(self) -> None: layer = self.basemap_layer() name = self._gewaehlte_vorlage() if layer is None or not name: warning(TITEL, "Kein Basemap-Layer oder keine Stilvorlage gewählt.") return if not ask_yes_no("Stilvorlage laden", f"Darstellung von „{layer.name()}“ durch die Stilvorlage ersetzen?", default=False): return unbekannt = self._vorlagen().pruefe_passung(layer, name) if unbekannt and not ask_yes_no( "Stilvorlage laden", f"{len(unbekannt)} Styles der Vorlage gibt es in diesem Layer nicht – die Vorlage stammt " "vermutlich von einer anderen Basemap-Variante. Trotzdem laden?", default=False, ): return try: self._vorlagen().laden(layer, name) except ValueError as fehler: warning(TITEL, str(fehler)) return auswahl = self._vorlagen().legendenauswahl(name) if auswahl is not None: import json from sn_basis.functions.variable_wrapper import set_variable set_variable(legende.AUSWAHL_VAR, json.dumps(auswahl), scope="project") success(TITEL, "Stilvorlage geladen.") def _on_stil_speichern(self) -> None: layer = self.basemap_layer() if layer is None: warning(TITEL, KEIN_LAYER_TEXT) return anzeigename, ok = ask_text("Stilvorlage speichern", "Name der Stilvorlage:", default_text=self._stil_combo.currentText().replace(" ★", "")) if not ok or not anzeigename.strip(): return if self._vorlagen().vorhanden(anzeigename) and not ask_yes_no( "Stilvorlage speichern", f"Die Stilvorlage „{anzeigename}“ gibt es schon. Ersetzen?", default=False ): return try: self._vorlagen().speichern(layer, anzeigename, legende.lies_auswahl()) except ValueError as fehler: warning(TITEL, str(fehler)) return self.aktualisieren() success(TITEL, f"Stilvorlage „{anzeigename.strip()}“ gespeichert.") def _on_stil_standard(self) -> None: name = self._gewaehlte_vorlage() if not name: return try: self._vorlagen().setze_standard(name) except ValueError as fehler: warning(TITEL, str(fehler)) return self.aktualisieren() def _on_stil_loeschen(self) -> None: name = self._gewaehlte_vorlage() if not name or not ask_yes_no("Stilvorlage löschen", f"Stilvorlage „{self._stil_combo.currentText()}“ löschen?", default=False): return self._vorlagen().loeschen(name) self.aktualisieren()