Compare commits

..

10 Commits

Author SHA1 Message Date
788bac2a23 Merge pull request 'Funktionen von logic nach functions verschoben' (#4) from 22ottomi/Plugin_SN_Basis:main into main
Reviewed-on: #4
2025-11-20 12:22:00 +01:00
Michael Otto
1afe7f2bf6 Funktionen von ligic nach functions verschoben 2025-11-20 12:18:38 +01:00
Michael Otto
4336a70cff Ablauf optimiert, Styles laden implementiert 2025-11-18 16:10:40 +01:00
Michael Otto
a12fdae45e 25.11.4 2025-11-18 12:45:41 +01:00
Michael Otto
2ad36c812f Laden und Speichern der Variablen 2025-11-18 12:17:15 +01:00
Michael Otto
7f62696b51 Fehler beim Entladen / Update behoben. 2025-11-17 12:48:16 +01:00
Michael Otto
617ee30650 Menü und Symbolleiste überarbeitet. 2025-11-17 12:23:04 +01:00
Michael Otto
f305eaeff8 Aufgeräumt und Widget fixiert. 2025-11-17 11:29:04 +01:00
Michael Otto
a302ce2228 Refactoring Aufgrund Fehler beim Beenden. 2025-11-17 10:05:42 +01:00
Michael Otto
c36dc8cae9 Anpassung an Qt6, Fehler beim Beenden behoben 2025-11-13 09:32:36 +01:00
17 changed files with 668 additions and 428 deletions

View File

@@ -1,3 +1,5 @@
from .functions.variable_utils import get_variable
def classFactory(iface): def classFactory(iface):
from .main import lnoSachsenBasis from .main import BasisPlugin
return lnoSachsenBasis(iface) return BasisPlugin(iface)

0
functions/__init__.py Normal file
View File

44
functions/messages.py Normal file
View File

@@ -0,0 +1,44 @@
# sn_basis/functions/messages.py
from typing import Optional
from qgis.core import Qgis
from qgis.PyQt.QtWidgets import QWidget
from qgis.utils import iface
def push_message(
level: Qgis.MessageLevel,
title: str,
text: str,
duration: Optional[int] = 5,
parent: Optional[QWidget] = None,
):
"""
Zeigt eine Meldung in der QGIS-MessageBar.
- level: Qgis.Success | Qgis.Info | Qgis.Warning | Qgis.Critical
- title: Überschrift links (kurz halten)
- text: eigentliche Nachricht
- duration: Sekunden bis Auto-Ausblendung; None => bleibt sichtbar (mit Close-Button)
- parent: optionales Eltern-Widget (für Kontext), normalerweise nicht nötig
Rückgabe: MessageBarItem-Widget (kann später geschlossen/entfernt werden).
"""
bar = iface.messageBar()
# QGIS akzeptiert None als "sticky" Meldung
return bar.pushMessage(title, text, level=level, duration=duration)
def success(title: str, text: str, duration: int = 5):
return push_message(Qgis.Success, title, text, duration)
def info(title: str, text: str, duration: int = 5):
return push_message(Qgis.Info, title, text, duration)
def warning(title: str, text: str, duration: int = 5):
return push_message(Qgis.Warning, title, text, duration)
def error(title: str, text: str, duration: Optional[int] = 5):
# Fehler evtl. länger sichtbar lassen; setze duration=None falls gewünscht
return push_message(Qgis.Critical, title, text, duration)

View File

@@ -0,0 +1,37 @@
from qgis.core import QgsProject, QgsExpressionContextUtils
class SettingsLogic:
def __init__(self):
self.project = QgsProject.instance()
# Definition der Variablen-Namen
self.global_vars = ["amt", "behoerde", "landkreis_user", "sachgebiet"]
self.project_vars = ["bezeichnung", "verfahrensnummer", "gemeinden", "landkreise_proj"]
def save(self, fields: dict):
"""Speichert Felder als globale und projektbezogene Ausdrucksvariablen."""
# Globale Variablen
for key in self.global_vars:
QgsExpressionContextUtils.setGlobalVariable(f"sn_{key}", fields.get(key, ""))
# Projektvariablen
for key in self.project_vars:
QgsExpressionContextUtils.setProjectVariable(self.project, f"sn_{key}", fields.get(key, ""))
print("✅ Ausdrucksvariablen gespeichert.")
def load(self) -> dict:
"""Lädt Werte ausschließlich aus Ausdrucksvariablen (global + projektbezogen)."""
data = {}
# Globale Variablen
for key in self.global_vars:
data[key] = QgsExpressionContextUtils.globalScope().variable(f"sn_{key}") or ""
# Projektvariablen
for key in self.project_vars:
data[key] = QgsExpressionContextUtils.projectScope(self.project).variable(f"sn_{key}") or ""
return data

28
functions/styles.py Normal file
View File

@@ -0,0 +1,28 @@
# sn_basis/functions/styles.py
import os
from qgis.core import QgsVectorLayer
def apply_style(layer: QgsVectorLayer, style_name: str) -> bool:
"""
Lädt einen QML-Style aus dem styles-Ordner des Plugins und wendet ihn auf den Layer an.
style_name: Dateiname ohne Pfad, z.B. 'verfahrensgebiet.qml'
Rückgabe: True bei Erfolg, False sonst
"""
if not layer or not layer.isValid():
return False
# Basis-Pfad: sn_basis/styles
base_dir = os.path.dirname(os.path.dirname(__file__)) # geht von functions/ eins hoch
style_path = os.path.join(base_dir, "styles", style_name)
if not os.path.exists(style_path):
print(f"Style-Datei nicht gefunden: {style_path}")
return False
ok, error_msg = layer.loadNamedStyle(style_path)
if not ok:
print(f"Style konnte nicht geladen werden: {error_msg}")
return False
layer.triggerRepaint()
return True

View File

@@ -0,0 +1,35 @@
from qgis.core import QgsProject, QgsExpressionContextUtils
def get_variable(key: str, scope: str = "project") -> str:
"""
Liefert den Wert einer sn_* Variable zurück.
key: Name ohne Präfix, z.B. "verfahrensnummer"
scope: 'project' oder 'global'
"""
projekt = QgsProject.instance()
var_name = f"sn_{key}"
if scope == "project":
return QgsExpressionContextUtils.projectScope(projekt).variable(var_name) or ""
elif scope == "global":
return QgsExpressionContextUtils.globalScope().variable(var_name) or ""
else:
raise ValueError("Scope muss 'project' oder 'global' sein.")
def set_variable(key: str, value: str, scope: str = "project"):
"""
Schreibt den Wert einer sn_* Variable.
key: Name ohne Präfix, z.B. "verfahrensnummer"
value: Wert, der gespeichert werden soll
scope: 'project' oder 'global'
"""
projekt = QgsProject.instance()
var_name = f"sn_{key}"
if scope == "project":
QgsExpressionContextUtils.setProjectVariable(projekt, var_name, value)
elif scope == "global":
QgsExpressionContextUtils.setGlobalVariable(var_name, value)
else:
raise ValueError("Scope muss 'project' oder 'global' sein.")

34
main.py
View File

@@ -1,22 +1,26 @@
import os from qgis.PyQt.QtCore import QCoreApplication
from qgis.utils import plugins
class lnoSachsenBasis: from sn_basis.ui.navigation import Navigation
"""
Plugin-Klasse für Basisfunktionen. Stellt Funktionen und Klassen für andere Plugins bereit.
"""
class BasisPlugin:
def __init__(self, iface): def __init__(self, iface):
self.iface = iface self.iface = iface
self.plugin_dir = os.path.dirname(__file__) self.ui = None
QCoreApplication.instance().aboutToQuit.connect(self.unload)
def initGui(self): def initGui(self):
""" # Basis-Navigation neu aufbauen
Keine GUI-Integration nötig. self.ui = Navigation(self.iface)
"""
pass # Alle Fachplugins mit "sn_" prüfen und neu initialisieren
for name, plugin in plugins.items():
if name.startswith("sn_") and name != "sn_basis":
try:
plugin.initGui()
except Exception as e:
print(f"Fehler beim Neuinitialisieren von {name}: {e}")
def unload(self): def unload(self):
""" if self.ui:
Keine GUI-Elemente zu entfernen. self.ui.remove_all()
""" self.ui = None
pass

View File

@@ -1,21 +1,13 @@
[general] [general]
name=LNO Sachsen | Basisfunktionen name=LNO Sachsen | Basisfunktionen
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
description=Dieses Plugin ist ein Test description=Plugin mit Basisfunktionen
version=25.10.3 version=25.11.4
author=Michael Otto author=Michael Otto
email=michael.otto@landkreis-mittelsachsen.de email=michael.otto@landkreis-mittelsachsen.de
about=Plugin mit Basisfunktionen
about=Provide a brief description of the plugin and its purpose.
hasProcessingProvider=no
tags=python
category=Plugins category=Plugins
icon=icon.png homepage=https://entwicklung.vln-sn.de/AG_QGIS/Plugin_SN_Basis
experimental=True repository=https://entwicklung.vln-sn.de/AG_QGIS/Repository
supportsQt6=true
deprecated=False experimental=true
server=False

316
styles/verfahrensgebiet.qml Normal file
View File

@@ -0,0 +1,316 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis maxScale="0" simplifyDrawingHints="1" simplifyDrawingTol="1" styleCategories="AllStyleCategories" version="3.10.8-A Coruña" simplifyMaxScale="1" simplifyLocal="1" readOnly="0" hasScaleBasedVisibilityFlag="0" minScale="1e+08" simplifyAlgorithm="0" labelsEnabled="1">
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 type="singleSymbol" forceraster="0" symbollevels="0" enableorderby="0">
<symbols>
<symbol alpha="1" type="fill" clip_to_extent="1" name="0" force_rhr="0">
<layer class="SimpleFill" locked="0" pass="0" enabled="1">
<prop k="border_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="color" v="255,255,153,173"/>
<prop k="joinstyle" v="miter"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="161,2,213,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="1"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="style" v="solid"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<labeling type="simple">
<settings calloutType="simple">
<text-style fontFamily="MS Shell Dlg 2" fontSize="8.25" previewBkgrdColor="255,255,255,255" textOrientation="horizontal" fieldName="Name" textColor="0,0,0,255" fontItalic="0" fontUnderline="0" fontSizeUnit="Point" fontCapitals="0" isExpression="0" fontStrikeout="0" fontWeight="50" fontLetterSpacing="0" fontSizeMapUnitScale="3x:0,0,0,0,0,0" fontWordSpacing="0" textOpacity="1" useSubstitutions="0" fontKerning="1" blendMode="0" namedStyle="Standard" multilineHeight="1">
<text-buffer bufferSizeMapUnitScale="3x:0,0,0,0,0,0" bufferJoinStyle="128" bufferSizeUnits="MM" bufferSize="1" bufferDraw="0" bufferColor="255,255,255,255" bufferNoFill="0" bufferBlendMode="0" bufferOpacity="1"/>
<background shapeRadiiMapUnitScale="3x:0,0,0,0,0,0" shapeRotation="0" shapeSizeType="0" shapeOffsetX="0" shapeBlendMode="0" shapeFillColor="255,255,255,255" shapeBorderColor="128,128,128,255" shapeRadiiX="0" shapeRadiiUnit="MM" shapeDraw="0" shapeJoinStyle="64" shapeOffsetUnit="MM" shapeBorderWidthUnit="MM" shapeSizeX="0" shapeSizeUnit="MM" shapeSizeY="0" shapeRadiiY="0" shapeOpacity="1" shapeOffsetY="0" shapeSVGFile="" shapeType="0" shapeBorderWidth="0" shapeRotationType="0" shapeOffsetMapUnitScale="3x:0,0,0,0,0,0" shapeBorderWidthMapUnitScale="3x:0,0,0,0,0,0" shapeSizeMapUnitScale="3x:0,0,0,0,0,0">
<symbol alpha="1" type="marker" clip_to_extent="1" name="markerSymbol" force_rhr="0">
<layer class="SimpleMarker" locked="0" pass="0" enabled="1">
<prop k="angle" v="0"/>
<prop k="color" v="213,180,60,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="2"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</background>
<shadow shadowUnder="0" shadowOffsetUnit="MM" shadowScale="100" shadowColor="0,0,0,255" shadowBlendMode="6" shadowOffsetAngle="135" shadowOffsetDist="1" shadowRadiusUnit="MM" shadowOffsetMapUnitScale="3x:0,0,0,0,0,0" shadowRadiusMapUnitScale="3x:0,0,0,0,0,0" shadowOpacity="0.7" shadowDraw="0" shadowRadiusAlphaOnly="0" shadowOffsetGlobal="1" shadowRadius="1.5"/>
<dd_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</dd_properties>
<substitutions/>
</text-style>
<text-format autoWrapLength="0" placeDirectionSymbol="0" leftDirectionSymbol="&lt;" rightDirectionSymbol=">" decimals="3" wrapChar="" useMaxLineLengthForAutoWrap="1" plussign="0" formatNumbers="0" addDirectionSymbol="0" reverseDirectionSymbol="0" multilineAlign="4294967295"/>
<placement xOffset="0" distMapUnitScale="3x:0,0,0,0,0,0" dist="0" repeatDistanceUnits="MM" predefinedPositionOrder="TR,TL,BR,BL,R,L,TSR,BSR" centroidWhole="0" rotationAngle="0" overrunDistanceUnit="MM" placement="1" repeatDistance="0" geometryGeneratorEnabled="0" layerType="PolygonGeometry" offsetUnits="MapUnit" centroidInside="0" offsetType="0" yOffset="0" labelOffsetMapUnitScale="3x:0,0,0,0,0,0" overrunDistance="0" geometryGenerator="" geometryGeneratorType="PointGeometry" placementFlags="10" preserveRotation="1" distUnits="MM" fitInPolygonOnly="0" maxCurvedCharAngleOut="-25" repeatDistanceMapUnitScale="3x:0,0,0,0,0,0" quadOffset="4" maxCurvedCharAngleIn="25" priority="5" overrunDistanceMapUnitScale="3x:0,0,0,0,0,0"/>
<rendering fontLimitPixelSize="0" upsidedownLabels="0" zIndex="0" drawLabels="1" fontMinPixelSize="3" displayAll="0" scaleMax="10000000" labelPerPart="0" fontMaxPixelSize="10000" mergeLines="0" obstacle="1" minFeatureSize="0" obstacleType="0" obstacleFactor="1" scaleMin="1" limitNumLabels="0" scaleVisibility="0" maxNumLabels="2000"/>
<dd_properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</dd_properties>
<callout type="simple">
<Option type="Map">
<Option type="QString" value="pole_of_inaccessibility" name="anchorPoint"/>
<Option type="Map" name="ddProperties">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
<Option type="bool" value="false" name="drawToAllParts"/>
<Option type="QString" value="0" name="enabled"/>
<Option type="QString" value="&lt;symbol alpha=&quot;1&quot; type=&quot;line&quot; clip_to_extent=&quot;1&quot; name=&quot;symbol&quot; force_rhr=&quot;0&quot;>&lt;layer class=&quot;SimpleLine&quot; locked=&quot;0&quot; pass=&quot;0&quot; enabled=&quot;1&quot;>&lt;prop k=&quot;capstyle&quot; v=&quot;square&quot;/>&lt;prop k=&quot;customdash&quot; v=&quot;5;2&quot;/>&lt;prop k=&quot;customdash_map_unit_scale&quot; v=&quot;3x:0,0,0,0,0,0&quot;/>&lt;prop k=&quot;customdash_unit&quot; v=&quot;MM&quot;/>&lt;prop k=&quot;draw_inside_polygon&quot; v=&quot;0&quot;/>&lt;prop k=&quot;joinstyle&quot; v=&quot;bevel&quot;/>&lt;prop k=&quot;line_color&quot; v=&quot;60,60,60,255&quot;/>&lt;prop k=&quot;line_style&quot; v=&quot;solid&quot;/>&lt;prop k=&quot;line_width&quot; v=&quot;0.3&quot;/>&lt;prop k=&quot;line_width_unit&quot; v=&quot;MM&quot;/>&lt;prop k=&quot;offset&quot; v=&quot;0&quot;/>&lt;prop k=&quot;offset_map_unit_scale&quot; v=&quot;3x:0,0,0,0,0,0&quot;/>&lt;prop k=&quot;offset_unit&quot; v=&quot;MM&quot;/>&lt;prop k=&quot;ring_filter&quot; v=&quot;0&quot;/>&lt;prop k=&quot;use_custom_dash&quot; v=&quot;0&quot;/>&lt;prop k=&quot;width_map_unit_scale&quot; v=&quot;3x:0,0,0,0,0,0&quot;/>&lt;data_defined_properties>&lt;Option type=&quot;Map&quot;>&lt;Option type=&quot;QString&quot; value=&quot;&quot; name=&quot;name&quot;/>&lt;Option name=&quot;properties&quot;/>&lt;Option type=&quot;QString&quot; value=&quot;collection&quot; name=&quot;type&quot;/>&lt;/Option>&lt;/data_defined_properties>&lt;/layer>&lt;/symbol>" name="lineSymbol"/>
<Option type="double" value="0" name="minLength"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="minLengthMapUnitScale"/>
<Option type="QString" value="MM" name="minLengthUnit"/>
<Option type="double" value="0" name="offsetFromAnchor"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offsetFromAnchorMapUnitScale"/>
<Option type="QString" value="MM" name="offsetFromAnchorUnit"/>
<Option type="double" value="0" name="offsetFromLabel"/>
<Option type="QString" value="3x:0,0,0,0,0,0" name="offsetFromLabelMapUnitScale"/>
<Option type="QString" value="MM" name="offsetFromLabelUnit"/>
</Option>
</callout>
</settings>
</labeling>
<customproperties>
<property value="0" key="embeddedWidgets/count"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer attributeLegend="1" diagramType="Histogram">
<DiagramCategory minimumSize="0" enabled="0" scaleBasedVisibility="0" labelPlacementMethod="XHeight" rotationOffset="270" backgroundColor="#ffffff" width="15" backgroundAlpha="255" sizeType="MM" penWidth="0" penColor="#000000" lineSizeScale="3x:0,0,0,0,0,0" maxScaleDenominator="1e+08" sizeScale="3x:0,0,0,0,0,0" scaleDependency="Area" barWidth="5" lineSizeType="MM" minScaleDenominator="0" height="15" opacity="1" diagramOrientation="Up" penAlpha="255">
<fontProperties description="MS Shell Dlg 2,8.25,-1,5,50,0,0,0,0,0" style=""/>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings placement="1" showAll="1" priority="0" zIndex="0" obstacle="0" linePlacementFlags="18" dist="0">
<properties>
<Option type="Map">
<Option type="QString" value="" name="name"/>
<Option name="properties"/>
<Option type="QString" value="collection" name="type"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
<activeChecks/>
<checkConfiguration type="Map">
<Option type="Map" name="QgsGeometryGapCheck">
<Option type="double" value="0" name="allowedGapsBuffer"/>
<Option type="bool" value="false" name="allowedGapsEnabled"/>
<Option type="QString" value="" name="allowedGapsLayer"/>
</Option>
</checkConfiguration>
</geometryOptions>
<fieldConfiguration>
<field name="id">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="Name">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="Nummer">
<editWidget type="Range">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="Referat">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="Ansprechpartner">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="Telefon">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="E-Mail">
<editWidget type="TextEdit">
<config>
<Option/>
</config>
</editWidget>
</field>
<field name="Letzte Änderung">
<editWidget type="DateTime">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<aliases>
<alias index="0" name="" field="id"/>
<alias index="1" name="" field="Name"/>
<alias index="2" name="" field="Nummer"/>
<alias index="3" name="" field="Referat"/>
<alias index="4" name="" field="Ansprechpartner"/>
<alias index="5" name="" field="Telefon"/>
<alias index="6" name="" field="E-Mail"/>
<alias index="7" name="" field="Letzte Änderung"/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default expression="" applyOnUpdate="0" field="id"/>
<default expression="" applyOnUpdate="0" field="Name"/>
<default expression="" applyOnUpdate="0" field="Nummer"/>
<default expression="" applyOnUpdate="0" field="Referat"/>
<default expression="" applyOnUpdate="0" field="Ansprechpartner"/>
<default expression="" applyOnUpdate="0" field="Telefon"/>
<default expression="" applyOnUpdate="0" field="E-Mail"/>
<default expression="" applyOnUpdate="0" field="Letzte Änderung"/>
</defaults>
<constraints>
<constraint exp_strength="0" notnull_strength="1" unique_strength="1" field="id" constraints="3"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Name" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Nummer" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Referat" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Ansprechpartner" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Telefon" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="E-Mail" constraints="0"/>
<constraint exp_strength="0" notnull_strength="0" unique_strength="0" field="Letzte Änderung" constraints="0"/>
</constraints>
<constraintExpressions>
<constraint desc="" exp="" field="id"/>
<constraint desc="" exp="" field="Name"/>
<constraint desc="" exp="" field="Nummer"/>
<constraint desc="" exp="" field="Referat"/>
<constraint desc="" exp="" field="Ansprechpartner"/>
<constraint desc="" exp="" field="Telefon"/>
<constraint desc="" exp="" field="E-Mail"/>
<constraint desc="" exp="" field="Letzte Änderung"/>
</constraintExpressions>
<expressionfields/>
<attributeactions>
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
</attributeactions>
<attributetableconfig sortOrder="0" actionWidgetStyle="dropDown" sortExpression="">
<columns>
<column type="actions" hidden="1" width="-1"/>
<column type="field" hidden="0" width="-1" name="Nummer"/>
<column type="field" hidden="0" width="-1" name="Name"/>
<column type="field" hidden="0" width="-1" name="E-Mail"/>
<column type="field" hidden="0" width="-1" name="Letzte Änderung"/>
<column type="field" hidden="0" width="-1" name="id"/>
<column type="field" hidden="0" width="-1" name="Referat"/>
<column type="field" hidden="0" width="-1" name="Ansprechpartner"/>
<column type="field" hidden="0" width="-1" name="Telefon"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<storedexpressions/>
<editform tolerant="1">.</editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath>.</editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable>
<field name="Ansprechpartner" editable="1"/>
<field name="E-Mail" editable="1"/>
<field name="Letzte Änderung" editable="1"/>
<field name="Name" editable="1"/>
<field name="Nummer" editable="1"/>
<field name="Referat" editable="1"/>
<field name="Telefon" editable="1"/>
<field name="id" editable="1"/>
</editable>
<labelOnTop>
<field labelOnTop="0" name="Ansprechpartner"/>
<field labelOnTop="0" name="E-Mail"/>
<field labelOnTop="0" name="Letzte Änderung"/>
<field labelOnTop="0" name="Name"/>
<field labelOnTop="0" name="Nummer"/>
<field labelOnTop="0" name="Referat"/>
<field labelOnTop="0" name="Telefon"/>
<field labelOnTop="0" name="id"/>
</labelOnTop>
<widgets/>
<previewExpression>COALESCE( "name", '&lt;NULL>' )</previewExpression>
<mapTip></mapTip>
<layerGeometryType>2</layerGeometryType>
</qgis>

View File

@@ -1,3 +0,0 @@
from .tab_projekt import TabProjektWidget
from .dockmanager import DockManager
from .navigation import Navigation

28
ui/base_dockwidget.py Normal file
View File

@@ -0,0 +1,28 @@
from qgis.PyQt.QtWidgets import QDockWidget, QTabWidget
class BaseDockWidget(QDockWidget):
base_title = "LNO Sachsen"
tabs = []
action = None # Referenz auf die Toolbar-Action
def __init__(self, parent=None, subtitle=""):
super().__init__(parent)
# Titel zusammensetzen
title = self.base_title if not subtitle else f"{self.base_title} | {subtitle}"
self.setWindowTitle(title)
# Dock fixieren (nur schließen erlaubt)
self.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetClosable)
# Tabs hinzufügen
tab_widget = QTabWidget()
for tab_class in self.tabs:
tab_widget.addTab(tab_class(), getattr(tab_class, "tab_title", tab_class.__name__))
self.setWidget(tab_widget)
def closeEvent(self, event):
"""Wird aufgerufen, wenn das Dock geschlossen wird."""
if self.action:
self.action.setChecked(False) # Toolbar-Button zurücksetzen
super().closeEvent(event)

