From dbccb5322fee5a762b50a00d323707ed87b33d04 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 10 Apr 2026 21:30:44 +0200 Subject: [PATCH] =?UTF-8?q?Koordinatengitter=20eingef=C3=BCgt,=20Rahmenbre?= =?UTF-8?q?iten=20einheitlich=20auf=200,5mm=20gesetzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/qgiscore_wrapper.py | 291 ++++++++++++++++++++++++++++++++++ modules/print_layout.py | 264 +++++++++++++++++++++++++++--- tests/test_print_layout.py | 97 ++++++++++++ 3 files changed, 629 insertions(+), 23 deletions(-) diff --git a/functions/qgiscore_wrapper.py b/functions/qgiscore_wrapper.py index a1f7f90..366d38f 100644 --- a/functions/qgiscore_wrapper.py +++ b/functions/qgiscore_wrapper.py @@ -28,6 +28,9 @@ QgsCoordinateTransform: Type[Any] QgsCoordinateReferenceSystem: Type[Any] QgsPrintLayout: Type[Any] QgsLayoutItemMap: Type[Any] +QgsLayoutItemMapGrid: Type[Any] +QgsTextFormat: Type[Any] +QgsTextBackgroundSettings: Type[Any] QgsLayoutItemLabel: Type[Any] QgsLayoutItemLegend: Type[Any] QgsLayoutPoint: Type[Any] @@ -35,6 +38,25 @@ QgsLayoutSize: Type[Any] QgsUnitTypes: Type[Any] QgsLayoutItem: Type[Any] +MAP_GRID_STYLE_MARKERS: Any = None +MAP_GRID_STYLE_FRAME_ANNOTATIONS: Any = None +MAP_GRID_FRAME_STYLE_INTERIOR_TICKS: Any = None +MAP_GRID_UNIT_MAP_UNIT: Any = None +MAP_GRID_ANNOTATION_FORMAT_DECIMAL: Any = None +MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL: Any = None +MAP_GRID_ANNOTATION_DISPLAY_SHOW_ALL: Any = None +MAP_GRID_ANNOTATION_DISPLAY_SHOW_X: Any = None +MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y: Any = None +MAP_GRID_BORDER_LEFT: Any = None +MAP_GRID_BORDER_RIGHT: Any = None +MAP_GRID_BORDER_TOP: Any = None +MAP_GRID_BORDER_BOTTOM: Any = None +MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME: Any = None +TEXT_BACKGROUND_SHAPE_RECTANGLE: Any = None +TEXT_BACKGROUND_SIZE_BUFFER: Any = None +TEXT_RENDER_UNIT_MILLIMETERS: Any = None +TEXT_RENDER_UNIT_POINTS: Any = None + QGIS_AVAILABLE = False # --------------------------------------------------------- @@ -58,6 +80,9 @@ try: QgsCoordinateReferenceSystem as _QgsCoordinateReferenceSystem, QgsPrintLayout as _QgsPrintLayout, QgsLayoutItemMap as _QgsLayoutItemMap, + QgsLayoutItemMapGrid as _QgsLayoutItemMapGrid, + QgsTextFormat as _QgsTextFormat, + QgsTextBackgroundSettings as _QgsTextBackgroundSettings, QgsLayoutItemLabel as _QgsLayoutItemLabel, QgsLayoutItemLegend as _QgsLayoutItemLegend, QgsLayoutPoint as _QgsLayoutPoint, @@ -81,6 +106,9 @@ try: QgsCoordinateReferenceSystem = _QgsCoordinateReferenceSystem QgsPrintLayout = _QgsPrintLayout QgsLayoutItemMap = _QgsLayoutItemMap + QgsLayoutItemMapGrid = _QgsLayoutItemMapGrid + QgsTextFormat = _QgsTextFormat + QgsTextBackgroundSettings = _QgsTextBackgroundSettings QgsLayoutItemLabel = _QgsLayoutItemLabel QgsLayoutItemLegend = _QgsLayoutItemLegend QgsLayoutPoint = _QgsLayoutPoint @@ -88,6 +116,98 @@ try: QgsUnitTypes = _QgsUnitTypes QgsLayoutItem = _QgsLayoutItem + def _resolve_qgis_enum(*paths: tuple[Any, ...]) -> Any: + for path in paths: + value: Any = path[0] + ok = True + for name in path[1:]: + value = getattr(value, name, None) + if value is None: + ok = False + break + if ok: + return value + return None + + MAP_GRID_STYLE_MARKERS = _resolve_qgis_enum( + (_Qgis, "MapGridStyle", "Markers"), + (_QgsLayoutItemMapGrid, "Markers"), + ) + MAP_GRID_STYLE_FRAME_ANNOTATIONS = _resolve_qgis_enum( + (_Qgis, "MapGridStyle", "FrameAndAnnotationsOnly"), + (_QgsLayoutItemMapGrid, "FrameAndAnnotationsOnly"), + (_Qgis, "MapGridStyle", "FrameAnnotations"), + (_QgsLayoutItemMapGrid, "FrameAnnotations"), + ) + MAP_GRID_FRAME_STYLE_INTERIOR_TICKS = _resolve_qgis_enum( + (_Qgis, "MapGridFrameStyle", "InteriorTicks"), + (_QgsLayoutItemMapGrid, "InteriorTicks"), + ) + MAP_GRID_UNIT_MAP_UNIT = _resolve_qgis_enum( + (_Qgis, "MapGridUnit", "MapUnits"), + (_QgsLayoutItemMapGrid, "MapUnits"), + (_Qgis, "MapGridUnit", "MapUnit"), + (_QgsLayoutItemMapGrid, "MapUnit"), + ) + MAP_GRID_ANNOTATION_FORMAT_DECIMAL = _resolve_qgis_enum( + (_Qgis, "MapGridAnnotationFormat", "Decimal"), + (_QgsLayoutItemMapGrid, "Decimal"), + ) + MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL = _resolve_qgis_enum( + (_Qgis, "MapGridComponentVisibility", "HideAll"), + (_QgsLayoutItemMapGrid, "HideAll"), + ) + MAP_GRID_ANNOTATION_DISPLAY_SHOW_ALL = _resolve_qgis_enum( + (_Qgis, "MapGridComponentVisibility", "ShowAll"), + (_QgsLayoutItemMapGrid, "ShowAll"), + ) + MAP_GRID_ANNOTATION_DISPLAY_SHOW_X = _resolve_qgis_enum( + (_Qgis, "MapGridComponentVisibility", "LongitudeOnly"), + (_QgsLayoutItemMapGrid, "LongitudeOnly"), + (_Qgis, "MapGridComponentVisibility", "ShowLongitudeOnly"), + (_QgsLayoutItemMapGrid, "ShowLongitudeOnly"), + ) + MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y = _resolve_qgis_enum( + (_Qgis, "MapGridComponentVisibility", "LatitudeOnly"), + (_QgsLayoutItemMapGrid, "LatitudeOnly"), + (_Qgis, "MapGridComponentVisibility", "ShowLatitudeOnly"), + (_QgsLayoutItemMapGrid, "ShowLatitudeOnly"), + ) + MAP_GRID_BORDER_LEFT = _resolve_qgis_enum( + (_Qgis, "MapGridBorderSide", "Left"), + (_QgsLayoutItemMapGrid, "Left"), + ) + MAP_GRID_BORDER_RIGHT = _resolve_qgis_enum( + (_Qgis, "MapGridBorderSide", "Right"), + (_QgsLayoutItemMapGrid, "Right"), + ) + MAP_GRID_BORDER_TOP = _resolve_qgis_enum( + (_Qgis, "MapGridBorderSide", "Top"), + (_QgsLayoutItemMapGrid, "Top"), + ) + MAP_GRID_BORDER_BOTTOM = _resolve_qgis_enum( + (_Qgis, "MapGridBorderSide", "Bottom"), + (_QgsLayoutItemMapGrid, "Bottom"), + ) + MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME = _resolve_qgis_enum( + (_Qgis, "MapGridAnnotationPosition", "InsideMapFrame"), + (_QgsLayoutItemMapGrid, "InsideMapFrame"), + ) + TEXT_BACKGROUND_SHAPE_RECTANGLE = _resolve_qgis_enum( + (_QgsTextBackgroundSettings, "ShapeRectangle"), + ) + TEXT_BACKGROUND_SIZE_BUFFER = _resolve_qgis_enum( + (_QgsTextBackgroundSettings, "SizeBuffer"), + ) + TEXT_RENDER_UNIT_MILLIMETERS = _resolve_qgis_enum( + (_Qgis, "RenderUnit", "Millimeters"), + (_QgsUnitTypes, "RenderMillimeters"), + ) + TEXT_RENDER_UNIT_POINTS = _resolve_qgis_enum( + (_Qgis, "RenderUnit", "Points"), + (_QgsUnitTypes, "RenderPoints"), + ) + QGIS_AVAILABLE = True # --------------------------------------------------------- @@ -208,6 +328,7 @@ except Exception: self.follow_visibility_preset = False self.follow_visibility_preset_name = "" self.keep_layer_set = False + self._grids = _MockQgsLayoutItemMapGridStack(self) def setId(self, item_id: str) -> None: self.item_id = item_id @@ -233,6 +354,154 @@ except Exception: def setKeepLayerSet(self, enabled: bool) -> None: self.keep_layer_set = enabled + def grids(self): + return self._grids + + def updateBoundingRect(self) -> None: + pass + + def refresh(self) -> None: + pass + + class _MockQgsLayoutItemMapGrid: + Markers = "Markers" + FrameAndAnnotationsOnly = "FrameAndAnnotationsOnly" + InteriorTicks = "InteriorTicks" + MapUnits = "MapUnits" + Decimal = "Decimal" + HideAll = "HideAll" + ShowAll = "ShowAll" + LongitudeOnly = "LongitudeOnly" + LatitudeOnly = "LatitudeOnly" + Left = "Left" + Right = "Right" + Top = "Top" + Bottom = "Bottom" + InsideMapFrame = "InsideMapFrame" + + def __init__(self, name: str, map_item: Any): + self.name = name + self.map_item = map_item + self.enabled = False + self.style = None + self.units = None + self.interval_x = None + self.interval_y = None + self.annotation_enabled = False + self.annotation_format = None + self.annotation_precision = None + self.annotation_display: dict[Any, Any] = {} + self.annotation_position: dict[Any, Any] = {} + self.frame_style = None + self.annotation_text_format = None + + def setEnabled(self, enabled: bool) -> None: + self.enabled = enabled + + def setStyle(self, style: Any) -> None: + self.style = style + + def setUnits(self, units: Any) -> None: + self.units = units + + def setIntervalX(self, interval: float) -> None: + self.interval_x = interval + + def setIntervalY(self, interval: float) -> None: + self.interval_y = interval + + def setAnnotationEnabled(self, enabled: bool) -> None: + self.annotation_enabled = enabled + + def setAnnotationFormat(self, annotation_format: Any) -> None: + self.annotation_format = annotation_format + + def setAnnotationPrecision(self, precision: int) -> None: + self.annotation_precision = precision + + def setAnnotationDisplay(self, display: Any, border: Any) -> None: + self.annotation_display[border] = display + + def setAnnotationPosition(self, position: Any, border: Any) -> None: + self.annotation_position[border] = position + + def setFrameStyle(self, frame_style: Any) -> None: + self.frame_style = frame_style + + def setAnnotationTextFormat(self, annotation_text_format: Any) -> None: + self.annotation_text_format = annotation_text_format + + class _MockQgsTextBackgroundSettings: + ShapeRectangle = "ShapeRectangle" + SizeBuffer = "SizeBuffer" + + def __init__(self): + self.enabled = False + self.shape_type = None + self.size_type = None + self.size = None + self.size_unit = None + self.offset = None + self.offset_unit = None + self.fill_color = None + + def setEnabled(self, enabled: bool) -> None: + self.enabled = enabled + + def setType(self, shape_type: Any) -> None: + self.shape_type = shape_type + + def setSizeType(self, size_type: Any) -> None: + self.size_type = size_type + + def setSize(self, size: Any) -> None: + self.size = size + + def setSizeUnit(self, unit: Any) -> None: + self.size_unit = unit + + def setOffset(self, offset: Any) -> None: + self.offset = offset + + def setOffsetUnit(self, unit: Any) -> None: + self.offset_unit = unit + + def setFillColor(self, color: Any) -> None: + self.fill_color = color + + class _MockQgsTextFormat: + def __init__(self): + self.font = None + self.size = None + self.size_unit = None + self.background = None + + def setFont(self, font: Any) -> None: + self.font = font + + def setSize(self, size: float) -> None: + self.size = size + + def setSizeUnit(self, unit: Any) -> None: + self.size_unit = unit + + def setBackground(self, background: Any) -> None: + self.background = background + + class _MockQgsLayoutItemMapGridStack: + def __init__(self, map_item: Any): + self.map_item = map_item + self._grids: list[_MockQgsLayoutItemMapGrid] = [] + + def addGrid(self, grid: Any) -> None: + self._grids.append(grid) + + def removeGrid(self, grid_id: str) -> None: + self._grids = [grid for grid in self._grids if getattr(grid, "name", "") != grid_id] + + def asList(self) -> list[Any]: + return list(self._grids) + class _MockQgsLayoutItemLabel: ModeHtml = 1 @@ -338,6 +607,9 @@ except Exception: QgsPrintLayout = _MockQgsPrintLayout QgsLayoutItemMap = _MockQgsLayoutItemMap + QgsLayoutItemMapGrid = _MockQgsLayoutItemMapGrid + QgsTextFormat = _MockQgsTextFormat + QgsTextBackgroundSettings = _MockQgsTextBackgroundSettings QgsLayoutItemLabel = _MockQgsLayoutItemLabel QgsLayoutItemLegend = _MockQgsLayoutItemLegend QgsLayoutPoint = _MockQgsLayoutPoint @@ -345,6 +617,25 @@ except Exception: QgsUnitTypes = _MockQgsUnitTypes QgsLayoutItem = _MockQgsLayoutItem + MAP_GRID_STYLE_MARKERS = _MockQgsLayoutItemMapGrid.Markers + MAP_GRID_STYLE_FRAME_ANNOTATIONS = _MockQgsLayoutItemMapGrid.FrameAndAnnotationsOnly + MAP_GRID_FRAME_STYLE_INTERIOR_TICKS = _MockQgsLayoutItemMapGrid.InteriorTicks + MAP_GRID_UNIT_MAP_UNIT = _MockQgsLayoutItemMapGrid.MapUnits + MAP_GRID_ANNOTATION_FORMAT_DECIMAL = _MockQgsLayoutItemMapGrid.Decimal + MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL = _MockQgsLayoutItemMapGrid.HideAll + MAP_GRID_ANNOTATION_DISPLAY_SHOW_ALL = _MockQgsLayoutItemMapGrid.ShowAll + MAP_GRID_ANNOTATION_DISPLAY_SHOW_X = _MockQgsLayoutItemMapGrid.LongitudeOnly + MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y = _MockQgsLayoutItemMapGrid.LatitudeOnly + MAP_GRID_BORDER_LEFT = _MockQgsLayoutItemMapGrid.Left + MAP_GRID_BORDER_RIGHT = _MockQgsLayoutItemMapGrid.Right + MAP_GRID_BORDER_TOP = _MockQgsLayoutItemMapGrid.Top + MAP_GRID_BORDER_BOTTOM = _MockQgsLayoutItemMapGrid.Bottom + MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME = _MockQgsLayoutItemMapGrid.InsideMapFrame + TEXT_BACKGROUND_SHAPE_RECTANGLE = _MockQgsTextBackgroundSettings.ShapeRectangle + TEXT_BACKGROUND_SIZE_BUFFER = _MockQgsTextBackgroundSettings.SizeBuffer + TEXT_RENDER_UNIT_MILLIMETERS = "Millimeters" + TEXT_RENDER_UNIT_POINTS = "Points" + class _MockQgsFeature: def __init__(self): self._geometry = None diff --git a/modules/print_layout.py b/modules/print_layout.py index 4f150c2..d228c97 100644 --- a/modules/print_layout.py +++ b/modules/print_layout.py @@ -9,14 +9,33 @@ from typing import Any from sn_basis.functions.qt_wrapper import QFont from sn_basis.functions.qgiscore_wrapper import ( + MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL, + MAP_GRID_ANNOTATION_DISPLAY_SHOW_X, + MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y, + MAP_GRID_ANNOTATION_FORMAT_DECIMAL, + MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME, + MAP_GRID_BORDER_BOTTOM, + MAP_GRID_BORDER_LEFT, + MAP_GRID_BORDER_RIGHT, + MAP_GRID_BORDER_TOP, + MAP_GRID_FRAME_STYLE_INTERIOR_TICKS, + MAP_GRID_STYLE_FRAME_ANNOTATIONS, + MAP_GRID_UNIT_MAP_UNIT, + TEXT_BACKGROUND_SHAPE_RECTANGLE, + TEXT_BACKGROUND_SIZE_BUFFER, + TEXT_RENDER_UNIT_MILLIMETERS, + TEXT_RENDER_UNIT_POINTS, QgsLayoutItem, QgsLayoutItemLabel, QgsLayoutItemLegend, QgsLayoutItemMap, + QgsLayoutItemMapGrid, QgsLayoutPoint, QgsLayoutSize, QgsPrintLayout, QgsProject, + QgsTextBackgroundSettings, + QgsTextFormat, QgsUnitTypes, ) from sn_basis.functions.qgisui_wrapper import open_layout_designer @@ -28,6 +47,10 @@ MM = QgsUnitTypes.LayoutMillimeters class PrintLayout: """Erzeugt ein QGIS-Einzelblatt-Layout fuer den Druck.""" + _FRAME_STROKE_WIDTH_MM = 0.5 + _LEGEND_WIDTH_MM = 180.0 + _GRID_INTERVAL_MAP_UNITS = 500.0 + _TEXT_FORMAT_TEMPLATES = { "Format_1": {"font_family": "Arial", "font_size": 11, "font_style": "normal"}, "Format_2": {"font_family": "Arial", "font_size": 13, "font_style": "bold"}, @@ -239,14 +262,14 @@ class PrintLayout: hauptkarte, legende_x, legende_y, - width_mm=180.0, + width_mm=self._LEGEND_WIDTH_MM, height_mm=legende_height_mm, object_name="Legende", ) legende_hoehe_mm = self._configure_legend_size( legende, - width_limit_mm=180.0, + width_limit_mm=self._LEGEND_WIDTH_MM, column_count=1, equal_column_width=False, ) @@ -259,7 +282,7 @@ class PrintLayout: if legende_untere_y > quellenangabe_obere_y: self._configure_legend_size( legende, - width_limit_mm=180.0, + width_limit_mm=self._LEGEND_WIDTH_MM, column_count=2, equal_column_width=True, ) @@ -268,7 +291,7 @@ class PrintLayout: self._QUELLENANGABE_TEXT, quellenangabe_x, quellenangabe_y, - width_mm=180.0, + width_mm=self._LEGEND_WIDTH_MM, height_mm=20.0, text_format="Format_1", html_mode=True, @@ -282,9 +305,9 @@ class PrintLayout: "Kartenschild", kartenschild_x, kartenschild_y, - 180.0, + self._LEGEND_WIDTH_MM, 92.0, - stroke_width_mm=0.5, + stroke_width_mm=self._FRAME_STROKE_WIDTH_MM, ) kartenschild_line_x, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0) @@ -296,7 +319,7 @@ class PrintLayout: f"Kartenschild_Linie_{index}", kartenschild_line_x, line_y, - 180.0, + self._LEGEND_WIDTH_MM, 0.0, stroke_width_mm=0.1, ) @@ -527,7 +550,7 @@ class PrintLayout: except Exception: pass - self._set_item_frame_stroke_width(legend, 0.3) + self._set_item_frame_stroke_width(legend, self._FRAME_STROKE_WIDTH_MM) set_background_enabled = getattr(legend, "setBackgroundEnabled", None) if callable(set_background_enabled): @@ -635,20 +658,9 @@ class PrintLayout: except Exception: pass - width_mm, height_mm = self._get_item_size_mm(legend) - resolved_width = width_mm if width_mm is not None else width_limit_mm - resolved_height = height_mm if height_mm is not None else 20.0 - final_width = min(resolved_width, width_limit_mm) - - legend.attemptResize(QgsLayoutSize(final_width, resolved_height, MM)) - - if callable(refresh): - try: - refresh() - except Exception: - pass - _, updated_height_mm = self._get_item_size_mm(legend) + if updated_height_mm is None: + updated_height_mm = 20.0 if callable(set_locked): try: @@ -656,7 +668,211 @@ class PrintLayout: except Exception: pass - return updated_height_mm if updated_height_mm is not None else resolved_height + return updated_height_mm + + def _configure_hauptkarte_grids(self, hauptkarte: Any) -> None: + grids_method = getattr(hauptkarte, "grids", None) + if not callable(grids_method): + return + + try: + grid_stack = grids_method() + except Exception: + return + if grid_stack is None: + return + + remove_grid = getattr(grid_stack, "removeGrid", None) + if callable(remove_grid): + for grid_id in ("SN_FrameAnnotationGrid", "SN_MarkerGrid"): + try: + remove_grid(grid_id) + except Exception: + pass + + add_grid = getattr(grid_stack, "addGrid", None) + if not callable(add_grid): + return + + if QgsLayoutItemMapGrid is object: + return + + frame_annotation_grid = QgsLayoutItemMapGrid("SN_FrameAnnotationGrid", hauptkarte) + self._apply_grid_common_settings(frame_annotation_grid) + self._safe_grid_call(frame_annotation_grid, "setStyle", MAP_GRID_STYLE_FRAME_ANNOTATIONS) + self._safe_grid_call(frame_annotation_grid, "setFrameStyle", MAP_GRID_FRAME_STYLE_INTERIOR_TICKS) + self._safe_grid_call(frame_annotation_grid, "setAnnotationEnabled", True) + self._safe_grid_call(frame_annotation_grid, "setAnnotationFormat", MAP_GRID_ANNOTATION_FORMAT_DECIMAL) + self._safe_grid_call(frame_annotation_grid, "setAnnotationPrecision", 0) + self._safe_grid_call( + frame_annotation_grid, + "setAnnotationDisplay", + MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL, + MAP_GRID_BORDER_LEFT, + ) + self._safe_grid_call( + frame_annotation_grid, + "setAnnotationDisplay", + MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL, + MAP_GRID_BORDER_TOP, + ) + self._safe_grid_call( + frame_annotation_grid, + "setAnnotationDisplay", + MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y, + MAP_GRID_BORDER_RIGHT, + ) + self._safe_grid_call( + frame_annotation_grid, + "setAnnotationDisplay", + MAP_GRID_ANNOTATION_DISPLAY_SHOW_X, + MAP_GRID_BORDER_BOTTOM, + ) + self._safe_grid_call( + frame_annotation_grid, + "setAnnotationPosition", + MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME, + MAP_GRID_BORDER_RIGHT, + ) + self._safe_grid_call( + frame_annotation_grid, + "setAnnotationPosition", + MAP_GRID_ANNOTATION_POSITION_INSIDE_FRAME, + MAP_GRID_BORDER_BOTTOM, + ) + self._apply_grid_annotation_text_style(frame_annotation_grid) + add_grid(frame_annotation_grid) + + update_bounding_rect = getattr(hauptkarte, "updateBoundingRect", None) + if callable(update_bounding_rect): + try: + update_bounding_rect() + except Exception: + pass + + refresh = getattr(hauptkarte, "refresh", None) + if callable(refresh): + try: + refresh() + except Exception: + pass + + def _apply_grid_common_settings(self, grid: Any) -> None: + self._safe_grid_call(grid, "setEnabled", True) + self._safe_grid_call(grid, "setUnits", MAP_GRID_UNIT_MAP_UNIT) + self._safe_grid_call(grid, "setIntervalX", self._GRID_INTERVAL_MAP_UNITS) + self._safe_grid_call(grid, "setIntervalY", self._GRID_INTERVAL_MAP_UNITS) + + def _safe_grid_call(self, grid: Any, method_name: str, *args: Any) -> None: + if any(arg is None for arg in args): + return + method = getattr(grid, method_name, None) + if not callable(method): + return + try: + method(*args) + except Exception: + pass + + def _apply_grid_annotation_text_style(self, grid: Any) -> None: + if QgsTextFormat is object: + self._safe_grid_call( + grid, + "setAnnotationTextFormat", + { + "font_family": "Arial", + "font_size": 10, + "background": { + "enabled": True, + "shape": "rectangle", + "size_type": "buffer", + "buffer_mm": 1.0, + "fill_color": (255, 255, 255), + }, + }, + ) + return + + try: + text_format = QgsTextFormat() + text_format.setFont(QFont("Arial", 10)) + + if TEXT_RENDER_UNIT_POINTS is not None: + set_size_unit = getattr(text_format, "setSizeUnit", None) + if callable(set_size_unit): + set_size_unit(TEXT_RENDER_UNIT_POINTS) + set_size = getattr(text_format, "setSize", None) + if callable(set_size): + set_size(10) + + if QgsTextBackgroundSettings is not object: + try: + qt_core = importlib.import_module("qgis.PyQt.QtCore") + qt_gui = importlib.import_module("qgis.PyQt.QtGui") + QSizeF = getattr(qt_core, "QSizeF", None) + QPointF = getattr(qt_core, "QPointF", None) + QColor = getattr(qt_gui, "QColor", None) + except Exception: + QSizeF = None + QPointF = None + QColor = None + + background = QgsTextBackgroundSettings() + set_enabled = getattr(background, "setEnabled", None) + if callable(set_enabled): + set_enabled(True) + + set_type = getattr(background, "setType", None) + if callable(set_type) and TEXT_BACKGROUND_SHAPE_RECTANGLE is not None: + set_type(TEXT_BACKGROUND_SHAPE_RECTANGLE) + + set_size_type = getattr(background, "setSizeType", None) + if callable(set_size_type) and TEXT_BACKGROUND_SIZE_BUFFER is not None: + set_size_type(TEXT_BACKGROUND_SIZE_BUFFER) + + #steuert die Größe des Hintergrunds, wenn die Größe auf "Puffer" eingestellt ist + set_size = getattr(background, "setSize", None) + if callable(set_size) and QSizeF is not None: + set_size(QSizeF(0.0, 0.0)) + + set_size_unit = getattr(background, "setSizeUnit", None) + if callable(set_size_unit) and TEXT_RENDER_UNIT_MILLIMETERS is not None: + set_size_unit(TEXT_RENDER_UNIT_MILLIMETERS) + + set_offset = getattr(background, "setOffset", None) + if callable(set_offset): + try: + if QPointF is not None: + set_offset(QPointF(0.0, -1.0)) + elif QSizeF is not None: + set_offset(QSizeF(0.0, -1.0)) + else: + set_offset((0.0, -1.0)) + except Exception: + set_offset_x = getattr(background, "setOffsetX", None) + set_offset_y = getattr(background, "setOffsetY", None) + if callable(set_offset_x) and callable(set_offset_y): + try: + set_offset_x(0.0) + set_offset_y(-1.0) + except Exception: + pass + + set_offset_unit = getattr(background, "setOffsetUnit", None) + if callable(set_offset_unit) and TEXT_RENDER_UNIT_MILLIMETERS is not None: + set_offset_unit(TEXT_RENDER_UNIT_MILLIMETERS) + + set_fill_color = getattr(background, "setFillColor", None) + if callable(set_fill_color) and QColor is not None: + set_fill_color(QColor(255, 255, 255)) + + set_background = getattr(text_format, "setBackground", None) + if callable(set_background): + set_background(background) + + self._safe_grid_call(grid, "setAnnotationTextFormat", text_format) + except Exception: + pass def _create_rectangle_box_item( self, @@ -776,7 +992,7 @@ class PrintLayout: set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None) if callable(set_frame_stroke_width): try: - self._set_item_frame_stroke_width(hauptkarte, 0.5) + self._set_item_frame_stroke_width(hauptkarte, self._FRAME_STROKE_WIDTH_MM) except Exception: pass @@ -813,6 +1029,8 @@ class PrintLayout: except Exception: pass + self._configure_hauptkarte_grids(hauptkarte) + self._setup_layout_objects( kartenfenster_untere_rechte_ecke_mm, hauptkarte, diff --git a/tests/test_print_layout.py b/tests/test_print_layout.py index 7fd307b..608468e 100644 --- a/tests/test_print_layout.py +++ b/tests/test_print_layout.py @@ -2,6 +2,19 @@ import unittest from unittest.mock import patch from sn_basis.functions.qgiscore_wrapper import ( + MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL, + MAP_GRID_ANNOTATION_DISPLAY_SHOW_X, + MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y, + MAP_GRID_BORDER_BOTTOM, + MAP_GRID_BORDER_LEFT, + MAP_GRID_BORDER_RIGHT, + MAP_GRID_BORDER_TOP, + MAP_GRID_FRAME_STYLE_INTERIOR_TICKS, + MAP_GRID_STYLE_FRAME_ANNOTATIONS, + TEXT_BACKGROUND_SHAPE_RECTANGLE, + TEXT_BACKGROUND_SIZE_BUFFER, + TEXT_RENDER_UNIT_MILLIMETERS, + TEXT_RENDER_UNIT_POINTS, QgsLayoutItemLegend, QgsLayoutItemMap, QgsPrintLayout, @@ -15,6 +28,29 @@ class _FakeExtent: return False +class _FakeLegendForResize: + def __init__(self) -> None: + self.last_resize = None + + def setLocked(self, _enabled: bool) -> None: + pass + + def setColumnCount(self, _count: int) -> None: + pass + + def setEqualColumnWidth(self, _enabled: bool) -> None: + pass + + def refresh(self) -> None: + pass + + def adjustBoxSize(self) -> None: + pass + + def attemptResize(self, size) -> None: + self.last_resize = size + + class TestPrintLayout(unittest.TestCase): def test_create_legend_item_links_map_and_enables_map_filtering(self): @@ -54,6 +90,67 @@ class TestPrintLayout(unittest.TestCase): root_group = legend.model().rootGroup() self.assertEqual(root_group.layers, []) + def test_configure_legend_size_uses_content_adaptive_size(self): + project = QgsProject.instance() + print_layout = PrintLayout(project=project) + legend = _FakeLegendForResize() + + with patch.object(print_layout, "_get_item_size_mm", return_value=(95.0, 33.0)): + height = print_layout._configure_legend_size( + legend, + width_limit_mm=180.0, + column_count=2, + equal_column_width=True, + ) + + self.assertIsNone(legend.last_resize) + self.assertEqual(height, 33.0) + + def test_configure_hauptkarte_grids_uses_wrapper_constants(self): + project = QgsProject.instance() + print_layout = PrintLayout(project=project) + layout = QgsPrintLayout(project) + hauptkarte = QgsLayoutItemMap(layout) + + print_layout._configure_hauptkarte_grids(hauptkarte) + + grid_stack = hauptkarte.grids() + grids = grid_stack.asList() + self.assertEqual(len(grids), 1) + + frame_grid = grids[0] + + self.assertEqual(frame_grid.style, MAP_GRID_STYLE_FRAME_ANNOTATIONS) + self.assertEqual(frame_grid.frame_style, MAP_GRID_FRAME_STYLE_INTERIOR_TICKS) + self.assertTrue(frame_grid.annotation_enabled) + self.assertEqual(frame_grid.annotation_display[MAP_GRID_BORDER_LEFT], MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL) + self.assertEqual(frame_grid.annotation_display[MAP_GRID_BORDER_TOP], MAP_GRID_ANNOTATION_DISPLAY_HIDE_ALL) + self.assertEqual(frame_grid.annotation_display[MAP_GRID_BORDER_RIGHT], MAP_GRID_ANNOTATION_DISPLAY_SHOW_Y) + self.assertEqual(frame_grid.annotation_display[MAP_GRID_BORDER_BOTTOM], MAP_GRID_ANNOTATION_DISPLAY_SHOW_X) + + text_format = frame_grid.annotation_text_format + self.assertIsNotNone(text_format) + self.assertEqual(getattr(text_format, "size", None), 10) + self.assertEqual(getattr(text_format, "size_unit", None), TEXT_RENDER_UNIT_POINTS) + + background = getattr(text_format, "background", None) + self.assertIsNotNone(background) + self.assertTrue(getattr(background, "enabled", False)) + self.assertEqual(getattr(background, "shape_type", None), TEXT_BACKGROUND_SHAPE_RECTANGLE) + self.assertEqual(getattr(background, "size_type", None), TEXT_BACKGROUND_SIZE_BUFFER) + self.assertEqual(getattr(background, "size_unit", None), TEXT_RENDER_UNIT_MILLIMETERS) + self.assertEqual(getattr(background, "offset_unit", None), TEXT_RENDER_UNIT_MILLIMETERS) + offset = getattr(background, "offset", None) + self.assertIsNotNone(offset) + if isinstance(offset, tuple): + self.assertEqual(offset[1], -1.0) + else: + y_value = getattr(offset, "y", None) + if callable(y_value): + self.assertEqual(y_value(), -1.0) + else: + self.assertEqual(getattr(offset, "y", None), -1.0) + @patch("sn_basis.modules.print_layout.open_layout_designer") def test_create_single_page_layout_keeps_legend_linked_to_hauptkarte_and_theme(self, _mock_open_designer): project = QgsProject.instance()