From 9cff75b674ad1d58bf17a95cef4c37de38cc61aa Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 2 Oct 2026 13:20:15 +0200 Subject: [PATCH 1/2] Mehrspaltige Legenden schmaler (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- changelog.txt | 1 + modules/basemap_legende_layout.py | 10 +++++++--- modules/print_kartenrand.py | 6 +++++- tests/test_basemap_legende_layout.py | 27 ++++++++++++++++++++++++++- tests/test_print_kartenrand.py | 11 ++++++++++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/changelog.txt b/changelog.txt index 39dd0c8..eea5103 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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) diff --git a/modules/basemap_legende_layout.py b/modules/basemap_legende_layout.py index 4a68918..f13b3e0 100644 --- a/modules/basemap_legende_layout.py +++ b/modules/basemap_legende_layout.py @@ -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: diff --git a/modules/print_kartenrand.py b/modules/print_kartenrand.py index aa6dc3d..8278131 100644 --- a/modules/print_kartenrand.py +++ b/modules/print_kartenrand.py @@ -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") diff --git a/tests/test_basemap_legende_layout.py b/tests/test_basemap_legende_layout.py index 33f2af2..79cc2d5 100644 --- a/tests/test_basemap_legende_layout.py +++ b/tests/test_basemap_legende_layout.py @@ -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), diff --git a/tests/test_print_kartenrand.py b/tests/test_print_kartenrand.py index a2ca1db..20450e5 100644 --- a/tests/test_print_kartenrand.py +++ b/tests/test_print_kartenrand.py @@ -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)}) From bf898dcca3c3707a03b7e3518cc48f4a3d9cbfe6 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 2 Oct 2026 13:26:19 +0200 Subject: [PATCH 2/2] Legenden mindestens so breit wie das Kartenschild (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standard- und Mindestbreite der Haupt- und Basemap-Legende ist die Breite des Kartenschilds (180 mm, MIN_SPALTENBREITE_MM) – die zunächst genannten 210 mm sind der gesamte Kartenrand einschließlich Abständen. Die Legenden passen sich weiter ihrem Inhalt an, werden aber nie schmaler (setMinimumSize). QGIS speichert die Mindestgröße nicht im Projekt; nach dem Neuladen schrumpfte die Legende bei der nächsten Größenanpassung wieder (QGIS-Test: 43,7 mm). Deshalb vermerkt das Layout die Mindestbreite (sn_legende_mindestbreite), und ein Listener auf readProject setzt sie beim Laden wieder (setup/teardown_mindestbreiten_listener in main.py, wie der Verfahrensgebiet-Listener). Co-Authored-By: Claude Opus 5.5 --- changelog.txt | 2 +- main.py | 7 ++ modules/basemap_legende_layout.py | 4 +- modules/print_kartenrand.py | 95 +++++++++++++++++++++++++++- tests/test_basemap_legende_layout.py | 13 ++++ tests/test_print_kartenrand.py | 53 ++++++++++++++++ 6 files changed, 170 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index eea5103..d795f23 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +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: 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. Standard- und Mindestbreite der Legenden ist die Breite des Kartenschilds (180 mm); sie wird am Layout vermerkt und beim Laden des Projekts wieder gesetzt, weil QGIS sie nicht speichert (#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) diff --git a/main.py b/main.py index cdace26..429b13b 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,10 @@ from sn_basis.functions.verfahrensgebiet_manager import ( setup_verfahrensgebiet_listener, teardown_verfahrensgebiet_listener, ) +from sn_basis.modules.print_kartenrand import ( + setup_mindestbreiten_listener, + teardown_mindestbreiten_listener, +) from sn_basis.ui.navigation import Navigation @@ -41,6 +45,8 @@ class BasisPlugin: # Zentrale Verwaltung des Verfahrensgebiet-Layers starten setup_verfahrensgebiet_listener() + # Mindestbreite der Drucklegenden nach dem Laden eines Projekts wieder setzen (Basis #96) + setup_mindestbreiten_listener() for name, plugin in plugins.items(): if name.startswith("sn_") and name != "sn_basis": @@ -57,6 +63,7 @@ class BasisPlugin: Räumt UI-Komponenten sauber auf. """ teardown_verfahrensgebiet_listener() + teardown_mindestbreiten_listener() if self.ui: self.ui.remove_all() diff --git a/modules/basemap_legende_layout.py b/modules/basemap_legende_layout.py index f13b3e0..d37d7da 100644 --- a/modules/basemap_legende_layout.py +++ b/modules/basemap_legende_layout.py @@ -40,7 +40,7 @@ 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 +from sn_basis.modules.print_kartenrand import MIN_SPALTENBREITE_MM, TEXTUMBRUCH_MM MM = QgsUnitTypes.LayoutMillimeters @@ -332,6 +332,8 @@ def _neues_legendenelement(layout: Any) -> Any: _aufruf(legende, "setFrameEnabled", True) _aufruf(legende, "setBackgroundEnabled", False) _aufruf(legende, "setFrameStrokeWidth", _layout_messung(RAHMENSTAERKE_MM)) + # Mindestens so breit wie das Kartenschild (Basis #96) + _aufruf(legende, "setMinimumSize", QgsLayoutSize(MIN_SPALTENBREITE_MM, 0.0, MM)) layout.addLayoutItem(legende) return legende diff --git a/modules/print_kartenrand.py b/modules/print_kartenrand.py index 8278131..d39dbf5 100644 --- a/modules/print_kartenrand.py +++ b/modules/print_kartenrand.py @@ -21,13 +21,22 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any, Callable -from sn_basis.functions.qgiscore_wrapper import QgsLayoutExporter, QgsLayoutPoint, QgsLayoutSize, QgsUnitTypes +from sn_basis.functions.qgiscore_wrapper import ( + QGIS_AVAILABLE, + QgsLayoutExporter, + QgsLayoutPoint, + QgsLayoutSize, + QgsProject, + QgsUnitTypes, +) from sn_basis.functions.qt_wrapper import QSize MM = QgsUnitTypes.LayoutMillimeters RAND_MM = 10.0 # Seitenrand und Abstand Karte → Kartenrand -MIN_SPALTENBREITE_MM = 180.0 # Breite von Kartenschild und Quellenangabe +# Breite von Kartenschild und Quellenangabe (PrintLayout._LEGEND_WIDTH_MM) – zugleich Standard- und +# Mindestbreite der Legenden, damit sie optisch mit dem Kartenschild übereinstimmen. +MIN_SPALTENBREITE_MM = 180.0 ABSTAND_LEGENDEN_MM = 4.0 # unter jeder Legende QUELLE_HOEHE_MM = 20.0 ABSTAND_QUELLE_SCHILD_MM = 10.0 @@ -41,6 +50,9 @@ TEXTUMBRUCH_MM = 60.0 HAUPTKARTE_ID = "Hauptkarte" LEGENDEN_IDS = ("Legende", "Legende Basemap") # von oben nach unten BASEMAP_LEGENDE_ID = "Legende Basemap" +# Layout-Eigenschaft: Mindestbreite der Legenden. QGIS speichert setMinimumSize nicht im Projekt; +# der Vermerk erlaubt, sie beim Laden wieder zu setzen (wende_mindestbreiten_an). +LAYOUT_MINDESTBREITE = "sn_legende_mindestbreite" QUELLENANGABE_ID = "Quellenangabe" # Elemente unterhalb der Legenden, die bei höherer Seite gemeinsam nach unten rutschen UNTERE_ELEMENTE = ( @@ -141,6 +153,84 @@ def _rendere_legenden(layout: Any, legenden: list[Any]) -> None: _aufruf(exporter, "renderRegionToImage", legende.sceneBoundingRect(), QSize(), VERMESSUNG_DPI) +def setze_legenden_mindestbreite(layout: Any, breite: float = MIN_SPALTENBREITE_MM, merken: bool = True) -> int: + """Gibt den Legenden des Layouts eine Mindestbreite (Standard: Breite des Kartenschilds). + + Die Legenden passen sich weiter ihrem Inhalt an, werden aber nie schmaler. Mit ``merken`` wird die + Breite am Layout vermerkt, damit sie nach dem Laden des Projekts wieder gesetzt werden kann. + + Returns + ------- + int + Anzahl der Legenden, die eine Mindestbreite bekommen haben. + """ + anzahl = 0 + for legenden_id in LEGENDEN_IDS: + legende = _aufruf(layout, "itemById", legenden_id) + if legende is not None: + legende.setMinimumSize(QgsLayoutSize(breite, 0.0, MM)) + anzahl += 1 + if merken: + layout.setCustomProperty(LAYOUT_MINDESTBREITE, float(breite)) + return anzahl + + +def wende_mindestbreiten_an(project: Any | None = None) -> int: + """Setzt die vermerkten Mindestbreiten der Legenden aller Drucklayouts erneut (nach dem Laden). + + Returns + ------- + int + Anzahl der Layouts, an denen eine Mindestbreite gesetzt wurde. + """ + project = project or QgsProject.instance() + verwaltung = _aufruf(project, "layoutManager") + anzahl = 0 + for layout in _aufruf(verwaltung, "printLayouts", standard=[]) or []: + wert = _aufruf(layout, "customProperty", LAYOUT_MINDESTBREITE, None) + try: + breite = float(wert) + except (TypeError, ValueError): + continue + if breite > 0 and setze_legenden_mindestbreite(layout, breite, merken=False): + anzahl += 1 + return anzahl + + +_listener_aktiv = False + + +def _on_projekt_gelesen(*_: Any) -> None: + wende_mindestbreiten_an() + + +def setup_mindestbreiten_listener() -> None: + """Setzt die Mindestbreiten bei jedem Laden eines Projekts neu (einmal beim Plugin-Start aufrufen).""" + global _listener_aktiv + if _listener_aktiv or not QGIS_AVAILABLE: + return + try: + project = QgsProject.instance() + project.readProject.connect(_on_projekt_gelesen) + _listener_aktiv = True + wende_mindestbreiten_an(project) # bereits geöffnetes Projekt + except Exception: + pass + + +def teardown_mindestbreiten_listener() -> None: + """Trennt den Listener wieder (beim Entladen des Plugins; idempotent).""" + global _listener_aktiv + if not _listener_aktiv: + return + try: + QgsProject.instance().readProject.disconnect(_on_projekt_gelesen) + except Exception: + pass + finally: + _listener_aktiv = False + + def _verschiebe(element: Any, dy: float) -> None: position = element.positionWithUnits() element.attemptMove(QgsLayoutPoint(_wert(position, "x"), _wert(position, "y") + dy, MM)) @@ -160,6 +250,7 @@ def ordne_kartenrand_an(layout: Any, rendern: Callable[[Any, list], None] | None legenden = [l for l in (_aufruf(layout, "itemById", i) for i in LEGENDEN_IDS) if l is not None] for legende in legenden: _aufruf(legende, "setLocked", False) + setze_legenden_mindestbreite(layout) (rendern or _rendere_legenden)(layout, legenden) groessen = {n: [_legende_groesse(l, n) for l in legenden] for n in range(1, MAX_SPALTEN + 1)} diff --git a/tests/test_basemap_legende_layout.py b/tests/test_basemap_legende_layout.py index 79cc2d5..3d678d1 100644 --- a/tests/test_basemap_legende_layout.py +++ b/tests/test_basemap_legende_layout.py @@ -235,6 +235,19 @@ class TestPlatzierung(unittest.TestCase): self.assertFalse(einstellungen.werte["setEqualColumnWidth"]) self.assertEqual(einstellungen.werte["setAutoWrapLinesAfter"], bll.TEXTUMBRUCH_MM) + def test_neue_basemap_legende_mindestens_so_breit_wie_kartenschild(self): + """Prüft, dass eine neu angelegte Basemap-Legende die Breite des Kartenschilds als Mindestbreite bekommt.""" + class Element: + def __init__(self, layout): self.mindestbreite = None + def __getattr__(self, name): return lambda *a: None + def setMinimumSize(self, groesse): + self.mindestbreite = groesse.width() if callable(groesse.width) else groesse.width + + layout = type("L", (), {"addLayoutItem": lambda s, e: None})() + with patch.object(bll, "QgsLayoutItemLegend", Element): + legende = bll._neues_legendenelement(layout) + self.assertEqual(legende.mindestbreite, 180.0) + 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)"]) diff --git a/tests/test_print_kartenrand.py b/tests/test_print_kartenrand.py index 20450e5..f584d9e 100644 --- a/tests/test_print_kartenrand.py +++ b/tests/test_print_kartenrand.py @@ -97,11 +97,14 @@ class _Legende(_Element): 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 + self.mindestbreite = None def setColumnCount(self, n): self.spalten = n def setSplitLayer(self, wert): pass def setEqualColumnWidth(self, wert): self.gleiche_breite = wert def setAutoWrapLinesAfter(self, mm): self.umbruch = mm + def setMinimumSize(self, groesse): + self.mindestbreite = groesse.width() if callable(groesse.width) else groesse.width def setLocked(self, wert): pass def adjustBoxSize(self): self.b, self.h = self.groessen[self.spalten] @@ -121,6 +124,10 @@ class _Seite: class _Layout: def __init__(self, elemente, seite): self.elemente, self.seite = elemente, seite + self.eigenschaften = {} + + def setCustomProperty(self, k, v): self.eigenschaften[k] = v + def customProperty(self, k, standard=None): return self.eigenschaften.get(k, standard) def itemById(self, element_id): return self.elemente.get(element_id) def pageCollection(self): return type("P", (), {"page": lambda s, i: self.seite})() @@ -160,6 +167,14 @@ class TestOrdneKartenrandAn(unittest.TestCase): self.assertFalse(elemente["Legende"].gleiche_breite) self.assertEqual(elemente["Legende"].umbruch, kr.TEXTUMBRUCH_MM) + def test_legenden_mindestens_so_breit_wie_kartenschild(self): + """Prüft, dass die Legenden die Breite des Kartenschilds als Mindestbreite bekommen und das Layout sie für das Neuladen vermerkt.""" + layout, elemente = self._layout({1: (60, 300), 2: (120, 160), 3: (175, 110)}) + ordne_kartenrand_an(layout, rendern=lambda *a: None) + self.assertEqual(elemente["Legende"].mindestbreite, kr.MIN_SPALTENBREITE_MM) + self.assertEqual(kr.MIN_SPALTENBREITE_MM, 180.0) # Breite von Kartenschild und Quellenangabe + self.assertEqual(float(layout.customProperty(kr.LAYOUT_MINDESTBREITE)), kr.MIN_SPALTENBREITE_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)}) @@ -174,6 +189,44 @@ class TestOrdneKartenrandAn(unittest.TestCase): self.assertIsNone(ordne_kartenrand_an(_Layout({}, _Seite(100, 100)), rendern=lambda *a: None)) +class TestMindestbreiteBeimLaden(unittest.TestCase): + + def _projekt(self, *layouts): + verwaltung = type("V", (), {"printLayouts": lambda s: list(layouts)})() + return type("P", (), {"layoutManager": lambda s: verwaltung})() + + def test_mindestbreite_nur_fuer_vermerkte_layouts(self): + """Prüft, dass beim Laden die Mindestbreite nur an Layouts mit Vermerk wieder gesetzt wird.""" + mit = _Layout({"Legende": _Legende(0, 0, {1: (60, 10)}), "Legende Basemap": _Legende(0, 0, {1: (90, 10)})}, _Seite(1, 1)) + mit.setCustomProperty(kr.LAYOUT_MINDESTBREITE, "180.0") + ohne = _Layout({"Legende": _Legende(0, 0, {1: (60, 10)})}, _Seite(1, 1)) + self.assertEqual(kr.wende_mindestbreiten_an(self._projekt(mit, ohne)), 1) + self.assertEqual(mit.elemente["Legende"].mindestbreite, 180.0) + self.assertEqual(mit.elemente["Legende Basemap"].mindestbreite, 180.0) + self.assertIsNone(ohne.elemente["Legende"].mindestbreite) + + def test_listener_verbinden_und_trennen(self): + """Prüft, dass der Listener readProject einmal verbindet, beim Trennen wieder löst und beides mehrfach aufgerufen werden darf.""" + signal = MagicMock() + projekt = MagicMock() + projekt.readProject = signal + projekt.layoutManager.return_value.printLayouts.return_value = [] + with patch.object(kr, "QGIS_AVAILABLE", True), patch.object(kr, "QgsProject") as qgs_projekt: + qgs_projekt.instance.return_value = projekt + kr.setup_mindestbreiten_listener() + kr.setup_mindestbreiten_listener() + self.assertEqual(signal.connect.call_count, 1) + kr.teardown_mindestbreiten_listener() + kr.teardown_mindestbreiten_listener() + self.assertEqual(signal.disconnect.call_count, 1) + + def test_ohne_qgis_kein_listener(self): + """Prüft, dass im Mock-Modus kein Listener verbunden wird.""" + with patch.object(kr, "QGIS_AVAILABLE", False), patch.object(kr, "QgsProject") as qgs_projekt: + kr.setup_mindestbreiten_listener() + qgs_projekt.instance.assert_not_called() + + class TestAnbindungPrintLayout(unittest.TestCase): def test_vorlage_ordnet_kartenrand_an(self):