View File

@@ -1,60 +1,21 @@
from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QDockWidget from qgis.PyQt.QtWidgets import QDockWidget
from qgis.utils import iface from qgis.utils import iface
import inspect
class DockManager: class DockManager:
""" default_area = Qt.DockWidgetArea.RightDockWidgetArea
Zeigt ein Dockwidget an und schließt alle anderen mit dem Namensschema 'sn_dock_'.
Der Dockname wird automatisch aus dem Pluginmodul abgeleitet.
"""
# Standard-Dockbereich: Rechts (wie die Verarbeitungswerkzeuge)
default_area = Qt.RightDockWidgetArea
@classmethod @classmethod
def show(cls, dock_widget, area=None): def show(cls, dock_widget, area=None):
# Falls kein Bereich übergeben wurde, verwende den Standardwert area = area or cls.default_area
if area is None:
area = cls.default_area
# Pluginname automatisch aus dem Modulpfad ableiten (z.B. 'sn_plugin1' → 'plugin1') # Bestehende Plugin-Docks mit Präfix schließen
caller_module = inspect.getmodule(inspect.stack()[1][0]) for widget in iface.mainWindow().findChildren(QDockWidget):
full_module_name = caller_module.__name__ # z.B. 'sn_plugin1.main' if widget is not dock_widget and widget.objectName().startswith("sn_dock_"):
plugin_name = full_module_name.split('.')[0] # → 'sn_plugin1' iface.removeDockWidget(widget)
dock_name = f"sn_dock_{plugin_name.replace('sn_', '')}" # → 'sn_dock_plugin1' widget.deleteLater()
# Objektname für das Dock setzen, damit es eindeutig identifizierbar ist
dock_widget.setObjectName(dock_name)
# Nur rechts andocken erlauben, wie bei der Toolbox
dock_widget.setAllowedAreas(Qt.RightDockWidgetArea)
# Dock-Features setzen: schließbar und verschiebbar
dock_widget.setFeatures(QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable)
# Alle vorhandenen Dockwidgets im Hauptfenster durchsuchen
# und solche mit dem Namensschema 'sn_dock_' schließen außer dem aktuellen
all_docks = iface.mainWindow().findChildren(QDockWidget)
for widget in all_docks:
if widget.objectName().startswith("sn_dock_") and widget != dock_widget:
try:
iface.removeDockWidget(widget)
widget.close()
except Exception:
pass # Fehler beim Schließen ignorieren (z.B. falls bereits entfernt)
# Neues Dock anzeigen # Neues Dock anzeigen
iface.addDockWidget(area, dock_widget) iface.addDockWidget(area, dock_widget)
# Tabifizierung verhindern andere Docks im selben Bereich entfernen
for widget in iface.mainWindow().findChildren(QDockWidget):
if widget != dock_widget and iface.mainWindow().dockWidgetArea(widget) == area:
iface.mainWindow().removeDockWidget(widget)
# Breite setzen wie bei der Toolbox (optional, anpassbar)
dock_widget.setMinimumWidth(300)
dock_widget.setMaximumWidth(400)
# Höhe nicht erzwingen Qt passt sie automatisch an
dock_widget.show() dock_widget.show()

