Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffc56f5bc4 | ||
|
|
c01bf241db | ||
|
|
9ec465e276 | ||
|
|
d5b7482ebd | ||
|
|
8e4031a884 | ||
|
|
f1f104a464 | ||
|
|
f34e4ce458 | ||
|
|
30b17e0b83 | ||
|
|
a96560b21b | ||
|
|
bb95e0d536 | ||
|
|
4f0d3c12ba | ||
|
|
493e14c67f | ||
|
|
5303a44ce8 | ||
|
|
b014b92c19 | ||
|
|
3d8c0aff5d | ||
|
|
e397ccde71 | ||
|
|
81c80f4e79 |
@@ -1,3 +1,12 @@
|
||||
---
|
||||
Version 26.04.1-unstable:
|
||||
---
|
||||
Version 26.3.3-unstable:
|
||||
---
|
||||
Version 26.3.2-unstable:
|
||||
---
|
||||
Version 26.3.1-testing:
|
||||
- unstable als testing veröffentlicht
|
||||
---
|
||||
Version 26.3.1-unstable:
|
||||
- erster Test als echtes PlugIn
|
||||
@@ -174,6 +174,7 @@ except Exception:
|
||||
self.project = project
|
||||
self._name = ""
|
||||
self._page = _MockQgsLayoutPage()
|
||||
self.items: list[Any] = []
|
||||
|
||||
def initializeDefaults(self) -> None:
|
||||
pass
|
||||
@@ -188,7 +189,7 @@ except Exception:
|
||||
return self._page
|
||||
|
||||
def addLayoutItem(self, item: Any) -> None:
|
||||
pass
|
||||
self.items.append(item)
|
||||
|
||||
class _MockQgsLayoutPage:
|
||||
def setPageSize(self, size: Any) -> None:
|
||||
@@ -201,15 +202,21 @@ except Exception:
|
||||
class _MockQgsLayoutItemMap:
|
||||
def __init__(self, layout: Any):
|
||||
self.layout = layout
|
||||
self.item_id = ""
|
||||
self.extent = None
|
||||
self.scale = None
|
||||
self.follow_visibility_preset = False
|
||||
self.follow_visibility_preset_name = ""
|
||||
self.keep_layer_set = False
|
||||
|
||||
def setId(self, item_id: str) -> None:
|
||||
pass
|
||||
self.item_id = item_id
|
||||
|
||||
def setExtent(self, extent: Any) -> None:
|
||||
pass
|
||||
self.extent = extent
|
||||
|
||||
def setScale(self, scale: float) -> None:
|
||||
pass
|
||||
self.scale = scale
|
||||
|
||||
def attemptMove(self, point: Any) -> None:
|
||||
pass
|
||||
@@ -218,10 +225,13 @@ except Exception:
|
||||
pass
|
||||
|
||||
def setFollowVisibilityPreset(self, active: bool) -> None:
|
||||
pass
|
||||
self.follow_visibility_preset = active
|
||||
|
||||
def setFollowVisibilityPresetName(self, name: str) -> None:
|
||||
pass
|
||||
self.follow_visibility_preset_name = name
|
||||
|
||||
def setKeepLayerSet(self, enabled: bool) -> None:
|
||||
self.keep_layer_set = enabled
|
||||
|
||||
class _MockQgsLayoutItemLabel:
|
||||
ModeHtml = 1
|
||||
@@ -271,24 +281,30 @@ except Exception:
|
||||
def __init__(self, layout: Any):
|
||||
self.layout = layout
|
||||
self._model = _MockLegendModel()
|
||||
self.item_id = ""
|
||||
self.object_name = ""
|
||||
self.linked_map = None
|
||||
self.auto_update_model = None
|
||||
self.legend_filter_by_map_enabled = None
|
||||
self.title = ""
|
||||
|
||||
def setId(self, item_id: str) -> None:
|
||||
pass
|
||||
self.item_id = item_id
|
||||
|
||||
def setObjectName(self, name: str) -> None:
|
||||
pass
|
||||
self.object_name = name
|
||||
|
||||
def setLinkedMap(self, map_item: Any) -> None:
|
||||
pass
|
||||
self.linked_map = map_item
|
||||
|
||||
def setAutoUpdateModel(self, enabled: bool) -> None:
|
||||
pass
|
||||
self.auto_update_model = enabled
|
||||
|
||||
def setLegendFilterByMapEnabled(self, enabled: bool) -> None:
|
||||
pass
|
||||
self.legend_filter_by_map_enabled = enabled
|
||||
|
||||
def setTitle(self, title: str) -> None:
|
||||
pass
|
||||
self.title = title
|
||||
|
||||
def setReferencePoint(self, point: Any) -> None:
|
||||
pass
|
||||
@@ -329,6 +345,34 @@ except Exception:
|
||||
QgsUnitTypes = _MockQgsUnitTypes
|
||||
QgsLayoutItem = _MockQgsLayoutItem
|
||||
|
||||
class _MockQgsFeature:
|
||||
def __init__(self):
|
||||
self._geometry = None
|
||||
self._attributes = []
|
||||
|
||||
def setGeometry(self, geometry: Any) -> None:
|
||||
self._geometry = geometry
|
||||
|
||||
def setAttributes(self, attributes: list[Any]) -> None:
|
||||
self._attributes = attributes
|
||||
|
||||
class _MockQgsField:
|
||||
def __init__(self, name: str = "", field_type: Any = None):
|
||||
self.name = name
|
||||
self.field_type = field_type
|
||||
|
||||
class _MockQgsGeometry:
|
||||
@staticmethod
|
||||
def unaryUnion(_geometries: list[Any]):
|
||||
return None
|
||||
|
||||
def isEmpty(self) -> bool:
|
||||
return True
|
||||
|
||||
QgsFeature = _MockQgsFeature
|
||||
QgsField = _MockQgsField
|
||||
QgsGeometry = _MockQgsGeometry
|
||||
|
||||
class _MockQgsFeatureRequest:
|
||||
def __init__(self):
|
||||
self._filter_rect = None
|
||||
|
||||
@@ -97,6 +97,10 @@ def set_variable(key: str, value: str, scope: str = "project") -> None:
|
||||
var_name = f"sn_{key}"
|
||||
|
||||
if scope == "project":
|
||||
current_value = get_variable(key, scope="project")
|
||||
if current_value == value:
|
||||
return
|
||||
|
||||
project = QgsProject.instance()
|
||||
QgsExpressionContextUtils.setProjectVariable(
|
||||
project,
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
sn_basis/functions/verfahrensgebiet_manager.py
|
||||
|
||||
Zentrale Verwaltung des Verfahrensgebiet-Layers für alle sn_-Plugins.
|
||||
|
||||
Die Variable `sn_verfahrensgebiet_layer` wird automatisch aktualisiert, wenn:
|
||||
- Ein Layer mit dem Namen "Verfahrensgebiet" ins Projekt geladen wird
|
||||
- Das Projekt neu geladen wird
|
||||
- Der Layer aus dem Projekt gelöscht wird
|
||||
|
||||
Nutzer können diese Auswahl über das Dropdown in sn_verfahrensgebiet/working_tab
|
||||
manuell überschreiben.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from sn_basis.functions.qgiscore_wrapper import QgsProject, QGIS_AVAILABLE
|
||||
from sn_basis.functions.variable_wrapper import get_variable, set_variable
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Globale State für Listener-Registrierung
|
||||
# ---------------------------------------------------------
|
||||
|
||||
_listener_installed = False
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Zentrale Verwaltung
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def get_verfahrensgebiet_layer_id() -> Optional[str]:
|
||||
"""
|
||||
Gibt die Layer-ID des aktuellen Verfahrensgebiet-Layers zurück.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[str]
|
||||
Die Layer-ID oder None, wenn keine Variable gesetzt ist.
|
||||
"""
|
||||
value = get_variable("verfahrensgebiet_layer", scope="project")
|
||||
return value if value else None
|
||||
|
||||
|
||||
def set_verfahrensgebiet_layer_id(layer_id: str) -> None:
|
||||
"""
|
||||
Setzt die Layer-ID des Verfahrensgebiet-Layers (von Nutzer-Auswahl im Dropdown).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
layer_id : str
|
||||
Die eindeutige Layer-ID des gewählten Layers.
|
||||
"""
|
||||
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
|
||||
|
||||
|
||||
def _find_verfahrensgebiet_layer():
|
||||
"""
|
||||
Sucht einen Layer mit dem Namen 'Verfahrensgebiet' im aktuellen Projekt.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[Optional[layer], Optional[str]]
|
||||
(Layer-Objekt, Layer-ID) oder (None, None), wenn nicht gefunden.
|
||||
"""
|
||||
if not QGIS_AVAILABLE:
|
||||
return None, None
|
||||
|
||||
try:
|
||||
project = QgsProject.instance()
|
||||
layers = project.mapLayersByName("Verfahrensgebiet")
|
||||
|
||||
if layers:
|
||||
# Nutze den ersten (bzw. zuletzt hinzugefügten) Layer mit diesem Namen
|
||||
layer = layers[0]
|
||||
return layer, layer.id()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def _update_verfahrensgebiet_from_project() -> bool:
|
||||
"""
|
||||
Sucht automatisch nach einem Layer namens 'Verfahrensgebiet' im Projekt
|
||||
und aktualisiert die Variable `sn_verfahrensgebiet_layer`.
|
||||
|
||||
Diese Funktion wird aufgerufen bei:
|
||||
- layersAdded Signal
|
||||
- readProject Signal (Projekt-Reload)
|
||||
- Projekt-Neuerstellung
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True, wenn ein Layer gefunden und gespeichert wurde, False sonst.
|
||||
"""
|
||||
layer, layer_id = _find_verfahrensgebiet_layer()
|
||||
|
||||
if layer and layer_id:
|
||||
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
|
||||
return True
|
||||
else:
|
||||
# Layer nicht gefunden: Variable zurücksetzen
|
||||
set_variable("verfahrensgebiet_layer", "", scope="project")
|
||||
return False
|
||||
|
||||
|
||||
def _on_layers_added(layers) -> None:
|
||||
"""Signal-Handler für QgsProject.layersAdded"""
|
||||
_update_verfahrensgebiet_from_project()
|
||||
|
||||
|
||||
def _on_layer_removed(layer_id: str) -> None:
|
||||
"""Signal-Handler für QgsProject.layerRemoved"""
|
||||
# Prüfe, ob der entfernte Layer der aktuelle Verfahrensgebiet-Layer war
|
||||
current_id = get_verfahrensgebiet_layer_id()
|
||||
if current_id and current_id == layer_id:
|
||||
# Layer wurde entfernt; versuche, neu zu suchen
|
||||
_update_verfahrensgebiet_from_project()
|
||||
|
||||
|
||||
def _on_project_changed() -> None:
|
||||
"""Signal-Handler für QgsProject.readProject, newProjectCreated, cleared"""
|
||||
_update_verfahrensgebiet_from_project()
|
||||
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Listener-Setup
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def setup_verfahrensgebiet_listener() -> None:
|
||||
"""
|
||||
Registriert globale Event-Listener auf dem QgsProject-Singleton.
|
||||
|
||||
Diese Funktion sollte EINMAL beim Plugin-Start aufgerufen werden
|
||||
(z.B. in sn_basis.main.BasisPlugin.initGui()).
|
||||
|
||||
Listener:
|
||||
- layersAdded: automatische Aktualisierung, wenn Layer hinzugefügt werden
|
||||
- layerRemoved: Validierung, wenn Layer gelöscht werden
|
||||
- readProject: Neuinitialisierung beim Projekt-Reload
|
||||
"""
|
||||
global _listener_installed
|
||||
|
||||
if _listener_installed:
|
||||
return # Bereits registriert
|
||||
|
||||
if not QGIS_AVAILABLE:
|
||||
return # QGIS nicht verfügbar
|
||||
|
||||
try:
|
||||
project = QgsProject.instance()
|
||||
|
||||
# Verbinde Signale
|
||||
project.layersAdded.connect(_on_layers_added)
|
||||
project.layerRemoved.connect(_on_layer_removed)
|
||||
|
||||
# readProject wird emittiert, wenn Projekt geladen wird
|
||||
# newProjectCreated wird emittiert bei neuem Projekt
|
||||
# cleared wird emittiert, wenn Projekt gelöscht wird
|
||||
for signal_name in ("readProject", "newProjectCreated", "cleared"):
|
||||
signal = getattr(project, signal_name, None)
|
||||
if signal is not None:
|
||||
signal.connect(_on_project_changed)
|
||||
|
||||
_listener_installed = True
|
||||
|
||||
# Initial: versuche, existierenden Layer zu finden
|
||||
_update_verfahrensgebiet_from_project()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
@@ -4,6 +4,7 @@ from qgis.utils import plugins
|
||||
|
||||
from sn_basis.functions.qt_wrapper import QCoreApplication
|
||||
from sn_basis.functions.sys_wrapper import getattr_safe
|
||||
from sn_basis.functions.verfahrensgebiet_manager import setup_verfahrensgebiet_listener
|
||||
from sn_basis.ui.navigation import Navigation
|
||||
|
||||
|
||||
@@ -34,6 +35,10 @@ class BasisPlugin:
|
||||
"""
|
||||
self.ui = Navigation()
|
||||
self.ui.init_ui()
|
||||
|
||||
# Zentrale Verwaltung des Verfahrensgebiet-Layers starten
|
||||
setup_verfahrensgebiet_listener()
|
||||
|
||||
for name, plugin in plugins.items():
|
||||
if name.startswith("sn_") and name != "sn_basis":
|
||||
try:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[general]
|
||||
version=26.3.1-unstable
|
||||
version=26.04.1-unstable
|
||||
name=LNO Sachsen | Plugin Basisfunktionen
|
||||
description=Plugin mit Basisfunktionen
|
||||
author=Daniel Helbig, Michael Otto
|
||||
|
||||
@@ -0,0 +1,826 @@
|
||||
"""
|
||||
sn_basis/modules/print_layout.py - Aufbau von Einzelblatt-Drucklayouts.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
from sn_basis.functions.qt_wrapper import QFont
|
||||
from sn_basis.functions.qgiscore_wrapper import (
|
||||
QgsLayoutItem,
|
||||
QgsLayoutItemLabel,
|
||||
QgsLayoutItemLegend,
|
||||
QgsLayoutItemMap,
|
||||
QgsLayoutPoint,
|
||||
QgsLayoutSize,
|
||||
QgsPrintLayout,
|
||||
QgsProject,
|
||||
QgsUnitTypes,
|
||||
)
|
||||
from sn_basis.functions.qgisui_wrapper import open_layout_designer
|
||||
|
||||
|
||||
MM = QgsUnitTypes.LayoutMillimeters
|
||||
|
||||
|
||||
class PrintLayout:
|
||||
"""Erzeugt ein QGIS-Einzelblatt-Layout fuer den Druck."""
|
||||
|
||||
_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"},
|
||||
"Format_3": {"font_family": "Arial", "font_size": 16, "font_style": "bold"},
|
||||
}
|
||||
|
||||
_OBJECT_TEMPLATES = {
|
||||
"hauptkarte": {"id": "Hauptkarte", "object_name": "Hauptkarte"},
|
||||
"quellenangabe": {
|
||||
"id": "Quellenangabe",
|
||||
"object_name": "Quellenangabe",
|
||||
"size_mm": (180.0, 100.0),
|
||||
"format": "Format_1",
|
||||
},
|
||||
"textfeld": {
|
||||
"id": "Textfeld",
|
||||
"object_name": "Textfeld",
|
||||
"size_mm": (60.0, 8.0),
|
||||
"format": "Format_1",
|
||||
"html_mode": True,
|
||||
"text": "",
|
||||
},
|
||||
"legende": {
|
||||
"id": "Legende",
|
||||
"object_name": "Legende",
|
||||
"size_mm": (180.0, 60.0),
|
||||
},
|
||||
}
|
||||
|
||||
_QUELLENANGABE_TEXT = (
|
||||
"Quelle Geobasisdaten: GeoSN, "
|
||||
"<a href=\"https://www.govdata.de/dl-de/by-2-0\">dl-de/by-2-0</a><br>"
|
||||
"Quelle Fachdaten: Darstellung auf der Grundlage von Daten und mit Erlaubnis des "
|
||||
"Saechsischen Landesamtes fuer Umwelt, Landwirtschaft und Geologie<br>"
|
||||
"Basemap:"
|
||||
"© GeoBasis-DE / <a href=\"https://www.bkg.bund.de\">BKG</a> ([%year($now)%]) "
|
||||
"<a href=\"https://creativecommons.org/licences/by/4.0/\">CC BY 4.0</a> "
|
||||
"mit teilweise angepasster Signatur<br>"
|
||||
)
|
||||
|
||||
def __init__(self, project: Any | None = None) -> None:
|
||||
self.project = project or QgsProject.instance()
|
||||
self._current_layout: Any | None = None
|
||||
|
||||
def _create_map_item(
|
||||
self,
|
||||
layout: Any,
|
||||
template_name: str,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
width_mm: float,
|
||||
height_mm: float,
|
||||
object_name: str | None = None,
|
||||
) -> Any:
|
||||
template = self._OBJECT_TEMPLATES[template_name]
|
||||
map_item = QgsLayoutItemMap(layout)
|
||||
map_item.setId(template["id"])
|
||||
set_object_name = getattr(map_item, "setObjectName", None)
|
||||
if callable(set_object_name):
|
||||
set_object_name(object_name or template.get("object_name", template["id"]))
|
||||
layout.addLayoutItem(map_item)
|
||||
map_item.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
|
||||
map_item.attemptResize(QgsLayoutSize(width_mm, height_mm, MM))
|
||||
return map_item
|
||||
|
||||
def _set_item_frame_stroke_width(self, item: Any, stroke_width_mm: float) -> None:
|
||||
set_frame_stroke_width = getattr(item, "setFrameStrokeWidth", None)
|
||||
if not callable(set_frame_stroke_width):
|
||||
return
|
||||
|
||||
try:
|
||||
set_frame_stroke_width(stroke_width_mm)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
qgis_core = importlib.import_module("qgis.core")
|
||||
QgsLayoutMeasurement = getattr(qgis_core, "QgsLayoutMeasurement", None)
|
||||
except Exception:
|
||||
QgsLayoutMeasurement = None
|
||||
|
||||
if QgsLayoutMeasurement is not None:
|
||||
try:
|
||||
set_frame_stroke_width(QgsLayoutMeasurement(stroke_width_mm, MM))
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
frame_stroke_width = getattr(item, "frameStrokeWidth", None)
|
||||
if callable(frame_stroke_width):
|
||||
try:
|
||||
measurement = frame_stroke_width()
|
||||
set_length = getattr(measurement, "setLength", None)
|
||||
if callable(set_length):
|
||||
set_length(stroke_width_mm)
|
||||
set_frame_stroke_width(measurement)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _fit_extent_to_map_item(self, extent: Any, map_width_mm: float, map_height_mm: float) -> Any:
|
||||
if extent is None:
|
||||
return extent
|
||||
if map_width_mm <= 0 or map_height_mm <= 0:
|
||||
return extent
|
||||
|
||||
try:
|
||||
extent_width = float(extent.width())
|
||||
extent_height = float(extent.height())
|
||||
except Exception:
|
||||
return extent
|
||||
|
||||
if extent_width <= 0 or extent_height <= 0:
|
||||
return extent
|
||||
|
||||
target_ratio = map_width_mm / map_height_mm
|
||||
current_ratio = extent_width / extent_height
|
||||
if not math.isfinite(target_ratio) or not math.isfinite(current_ratio) or target_ratio <= 0:
|
||||
return extent
|
||||
|
||||
try:
|
||||
center_x = (float(extent.xMinimum()) + float(extent.xMaximum())) / 2.0
|
||||
center_y = (float(extent.yMinimum()) + float(extent.yMaximum())) / 2.0
|
||||
except Exception:
|
||||
return extent
|
||||
|
||||
fitted_width = extent_width
|
||||
fitted_height = extent_height
|
||||
if not math.isclose(current_ratio, target_ratio, rel_tol=1e-9, abs_tol=1e-9):
|
||||
if current_ratio > target_ratio:
|
||||
fitted_height = extent_width / target_ratio
|
||||
else:
|
||||
fitted_width = extent_height * target_ratio
|
||||
|
||||
safety_factor = 1.002
|
||||
half_width = (fitted_width * safety_factor) / 2.0
|
||||
half_height = (fitted_height * safety_factor) / 2.0
|
||||
|
||||
try:
|
||||
qgis_core = importlib.import_module("qgis.core")
|
||||
qgs_rectangle = getattr(qgis_core, "QgsRectangle", None)
|
||||
except Exception:
|
||||
qgs_rectangle = None
|
||||
|
||||
if qgs_rectangle is None:
|
||||
return extent
|
||||
|
||||
try:
|
||||
return qgs_rectangle(
|
||||
center_x - half_width,
|
||||
center_y - half_height,
|
||||
center_x + half_width,
|
||||
center_y + half_height,
|
||||
)
|
||||
except Exception:
|
||||
return extent
|
||||
|
||||
def add_text_label(
|
||||
self,
|
||||
text: str,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
template_name: str = "textfeld",
|
||||
text_format: str | None = None,
|
||||
width_mm: float | None = None,
|
||||
height_mm: float | None = None,
|
||||
html_mode: bool = False,
|
||||
expression_enabled: bool = False,
|
||||
object_name: str | None = None,
|
||||
) -> Any:
|
||||
if self._current_layout is None:
|
||||
raise RuntimeError("Kein Layout aktiv. Bitte create_single_page_layout() aufrufen.")
|
||||
return self._create_label_item(
|
||||
self._current_layout,
|
||||
template_name,
|
||||
text,
|
||||
x_mm,
|
||||
y_mm,
|
||||
text_format=text_format,
|
||||
width_mm=width_mm,
|
||||
height_mm=height_mm,
|
||||
html_mode=html_mode,
|
||||
expression_enabled=expression_enabled,
|
||||
object_name=object_name,
|
||||
)
|
||||
|
||||
def _setup_layout_objects(
|
||||
self,
|
||||
kartenfenster_untere_rechte_ecke_mm: tuple,
|
||||
hauptkarte: Any,
|
||||
map_top_mm: float,
|
||||
map_height_mm: float,
|
||||
) -> None:
|
||||
if self._current_layout is None:
|
||||
raise RuntimeError("Kein Layout aktiv.")
|
||||
|
||||
def _coords_rel_untere_rechte_ecke(rel_x_mm: float, rel_y_mm: float) -> tuple[float, float]:
|
||||
return (
|
||||
kartenfenster_untere_rechte_ecke_mm[0] + rel_x_mm,
|
||||
kartenfenster_untere_rechte_ecke_mm[1] + rel_y_mm,
|
||||
)
|
||||
|
||||
legende_x = kartenfenster_untere_rechte_ecke_mm[0] + 10.0
|
||||
legende_height_mm = max(map_height_mm - 122.0, 20.0)
|
||||
legende_y = map_top_mm
|
||||
legende = self._create_legend_item(
|
||||
self._current_layout,
|
||||
"legende",
|
||||
hauptkarte,
|
||||
legende_x,
|
||||
legende_y,
|
||||
width_mm=180.0,
|
||||
height_mm=legende_height_mm,
|
||||
object_name="Legende",
|
||||
)
|
||||
|
||||
legende_hoehe_mm = self._configure_legend_size(
|
||||
legende,
|
||||
width_limit_mm=180.0,
|
||||
column_count=1,
|
||||
equal_column_width=False,
|
||||
)
|
||||
|
||||
quellenangabe_x, quellenangabe_y = _coords_rel_untere_rechte_ecke(10.0, -102.0)
|
||||
quellenangabe_hoehe_mm = 20.0
|
||||
quellenangabe_obere_y = quellenangabe_y - quellenangabe_hoehe_mm
|
||||
legende_untere_y = legende_y + legende_hoehe_mm
|
||||
|
||||
if legende_untere_y > quellenangabe_obere_y:
|
||||
self._configure_legend_size(
|
||||
legende,
|
||||
width_limit_mm=180.0,
|
||||
column_count=2,
|
||||
equal_column_width=True,
|
||||
)
|
||||
|
||||
self.add_text_label(
|
||||
self._QUELLENANGABE_TEXT,
|
||||
quellenangabe_x,
|
||||
quellenangabe_y,
|
||||
width_mm=180.0,
|
||||
height_mm=20.0,
|
||||
text_format="Format_1",
|
||||
html_mode=True,
|
||||
expression_enabled=True,
|
||||
object_name="Quellenangabe",
|
||||
)
|
||||
|
||||
kartenschild_x, kartenschild_y = _coords_rel_untere_rechte_ecke(10.0, 0.0)
|
||||
self._create_rectangle_box_item(
|
||||
self._current_layout,
|
||||
"Kartenschild",
|
||||
kartenschild_x,
|
||||
kartenschild_y,
|
||||
180.0,
|
||||
92.0,
|
||||
stroke_width_mm=1.0,
|
||||
)
|
||||
|
||||
kartenschild_line_x, _ = _coords_rel_untere_rechte_ecke(10.0, 0.0)
|
||||
kartenschild_line_offsets_y = (-10.0, -20.0, -30.0, -46.0, -62.0, -82.0)
|
||||
for index, offset_y in enumerate(kartenschild_line_offsets_y, start=1):
|
||||
_, line_y = _coords_rel_untere_rechte_ecke(0.0, offset_y)
|
||||
self._create_rectangle_box_item(
|
||||
self._current_layout,
|
||||
f"Kartenschild_Linie_{index}",
|
||||
kartenschild_line_x,
|
||||
line_y,
|
||||
180.0,
|
||||
0.0,
|
||||
stroke_width_mm=0.1,
|
||||
)
|
||||
|
||||
labels = [
|
||||
("[% @sn_amt %]", 16.0, -84.0, 171.0, 4.0, "Format_1", "Herausgeber"),
|
||||
("[% @sn_bezeichnung %]", 16.0, -75.0, 171.0, 4.0, "Format_1", "Bezeichnung_Flurbereinigung"),
|
||||
("[% @sn_name %]", 16.0, -64.0, 171.0, 5.0, "Format_2", "Name_Flurbereinigung"),
|
||||
("[% @sn_gemeinden %]", 16.0, -56.0, 171.0, 4.0, "Format_1", "Stadt_Gemeinde"),
|
||||
("[% @sn_landkreise_proj %]", 16.0, -48.0, 171.0, 4.0, "Format_1", "Landkreis"),
|
||||
("[% @sn_kartenname %]", 16.0, -34.0, 137.0, 5.0, "Format_3", "Kartenname"),
|
||||
("Gefertigt: ", 16.0, -23.0, 81.0, 4.0, "Format_1", "Gefertigt"),
|
||||
("Geprueft: ", 101.0, -23.0, 81.0, 4.0, "Format_1", "Geprueft"),
|
||||
(
|
||||
"Massstab: 1 : [%round(map_get(item_variables('Hauptkarte'),'map_scale'),0)%]",
|
||||
16.0,
|
||||
-13.0,
|
||||
81.0,
|
||||
4.0,
|
||||
"Format_1",
|
||||
"Massstab",
|
||||
),
|
||||
("Ausgabedatum: [%format_date(now(), 'dd.MM.yyyy')%]", 101.0, -13.0, 81.0, 4.0, "Format_1", "Ausgabedatum"),
|
||||
("Aktenzeichen: ", 16.0, -3.0, 171.0, 4.0, "Format_1", "Aktenzeichen"),
|
||||
]
|
||||
|
||||
for text, rx, ry, w, h, fmt, obj_name in labels:
|
||||
x, y = _coords_rel_untere_rechte_ecke(rx, ry)
|
||||
self.add_text_label(
|
||||
text,
|
||||
x,
|
||||
y,
|
||||
width_mm=w,
|
||||
height_mm=h,
|
||||
text_format=fmt,
|
||||
expression_enabled=True,
|
||||
object_name=obj_name,
|
||||
)
|
||||
|
||||
def _create_label_item(
|
||||
self,
|
||||
layout: Any,
|
||||
template_name: str,
|
||||
text: str,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
*,
|
||||
text_format: str | None = None,
|
||||
font_name: str | None = None,
|
||||
font_size: int | None = None,
|
||||
font_style: str | None = None,
|
||||
width_mm: float | None = None,
|
||||
height_mm: float | None = None,
|
||||
html_mode: bool = False,
|
||||
expression_enabled: bool = False,
|
||||
object_name: str | None = None,
|
||||
) -> Any:
|
||||
template = self._OBJECT_TEMPLATES[template_name]
|
||||
label = QgsLayoutItemLabel(layout)
|
||||
item_id = object_name or template["id"]
|
||||
label.setId(item_id)
|
||||
set_object_name = getattr(label, "setObjectName", None)
|
||||
if callable(set_object_name):
|
||||
set_object_name(item_id)
|
||||
label.setText(text)
|
||||
|
||||
if html_mode:
|
||||
set_mode = getattr(label, "setMode", None)
|
||||
mode_html = getattr(QgsLayoutItemLabel, "ModeHtml", None)
|
||||
if callable(set_mode) and mode_html is not None:
|
||||
set_mode(mode_html)
|
||||
|
||||
if expression_enabled:
|
||||
set_expr_enabled = getattr(label, "setExpressionEnabled", None)
|
||||
if callable(set_expr_enabled):
|
||||
try:
|
||||
set_expr_enabled(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
format_name = text_format or template.get("format", "Format_1")
|
||||
format_template = self._TEXT_FORMAT_TEMPLATES.get(format_name, self._TEXT_FORMAT_TEMPLATES["Format_1"])
|
||||
resolved_font_name = font_name or format_template["font_family"]
|
||||
resolved_font_size = font_size or format_template["font_size"]
|
||||
resolved_font_style = font_style or format_template["font_style"]
|
||||
|
||||
font = QFont(resolved_font_name, resolved_font_size)
|
||||
if hasattr(font, "setBold"):
|
||||
font.setBold(resolved_font_style == "bold")
|
||||
if hasattr(font, "setItalic"):
|
||||
font.setItalic(resolved_font_style == "italic")
|
||||
label.setFont(font)
|
||||
|
||||
set_reference_point = getattr(label, "setReferencePoint", None)
|
||||
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
|
||||
if callable(set_reference_point) and lower_left is not None:
|
||||
set_reference_point(lower_left)
|
||||
|
||||
template_width, template_height = template.get("size_mm", (0.0, 0.0))
|
||||
label.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
|
||||
label.attemptResize(
|
||||
QgsLayoutSize(
|
||||
width_mm if width_mm is not None else template_width,
|
||||
height_mm if height_mm is not None else template_height,
|
||||
MM,
|
||||
)
|
||||
)
|
||||
set_locked = getattr(label, "setLocked", None)
|
||||
if callable(set_locked):
|
||||
try:
|
||||
set_locked(True)
|
||||
except Exception:
|
||||
pass
|
||||
layout.addLayoutItem(label)
|
||||
return label
|
||||
|
||||
def _resolve_visible_map_layers(self, map_item: Any) -> list[Any]:
|
||||
layers_method = getattr(map_item, "layers", None)
|
||||
if callable(layers_method):
|
||||
try:
|
||||
resolved_layers = layers_method()
|
||||
if isinstance(resolved_layers, (list, tuple)):
|
||||
layers = [layer for layer in resolved_layers if layer is not None]
|
||||
if layers:
|
||||
return layers
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
layer_tree_root_method = getattr(self.project, "layerTreeRoot", None)
|
||||
if not callable(layer_tree_root_method):
|
||||
return []
|
||||
|
||||
try:
|
||||
root = layer_tree_root_method()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
find_layers = getattr(root, "findLayers", None)
|
||||
if not callable(find_layers):
|
||||
return []
|
||||
|
||||
visible_layers: list[Any] = []
|
||||
try:
|
||||
tree_layers = find_layers()
|
||||
if not isinstance(tree_layers, (list, tuple)):
|
||||
return []
|
||||
for tree_layer in tree_layers:
|
||||
is_visible = getattr(tree_layer, "itemVisibilityChecked", None)
|
||||
if callable(is_visible) and not is_visible():
|
||||
continue
|
||||
layer = getattr(tree_layer, "layer", None)
|
||||
resolved_layer = layer() if callable(layer) else None
|
||||
if resolved_layer is not None:
|
||||
visible_layers.append(resolved_layer)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
return visible_layers
|
||||
|
||||
def _create_legend_item(
|
||||
self,
|
||||
layout: Any,
|
||||
template_name: str,
|
||||
linked_map: Any,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
*,
|
||||
width_mm: float | None = None,
|
||||
height_mm: float | None = None,
|
||||
object_name: str | None = None,
|
||||
) -> Any:
|
||||
template = self._OBJECT_TEMPLATES[template_name]
|
||||
legend = QgsLayoutItemLegend(layout)
|
||||
item_id = object_name or template["id"]
|
||||
legend.setId(item_id)
|
||||
|
||||
set_object_name = getattr(legend, "setObjectName", None)
|
||||
if callable(set_object_name):
|
||||
set_object_name(item_id)
|
||||
|
||||
set_linked_map = getattr(legend, "setLinkedMap", None)
|
||||
if callable(set_linked_map):
|
||||
try:
|
||||
set_linked_map(linked_map)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_title = getattr(legend, "setTitle", None)
|
||||
if callable(set_title):
|
||||
try:
|
||||
set_title("Legende")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_auto_update_model = getattr(legend, "setAutoUpdateModel", None)
|
||||
if callable(set_auto_update_model):
|
||||
try:
|
||||
set_auto_update_model(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_filter_by_map = getattr(legend, "setLegendFilterByMapEnabled", None)
|
||||
if callable(set_filter_by_map):
|
||||
try:
|
||||
set_filter_by_map(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_reference_point = getattr(legend, "setReferencePoint", None)
|
||||
upper_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "UpperLeft", None)
|
||||
if callable(set_reference_point) and upper_left is not None:
|
||||
set_reference_point(upper_left)
|
||||
|
||||
template_width, template_height = template.get("size_mm", (0.0, 0.0))
|
||||
legend.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
|
||||
legend.attemptResize(
|
||||
QgsLayoutSize(
|
||||
width_mm if width_mm is not None else template_width,
|
||||
height_mm if height_mm is not None else template_height,
|
||||
MM,
|
||||
)
|
||||
)
|
||||
|
||||
set_frame_enabled = getattr(legend, "setFrameEnabled", None)
|
||||
if callable(set_frame_enabled):
|
||||
try:
|
||||
set_frame_enabled(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._set_item_frame_stroke_width(legend, 0.3)
|
||||
|
||||
set_background_enabled = getattr(legend, "setBackgroundEnabled", None)
|
||||
if callable(set_background_enabled):
|
||||
try:
|
||||
set_background_enabled(False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_locked = getattr(legend, "setLocked", None)
|
||||
if callable(set_locked):
|
||||
try:
|
||||
set_locked(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
layout.addLayoutItem(legend)
|
||||
|
||||
refresh = getattr(legend, "refresh", None)
|
||||
if callable(refresh):
|
||||
try:
|
||||
refresh()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return legend
|
||||
|
||||
def _get_item_size_mm(self, item: Any) -> tuple[float | None, float | None]:
|
||||
def _to_float(value: Any) -> float | None:
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value)
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return float(value)
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
size_with_units = getattr(item, "sizeWithUnits", None)
|
||||
if callable(size_with_units):
|
||||
try:
|
||||
size = size_with_units()
|
||||
get_width = getattr(size, "width", None)
|
||||
get_height = getattr(size, "height", None)
|
||||
width = _to_float(get_width()) if callable(get_width) else None
|
||||
height = _to_float(get_height()) if callable(get_height) else None
|
||||
if width is not None and height is not None:
|
||||
return width, height
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
rect = getattr(item, "rect", None)
|
||||
if callable(rect):
|
||||
try:
|
||||
item_rect = rect()
|
||||
get_width = getattr(item_rect, "width", None)
|
||||
get_height = getattr(item_rect, "height", None)
|
||||
width = _to_float(get_width()) if callable(get_width) else None
|
||||
height = _to_float(get_height()) if callable(get_height) else None
|
||||
return width, height
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
def _configure_legend_size(
|
||||
self,
|
||||
legend: Any,
|
||||
*,
|
||||
width_limit_mm: float,
|
||||
column_count: int,
|
||||
equal_column_width: bool,
|
||||
) -> float:
|
||||
set_locked = getattr(legend, "setLocked", None)
|
||||
if callable(set_locked):
|
||||
try:
|
||||
set_locked(False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_column_count = getattr(legend, "setColumnCount", None)
|
||||
if callable(set_column_count):
|
||||
try:
|
||||
set_column_count(column_count)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_equal_column_width = getattr(legend, "setEqualColumnWidth", None)
|
||||
if callable(set_equal_column_width):
|
||||
try:
|
||||
set_equal_column_width(equal_column_width)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
refresh = getattr(legend, "refresh", None)
|
||||
if callable(refresh):
|
||||
try:
|
||||
refresh()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
adjust_box_size = getattr(legend, "adjustBoxSize", None)
|
||||
if callable(adjust_box_size):
|
||||
try:
|
||||
adjust_box_size()
|
||||
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 callable(set_locked):
|
||||
try:
|
||||
set_locked(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return updated_height_mm if updated_height_mm is not None else resolved_height
|
||||
|
||||
def _create_rectangle_box_item(
|
||||
self,
|
||||
layout: Any,
|
||||
object_name: str,
|
||||
x_mm: float,
|
||||
y_mm: float,
|
||||
width_mm: float,
|
||||
height_mm: float,
|
||||
stroke_width_mm: float = 0.3,
|
||||
) -> Any:
|
||||
box = QgsLayoutItemLabel(layout)
|
||||
box.setId(object_name)
|
||||
|
||||
set_object_name = getattr(box, "setObjectName", None)
|
||||
if callable(set_object_name):
|
||||
set_object_name(object_name)
|
||||
|
||||
box.setText("")
|
||||
|
||||
set_reference_point = getattr(box, "setReferencePoint", None)
|
||||
lower_left = getattr(getattr(QgsLayoutItem, "ReferencePoint", object), "LowerLeft", None)
|
||||
if callable(set_reference_point) and lower_left is not None:
|
||||
set_reference_point(lower_left)
|
||||
|
||||
box.attemptMove(QgsLayoutPoint(x_mm, y_mm, MM))
|
||||
box.attemptResize(QgsLayoutSize(width_mm, height_mm, MM))
|
||||
|
||||
set_frame_enabled = getattr(box, "setFrameEnabled", None)
|
||||
if callable(set_frame_enabled):
|
||||
try:
|
||||
set_frame_enabled(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._set_item_frame_stroke_width(box, stroke_width_mm)
|
||||
|
||||
set_background_enabled = getattr(box, "setBackgroundEnabled", None)
|
||||
if callable(set_background_enabled):
|
||||
try:
|
||||
set_background_enabled(False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_locked = getattr(box, "setLocked", None)
|
||||
if callable(set_locked):
|
||||
try:
|
||||
set_locked(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
layout.addLayoutItem(box)
|
||||
return box
|
||||
|
||||
def create_single_page_layout(
|
||||
self,
|
||||
name: str,
|
||||
page_width_mm: float,
|
||||
page_height_mm: float,
|
||||
map_width_mm: float,
|
||||
map_height_mm: float,
|
||||
extent: Any,
|
||||
plotmassstab: float,
|
||||
thema: str = "",
|
||||
fit_extent_to_map: bool = True,
|
||||
) -> Any:
|
||||
layout_manager = self.project.layoutManager()
|
||||
existing_layout = layout_manager.layoutByName(name)
|
||||
if existing_layout is not None:
|
||||
raise ValueError(f"Eine Vorlage mit der Bezeichnung '{name}' existiert bereits.")
|
||||
|
||||
layout = QgsPrintLayout(self.project)
|
||||
layout.initializeDefaults()
|
||||
layout.setName(name)
|
||||
self._current_layout = layout
|
||||
|
||||
page = layout.pageCollection().page(0)
|
||||
page.setPageSize(QgsLayoutSize(page_width_mm, page_height_mm, MM))
|
||||
|
||||
map_left_mm = 10.0
|
||||
map_top_mm = 10.0
|
||||
map_right_mm = map_left_mm + map_width_mm
|
||||
map_bottom_mm = map_top_mm + map_height_mm
|
||||
kartenfenster_untere_rechte_ecke_mm = (map_right_mm, map_bottom_mm)
|
||||
|
||||
hauptkarte = self._create_map_item(
|
||||
layout,
|
||||
"hauptkarte",
|
||||
map_left_mm,
|
||||
map_top_mm,
|
||||
map_width_mm,
|
||||
map_height_mm,
|
||||
)
|
||||
|
||||
fitted_extent = extent
|
||||
if fit_extent_to_map:
|
||||
fitted_extent = self._fit_extent_to_map_item(extent, map_width_mm, map_height_mm)
|
||||
if fitted_extent is not None and hasattr(fitted_extent, "isNull") and callable(fitted_extent.isNull) and not fitted_extent.isNull():
|
||||
try:
|
||||
hauptkarte.setExtent(fitted_extent)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if isinstance(plotmassstab, (int, float)) and math.isfinite(plotmassstab) and plotmassstab > 0:
|
||||
try:
|
||||
hauptkarte.setScale(plotmassstab)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_frame_enabled = getattr(hauptkarte, "setFrameEnabled", None)
|
||||
if callable(set_frame_enabled):
|
||||
try:
|
||||
set_frame_enabled(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_frame_stroke_width = getattr(hauptkarte, "setFrameStrokeWidth", None)
|
||||
if callable(set_frame_stroke_width):
|
||||
try:
|
||||
self._set_item_frame_stroke_width(hauptkarte, 0.5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if thema and thema != "aktuell":
|
||||
follow_theme = getattr(hauptkarte, "setFollowVisibilityPreset", None)
|
||||
set_theme_name = getattr(hauptkarte, "setFollowVisibilityPresetName", None)
|
||||
if callable(follow_theme):
|
||||
try:
|
||||
follow_theme(True)
|
||||
except Exception:
|
||||
pass
|
||||
if callable(set_theme_name):
|
||||
try:
|
||||
set_theme_name(thema)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_keep_layer_set = getattr(hauptkarte, "setKeepLayerSet", None)
|
||||
if callable(set_keep_layer_set):
|
||||
try:
|
||||
set_keep_layer_set(True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_refresh_strategy = getattr(hauptkarte, "setRefreshStrategy", None)
|
||||
if callable(set_refresh_strategy):
|
||||
refresh_cache = (
|
||||
getattr(hauptkarte, "RefreshLaterOnly", None)
|
||||
or getattr(hauptkarte, "RefreshWhenRequested", None)
|
||||
or 0
|
||||
)
|
||||
try:
|
||||
set_refresh_strategy(refresh_cache)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._setup_layout_objects(
|
||||
kartenfenster_untere_rechte_ecke_mm,
|
||||
hauptkarte,
|
||||
map_top_mm,
|
||||
map_height_mm,
|
||||
)
|
||||
|
||||
layout_manager.addLayout(layout)
|
||||
self._current_layout = None
|
||||
open_layout_designer(layout)
|
||||
return layout
|
||||
@@ -0,0 +1,696 @@
|
||||
"""
|
||||
sn_basis/modules/print_logic.py - Fachlogik fuer den Druck-Tab (nur Einzelblatt).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from typing import Any, cast
|
||||
|
||||
from sn_basis.functions.qgiscore_wrapper import QgsProject, get_layer_extent
|
||||
from sn_basis.functions.qgisui_wrapper import iface
|
||||
from sn_basis.functions.variable_wrapper import get_variable, set_variable
|
||||
from sn_basis.modules.layerpruefer import Layerpruefer
|
||||
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||
from sn_basis.modules.print_layout import PrintLayout
|
||||
|
||||
|
||||
KARTENNAME_VAR = "kartenname"
|
||||
PLOTMASSSTAB_VAR = "plotmassstab"
|
||||
VIEW_VAR = "view"
|
||||
ZIELGROESSE_VAR = "zielgroesse"
|
||||
FORMFAKTOR_VAR = "formfaktor"
|
||||
DRUCKBEREICH_VAR = "druckbereich"
|
||||
DRUCKPUFFER_VAR = "druckpuffer"
|
||||
DRUCKRECHTECK_LAYER_NAME = "Druckrechteck"
|
||||
|
||||
KARTENNAME_38 = "§38"
|
||||
KARTENNAME_41 = "§41"
|
||||
MASSSTAB_WIE_KARTENFENSTER = "Wie Kartenfenster"
|
||||
THEMA_WIE_KARTENFENSTER = "wie kartenfenster"
|
||||
|
||||
DRUCKBEREICH_VERFAHRENSGEBIET = "Verfahrensgebiet"
|
||||
DRUCKBEREICH_AKTUELLE_AUSWAHL = "aktuelle Auswahl"
|
||||
DRUCKBEREICH_RECHTECK = "Rechteck"
|
||||
|
||||
KARTENNAME_BY_AUSWAHL = {
|
||||
KARTENNAME_38: "Planungsuebersicht §38 FlurbG",
|
||||
KARTENNAME_41: "Karte zum Plan ueber die gemeinschaftlichen und oeffentlichen Anlagen (§ 41 FlurbG)",
|
||||
}
|
||||
|
||||
PLOTMASSSTAB_BY_AUSWAHL = {
|
||||
"1:5.000": "5000",
|
||||
"1:10.000": "10000",
|
||||
"1:15.000": "15000",
|
||||
"1:20.000": "20000",
|
||||
"1:25.000": "25000",
|
||||
"1:50.000": "50000",
|
||||
"1:100.000": "100000",
|
||||
}
|
||||
|
||||
DIN_GROESSEN: dict[str, tuple[int, int]] = {
|
||||
"DIN A0": (841, 1189),
|
||||
"DIN A1": (594, 841),
|
||||
"DIN A2": (420, 594),
|
||||
"DIN A3": (297, 420),
|
||||
"DIN A4": (210, 297),
|
||||
}
|
||||
DIN_STANDARD = "DIN A0"
|
||||
DRUCKPUFFER_STANDARD_CM = 1
|
||||
|
||||
|
||||
class PrintLogic:
|
||||
"""Kapselt die Fachlogik fuer den Druck-Tab (ohne Atlas)."""
|
||||
|
||||
def __init__(self, pruefmanager: Pruefmanager) -> None:
|
||||
self.pruefmanager = pruefmanager
|
||||
self._rectangle_tool: Any | None = None
|
||||
self._previous_map_tool: Any | None = None
|
||||
|
||||
def _resolve_layer_from_project(self) -> object | None:
|
||||
project = QgsProject.instance()
|
||||
# Primär: neue zentrale Variable aus sn_verfahrensgebiet_manager
|
||||
layer_id = (get_variable("verfahrensgebiet_layer", scope="project") or "").strip()
|
||||
# Rückwärtskompatibilität zu älteren Projekten
|
||||
if not layer_id:
|
||||
layer_id = (get_variable("verfahrensgebietslayer", scope="project") or "").strip()
|
||||
if not layer_id:
|
||||
layer_id = (get_variable("tab_a_layer_id", scope="project") or "").strip()
|
||||
if not layer_id:
|
||||
return None
|
||||
|
||||
map_layer = getattr(project, "mapLayer", None)
|
||||
if not callable(map_layer):
|
||||
return None
|
||||
try:
|
||||
return map_layer(layer_id)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _resolve_active_layer(self) -> object | None:
|
||||
active_layer_fn = getattr(iface, "activeLayer", None)
|
||||
if not callable(active_layer_fn):
|
||||
return None
|
||||
try:
|
||||
return active_layer_fn()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _get_druckpuffer_cm(self) -> int:
|
||||
raw_value = get_variable(DRUCKPUFFER_VAR, scope="project")
|
||||
try:
|
||||
value = int(str(raw_value).strip())
|
||||
except (TypeError, ValueError):
|
||||
value = DRUCKPUFFER_STANDARD_CM
|
||||
return max(0, value)
|
||||
|
||||
def _get_druckpuffer_m(self, plotmassstab: float) -> float:
|
||||
return (self._get_druckpuffer_cm() * 10.0 * plotmassstab) / 1000.0
|
||||
|
||||
def _import_qgis_symbol(self, module_name: str, symbol_name: str) -> Any | None:
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except Exception:
|
||||
return None
|
||||
return getattr(module, symbol_name, None)
|
||||
|
||||
def _build_rectangle(self, x_min: float, y_min: float, x_max: float, y_max: float) -> Any | None:
|
||||
qgs_rectangle = self._import_qgis_symbol("qgis.core", "QgsRectangle")
|
||||
if qgs_rectangle is None:
|
||||
return None
|
||||
try:
|
||||
return qgs_rectangle(x_min, y_min, x_max, y_max)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _clone_extent(self, extent: Any) -> Any | None:
|
||||
if extent is None:
|
||||
return None
|
||||
|
||||
buffered = getattr(extent, "buffered", None)
|
||||
if callable(buffered):
|
||||
try:
|
||||
return buffered(0.0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
x_min = getattr(extent, "xMinimum", lambda: None)()
|
||||
y_min = getattr(extent, "yMinimum", lambda: None)()
|
||||
x_max = getattr(extent, "xMaximum", lambda: None)()
|
||||
y_max = getattr(extent, "yMaximum", lambda: None)()
|
||||
if None in (x_min, y_min, x_max, y_max):
|
||||
return None
|
||||
assert x_min is not None
|
||||
assert y_min is not None
|
||||
assert x_max is not None
|
||||
assert y_max is not None
|
||||
x_min_f = float(x_min)
|
||||
y_min_f = float(y_min)
|
||||
x_max_f = float(x_max)
|
||||
y_max_f = float(y_max)
|
||||
return self._build_rectangle(x_min_f, y_min_f, x_max_f, y_max_f)
|
||||
|
||||
def _buffer_extent(self, extent: Any, plotmassstab: float) -> Any | None:
|
||||
if extent is None:
|
||||
return None
|
||||
|
||||
buffer_m = self._get_druckpuffer_m(plotmassstab)
|
||||
if buffer_m <= 0:
|
||||
return extent
|
||||
|
||||
buffered = getattr(extent, "buffered", None)
|
||||
if callable(buffered):
|
||||
try:
|
||||
return buffered(buffer_m)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
x_min = getattr(extent, "xMinimum", lambda: None)()
|
||||
y_min = getattr(extent, "yMinimum", lambda: None)()
|
||||
x_max = getattr(extent, "xMaximum", lambda: None)()
|
||||
y_max = getattr(extent, "yMaximum", lambda: None)()
|
||||
if None in (x_min, y_min, x_max, y_max):
|
||||
return extent
|
||||
assert x_min is not None
|
||||
assert y_min is not None
|
||||
assert x_max is not None
|
||||
assert y_max is not None
|
||||
|
||||
x_min_f = float(x_min)
|
||||
y_min_f = float(y_min)
|
||||
x_max_f = float(x_max)
|
||||
y_max_f = float(y_max)
|
||||
|
||||
expanded = self._build_rectangle(
|
||||
x_min_f - buffer_m,
|
||||
y_min_f - buffer_m,
|
||||
x_max_f + buffer_m,
|
||||
y_max_f + buffer_m,
|
||||
)
|
||||
return expanded or extent
|
||||
|
||||
def _get_existing_layer_by_name(self, layer_name: str) -> object | None:
|
||||
project = QgsProject.instance()
|
||||
map_layers_by_name = getattr(project, "mapLayersByName", None)
|
||||
if not callable(map_layers_by_name):
|
||||
return None
|
||||
try:
|
||||
matches = map_layers_by_name(layer_name)
|
||||
except Exception:
|
||||
return None
|
||||
if not isinstance(matches, (list, tuple)) or not matches:
|
||||
return None
|
||||
return matches[0]
|
||||
|
||||
def _create_druckrechteck_layer(self, reference_layer: object | None) -> object | None:
|
||||
existing_layer = self._get_existing_layer_by_name(DRUCKRECHTECK_LAYER_NAME)
|
||||
if existing_layer is not None:
|
||||
return existing_layer
|
||||
|
||||
qgs_vector_layer = self._import_qgis_symbol("qgis.core", "QgsVectorLayer")
|
||||
if qgs_vector_layer is None:
|
||||
return None
|
||||
|
||||
crs_authid = "EPSG:25832"
|
||||
if reference_layer is not None:
|
||||
try:
|
||||
layer_crs = getattr(reference_layer, "crs", lambda: None)()
|
||||
if layer_crs is not None:
|
||||
crs_authid = layer_crs.authid()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
layer = qgs_vector_layer(f"Polygon?crs={crs_authid}", DRUCKRECHTECK_LAYER_NAME, "memory")
|
||||
if not layer or not getattr(layer, "isValid", lambda: False)():
|
||||
return None
|
||||
|
||||
try:
|
||||
QgsProject.instance().addMapLayer(layer)
|
||||
except Exception:
|
||||
return None
|
||||
return layer
|
||||
|
||||
def _clear_layer_features(self, layer: object) -> None:
|
||||
get_features = getattr(layer, "getFeatures", None)
|
||||
data_provider = getattr(layer, "dataProvider", lambda: None)()
|
||||
if not callable(get_features) or data_provider is None:
|
||||
return
|
||||
|
||||
feature_ids: list[int] = []
|
||||
try:
|
||||
features = cast(Any, get_features())
|
||||
for feature in features:
|
||||
feature_id = getattr(feature, "id", lambda: None)()
|
||||
if feature_id is not None:
|
||||
feature_ids.append(int(feature_id))
|
||||
except Exception:
|
||||
feature_ids = []
|
||||
|
||||
if feature_ids:
|
||||
delete_features = getattr(data_provider, "deleteFeatures", None)
|
||||
if callable(delete_features):
|
||||
try:
|
||||
delete_features(feature_ids)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _store_druckrechteck(self, layer: object, rectangle: Any) -> bool:
|
||||
qgs_feature = self._import_qgis_symbol("qgis.core", "QgsFeature")
|
||||
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
|
||||
if qgs_feature is None or qgs_geometry is None:
|
||||
return False
|
||||
|
||||
if layer is None or rectangle is None:
|
||||
return False
|
||||
|
||||
data_provider = getattr(layer, "dataProvider", lambda: None)()
|
||||
if data_provider is None:
|
||||
return False
|
||||
|
||||
self._clear_layer_features(layer)
|
||||
|
||||
feature = qgs_feature()
|
||||
try:
|
||||
feature.setGeometry(qgs_geometry.fromRect(rectangle))
|
||||
add_features = getattr(data_provider, "addFeatures", None)
|
||||
if callable(add_features):
|
||||
add_features([feature])
|
||||
update_extents = getattr(layer, "updateExtents", None)
|
||||
if callable(update_extents):
|
||||
update_extents()
|
||||
trigger_repaint = getattr(layer, "triggerRepaint", None)
|
||||
if callable(trigger_repaint):
|
||||
trigger_repaint()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
try:
|
||||
set_variable("druckrechteck_xmin", str(rectangle.xMinimum()), scope="project")
|
||||
set_variable("druckrechteck_ymin", str(rectangle.yMinimum()), scope="project")
|
||||
set_variable("druckrechteck_xmax", str(rectangle.xMaximum()), scope="project")
|
||||
set_variable("druckrechteck_ymax", str(rectangle.yMaximum()), scope="project")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
map_canvas = getattr(iface, "mapCanvas", lambda: None)()
|
||||
if map_canvas is not None:
|
||||
refresh = getattr(map_canvas, "refresh", None)
|
||||
if callable(refresh):
|
||||
try:
|
||||
refresh()
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
|
||||
def activate_druckrechteck_capture(self, reference_layer: object | None = None) -> bool:
|
||||
layer = self._create_druckrechteck_layer(reference_layer)
|
||||
if layer is None:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Der temporaere Layer 'Druckrechteck' konnte nicht erzeugt werden.",
|
||||
)
|
||||
return False
|
||||
|
||||
map_canvas = getattr(iface, "mapCanvas", lambda: None)()
|
||||
if map_canvas is None:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Das Kartenfenster ist nicht verfuegbar.",
|
||||
)
|
||||
return False
|
||||
|
||||
qgs_geometry = self._import_qgis_symbol("qgis.core", "QgsGeometry")
|
||||
qgs_rectangle = self._import_qgis_symbol("qgis.core", "QgsRectangle")
|
||||
qgs_wkb_types = self._import_qgis_symbol("qgis.core", "QgsWkbTypes")
|
||||
qgs_map_tool_emit_point = self._import_qgis_symbol("qgis.gui", "QgsMapToolEmitPoint")
|
||||
qgs_rubber_band = self._import_qgis_symbol("qgis.gui", "QgsRubberBand")
|
||||
if None in (qgs_geometry, qgs_rectangle, qgs_wkb_types, qgs_map_tool_emit_point, qgs_rubber_band):
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Die Rechteck-Erfassung ist in dieser QGIS-Umgebung nicht verfuegbar.",
|
||||
)
|
||||
return False
|
||||
|
||||
logic = self
|
||||
self._previous_map_tool = getattr(map_canvas, "mapTool", lambda: None)()
|
||||
base_tool_class = cast(type, qgs_map_tool_emit_point)
|
||||
rubber_band_class = cast(Any, qgs_rubber_band)
|
||||
geometry_class = cast(Any, qgs_geometry)
|
||||
rectangle_class = cast(Any, qgs_rectangle)
|
||||
polygon_geometry = cast(Any, getattr(qgs_wkb_types, "PolygonGeometry", None))
|
||||
|
||||
class RectangleCaptureTool(base_tool_class):
|
||||
def __init__(self, canvas_widget: Any) -> None:
|
||||
super().__init__(canvas_widget)
|
||||
self.canvas = canvas_widget
|
||||
self.start_point: Any | None = None
|
||||
self.rubber_band: Any = rubber_band_class(canvas_widget, polygon_geometry)
|
||||
|
||||
def _to_rect(self, start_point: Any, end_point: Any) -> Any:
|
||||
x_min = min(start_point.x(), end_point.x())
|
||||
x_max = max(start_point.x(), end_point.x())
|
||||
y_min = min(start_point.y(), end_point.y())
|
||||
y_max = max(start_point.y(), end_point.y())
|
||||
return rectangle_class(x_min, y_min, x_max, y_max)
|
||||
|
||||
def _show_rect(self, rect: Any) -> None:
|
||||
try:
|
||||
self.rubber_band.setToGeometry(geometry_class.fromRect(rect), None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def canvasPressEvent(self, event: Any) -> None:
|
||||
self.start_point = self.toMapCoordinates(event.pos())
|
||||
|
||||
def canvasMoveEvent(self, event: Any) -> None:
|
||||
if self.start_point is None:
|
||||
return
|
||||
end_point = self.toMapCoordinates(event.pos())
|
||||
self._show_rect(self._to_rect(self.start_point, end_point))
|
||||
|
||||
def canvasReleaseEvent(self, event: Any) -> None:
|
||||
if self.start_point is None:
|
||||
return
|
||||
end_point = self.toMapCoordinates(event.pos())
|
||||
rectangle = self._to_rect(self.start_point, end_point)
|
||||
self._show_rect(rectangle)
|
||||
width = getattr(rectangle, "width", lambda: 0.0)()
|
||||
height = getattr(rectangle, "height", lambda: 0.0)()
|
||||
if width <= 0 or height <= 0:
|
||||
logic.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Das gezeichnete Rechteck ist ungueltig.",
|
||||
)
|
||||
else:
|
||||
if logic._store_druckrechteck(layer, rectangle):
|
||||
logic.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Das Druckrechteck wurde uebernommen.",
|
||||
)
|
||||
try:
|
||||
self.rubber_band.reset(polygon_geometry)
|
||||
except Exception:
|
||||
pass
|
||||
if logic._previous_map_tool is not None:
|
||||
try:
|
||||
self.canvas.setMapTool(logic._previous_map_tool)
|
||||
except Exception:
|
||||
pass
|
||||
logic._rectangle_tool = None
|
||||
|
||||
rectangle_tool = RectangleCaptureTool(map_canvas)
|
||||
self._rectangle_tool = rectangle_tool
|
||||
|
||||
set_active_layer = getattr(iface, "setActiveLayer", None)
|
||||
if callable(set_active_layer):
|
||||
try:
|
||||
set_active_layer(layer)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
start_editing = getattr(layer, "startEditing", None)
|
||||
if callable(start_editing):
|
||||
try:
|
||||
start_editing()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
set_map_tool = getattr(map_canvas, "setMapTool", None)
|
||||
if callable(set_map_tool):
|
||||
try:
|
||||
set_map_tool(rectangle_tool)
|
||||
except Exception:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Das Rechteck-Werkzeug konnte nicht aktiviert werden.",
|
||||
)
|
||||
return False
|
||||
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Druckrechteck",
|
||||
"Bitte ziehen Sie ein Rechteck in der Karte auf.",
|
||||
)
|
||||
return True
|
||||
|
||||
def _resolve_selection_layer(self, fallback_layer: object | None) -> object | None:
|
||||
active_layer = self._resolve_active_layer()
|
||||
if active_layer is not None:
|
||||
selected_count = getattr(active_layer, "selectedFeatureCount", None)
|
||||
if callable(selected_count):
|
||||
try:
|
||||
selected_count_value = selected_count()
|
||||
if int(cast(Any, selected_count_value)) > 0:
|
||||
return active_layer
|
||||
except Exception:
|
||||
pass
|
||||
return fallback_layer
|
||||
|
||||
def _get_selected_extent(self, layer: object | None) -> Any | None:
|
||||
if layer is None:
|
||||
return None
|
||||
|
||||
get_selected_features = getattr(layer, "selectedFeatures", None)
|
||||
if not callable(get_selected_features):
|
||||
return None
|
||||
|
||||
try:
|
||||
selected_features_any = get_selected_features()
|
||||
selected_features = list(cast(Any, selected_features_any))
|
||||
except Exception:
|
||||
return None
|
||||
if not selected_features:
|
||||
return None
|
||||
|
||||
merged_extent: Any | None = None
|
||||
for feature in selected_features:
|
||||
geometry = getattr(feature, "geometry", lambda: None)()
|
||||
if geometry is None:
|
||||
continue
|
||||
bounding_box = getattr(geometry, "boundingBox", lambda: None)()
|
||||
if bounding_box is None:
|
||||
continue
|
||||
if merged_extent is None:
|
||||
merged_extent = self._clone_extent(bounding_box)
|
||||
continue
|
||||
combine_extent = getattr(merged_extent, "combineExtentWith", None)
|
||||
if callable(combine_extent):
|
||||
try:
|
||||
combine_extent(bounding_box)
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
x_min = min(float(merged_extent.xMinimum()), float(bounding_box.xMinimum()))
|
||||
y_min = min(float(merged_extent.yMinimum()), float(bounding_box.yMinimum()))
|
||||
x_max = max(float(merged_extent.xMaximum()), float(bounding_box.xMaximum()))
|
||||
y_max = max(float(merged_extent.yMaximum()), float(bounding_box.yMaximum()))
|
||||
merged_extent = self._build_rectangle(x_min, y_min, x_max, y_max)
|
||||
return merged_extent
|
||||
|
||||
def _get_druckrechteck_extent(self) -> Any | None:
|
||||
layer = self._get_existing_layer_by_name(DRUCKRECHTECK_LAYER_NAME)
|
||||
extent = get_layer_extent(layer)
|
||||
if extent is not None:
|
||||
width = getattr(extent, "width", lambda: 0.0)()
|
||||
height = getattr(extent, "height", lambda: 0.0)()
|
||||
if width > 0 and height > 0:
|
||||
return extent
|
||||
|
||||
try:
|
||||
x_min = float(get_variable("druckrechteck_xmin", scope="project"))
|
||||
y_min = float(get_variable("druckrechteck_ymin", scope="project"))
|
||||
x_max = float(get_variable("druckrechteck_xmax", scope="project"))
|
||||
y_max = float(get_variable("druckrechteck_ymax", scope="project"))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return self._build_rectangle(x_min, y_min, x_max, y_max)
|
||||
|
||||
def _resolve_extent(
|
||||
self,
|
||||
resolved_layer: object | None,
|
||||
druckbereich_auswahl: str,
|
||||
plotmassstab: float,
|
||||
) -> tuple[Any | None, object | None]:
|
||||
if druckbereich_auswahl == DRUCKBEREICH_AKTUELLE_AUSWAHL:
|
||||
selection_layer = self._resolve_selection_layer(resolved_layer)
|
||||
extent = self._get_selected_extent(selection_layer)
|
||||
if extent is None:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Druckbereich",
|
||||
"Auf dem aktuellen Layer sind keine gueltigen Objekte ausgewaehlt.",
|
||||
)
|
||||
return None, None
|
||||
return self._buffer_extent(extent, plotmassstab), None
|
||||
|
||||
if druckbereich_auswahl == DRUCKBEREICH_RECHTECK:
|
||||
extent = self._get_druckrechteck_extent()
|
||||
if extent is None:
|
||||
self.activate_druckrechteck_capture(resolved_layer)
|
||||
return None, None
|
||||
return self._buffer_extent(extent, plotmassstab), None
|
||||
|
||||
lp = Layerpruefer(layer=resolved_layer)
|
||||
ergebnis = lp.pruefe()
|
||||
if not ergebnis.ok:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Verfahrensgebiets-Layer angeben",
|
||||
"Kein gueltiger Verfahrensgebiets-Layer gefunden. Bitte Layerauswahl im jeweiligen Plugin setzen.",
|
||||
)
|
||||
return None, None
|
||||
|
||||
extent = get_layer_extent(resolved_layer)
|
||||
if extent is None:
|
||||
self.pruefmanager.zeige_hinweis("Fehler", "Layer-Ausdehnung konnte nicht ermittelt werden.")
|
||||
return None, None
|
||||
return self._buffer_extent(extent, plotmassstab), resolved_layer
|
||||
|
||||
def set_kartenname_for_auswahl(self, auswahl: str) -> None:
|
||||
kartenname = KARTENNAME_BY_AUSWAHL.get(auswahl, "")
|
||||
set_variable(KARTENNAME_VAR, kartenname, scope="project")
|
||||
|
||||
def set_plotmassstab_for_auswahl(self, auswahl: str, aktueller_massstab: float | None = None) -> None:
|
||||
if auswahl == MASSSTAB_WIE_KARTENFENSTER:
|
||||
if aktueller_massstab and aktueller_massstab > 0:
|
||||
set_variable(PLOTMASSSTAB_VAR, str(int(round(aktueller_massstab))), scope="project")
|
||||
else:
|
||||
set_variable(PLOTMASSSTAB_VAR, "", scope="project")
|
||||
return
|
||||
|
||||
value = PLOTMASSSTAB_BY_AUSWAHL.get(auswahl, "")
|
||||
set_variable(PLOTMASSSTAB_VAR, value, scope="project")
|
||||
|
||||
def set_view_for_auswahl(self, auswahl: str) -> None:
|
||||
if auswahl == THEMA_WIE_KARTENFENSTER:
|
||||
set_variable(VIEW_VAR, "aktuell", scope="project")
|
||||
return
|
||||
set_variable(VIEW_VAR, auswahl or "", scope="project")
|
||||
|
||||
def set_zielgroesse_for_auswahl(self, auswahl: str) -> None:
|
||||
set_variable(ZIELGROESSE_VAR, auswahl if auswahl in DIN_GROESSEN else DIN_STANDARD, scope="project")
|
||||
|
||||
def set_formfaktor(self, endlosrolle: bool) -> None:
|
||||
set_variable(FORMFAKTOR_VAR, "Endlosrolle" if endlosrolle else "Blatt", scope="project")
|
||||
|
||||
def set_druckbereich_for_auswahl(self, auswahl: str) -> None:
|
||||
if auswahl not in {
|
||||
DRUCKBEREICH_VERFAHRENSGEBIET,
|
||||
DRUCKBEREICH_AKTUELLE_AUSWAHL,
|
||||
DRUCKBEREICH_RECHTECK,
|
||||
}:
|
||||
auswahl = DRUCKBEREICH_VERFAHRENSGEBIET
|
||||
set_variable(DRUCKBEREICH_VAR, auswahl, scope="project")
|
||||
|
||||
def druckvorlage_anlegen(
|
||||
self,
|
||||
layer: object | None,
|
||||
kartenname_auswahl: str,
|
||||
massstab_auswahl: str,
|
||||
zielgroesse: str,
|
||||
formfaktor: bool,
|
||||
druckbereich_auswahl: str,
|
||||
) -> dict:
|
||||
resolved_layer = layer or self._resolve_layer_from_project()
|
||||
|
||||
if kartenname_auswahl not in (KARTENNAME_38, KARTENNAME_41):
|
||||
self.pruefmanager.zeige_hinweis("Kartennamen waehlen", "Kartennamen waehlen")
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
|
||||
if massstab_auswahl == MASSSTAB_WIE_KARTENFENSTER:
|
||||
massstab_str = get_variable(PLOTMASSSTAB_VAR, scope="project") or ""
|
||||
try:
|
||||
massstab_zahl = float(massstab_str)
|
||||
except (ValueError, TypeError):
|
||||
massstab_zahl = 0.0
|
||||
else:
|
||||
massstab_zahl = float(PLOTMASSSTAB_BY_AUSWAHL.get(massstab_auswahl, 0))
|
||||
|
||||
if massstab_zahl <= 0:
|
||||
self.pruefmanager.zeige_hinweis("Massstab fehlt", "Kein gueltiger Massstab angegeben.")
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
|
||||
extent, layer_for_extent = self._resolve_extent(resolved_layer, druckbereich_auswahl, massstab_zahl)
|
||||
if extent is None:
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
|
||||
if layer_for_extent is not None:
|
||||
layer_id = getattr(layer_for_extent, "id", lambda: "")() or ""
|
||||
if layer_id:
|
||||
# Neu: zentrale Variable
|
||||
set_variable("verfahrensgebiet_layer", layer_id, scope="project")
|
||||
# Alt: Rückwärtskompatibilität
|
||||
set_variable("verfahrensgebietslayer", layer_id, scope="project")
|
||||
|
||||
kartenbild_w = extent.width() * 1000.0 / massstab_zahl
|
||||
kartenbild_h = extent.height() * 1000.0 / massstab_zahl
|
||||
|
||||
plotgroesse_w = kartenbild_w + 210.0
|
||||
plotgroesse_h = kartenbild_h + 20.0
|
||||
|
||||
din_dims = DIN_GROESSEN.get(zielgroesse, DIN_GROESSEN[DIN_STANDARD])
|
||||
din_w = float(din_dims[0])
|
||||
din_h = float(din_dims[1])
|
||||
din_max_h = float(max(din_dims))
|
||||
if formfaktor:
|
||||
ziel_w, ziel_h = plotgroesse_w, min(plotgroesse_h, din_max_h)
|
||||
else:
|
||||
cap_w, cap_h = din_w, din_h
|
||||
if not (plotgroesse_w <= cap_w and plotgroesse_h <= cap_h):
|
||||
if plotgroesse_w <= din_h and plotgroesse_h <= din_w:
|
||||
cap_w, cap_h = din_h, din_w
|
||||
ziel_w = min(plotgroesse_w, cap_w)
|
||||
ziel_h = min(plotgroesse_h, cap_h)
|
||||
|
||||
if ziel_w < DIN_GROESSEN["DIN A4"][0] or ziel_h < DIN_GROESSEN["DIN A4"][1]:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Blattgroesse kleiner als DIN A4",
|
||||
"Die Layouterstellung wird trotzdem fortgesetzt.",
|
||||
)
|
||||
|
||||
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
|
||||
if plotgroesse_w <= ziel_h and plotgroesse_h <= ziel_w:
|
||||
ziel_w, ziel_h = ziel_h, ziel_w
|
||||
|
||||
if plotgroesse_w > ziel_w or plotgroesse_h > ziel_h:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Zielgroesse zu klein",
|
||||
"Die gewaehlte Zielgroesse reicht fuer den Einzelblatt-Druck nicht aus.",
|
||||
)
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
|
||||
kartenname = get_variable(KARTENNAME_VAR, scope="project") or KARTENNAME_BY_AUSWAHL.get(
|
||||
kartenname_auswahl, "Vorlage"
|
||||
)
|
||||
thema = get_variable(VIEW_VAR, scope="project") or ""
|
||||
|
||||
vorlage_name, bestaetigt = self.pruefmanager.frage_text(
|
||||
"Neue Vorlage anlegen",
|
||||
"Bezeichnung der Vorlage:",
|
||||
default_text=kartenname,
|
||||
)
|
||||
if not bestaetigt:
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
|
||||
vorlage_name = (vorlage_name or "").strip() or kartenname
|
||||
|
||||
try:
|
||||
PrintLayout().create_single_page_layout(
|
||||
name=vorlage_name,
|
||||
page_width_mm=ziel_w,
|
||||
page_height_mm=ziel_h,
|
||||
map_width_mm=kartenbild_w,
|
||||
map_height_mm=kartenbild_h,
|
||||
extent=extent,
|
||||
plotmassstab=massstab_zahl,
|
||||
thema=thema,
|
||||
fit_extent_to_map=(druckbereich_auswahl != DRUCKBEREICH_VERFAHRENSGEBIET),
|
||||
)
|
||||
except ValueError as exc:
|
||||
self.pruefmanager.zeige_hinweis("Vorlage anlegen", str(exc))
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
except Exception as exc:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Vorlage anlegen",
|
||||
f"Die Vorlage konnte nicht angelegt werden: {exc}",
|
||||
)
|
||||
return {"ok": False, "switch_to_tab_a": False, "atlas_seiten": 0}
|
||||
|
||||
return {"ok": True, "switch_to_tab_a": False, "atlas_seiten": 1}
|
||||
@@ -0,0 +1,85 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from sn_basis.functions.qgiscore_wrapper import (
|
||||
QgsLayoutItemLegend,
|
||||
QgsLayoutItemMap,
|
||||
QgsPrintLayout,
|
||||
QgsProject,
|
||||
)
|
||||
from sn_basis.modules.print_layout import PrintLayout
|
||||
|
||||
|
||||
class _FakeExtent:
|
||||
def isNull(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class TestPrintLayout(unittest.TestCase):
|
||||
|
||||
def test_create_legend_item_links_map_and_enables_map_filtering(self):
|
||||
project = QgsProject.instance()
|
||||
print_layout = PrintLayout(project=project)
|
||||
layout = QgsPrintLayout(project)
|
||||
linked_map = QgsLayoutItemMap(layout)
|
||||
|
||||
legend = print_layout._create_legend_item(
|
||||
layout,
|
||||
"legende",
|
||||
linked_map,
|
||||
10.0,
|
||||
20.0,
|
||||
width_mm=50.0,
|
||||
height_mm=30.0,
|
||||
)
|
||||
|
||||
self.assertIs(legend.linked_map, linked_map)
|
||||
self.assertTrue(legend.auto_update_model)
|
||||
self.assertTrue(legend.legend_filter_by_map_enabled)
|
||||
|
||||
def test_create_legend_item_does_not_copy_project_layers_into_legend_model(self):
|
||||
project = QgsProject.instance()
|
||||
print_layout = PrintLayout(project=project)
|
||||
layout = QgsPrintLayout(project)
|
||||
linked_map = QgsLayoutItemMap(layout)
|
||||
|
||||
legend = print_layout._create_legend_item(
|
||||
layout,
|
||||
"legende",
|
||||
linked_map,
|
||||
10.0,
|
||||
20.0,
|
||||
)
|
||||
|
||||
root_group = legend.model().rootGroup()
|
||||
self.assertEqual(root_group.layers, [])
|
||||
|
||||
@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()
|
||||
print_layout = PrintLayout(project=project)
|
||||
|
||||
layout = print_layout.create_single_page_layout(
|
||||
name="Testlayout",
|
||||
page_width_mm=420.0,
|
||||
page_height_mm=297.0,
|
||||
map_width_mm=200.0,
|
||||
map_height_mm=150.0,
|
||||
extent=_FakeExtent(),
|
||||
plotmassstab=5000.0,
|
||||
thema="Thema A",
|
||||
)
|
||||
|
||||
hauptkarte = next(item for item in layout.items if isinstance(item, QgsLayoutItemMap))
|
||||
legende = next(item for item in layout.items if isinstance(item, QgsLayoutItemLegend))
|
||||
|
||||
self.assertTrue(hauptkarte.follow_visibility_preset)
|
||||
self.assertEqual(hauptkarte.follow_visibility_preset_name, "Thema A")
|
||||
self.assertTrue(hauptkarte.keep_layer_set)
|
||||
self.assertIs(legende.linked_map, hauptkarte)
|
||||
self.assertTrue(legende.auto_update_model)
|
||||
self.assertTrue(legende.legend_filter_by_map_enabled)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,56 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from sn_basis.functions import variable_wrapper
|
||||
|
||||
|
||||
class _Scope:
|
||||
def __init__(self, value: str):
|
||||
self._value = value
|
||||
|
||||
def variable(self, _name: str) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
class TestVariableWrapper(unittest.TestCase):
|
||||
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsProject.instance")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.setProjectVariable")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.projectScope")
|
||||
def test_set_variable_project_noop_write_is_skipped(
|
||||
self,
|
||||
mock_project_scope,
|
||||
mock_set_project_variable,
|
||||
mock_project_instance,
|
||||
):
|
||||
mock_project_instance.return_value = object()
|
||||
mock_project_scope.return_value = _Scope("layer_1")
|
||||
|
||||
variable_wrapper.set_variable("verfahrensgebiet_layer", "layer_1", scope="project")
|
||||
|
||||
mock_set_project_variable.assert_not_called()
|
||||
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsProject.instance")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.setProjectVariable")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.projectScope")
|
||||
def test_set_variable_project_changed_value_is_written(
|
||||
self,
|
||||
mock_project_scope,
|
||||
mock_set_project_variable,
|
||||
mock_project_instance,
|
||||
):
|
||||
fake_project = object()
|
||||
mock_project_instance.return_value = fake_project
|
||||
mock_project_scope.return_value = _Scope("old_value")
|
||||
|
||||
variable_wrapper.set_variable("verfahrensgebiet_layer", "new_value", scope="project")
|
||||
|
||||
mock_set_project_variable.assert_called_once_with(
|
||||
fake_project,
|
||||
"sn_verfahrensgebiet_layer",
|
||||
"new_value",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,68 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from sn_basis.functions import verfahrensgebiet_manager
|
||||
|
||||
|
||||
class _Scope:
|
||||
def __init__(self, value: str):
|
||||
self._value = value
|
||||
|
||||
def variable(self, _name: str) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
class TestVerfahrensgebietManager(unittest.TestCase):
|
||||
|
||||
@patch("sn_basis.functions.verfahrensgebiet_manager.get_variable", return_value="layer_123")
|
||||
def test_get_verfahrensgebiet_layer_id_returns_value(self, _mock_get_variable):
|
||||
result = verfahrensgebiet_manager.get_verfahrensgebiet_layer_id()
|
||||
|
||||
self.assertEqual(result, "layer_123")
|
||||
|
||||
@patch("sn_basis.functions.verfahrensgebiet_manager.get_variable", return_value="")
|
||||
def test_get_verfahrensgebiet_layer_id_returns_none_for_empty_value(self, _mock_get_variable):
|
||||
result = verfahrensgebiet_manager.get_verfahrensgebiet_layer_id()
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch("sn_basis.functions.verfahrensgebiet_manager.set_variable")
|
||||
def test_set_verfahrensgebiet_layer_id_writes_project_variable(self, mock_set_variable):
|
||||
verfahrensgebiet_manager.set_verfahrensgebiet_layer_id("layer_abc")
|
||||
|
||||
mock_set_variable.assert_called_once_with("verfahrensgebiet_layer", "layer_abc", scope="project")
|
||||
|
||||
@patch("sn_basis.functions.verfahrensgebiet_manager._find_verfahrensgebiet_layer")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsProject.instance")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.setProjectVariable")
|
||||
@patch("sn_basis.functions.variable_wrapper.QgsExpressionContextUtils.projectScope")
|
||||
def test_update_without_layer_does_not_write_when_already_empty(
|
||||
self,
|
||||
mock_project_scope,
|
||||
mock_set_project_variable,
|
||||
mock_project_instance,
|
||||
mock_find_layer,
|
||||
):
|
||||
mock_find_layer.return_value = (None, None)
|
||||
mock_project_instance.return_value = object()
|
||||
mock_project_scope.return_value = _Scope("")
|
||||
|
||||
result = verfahrensgebiet_manager._update_verfahrensgebiet_from_project()
|
||||
|
||||
self.assertFalse(result)
|
||||
mock_set_project_variable.assert_not_called()
|
||||
|
||||
@patch("sn_basis.functions.verfahrensgebiet_manager._find_verfahrensgebiet_layer")
|
||||
@patch("sn_basis.functions.verfahrensgebiet_manager.set_variable")
|
||||
def test_update_with_layer_writes_variable(self, mock_set_variable, mock_find_layer):
|
||||
mock_layer = object()
|
||||
mock_find_layer.return_value = (mock_layer, "layer_99")
|
||||
|
||||
result = verfahrensgebiet_manager._update_verfahrensgebiet_from_project()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_set_variable.assert_called_once_with("verfahrensgebiet_layer", "layer_99", scope="project")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,4 @@
|
||||
from sn_basis.ui.tabs.print_tab import PrintTab
|
||||
from sn_basis.ui.tabs.settings_tab import SettingsTab
|
||||
|
||||
__all__ = ["PrintTab", "SettingsTab"]
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
"""
|
||||
sn_basis/ui/tabs/print_tab.py - Zentraler Druck-Tab fuer sn-Plugins.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from sn_basis.functions.qt_wrapper import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QComboBox,
|
||||
QCheckBox,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
)
|
||||
from sn_basis.functions.qgiscore_wrapper import QgsProject
|
||||
from sn_basis.functions.qgisui_wrapper import iface
|
||||
from sn_basis.functions.variable_wrapper import get_variable, set_variable
|
||||
from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||
from sn_basis.modules.DataGrabber import DataGrabber
|
||||
from sn_basis.modules.print_logic import (
|
||||
PrintLogic,
|
||||
MASSSTAB_WIE_KARTENFENSTER,
|
||||
PLOTMASSSTAB_BY_AUSWAHL,
|
||||
THEMA_WIE_KARTENFENSTER,
|
||||
DIN_GROESSEN,
|
||||
DIN_STANDARD,
|
||||
DRUCKBEREICH_VAR,
|
||||
DRUCKPUFFER_VAR,
|
||||
DRUCKPUFFER_STANDARD_CM,
|
||||
DRUCKBEREICH_VERFAHRENSGEBIET,
|
||||
DRUCKBEREICH_AKTUELLE_AUSWAHL,
|
||||
DRUCKBEREICH_RECHTECK,
|
||||
)
|
||||
|
||||
|
||||
KARTENNAME_VAR = "tab_b_kartenname"
|
||||
KARTENNAME_PLACEHOLDER = "Kartenname waehlen"
|
||||
KARTENNAME_38 = "§38"
|
||||
KARTENNAME_41 = "§41"
|
||||
MASSSTAB_VAR = "tab_b_massstab"
|
||||
THEMA_VAR = "tab_b_thema"
|
||||
ZIELGROESSE_UI_VAR = "tab_b_zielgroesse"
|
||||
FORMFAKTOR_UI_VAR = "tab_b_formfaktor"
|
||||
DRUCKBEREICH_OPTIONEN = [
|
||||
DRUCKBEREICH_VERFAHRENSGEBIET,
|
||||
DRUCKBEREICH_AKTUELLE_AUSWAHL,
|
||||
DRUCKBEREICH_RECHTECK,
|
||||
]
|
||||
|
||||
|
||||
class PrintTab(QWidget):
|
||||
"""UI-Klasse fuer den zentralen Druck-Tab."""
|
||||
|
||||
tab_title = "Druck"
|
||||
|
||||
def __init__(self, parent: Optional[Any] = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.pruefmanager: Optional[Pruefmanager] = None
|
||||
self.logic: Optional[PrintLogic] = None
|
||||
self._kartenname_combo: Optional[Any] = None
|
||||
self._massstab_combo: Optional[Any] = None
|
||||
self._thema_combo: Optional[Any] = None
|
||||
self._theme_signal_connected = False
|
||||
self._connected_theme_collection: object = None
|
||||
self._zielgroesse_combo: Optional[Any] = None
|
||||
self._druckbereich_combo: Optional[Any] = None
|
||||
self._endlosrolle_cb: Optional[Any] = None
|
||||
self._druckpuffer_button: Optional[Any] = None
|
||||
self._btn_vorlage_erstellen: Optional[Any] = None
|
||||
|
||||
self._build_ui()
|
||||
self._restore_state()
|
||||
self._connect_theme_collection_signals()
|
||||
self._connect_project_signals()
|
||||
|
||||
def set_services(self, pruefmanager: Pruefmanager, data_grabber: DataGrabber) -> None:
|
||||
_ = data_grabber
|
||||
self.pruefmanager = pruefmanager
|
||||
self.logic = PrintLogic(pruefmanager=self.pruefmanager)
|
||||
|
||||
if self._kartenname_combo:
|
||||
self.logic.set_kartenname_for_auswahl(self._kartenname_combo.currentText())
|
||||
if self._massstab_combo:
|
||||
self.logic.set_plotmassstab_for_auswahl(
|
||||
self._massstab_combo.currentText(),
|
||||
self._get_current_canvas_scale(),
|
||||
)
|
||||
if self._thema_combo:
|
||||
self.logic.set_view_for_auswahl(self._thema_combo.currentText())
|
||||
if self._zielgroesse_combo:
|
||||
self.logic.set_zielgroesse_for_auswahl(self._zielgroesse_combo.currentText())
|
||||
if self._druckbereich_combo:
|
||||
self.logic.set_druckbereich_for_auswahl(self._druckbereich_combo.currentText())
|
||||
if self._endlosrolle_cb:
|
||||
self.logic.set_formfaktor(self._endlosrolle_cb.isChecked())
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
main_layout = QVBoxLayout()
|
||||
main_layout.setSpacing(4)
|
||||
main_layout.setContentsMargins(4, 4, 4, 4)
|
||||
|
||||
kartenname_label = QLabel("Kartenname")
|
||||
kartenname_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||
main_layout.addWidget(kartenname_label)
|
||||
|
||||
kartenname_combo = QComboBox(self)
|
||||
kartenname_combo.addItem(KARTENNAME_PLACEHOLDER)
|
||||
kartenname_combo.addItem(KARTENNAME_38)
|
||||
kartenname_combo.addItem(KARTENNAME_41)
|
||||
kartenname_combo.currentTextChanged.connect(self._on_kartenname_changed)
|
||||
self._kartenname_combo = kartenname_combo
|
||||
main_layout.addWidget(kartenname_combo)
|
||||
|
||||
massstab_label = QLabel("Massstab")
|
||||
massstab_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||
main_layout.addWidget(massstab_label)
|
||||
|
||||
massstab_combo = QComboBox(self)
|
||||
massstab_combo.addItem(MASSSTAB_WIE_KARTENFENSTER)
|
||||
massstab_combo.addItems(list(PLOTMASSSTAB_BY_AUSWAHL.keys()))
|
||||
massstab_combo.currentTextChanged.connect(self._on_massstab_changed)
|
||||
self._massstab_combo = massstab_combo
|
||||
main_layout.addWidget(massstab_combo)
|
||||
|
||||
thema_label = QLabel("Thema")
|
||||
thema_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||
main_layout.addWidget(thema_label)
|
||||
|
||||
thema_combo = QComboBox(self)
|
||||
thema_combo.addItem(THEMA_WIE_KARTENFENSTER)
|
||||
thema_combo.addItems(self._get_gespeicherte_themen())
|
||||
thema_combo.currentTextChanged.connect(self._on_thema_changed)
|
||||
self._thema_combo = thema_combo
|
||||
main_layout.addWidget(thema_combo)
|
||||
|
||||
zielgroesse_label = QLabel("max. Blattgroesse")
|
||||
zielgroesse_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||
main_layout.addWidget(zielgroesse_label)
|
||||
|
||||
zielgroesse_row = QHBoxLayout()
|
||||
zielgroesse_row.setSpacing(6)
|
||||
zielgroesse_combo = QComboBox(self)
|
||||
zielgroesse_combo.addItems(list(DIN_GROESSEN.keys()))
|
||||
zielgroesse_combo.setCurrentText(DIN_STANDARD)
|
||||
zielgroesse_combo.currentTextChanged.connect(self._on_zielgroesse_changed)
|
||||
self._zielgroesse_combo = zielgroesse_combo
|
||||
zielgroesse_row.addWidget(zielgroesse_combo)
|
||||
|
||||
endlosrolle_cb = QCheckBox("Endlosrolle", self)
|
||||
endlosrolle_cb.setChecked(False)
|
||||
endlosrolle_cb.stateChanged.connect(self._on_formfaktor_changed)
|
||||
self._endlosrolle_cb = endlosrolle_cb
|
||||
zielgroesse_row.addWidget(endlosrolle_cb)
|
||||
main_layout.addLayout(zielgroesse_row)
|
||||
|
||||
druckbereich_row = QHBoxLayout()
|
||||
druckbereich_row.setSpacing(6)
|
||||
|
||||
druckbereich_column = QVBoxLayout()
|
||||
druckbereich_column.setSpacing(4)
|
||||
druckbereich_column.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
druckbereich_label = QLabel("Druckbereich")
|
||||
druckbereich_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||
druckbereich_column.addWidget(druckbereich_label)
|
||||
|
||||
druckbereich_combo = QComboBox(self)
|
||||
druckbereich_combo.addItems(DRUCKBEREICH_OPTIONEN)
|
||||
druckbereich_combo.currentTextChanged.connect(self._on_druckbereich_changed)
|
||||
self._druckbereich_combo = druckbereich_combo
|
||||
druckbereich_column.addWidget(druckbereich_combo)
|
||||
druckbereich_row.addLayout(druckbereich_column, 1)
|
||||
|
||||
druckpuffer_column = QVBoxLayout()
|
||||
druckpuffer_column.setSpacing(4)
|
||||
druckpuffer_column.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
druckpuffer_label = QLabel("Kartenpuffer")
|
||||
druckpuffer_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
||||
druckpuffer_column.addWidget(druckpuffer_label)
|
||||
|
||||
druckpuffer_button = QPushButton(self._format_druckpuffer_label(DRUCKPUFFER_STANDARD_CM), self)
|
||||
druckpuffer_button.setToolTip("Puffer")
|
||||
druckpuffer_button.clicked.connect(self._on_druckpuffer_clicked)
|
||||
self._druckpuffer_button = druckpuffer_button
|
||||
druckpuffer_column.addWidget(druckpuffer_button)
|
||||
druckbereich_row.addLayout(druckpuffer_column)
|
||||
|
||||
main_layout.addLayout(druckbereich_row)
|
||||
|
||||
btn_vorlage_erstellen = QPushButton("Vorlage erstellen", self)
|
||||
btn_vorlage_erstellen.clicked.connect(self._on_vorlage_erstellen)
|
||||
self._btn_vorlage_erstellen = btn_vorlage_erstellen
|
||||
main_layout.addWidget(btn_vorlage_erstellen)
|
||||
|
||||
main_layout.addStretch(1)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
if not self._kartenname_combo or not self._massstab_combo or not self._thema_combo:
|
||||
return
|
||||
if not self._zielgroesse_combo or not self._endlosrolle_cb:
|
||||
return
|
||||
if not self._druckbereich_combo or not self._druckpuffer_button:
|
||||
return
|
||||
|
||||
saved_kartenname = get_variable(KARTENNAME_VAR, scope="project")
|
||||
if saved_kartenname in (KARTENNAME_38, KARTENNAME_41):
|
||||
self._kartenname_combo.setCurrentText(saved_kartenname)
|
||||
else:
|
||||
self._kartenname_combo.setCurrentText(KARTENNAME_PLACEHOLDER)
|
||||
|
||||
saved_massstab = get_variable(MASSSTAB_VAR, scope="project")
|
||||
valid_massstaebe = [MASSSTAB_WIE_KARTENFENSTER, *PLOTMASSSTAB_BY_AUSWAHL.keys()]
|
||||
if saved_massstab in valid_massstaebe:
|
||||
self._massstab_combo.setCurrentText(saved_massstab)
|
||||
else:
|
||||
self._massstab_combo.setCurrentText(MASSSTAB_WIE_KARTENFENSTER)
|
||||
|
||||
aktuelle_themen = [THEMA_WIE_KARTENFENSTER, *self._get_gespeicherte_themen()]
|
||||
self._thema_combo.clear()
|
||||
self._thema_combo.addItems(aktuelle_themen)
|
||||
|
||||
saved_thema = get_variable(THEMA_VAR, scope="project")
|
||||
if saved_thema in aktuelle_themen:
|
||||
self._thema_combo.setCurrentText(saved_thema)
|
||||
else:
|
||||
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
|
||||
|
||||
saved_zielgroesse = get_variable(ZIELGROESSE_UI_VAR, scope="project")
|
||||
if saved_zielgroesse in DIN_GROESSEN:
|
||||
self._zielgroesse_combo.setCurrentText(saved_zielgroesse)
|
||||
else:
|
||||
self._zielgroesse_combo.setCurrentText(DIN_STANDARD)
|
||||
|
||||
saved_formfaktor = get_variable(FORMFAKTOR_UI_VAR, scope="project")
|
||||
self._endlosrolle_cb.setChecked(saved_formfaktor == "Endlosrolle")
|
||||
|
||||
saved_druckbereich = get_variable(DRUCKBEREICH_VAR, scope="project")
|
||||
if saved_druckbereich in DRUCKBEREICH_OPTIONEN:
|
||||
self._druckbereich_combo.setCurrentText(saved_druckbereich)
|
||||
else:
|
||||
self._druckbereich_combo.setCurrentText(DRUCKBEREICH_VERFAHRENSGEBIET)
|
||||
|
||||
saved_druckpuffer = get_variable(DRUCKPUFFER_VAR, scope="project")
|
||||
try:
|
||||
druckpuffer_cm = int(str(saved_druckpuffer).strip())
|
||||
except (TypeError, ValueError):
|
||||
druckpuffer_cm = DRUCKPUFFER_STANDARD_CM
|
||||
self._druckpuffer_button.setText(self._format_druckpuffer_label(max(0, druckpuffer_cm)))
|
||||
|
||||
def _on_kartenname_changed(self, value: str) -> None:
|
||||
if value in (KARTENNAME_38, KARTENNAME_41):
|
||||
set_variable(KARTENNAME_VAR, value, scope="project")
|
||||
else:
|
||||
set_variable(KARTENNAME_VAR, "", scope="project")
|
||||
|
||||
if self.logic:
|
||||
self.logic.set_kartenname_for_auswahl(value)
|
||||
|
||||
def _on_massstab_changed(self, value: str) -> None:
|
||||
set_variable(MASSSTAB_VAR, value, scope="project")
|
||||
if self.logic:
|
||||
self.logic.set_plotmassstab_for_auswahl(value, self._get_current_canvas_scale())
|
||||
|
||||
def _on_thema_changed(self, value: str) -> None:
|
||||
set_variable(THEMA_VAR, value, scope="project")
|
||||
if self.logic:
|
||||
self.logic.set_view_for_auswahl(value)
|
||||
|
||||
def _on_zielgroesse_changed(self, value: str) -> None:
|
||||
set_variable(ZIELGROESSE_UI_VAR, value, scope="project")
|
||||
if self.logic:
|
||||
self.logic.set_zielgroesse_for_auswahl(value)
|
||||
|
||||
def _on_formfaktor_changed(self, state: int) -> None:
|
||||
checked = bool(state)
|
||||
set_variable(FORMFAKTOR_UI_VAR, "Endlosrolle" if checked else "Blatt", scope="project")
|
||||
if self.logic:
|
||||
self.logic.set_formfaktor(checked)
|
||||
|
||||
def _on_druckbereich_changed(self, value: str) -> None:
|
||||
if value not in DRUCKBEREICH_OPTIONEN:
|
||||
value = DRUCKBEREICH_VERFAHRENSGEBIET
|
||||
set_variable(DRUCKBEREICH_VAR, value, scope="project")
|
||||
if self.logic:
|
||||
self.logic.set_druckbereich_for_auswahl(value)
|
||||
if value == DRUCKBEREICH_RECHTECK:
|
||||
self.logic.activate_druckrechteck_capture()
|
||||
|
||||
def _on_druckpuffer_clicked(self) -> None:
|
||||
if not self.pruefmanager:
|
||||
return
|
||||
|
||||
current_value = get_variable(DRUCKPUFFER_VAR, scope="project") or str(DRUCKPUFFER_STANDARD_CM)
|
||||
text_value, bestaetigt = self.pruefmanager.frage_text(
|
||||
"Puffer um den Druckbereich",
|
||||
"Puffer um den Druckbereich",
|
||||
default_text=str(current_value),
|
||||
)
|
||||
if not bestaetigt:
|
||||
return
|
||||
|
||||
try:
|
||||
puffer_value = int(str(text_value).strip())
|
||||
except (TypeError, ValueError):
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Puffer um den Druckbereich",
|
||||
"Der Puffer muss als ganze Zahl eingegeben werden.",
|
||||
)
|
||||
return
|
||||
|
||||
if puffer_value < 0:
|
||||
self.pruefmanager.zeige_hinweis(
|
||||
"Puffer um den Druckbereich",
|
||||
"Der Puffer darf nicht negativ sein.",
|
||||
)
|
||||
return
|
||||
|
||||
set_variable(DRUCKPUFFER_VAR, str(puffer_value), scope="project")
|
||||
if self._druckpuffer_button:
|
||||
self._druckpuffer_button.setText(self._format_druckpuffer_label(puffer_value))
|
||||
|
||||
def _format_druckpuffer_label(self, value_cm: int) -> str:
|
||||
return f"{value_cm} cm"
|
||||
|
||||
def _on_vorlage_erstellen(self) -> None:
|
||||
if not self.logic or not self.pruefmanager:
|
||||
return
|
||||
|
||||
self.logic.druckvorlage_anlegen(
|
||||
layer=None,
|
||||
kartenname_auswahl=self._kartenname_combo.currentText() if self._kartenname_combo else "",
|
||||
massstab_auswahl=self._massstab_combo.currentText() if self._massstab_combo else "",
|
||||
zielgroesse=self._zielgroesse_combo.currentText() if self._zielgroesse_combo else DIN_STANDARD,
|
||||
formfaktor=self._endlosrolle_cb.isChecked() if self._endlosrolle_cb else False,
|
||||
druckbereich_auswahl=self._druckbereich_combo.currentText() if self._druckbereich_combo else DRUCKBEREICH_VERFAHRENSGEBIET,
|
||||
)
|
||||
|
||||
def _connect_project_signals(self) -> None:
|
||||
project = QgsProject.instance()
|
||||
for signal_name in ("readProject", "newProjectCreated", "cleared"):
|
||||
signal = getattr(project, signal_name, None)
|
||||
if signal is None:
|
||||
continue
|
||||
try:
|
||||
signal.connect(self._on_project_changed)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _on_project_changed(self, *args) -> None:
|
||||
_ = args
|
||||
self._disconnect_theme_collection_signals()
|
||||
self._theme_signal_connected = False
|
||||
self._connect_theme_collection_signals()
|
||||
self._restore_state()
|
||||
|
||||
def _disconnect_theme_collection_signals(self) -> None:
|
||||
collection = self._connected_theme_collection
|
||||
if collection is not None:
|
||||
for signal_name in ("mapThemesChanged", "changed", "themeChanged"):
|
||||
try:
|
||||
signal = getattr(collection, signal_name, None)
|
||||
except RuntimeError:
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if signal is None:
|
||||
continue
|
||||
try:
|
||||
signal.disconnect(self._refresh_thema_combo_live)
|
||||
except Exception:
|
||||
pass
|
||||
self._connected_theme_collection = None
|
||||
self._theme_signal_connected = False
|
||||
|
||||
def _connect_theme_collection_signals(self) -> None:
|
||||
if self._theme_signal_connected:
|
||||
return
|
||||
|
||||
try:
|
||||
theme_collection = QgsProject.instance().mapThemeCollection()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if theme_collection is None:
|
||||
return
|
||||
|
||||
connected_any = False
|
||||
for signal_name in ("mapThemesChanged", "changed", "themeChanged"):
|
||||
signal = getattr(theme_collection, signal_name, None)
|
||||
if signal is None:
|
||||
continue
|
||||
try:
|
||||
signal.connect(self._refresh_thema_combo_live)
|
||||
connected_any = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if connected_any:
|
||||
self._connected_theme_collection = theme_collection
|
||||
self._theme_signal_connected = connected_any
|
||||
|
||||
def _refresh_thema_combo_live(self, *args) -> None:
|
||||
_ = args
|
||||
if not self._thema_combo:
|
||||
return
|
||||
|
||||
vorherige_auswahl = self._thema_combo.currentText() or THEMA_WIE_KARTENFENSTER
|
||||
eintraege = [THEMA_WIE_KARTENFENSTER, *self._get_gespeicherte_themen()]
|
||||
|
||||
self._thema_combo.blockSignals(True)
|
||||
self._thema_combo.clear()
|
||||
self._thema_combo.addItems(eintraege)
|
||||
|
||||
if vorherige_auswahl in eintraege:
|
||||
self._thema_combo.setCurrentText(vorherige_auswahl)
|
||||
else:
|
||||
self._thema_combo.setCurrentText(THEMA_WIE_KARTENFENSTER)
|
||||
self._thema_combo.blockSignals(False)
|
||||
|
||||
self._on_thema_changed(self._thema_combo.currentText())
|
||||
|
||||
def _get_gespeicherte_themen(self) -> list[str]:
|
||||
try:
|
||||
theme_collection = QgsProject.instance().mapThemeCollection()
|
||||
if theme_collection is None:
|
||||
return []
|
||||
themes = theme_collection.mapThemes()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
namen: list[str] = []
|
||||
for theme_name in themes:
|
||||
name = str(theme_name or "").strip()
|
||||
if name and name not in namen:
|
||||
namen.append(name)
|
||||
|
||||
return namen
|
||||
|
||||
def _get_current_canvas_scale(self) -> float | None:
|
||||
try:
|
||||
canvas = iface.mapCanvas()
|
||||
if canvas is None:
|
||||
return None
|
||||
scale = canvas.scale()
|
||||
return float(scale) if scale else None
|
||||
except Exception:
|
||||
return None
|
||||
Reference in New Issue
Block a user