Basemap hinzufügen und Thema wechseln im Basemap-Bereich (#100)

Neuer Abschnitt „Basemap-Layer“ im Basemap-Bereich des Druck-Tabs: Knopf
„Basemap hinzufügen“ und Auswahl „Thema“ (Farbe, Grau). Vorhandene Layer
werden an der Kachel-URL erkannt; Rückfrage Zusätzlich laden / Ersetzen /
Abbrechen. Vor dem Themenwechsel wird über einen Prüfwert der Darstellung
erkannt, ob sie ungespeichert verändert wurde.

Neues Modul modules/basemap_themen.py, neue Wrapper-Funktionen
ask_auswahl und get_active_layer, Stilvorlagen vermerken ihr Thema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 21:27:35 +02:00
co-authored by Claude Opus 5.5
parent d01b9998fb
commit 24ad564a08
7 changed files with 839 additions and 15 deletions
+151 -13
View File
@@ -1,21 +1,23 @@
"""
sn_basis/ui/widgets/basemap_bereich.py – einklappbarer Bereich „Basemap“ im Druck-Tab
Einstellungen der Basemap-Legende (Auswertebereich, Erzeugen beim Anlegen der Vorlage),
Basemap-Layer hinzufügen und sein Thema wechseln (Basis #100), Einstellungen der
Basemap-Legende (Auswertebereich, Erzeugen beim Anlegen der Vorlage),
„Basemap-Legende aktualisieren“ für bestehende Vorlagen, lokale Stilvorlagen des
Basemap-Layers (laden, speichern, als Standard markieren, löschen) und „Punktlinien
korrigieren“ (Basis #90). Basis #88.
Die Handler enthalten nur Ablauf und Rückfragen; die Logik liegt in
``modules/basemap_legende`` und ``modules/basemap_stilvorlagen``.
``modules/basemap_themen``, ``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.dialog_wrapper import ask_auswahl, 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.qgisui_wrapper import get_active_layer
from sn_basis.functions.qt_wrapper import (
ArrowDown,
ArrowRight,
@@ -30,6 +32,7 @@ from sn_basis.functions.qt_wrapper import (
ToolButtonTextBesideIcon,
)
from sn_basis.modules import basemap_legende as legende
from sn_basis.modules import basemap_themen as themen
from sn_basis.modules.basemap_legende_layout import layouts_mit_basemap_legende
from sn_basis.modules.basemap_stilkorrektur import korrigiere_punktlinien
from sn_basis.modules.basemap_stilvorlagen import BasemapStilvorlagen
@@ -42,10 +45,12 @@ 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:
stilvorlagen: BasemapStilvorlagen | None = None,
layer_fabrik: Any | None = None) -> None:
super().__init__(parent)
self._project = project
self._stilvorlagen = stilvorlagen
self._layer_fabrik = layer_fabrik
self._build_ui()
self.aktualisieren()
@@ -77,6 +82,25 @@ class BasemapBereich(QWidget):
self._status.setWordWrap(True)
spalte.addWidget(self._status)
spalte.addWidget(self._ueberschrift("Basemap-Layer"))
self._hinzufuegen_btn = QPushButton("Basemap hinzufügen", self)
self._hinzufuegen_btn.setToolTip(
"Lädt basemap.de (Vektorkacheln) im gewählten Thema ganz unten in den Layerbaum."
)
self._hinzufuegen_btn.clicked.connect(lambda *_: self._on_basemap_hinzufuegen())
spalte.addWidget(self._hinzufuegen_btn)
spalte.addWidget(QLabel("Thema"))
self._thema_combo = QComboBox(self)
self._thema_combo.setToolTip(
"Darstellung des Dienstes für den ausgewählten Basemap-Layer. "
"Ohne Basemap-Layer gilt die Wahl für „Basemap hinzufügen“."
)
for schluessel, eintrag in themen.THEMEN.items():
self._thema_combo.addItem(eintrag.bezeichnung, schluessel)
self._thema_combo.setCurrentIndex(max(0, self._thema_combo.findData(themen.STANDARD_THEMA)))
self._thema_combo.currentIndexChanged.connect(self._on_thema_gewaehlt)
spalte.addWidget(self._thema_combo)
spalte.addWidget(self._ueberschrift("Legende"))
spalte.addWidget(QLabel("Auswertebereich"))
self._bereich_combo = QComboBox(self)
@@ -150,6 +174,25 @@ class BasemapBereich(QWidget):
gefunden = legende.finde_basemap_layer(layer)
return gefunden[0] if gefunden else None
def _ziel_layer(self) -> Any | None:
"""Der im Layerbaum ausgewählte Basemap-Layer, sonst der erste des Projekts."""
aktiv = get_active_layer()
if aktiv is not None and legende.ist_basemap_layer(aktiv):
return aktiv
return self.basemap_layer()
def _gewaehltes_thema(self) -> str:
return str(self._thema_combo.currentData() or themen.STANDARD_THEMA)
def _zeige_thema(self, layer: Any | None) -> None:
"""Stellt das Thema-Dropdown ohne Themenwechsel auf das Thema des Layers."""
schluessel = themen.thema_des_layers(layer) if layer is not None else None
if schluessel is None:
return
self._thema_combo.blockSignals(True)
self._thema_combo.setCurrentIndex(max(0, self._thema_combo.findData(schluessel)))
self._thema_combo.blockSignals(False)
def _umschalten(self, offen: bool) -> None:
self._knopf.setArrowType(ArrowDown if offen else ArrowRight)
self._inhalt.setVisible(offen)
@@ -158,8 +201,9 @@ class BasemapBereich(QWidget):
def aktualisieren(self) -> None:
"""Liest Projektvariablen, Layouts und Stilvorlagen neu ein (z. B. nach Projektwechsel)."""
layer = self.basemap_layer()
layer = self._ziel_layer()
self._status.setText(f"Basemap-Layer: {layer.name()}" if layer is not None else KEIN_LAYER_TEXT)
self._zeige_thema(layer)
bereich = legende.lies_auswertebereich()
self._bereich_combo.blockSignals(True)
@@ -181,6 +225,96 @@ class BasemapBereich(QWidget):
if standard:
self._stil_combo.setCurrentIndex(max(0, self._stil_combo.findData(standard)))
# --- Handler: Basemap-Layer und Thema ------------------------------
def _aenderungen_klaeren(self, layer: Any) -> bool:
"""Fragt vor dem Überschreiben der Darstellung nach, ob Änderungen als Stil gesichert werden sollen.
``True``: weitermachen (gespeichert, verworfen oder nichts geändert); ``False``: abbrechen.
"""
stand = themen.aenderungsstand(layer)
if stand == themen.UNVERAENDERT:
return True
if stand == themen.GEAENDERT:
grund = f"Die Darstellung von „{layer.name()}“ wurde seit dem Laden des Themas oder Stils verändert."
else:
grund = (f"Für „{layer.name()}“ ist nicht bekannt, ob die Darstellung verändert wurde "
"(Layer wurde ohne diese Funktion geladen).")
wahl = ask_auswahl(
TITEL,
f"{grund} Beim Themenwechsel gehen Änderungen verloren.\n\nVorher als Stil speichern?",
[("speichern", "Als Stil speichern …"), ("verwerfen", "Verwerfen"), ("abbrechen", "Abbrechen")],
default="speichern",
abbrechen="abbrechen",
)
if wahl == "abbrechen":
return False
if wahl == "speichern":
return self._on_stil_speichern(layer)
return True
def _on_basemap_hinzufuegen(self) -> None:
schluessel = self._gewaehltes_thema()
try:
vorhanden = themen.layer_mit_quelle(list(self._projekt().mapLayers().values()))
except Exception:
vorhanden = []
if vorhanden:
namen = ", ".join(f"„{l.name()}“" for l in vorhanden)
wahl = ask_auswahl(
TITEL,
f"Im Projekt gibt es schon einen Basemap-Layer mit derselben Quelle: {namen}.\n\n"
"Einen weiteren Basemap-Layer zusätzlich laden, den vorhandenen ersetzen oder abbrechen?",
[("zusaetzlich", "Zusätzlich laden"), ("ersetzen", "Ersetzen"), ("abbrechen", "Abbrechen")],
default="abbrechen",
abbrechen="abbrechen",
)
if wahl == "abbrechen":
return
if wahl == "ersetzen":
ziel = self._ziel_layer()
self._ersetzen(ziel if ziel in vorhanden else vorhanden[0], schluessel)
return
try:
layer = themen.erzeuge_layer(schluessel, getattr(self, "_layer_fabrik", None))
except ValueError as fehler:
warning(TITEL, str(fehler))
return
themen.fuege_hinzu(self._projekt(), layer)
self.aktualisieren()
success(TITEL, f"„{layer.name()}“ hinzugefügt.")
def _ersetzen(self, layer: Any, schluessel: str) -> None:
"""Lädt den vorhandenen Layer im gewählten Thema neu; Position und Bezüge in Layouts bleiben."""
if not self._aenderungen_klaeren(layer):
return
try:
themen.wechsle_thema(layer, schluessel)
except ValueError as fehler:
warning(TITEL, str(fehler))
return
self.aktualisieren()
success(TITEL, f"„{layer.name()}“ im Thema {themen.thema(schluessel).bezeichnung} neu geladen.")
def _on_thema_gewaehlt(self, *_: Any) -> None:
layer = self._ziel_layer()
if layer is None:
return # Wahl gilt für „Basemap hinzufügen“
schluessel = self._gewaehltes_thema()
if themen.thema_des_layers(layer) == schluessel:
return
if not self._aenderungen_klaeren(layer):
self._zeige_thema(layer)
return
try:
themen.wechsle_thema(layer, schluessel)
except ValueError as fehler:
warning(TITEL, str(fehler))
self._zeige_thema(layer)
return
self.aktualisieren()
success(TITEL, f"Thema von „{layer.name()}“: {themen.thema(schluessel).bezeichnung}.")
# --- Handler: Legende -----------------------------------------------
def _on_bereich_geaendert(self, *_: Any) -> None:
@@ -210,7 +344,7 @@ class BasemapBereich(QWidget):
# --- Handler: Darstellung korrigieren ------------------------------
def _on_punktlinien_korrigieren(self) -> None:
layer = self.basemap_layer()
layer = self._ziel_layer()
if layer is None:
warning(TITEL, KEIN_LAYER_TEXT)
return
@@ -226,7 +360,7 @@ class BasemapBereich(QWidget):
return str(self._stil_combo.currentData() or "")
def _on_stil_laden(self) -> None:
layer = self.basemap_layer()
layer = self._ziel_layer()
name = self._gewaehlte_vorlage()
if layer is None or not name:
warning(TITEL, "Kein Basemap-Layer oder keine Stilvorlage gewählt.")
@@ -252,28 +386,32 @@ class BasemapBereich(QWidget):
import json
from sn_basis.functions.variable_wrapper import set_variable
set_variable(legende.AUSWAHL_VAR, json.dumps(auswahl), scope="project")
themen.merke_stand(layer)
success(TITEL, "Stilvorlage geladen.")
def _on_stil_speichern(self) -> None:
layer = self.basemap_layer()
def _on_stil_speichern(self, layer: Any | None = None) -> bool:
"""Speichert die Darstellung als Stilvorlage; ``True``, wenn gespeichert wurde."""
layer = layer if layer is not None else self._ziel_layer()
if layer is None:
warning(TITEL, KEIN_LAYER_TEXT)
return
return False
anzeigename, ok = ask_text("Stilvorlage speichern", "Name der Stilvorlage:",
default_text=self._stil_combo.currentText().replace(" ★", ""))
if not ok or not anzeigename.strip():
return
return False
if self._vorlagen().vorhanden(anzeigename) and not ask_yes_no(
"Stilvorlage speichern", f"Die Stilvorlage „{anzeigename}“ gibt es schon. Ersetzen?", default=False
):
return
return False
try:
self._vorlagen().speichern(layer, anzeigename, legende.lies_auswahl())
except ValueError as fehler:
warning(TITEL, str(fehler))
return
return False
themen.merke_stand(layer)
self.aktualisieren()
success(TITEL, f"Stilvorlage „{anzeigename.strip()}“ gespeichert.")
return True
def _on_stil_standard(self) -> None:
name = self._gewaehlte_vorlage()