View File

@@ -1,75 +1,83 @@
from qgis.PyQt.QtWidgets import QMenu, QToolBar, QAction from qgis.PyQt.QtWidgets import QAction, QMenu, QToolBar, QActionGroup
from qgis.PyQt.QtGui import QIcon
from qgis.utils import iface
_shared_toolbar = None # globale Toolbar-Instanz
_shared_menu = None # globale Menü-Instanz
class Navigation: class Navigation:
TITLE = "LNO Sachsen" def __init__(self, iface):
self.iface = iface
self.actions = []
# Menü und Toolbar einmalig anlegen
self.menu = QMenu("LNO Sachsen", iface.mainWindow())
iface.mainWindow().menuBar().addMenu(self.menu)
def __init__(self): self.toolbar = QToolBar("LNO Sachsen")
self.menu = self._get_or_create_menu() self.toolbar.setObjectName("LnoSachsenToolbar")
self.toolbar = self._get_or_create_toolbar() iface.addToolBar(self.toolbar)
def _get_or_create_menu(self): # Gruppe für exklusive Auswahl (nur ein Plugin aktiv)
global _shared_menu self.plugin_group = QActionGroup(iface.mainWindow())
if _shared_menu: self.plugin_group.setExclusive(True)
return _shared_menu
menubar = iface.mainWindow().menuBar() def add_action(self, text, callback, tooltip="", priority=100):
for action in menubar.actions(): action = QAction(text, self.iface.mainWindow())
if action.menu() and action.text() == self.TITLE: action.setToolTip(tooltip)
_shared_menu = action.menu() action.setCheckable(True) # Button kann aktiv sein
return _shared_menu action.triggered.connect(callback)
menu = QMenu(self.TITLE, iface.mainWindow()) # Action in Gruppe aufnehmen
menu.setObjectName(self.TITLE) self.plugin_group.addAction(action)
menubar.addMenu(menu)
_shared_menu = menu
return menu
def _get_or_create_toolbar(self): # Action mit Priority speichern
global _shared_toolbar self.actions.append((priority, action))
if _shared_toolbar: return action
return _shared_toolbar
def finalize_menu_and_toolbar(self):
# Sortieren nach Priority
self.actions.sort(key=lambda x: x[0])
main_window = iface.mainWindow() # Menüeinträge
toolbar = main_window.findChild(QToolBar, self.TITLE) self.menu.clear()
if not toolbar: for _, action in self.actions:
toolbar = QToolBar(self.TITLE, main_window)
toolbar.setObjectName(self.TITLE)
main_window.addToolBar(toolbar)
_shared_toolbar = toolbar
return toolbar
def add_action(self, text, callback, icon=None, tooltip=None):
# Menüeintrag
if not any(a.text() == text for a in self.menu.actions()):
action = QAction(icon, text, iface.mainWindow()) if icon else QAction(text, iface.mainWindow())
if tooltip:
action.setToolTip(tooltip)
action.triggered.connect(callback)
self.menu.addAction(action) self.menu.addAction(action)
# Symbolleistenaktion # Toolbar-Einträge
if not any(a.text() == text for a in self.toolbar.actions()): self.toolbar.clear()
action = QAction(icon, text, iface.mainWindow()) if icon else QAction(text, iface.mainWindow()) for _, action in self.actions:
if tooltip:
action.setToolTip(tooltip)
action.triggered.connect(callback)
self.toolbar.addAction(action) self.toolbar.addAction(action)
def remove_action(self, text): def set_active_plugin(self, active_action):
# Menüeintrag entfernen # Alle zurücksetzen, dann aktives Plugin markieren
for act in self.menu.actions(): for _, action in self.actions:
if act.text() == text: action.setChecked(False)
self.menu.removeAction(act) if active_action:
break active_action.setChecked(True)
# Symbolleistenaktion entfernen def remove_all(self):
for act in self.toolbar.actions(): """Alles entfernen beim Entladen des Basisplugins"""
if act.text() == text: # Menü entfernen
self.toolbar.removeAction(act) if self.menu:
break self.iface.mainWindow().menuBar().removeAction(self.menu.menuAction())
self.menu = None
# Toolbar entfernen
if self.toolbar:
self.iface.mainWindow().removeToolBar(self.toolbar)
self.toolbar = None
# Actions zurücksetzen
self.actions.clear()
# Gruppe leeren
self.plugin_group = None
def remove_action(self, action):
"""Entfernt eine einzelne Action aus Menü und Toolbar"""
if not action:
return
# Menüeintrag entfernen
if self.menu:
self.menu.removeAction(action)
# Toolbar-Eintrag entfernen
if self.toolbar:
self.toolbar.removeAction(action)
# Aus der internen Liste löschen
self.actions = [(p, a) for p, a in self.actions if a != action]

