Compare commits

...

12 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: https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Basis/pulls/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
d60dbd13a5 Merge pull request 'Navigation hinzugefügt' (#2) from refactor/navigation into main
Reviewed-on: #2
2025-10-09 13:57:30 +02:00
Michael Otto
dcb298cb4a Navigation hinzugefügt 2025-10-09 13:56:13 +02:00
17 changed files with 681 additions and 365 deletions

View File

@@ -1,3 +1,5 @@
from .functions.variable_utils import get_variable
def classFactory(iface):
from .main import lnoSachsenBasis
return lnoSachsenBasis(iface)
from .main import BasisPlugin
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
class lnoSachsenBasis:
"""
Plugin-Klasse für Basisfunktionen. Stellt Funktionen und Klassen für andere Plugins bereit.
"""
from qgis.PyQt.QtCore import QCoreApplication
from qgis.utils import plugins
from sn_basis.ui.navigation import Navigation
class BasisPlugin:
def __init__(self, iface):
self.iface = iface
self.plugin_dir = os.path.dirname(__file__)
self.ui = None
QCoreApplication.instance().aboutToQuit.connect(self.unload)
def initGui(self):
"""
Keine GUI-Integration nötig.
"""
pass
# Basis-Navigation neu aufbauen
self.ui = Navigation(self.iface)
# 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):
"""
Keine GUI-Elemente zu entfernen.
"""
pass
if self.ui:
self.ui.remove_all()
self.ui = None

View File

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

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,2 +0,0 @@
from .tab_projekt import TabProjektWidget
from .dockmanager import DockManager

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.QtWidgets import QDockWidget
from qgis.utils import iface
import inspect
class DockManager:
"""
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
default_area = Qt.DockWidgetArea.RightDockWidgetArea
@classmethod
def show(cls, dock_widget, area=None):
# Falls kein Bereich übergeben wurde, verwende den Standardwert
if area is None:
area = cls.default_area
area = area or cls.default_area
# Pluginname automatisch aus dem Modulpfad ableiten (z.B. 'sn_plugin1' → 'plugin1')
caller_module = inspect.getmodule(inspect.stack()[1][0])
full_module_name = caller_module.__name__ # z.B. 'sn_plugin1.main'
plugin_name = full_module_name.split('.')[0] # → 'sn_plugin1'
dock_name = f"sn_dock_{plugin_name.replace('sn_', '')}" # → 'sn_dock_plugin1'
# 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)
# Bestehende Plugin-Docks mit Präfix schließen
for widget in iface.mainWindow().findChildren(QDockWidget):
if widget is not dock_widget and widget.objectName().startswith("sn_dock_"):
iface.removeDockWidget(widget)
widget.deleteLater()
# Neues Dock anzeigen
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()

83
ui/navigation.py Normal file
View File

@@ -0,0 +1,83 @@
from qgis.PyQt.QtWidgets import QAction, QMenu, QToolBar, QActionGroup
class Navigation:
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)
self.toolbar = QToolBar("LNO Sachsen")
self.toolbar.setObjectName("LnoSachsenToolbar")
iface.addToolBar(self.toolbar)
# Gruppe für exklusive Auswahl (nur ein Plugin aktiv)
self.plugin_group = QActionGroup(iface.mainWindow())
self.plugin_group.setExclusive(True)
def add_action(self, text, callback, tooltip="", priority=100):
action = QAction(text, self.iface.mainWindow())
action.setToolTip(tooltip)
action.setCheckable(True) # Button kann aktiv sein
action.triggered.connect(callback)
# Action in Gruppe aufnehmen
self.plugin_group.addAction(action)
# Action mit Priority speichern
self.actions.append((priority, action))
return action
def finalize_menu_and_toolbar(self):
# Sortieren nach Priority
self.actions.sort(key=lambda x: x[0])
# Menüeinträge
self.menu.clear()
for _, action in self.actions:
self.menu.addAction(action)
# Toolbar-Einträge
self.toolbar.clear()
for _, action in self.actions:
self.toolbar.addAction(action)
def set_active_plugin(self, active_action):
# Alle zurücksetzen, dann aktives Plugin markieren
for _, action in self.actions:
action.setChecked(False)
if active_action:
active_action.setChecked(True)
def remove_all(self):
"""Alles entfernen beim Entladen des Basisplugins"""
# Menü entfernen
if self.menu:
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, ""))