Files
Plugin_SN_Basis/functions/qgisui_wrapper.py
T
DanielandClaude Opus 5.5 f58d814313 Druckbereich und Layoutkarte im KBS des Verfahrensgebiets (#99)
Bei anderem Projekt-KBS als dem des Verfahrensgebiets entstand das
Drucklayout mit leerer Karte und falschen Koordinaten: die Ausdehnung kam
im Layer-KBS, die Layoutkarte rechnete im Projekt-KBS. Die Hauptkarte
bekommt jetzt das KBS des Verfahrensgebiet-Layers, alle Druckbereiche
werden dorthin umgerechnet, ebenso das gezeichnete Druckrechteck.

Neue Wrapper-Funktionen transformiere_rechteck und zoom_auf_layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:15:34 +02:00

293 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
sn_basis/functions/qgisui_wrapper.py – zentrale QGIS-UI-Abstraktion
"""
from __future__ import annotations
from typing import Any, List, Type
from sn_basis.functions.qt_wrapper import QDockWidget
from sn_basis.functions.qgiscore_wrapper import QgsProject, QGIS_AVAILABLE, transformiere_rechteck
iface: Any
QGIS_UI_AVAILABLE = False
QgsFileWidget: Type[Any]
QgsMapLayerComboBox: Type[Any]
# ---------------------------------------------------------
# iface + QGIS-Widgets initialisieren (QGIS oder Mock)
# ---------------------------------------------------------
try:
from qgis.utils import iface as _iface
from qgis.gui import (
QgsFileWidget as _QgsFileWidget,
QgsMapLayerComboBox as _QgsMapLayerComboBox,
)
iface = _iface
QgsFileWidget = _QgsFileWidget
QgsMapLayerComboBox = _QgsMapLayerComboBox
QGIS_UI_AVAILABLE = True
except Exception:
QGIS_UI_AVAILABLE = False
class _MockSignal:
def __init__(self):
self._callbacks: list[Any] = []
def connect(self, callback):
self._callbacks.append(callback)
def emit(self, *args, **kwargs):
for cb in list(self._callbacks):
cb(*args, **kwargs)
class _MockMessageBar:
def pushMessage(self, title, text, level=0, duration=5):
return {
"title": title,
"text": text,
"level": level,
"duration": duration,
}
class _MockIface:
def messageBar(self):
return _MockMessageBar()
def mainWindow(self):
return None
def addDockWidget(self, *args, **kwargs):
pass
def removeDockWidget(self, *args, **kwargs):
pass
def addToolBar(self, *args, **kwargs):
pass
def removeToolBar(self, *args, **kwargs):
pass
def openLayoutDesigner(self, layout):
return layout
iface = _MockIface()
class _MockQgsFileWidget:
GetFile = 0
def __init__(self, *args, **kwargs):
self._path = ""
self.fileChanged = _MockSignal()
def setStorageMode(self, *args, **kwargs):
pass
def setFilter(self, *args, **kwargs):
pass
def setFilePath(self, path: str):
self._path = path
self.fileChanged.emit(path)
def filePath(self) -> str:
return self._path
class _MockQgsMapLayerComboBox:
def __init__(self, *args, **kwargs):
self.layerChanged = _MockSignal()
self._layer = None
self._count = 0
def setFilters(self, *args, **kwargs):
pass
def setLayer(self, layer):
self._layer = layer
self.layerChanged.emit(layer)
def count(self) -> int:
return self._count
def setCurrentIndex(self, idx: int):
pass
QgsFileWidget = _MockQgsFileWidget
QgsMapLayerComboBox = _MockQgsMapLayerComboBox
# ---------------------------------------------------------
# Main Window
# ---------------------------------------------------------
def get_main_window():
"""von Test abgesehen, weil vernachlässigbar; HD 18.09.2026"""
try:
return iface.mainWindow()
except Exception:
return None
def open_layout_designer(layout: Any) -> Any:
try:
return iface.openLayoutDesigner(layout)
except Exception:
return None
# ---------------------------------------------------------
# Dock-Handling
# ---------------------------------------------------------
def add_dock_widget(area, dock: Any) -> None:
"""von Test abgesehen, weil vernachlässigbar; HD 18.09.2026"""
try:
iface.addDockWidget(area, dock)
except Exception:
pass
def remove_dock_widget(dock: Any) -> None:
"""von Test abgesehen, weil vernachlässigbar; HD 18.09.2026"""
try:
iface.removeDockWidget(dock)
except Exception:
pass
def find_dock_widgets() -> List[Any]:
main_window = get_main_window()
if not main_window:
return []
try:
return main_window.findChildren(QDockWidget)
except Exception:
return []
# ---------------------------------------------------------
# Menü-Handling
# ---------------------------------------------------------
def add_menu(menu):
main_window = iface.mainWindow()
if not main_window:
return
# Nur echte Qt-Menüs an Qt übergeben
if hasattr(menu, "menuAction"):
main_window.menuBar().addMenu(menu)
def remove_menu(menu):
main_window = iface.mainWindow()
if not main_window:
return
if hasattr(menu, "menuAction"):
main_window.menuBar().removeAction(menu.menuAction())
# ---------------------------------------------------------
# Toolbar-Handling
# ---------------------------------------------------------
def add_toolbar(toolbar: Any) -> None:
"""von Test abgesehen, weil vernachlässigbar; HD 18.09.2026"""
try:
iface.addToolBar(toolbar)
except Exception:
pass
def remove_toolbar(toolbar: Any) -> None:
"""von Test abgesehen, weil vernachlässigbar; HD 18.09.2026"""
try:
iface.removeToolBar(toolbar)
except Exception:
pass
# ---------------------------------------------------------
# Layer zum Projekt hinzufügen
# ---------------------------------------------------------
def zoom_auf_layer(layer: Any, canvas: Any = None) -> bool:
"""Zoomt die Kartenansicht auf den Layer; die Ausdehnung wird in das KBS der Kartenansicht umgerechnet.
``layer.extent()`` steht im KBS des Layers. Direkt an ``setExtent`` übergeben, landet der
Sprung an der falschen Stelle, sobald das Projekt-KBS ein anderes ist (Basis #99).
Gibt ``False`` zurück, wenn Layer oder Kartenansicht fehlen oder das Zoomen scheitert.
"""
if canvas is None:
map_canvas = getattr(iface, "mapCanvas", None)
canvas = map_canvas() if callable(map_canvas) else None
if layer is None or canvas is None:
return False
try:
ziel_crs = canvas.mapSettings().destinationCrs()
canvas.setExtent(transformiere_rechteck(layer.extent(), layer.crs(), ziel_crs))
canvas.refresh()
return True
except Exception:
return False
def get_active_layer() -> Any:
"""Im Layerbaum ausgewählter Layer oder ``None`` (Mock-Modus, kein Layer gewählt)."""
try:
aktiv = getattr(iface, "activeLayer", None)
return aktiv() if callable(aktiv) else None
except Exception:
return None
def add_layer_to_project(layer: Any) -> bool:
"""
Fügt einen Layer dem aktuellen QGIS-Projekt hinzu.
Diese Funktion kapselt den Zugriff auf ``QgsProject.instance().addMapLayer``
und dient als zentrale Abstraktion für alle Stellen, die Layer dynamisch
ins Projekt einfügen möchten (z.B. Pufferlayer im Datenabruf).
Verhalten
---------
- Wenn QGIS verfügbar ist und der Layer gültig ist, wird er dem Projekt
hinzugefügt und ``True`` zurückgegeben.
- Wenn QGIS nicht verfügbar ist oder der Layer ungültig ist, wird
``False`` zurückgegeben.
- Im Mock-Modus wird kein Layer hinzugefügt, aber ``True`` zurückgegeben,
damit Tests ohne QGIS nicht fehlschlagen.
Parameters
----------
layer:
Ein QGIS-Layer (typischerweise ``QgsVectorLayer``), der dem Projekt
hinzugefügt werden soll.
Returns
-------
bool
``True`` bei Erfolg oder im Mock-Modus, sonst ``False``.
"""
if layer is None:
return False
# Mock-Modus: Erfolg simulieren
if not QGIS_AVAILABLE:
return True
try:
project = QgsProject.instance()
project.addMapLayer(layer)
return True
except Exception:
return False