View File

@@ -1,20 +0,0 @@
# tab_info.py
from qgis.PyQt import uic
from qgis.PyQt.QtWidgets import QWidget
import os
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'tab_projekt.ui'))
class TabProjektWidget(QWidget, FORM_CLASS):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
# Zugriff auf den Button
self.btn_save.setText("Sichern") # Text ändern
self.btn_save.setEnabled(True) # Aktivieren
self.btn_save.clicked.connect(self.speichern) # Klick-Event verbinden
def speichern(self):
print("Speichern wurde geklickt!")

View File

@@ -1,264 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>604</width>
<height>939</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="title">
<string>Benutzerspezifische Festlegungen___</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Amt</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_5"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Behörde</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_4"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Landkreis</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Sachgebiet</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_2"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Projektspezifische Festlegungen</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_8">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Bezeichnung</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_8"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_7">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Verfahrensnummer</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_7"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Gemeinde(n)</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_3"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_6">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Landkreis(e)</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_6"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_save">
<property name="text">
<string>Speichern</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

0
ui/tabs/__init__.py Normal file
View File

72
ui/tabs/settings_tab.py Normal file
View File

@@ -0,0 +1,72 @@
from qgis.PyQt.QtWidgets import (
QWidget, QGridLayout, QLabel, QLineEdit,
QGroupBox, QVBoxLayout, QPushButton
)
from sn_basis.functions.settings_logic import SettingsLogic
class SettingsTab(QWidget):
tab_title = "Projekteigenschaften" # Titel für den Tab
def __init__(self, parent=None):
super().__init__(parent)
self.logic = SettingsLogic()
main_layout = QVBoxLayout()
# Definition der Felder
self.user_fields = {
"amt": "Amt:",
"behoerde": "Behörde:",
"landkreis_user": "Landkreis:",
"sachgebiet": "Sachgebiet:"
}
self.project_fields = {
"bezeichnung": "Bezeichnung:",
"verfahrensnummer": "Verfahrensnummer:",
"gemeinden": "Gemeinde(n):",
"landkreise_proj": "Landkreis(e):"
}
# 🟦 Benutzerspezifische Festlegungen
user_group = QGroupBox("Benutzerspezifische Festlegungen")
user_layout = QGridLayout()
self.user_inputs = {}
for row, (key, label) in enumerate(self.user_fields.items()):
self.user_inputs[key] = QLineEdit()
user_layout.addWidget(QLabel(label), row, 0)
user_layout.addWidget(self.user_inputs[key], row, 1)
user_group.setLayout(user_layout)
# 🟨 Projektspezifische Festlegungen
project_group = QGroupBox("Projektspezifische Festlegungen")
project_layout = QGridLayout()
self.project_inputs = {}
for row, (key, label) in enumerate(self.project_fields.items()):
self.project_inputs[key] = QLineEdit()
project_layout.addWidget(QLabel(label), row, 0)
project_layout.addWidget(self.project_inputs[key], row, 1)
project_group.setLayout(project_layout)
# 🟩 Speichern-Button
save_button = QPushButton("Speichern")
save_button.clicked.connect(self.save_data)
# Layout zusammenfügen
main_layout.addWidget(user_group)
main_layout.addWidget(project_group)
main_layout.addStretch()
main_layout.addWidget(save_button)
self.setLayout(main_layout)
self.load_data()
def save_data(self):
# Alle Felder zusammenführen
fields = {key: widget.text() for key, widget in {**self.user_inputs, **self.project_inputs}.items()}
self.logic.save(fields)
def load_data(self):
data = self.logic.load()
for key, widget in {**self.user_inputs, **self.project_inputs}.items():
widget.setText(data.get(key, ""))