Mehrspaltige Legenden schmaler (#96)

Mit gleichen Spaltenbreiten bestimmte der längste Legendentext die Breite aller Spalten;
die dreispaltige Basemap-Legende wurde 431 mm breit. Jetzt: ungleich breite Spalten,
automatischer Textumbruch nach 60 mm (TEXTUMBRUCH_MM in print_kartenrand, auch für die
Basemap-Legende) und zweizeiliger Titel „Kartengrundlage / (basemap.de)“, der sonst die
Mindestbreite bestimmte. Im QGIS-Test: dreispaltig 226 statt 431 mm, einspaltig 89 statt 163 mm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 13:20:15 +02:00
co-authored by Claude Opus 5.5
parent 463a546f79
commit 9cff75b674
5 changed files with 49 additions and 6 deletions
+1
View File
@@ -1,3 +1,4 @@
- Bugfix: Mehrspaltige Legenden waren sehr breit, weil alle Spalten so breit wie der längste Text waren. Legendentext bricht jetzt nach 60 mm um, Spalten sind ungleich breit, der Titel der Basemap-Legende ist zweizeilig – im Test wurde die dreispaltige Basemap-Legende 226 statt 431 mm breit (#96)
- Bugfix: Bei kleinen Verfahren oder großem Maßstab überlappten sich Legenden, Quellenangabe und Kartenschild, weil sie zusammen höher als die Karte waren. Beim Anlegen einer §38-/§41-Vorlage werden die Legenden jetzt bei Bedarf zwei- oder dreispaltig gesetzt (Kartenrand wird dafür breiter); reicht auch das nicht, wird die Seite höher und Quellenangabe und Kartenschild rutschen nach unten. Größe und Lage der Karte bleiben gleich (#93)
- Bugfix: Weiße Basemap-Flächen ohne Kontur (z. B. „Platz“) waren in der Basemap-Legende auf weißem Grund unsichtbar; ihr Legendensymbol bekommt jetzt einen dünnen grauen Rand (0,15 mm), die Karte bleibt unverändert (#92)
- Bugfix: Punktlinien des Basemap-Layers (Baumreihe, Hecke, Durchlass, Seilbahnen u. a., 12 Styles) wurden in Karte und Legende nicht gezeichnet – basemap.de nutzt Strichlänge 0 mit runder Kappe, Qt zeichnet Striche der Länge 0 nicht. Neuer Knopf „Punktlinien korrigieren“ im Basemap-Bereich des Druck-Tabs ersetzt die Strichlänge 0 durch 0,01 (festes Muster und zoomabhängiger Ausdruck); die Korrektur lässt sich als Stilvorlage sichern (#90)
+7 -3
View File
@@ -40,11 +40,13 @@ from sn_basis.functions.qgiscore_wrapper import (
QgsVectorLayer,
)
from sn_basis.functions.qt_wrapper import QSize
from sn_basis.modules.print_kartenrand import TEXTUMBRUCH_MM
MM = QgsUnitTypes.LayoutMillimeters
LEGENDE_ID = "Legende Basemap"
LEGENDE_TITEL = "Kartengrundlage (basemap.de)"
# Zweizeilig, damit der Titel nicht die Mindestbreite der Legende bestimmt (Basis #96)
LEGENDE_TITEL = "Kartengrundlage\n(basemap.de)"
HAUPTKARTE_ID = "Hauptkarte"
HAUPTLEGENDE_ID = "Legende"
QUELLENANGABE_ID = "Quellenangabe"
@@ -350,7 +352,8 @@ def mindestgroesse(legende: Any, layout: Any, spalten: int) -> tuple[float, floa
return None
einstellungen.setColumnCount(spalten)
einstellungen.setSplitLayer(True)
einstellungen.setEqualColumnWidth(True)
einstellungen.setEqualColumnWidth(False)
einstellungen.setAutoWrapLinesAfter(TEXTUMBRUCH_MM)
try:
groesse = QgsLegendRenderer(legende.model(), einstellungen).minimumSize(kontext)
except Exception:
@@ -399,7 +402,7 @@ def platziere(layout: Any, legende: Any) -> str:
spalten, groesse = spalten + 1, breiter
_aufruf(legende, "setColumnCount", spalten)
_aufruf(legende, "setSplitLayer", True)
_aufruf(legende, "setEqualColumnWidth", True)
_aufruf(legende, "setEqualColumnWidth", False)
unten_links = getattr(getattr(QgsLayoutItem, "ReferencePoint", QgsLayoutItem), "LowerLeft", None)
if unten_links is not None:
@@ -450,6 +453,7 @@ def schreibe_legende(layout: Any, map_item: Any, eintraege: list, symbole: dict[
legende.setAutoUpdateModel(False)
legende.setLegendFilterByMapEnabled(False)
legende.setTitle(LEGENDE_TITEL)
_aufruf(legende, "setAutoWrapLinesAfter", TEXTUMBRUCH_MM)
wurzel = legende.model().rootGroup()
wurzel.removeAllChildren()
for gruppe, layer_liste in gruppen:
+5 -1
View File
@@ -34,6 +34,9 @@ ABSTAND_QUELLE_SCHILD_MM = 10.0
SCHILD_HOEHE_MM = 92.0
MAX_SPALTEN = 3
VERMESSUNG_DPI = 25 # Auflösung des einmaligen Zeichnens zum Vermessen
# Legendentext bricht nach dieser Breite um, Spalten sind ungleich breit – sonst bestimmt der längste
# Text die Breite aller Spalten (dreispaltige Basemap-Legende 431 statt 226 mm, Basis #96).
TEXTUMBRUCH_MM = 60.0
HAUPTKARTE_ID = "Hauptkarte"
LEGENDEN_IDS = ("Legende", "Legende Basemap") # von oben nach unten
@@ -124,7 +127,8 @@ def _rechteck(element: Any) -> tuple[float, float, float, float]:
def _legende_groesse(legende: Any, spalten: int) -> tuple[float, float]:
_aufruf(legende, "setColumnCount", spalten)
_aufruf(legende, "setSplitLayer", True)
_aufruf(legende, "setEqualColumnWidth", True)
_aufruf(legende, "setEqualColumnWidth", False)
_aufruf(legende, "setAutoWrapLinesAfter", TEXTUMBRUCH_MM)
_aufruf(legende, "adjustBoxSize")
groesse = legende.sizeWithUnits()
return _wert(groesse, "width"), _wert(groesse, "height")
+26 -1
View File
@@ -62,10 +62,11 @@ class _Legende(_Element):
self.spalten = 1
self.bewegt = None
self.groesse = None
self.gleiche_breite = True
def setColumnCount(self, n): self.spalten = n
def setSplitLayer(self, wert): pass
def setEqualColumnWidth(self, wert): pass
def setEqualColumnWidth(self, wert): self.gleiche_breite = wert
def setReferencePoint(self, punkt): pass
def attemptMove(self, punkt): self.bewegt = punkt
def attemptResize(self, groesse): self.groesse = groesse
@@ -214,6 +215,30 @@ class TestPlatzierung(unittest.TestCase):
self.assertEqual(hinweis, "")
self.assertEqual(legende.spalten, 3)
def test_spalten_ungleich_breit(self):
"""Prüft, dass die Basemap-Legende ungleich breite Spalten bekommt (#96)."""
legende, _ = self._platziere(self._layout(), {1: (100.0, 300.0), 2: (200.0, 150.0), 3: (300.0, 100.0)})
self.assertFalse(legende.gleiche_breite)
def test_mindestgroesse_mit_ungleichen_spalten_und_umbruch(self):
"""Prüft, dass der Platzbedarf mit ungleich breiten Spalten und Textumbruch berechnet wird (#96)."""
class Einstellungen:
def __init__(self): self.werte = {}
def __getattr__(self, name): return lambda wert: self.werte.__setitem__(name, wert)
einstellungen = Einstellungen()
legende = type("L", (), {"legendSettings": lambda s: einstellungen, "model": lambda s: None})()
renderer = type("R", (), {"minimumSize": lambda s, k: type("G", (), {"width": lambda s: 80.0, "height": lambda s: 40.0})()})
with patch.object(bll, "QgsLayoutUtils", type("U", (), {"createRenderContextForLayout": staticmethod(lambda l, p: object())})), patch.object(bll, "QgsLegendRenderer", lambda modell, einst: renderer()):
self.assertEqual(bll.mindestgroesse(legende, object(), 2), (80.0, 40.0))
self.assertEqual(einstellungen.werte["setColumnCount"], 2)
self.assertFalse(einstellungen.werte["setEqualColumnWidth"])
self.assertEqual(einstellungen.werte["setAutoWrapLinesAfter"], bll.TEXTUMBRUCH_MM)
def test_titel_zweizeilig(self):
"""Prüft, dass der Titel der Basemap-Legende zweizeilig ist, damit er die Mindestbreite nicht bestimmt (#96)."""
self.assertEqual(bll.LEGENDE_TITEL.splitlines(), ["Kartengrundlage", "(basemap.de)"])
def test_nie_breiter_als_der_seitenrand_mit_hinweis(self):
"""Prüft, dass keine Spaltenzahl gewählt wird, die über den Seitenrand ragt, und dann ein Hinweis kommt."""
legende, hinweis = self._platziere(self._layout(breite=600.0),
+10 -1
View File
@@ -96,10 +96,12 @@ class _Legende(_Element):
def __init__(self, x, y, groessen, unten_verankert=False):
super().__init__(x, y, groessen[1][0], groessen[1][1])
self.groessen, self.spalten, self.unten = groessen, 1, unten_verankert
self.gleiche_breite, self.umbruch = True, 0
def setColumnCount(self, n): self.spalten = n
def setSplitLayer(self, wert): pass
def setEqualColumnWidth(self, wert): pass
def setEqualColumnWidth(self, wert): self.gleiche_breite = wert
def setAutoWrapLinesAfter(self, mm): self.umbruch = mm
def setLocked(self, wert): pass
def adjustBoxSize(self):
self.b, self.h = self.groessen[self.spalten]
@@ -151,6 +153,13 @@ class TestOrdneKartenrandAn(unittest.TestCase):
self.assertEqual(elemente["Hauptkarte"].verschoben, [])
self.assertEqual(layout.seite.b, 180 + 450 + kr.RAND_MM)
def test_spalten_ungleich_breit_und_text_umgebrochen(self):
"""Prüft, dass die Legenden ungleich breite Spalten und einen automatischen Textumbruch bekommen, damit mehrspaltige Legenden nicht unnötig breit werden (#96)."""
layout, elemente = self._layout({1: (150, 300), 2: (300, 160), 3: (450, 110)})
ordne_kartenrand_an(layout, rendern=lambda *a: None)
self.assertFalse(elemente["Legende"].gleiche_breite)
self.assertEqual(elemente["Legende"].umbruch, kr.TEXTUMBRUCH_MM)
def test_passt_nichts_wird_verschoben(self):
"""Prüft, dass bei ausreichend hoher Karte weder Seite noch Kartenschild verändert werden."""
layout, elemente = self._layout({1: (100, 10), 2: (200, 6), 3: (300, 4)})