2026-09-08 08:28:11 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""Seitenleiste (andockbares Panel) des Plugins — rechts in QGIS.
|
|
|
|
|
|
|
|
|
|
Ersetzt die frühere Werkzeugleiste (Erik, 08.09.2026: „keine Toolbar, sondern eine
|
|
|
|
|
Seitenleiste auf der rechten Seite"). Das Panel ist reine Oberfläche: es hält keinen
|
|
|
|
|
Zustand der Fachlogik, sondern meldet Klicks über Rückrufe (``callbacks``) an
|
|
|
|
|
``VlnKartenPlugin`` und wird von dort über ``set_angemeldet`` / ``set_verfahren``
|
|
|
|
|
umgeschaltet.
|
|
|
|
|
|
|
|
|
|
Aufbau von oben nach unten:
|
|
|
|
|
1. Anmeldung — Status + Knopf (Anmelden/Abmelden)
|
|
|
|
|
2. Verfahren — Suchfeld (filtert) + Auswahlliste
|
2026-09-08 10:06:45 +02:00
|
|
|
3. Layer — Auswahl der Gruppen, laden, Änderungen speichern
|
|
|
|
|
4. Darstellung — Stil speichern (mit Häkchen „für alle Verfahren")
|
2026-09-08 08:28:11 +02:00
|
|
|
5. Statuszeile
|
2026-09-08 10:06:45 +02:00
|
|
|
|
|
|
|
|
Der frühere GeoJSON-Weg (Kopie laden, später als Ganzes hochladen) ist am 08.09.2026
|
|
|
|
|
entfallen (Erik: „das Speichern via OGC einbauen, GeoJSON nicht mehr verwenden"). Über
|
|
|
|
|
die OGC API schreibt QGIS jede Änderung einzeln zurück; „Änderungen speichern" schließt
|
|
|
|
|
nur die offenen Bearbeitungssitzungen.
|
2026-09-08 08:28:11 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from qgis.PyQt.QtCore import Qt
|
|
|
|
|
from qgis.PyQt.QtWidgets import (QCheckBox, QComboBox, QDockWidget, QFrame, QGroupBox,
|
|
|
|
|
QHBoxLayout, QLabel, QLineEdit, QPushButton, QSizePolicy,
|
|
|
|
|
QVBoxLayout, QWidget)
|
|
|
|
|
|
|
|
|
|
PANEL_TITEL = "VLN Karten"
|
|
|
|
|
PANEL_OBJEKT = "VlnKartenPanel"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _klein(widget):
|
|
|
|
|
"""Hinweistexte eine Stufe kleiner und gedämpft — wie in QGIS-Panels üblich.
|
|
|
|
|
|
|
|
|
|
Die Farbe kommt aus der Palette (``palette(mid)``), damit der Text in hellen wie
|
|
|
|
|
dunklen QGIS-Themen lesbar bleibt; ``setEnabled(False)`` wäre dafür der falsche Weg,
|
|
|
|
|
weil es das Widget zugleich unbedienbar macht.
|
|
|
|
|
"""
|
|
|
|
|
schrift = widget.font()
|
|
|
|
|
schrift.setPointSizeF(max(8.0, schrift.pointSizeF() - 1.0))
|
|
|
|
|
widget.setFont(schrift)
|
|
|
|
|
widget.setWordWrap(True)
|
|
|
|
|
widget.setStyleSheet("color: palette(mid);")
|
|
|
|
|
return widget
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VlnPanel(QDockWidget):
|
|
|
|
|
"""Die Seitenleiste. ``callbacks`` bündelt die Rückrufe ins Plugin."""
|
|
|
|
|
|
2026-09-08 10:06:45 +02:00
|
|
|
def __init__(self, parent=None, callbacks=None):
|
2026-09-08 08:28:11 +02:00
|
|
|
super().__init__(PANEL_TITEL, parent)
|
|
|
|
|
self.setObjectName(PANEL_OBJEKT)
|
|
|
|
|
self.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea
|
|
|
|
|
| Qt.DockWidgetArea.LeftDockWidgetArea)
|
|
|
|
|
self._cb = callbacks or {}
|
|
|
|
|
self._verfahren = [] # [(beschriftung, vkz)] — ungefiltert
|
|
|
|
|
self._stumm = False # unterdrückt Signale beim programmatischen Füllen
|
|
|
|
|
|
|
|
|
|
inhalt = QWidget()
|
|
|
|
|
aussen = QVBoxLayout(inhalt)
|
|
|
|
|
aussen.setContentsMargins(8, 8, 8, 8)
|
|
|
|
|
aussen.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
aussen.addWidget(self._bau_anmeldung())
|
|
|
|
|
aussen.addWidget(self._bau_verfahren())
|
2026-09-08 10:06:45 +02:00
|
|
|
aussen.addWidget(self._bau_layer())
|
|
|
|
|
aussen.addWidget(self._bau_darstellung())
|
2026-09-08 08:28:11 +02:00
|
|
|
aussen.addStretch(1)
|
|
|
|
|
aussen.addWidget(self._bau_status())
|
|
|
|
|
|
|
|
|
|
inhalt.setMinimumWidth(260)
|
|
|
|
|
self.setWidget(inhalt)
|
|
|
|
|
self.set_angemeldet(False)
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Aufbau
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def _bau_anmeldung(self):
|
|
|
|
|
kasten = QGroupBox("Anmeldung")
|
|
|
|
|
lage = QVBoxLayout(kasten)
|
|
|
|
|
lage.setSpacing(6)
|
|
|
|
|
self.status_label = QLabel("Nicht angemeldet")
|
|
|
|
|
self.status_label.setWordWrap(True)
|
|
|
|
|
self.login_knopf = QPushButton("Anmelden …")
|
|
|
|
|
self.login_knopf.clicked.connect(lambda: self._ruf("login"))
|
|
|
|
|
self.logout_knopf = QPushButton("Abmelden")
|
|
|
|
|
self.logout_knopf.clicked.connect(lambda: self._ruf("logout"))
|
|
|
|
|
zeile = QHBoxLayout()
|
|
|
|
|
zeile.addWidget(self.login_knopf)
|
|
|
|
|
zeile.addWidget(self.logout_knopf)
|
|
|
|
|
lage.addWidget(self.status_label)
|
|
|
|
|
lage.addLayout(zeile)
|
|
|
|
|
return kasten
|
|
|
|
|
|
|
|
|
|
def _bau_verfahren(self):
|
|
|
|
|
kasten = QGroupBox("Verfahren")
|
|
|
|
|
lage = QVBoxLayout(kasten)
|
|
|
|
|
lage.setSpacing(6)
|
|
|
|
|
self.filter_feld = QLineEdit()
|
|
|
|
|
self.filter_feld.setPlaceholderText("filtern (Name oder Kennzeichen) …")
|
|
|
|
|
self.filter_feld.setClearButtonEnabled(True)
|
|
|
|
|
self.filter_feld.textChanged.connect(self._filter_geaendert)
|
|
|
|
|
self.combo = QComboBox()
|
|
|
|
|
self.combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
|
|
|
self.combo.setToolTip("Teilnehmergemeinschaft, für die geladen und gespeichert wird")
|
|
|
|
|
self.combo.currentIndexChanged.connect(self._auswahl_geaendert)
|
|
|
|
|
lage.addWidget(self.filter_feld)
|
|
|
|
|
lage.addWidget(self.combo)
|
|
|
|
|
return kasten
|
|
|
|
|
|
2026-09-08 10:06:45 +02:00
|
|
|
def _bau_layer(self):
|
|
|
|
|
kasten = QGroupBox("Layer")
|
2026-09-08 08:28:11 +02:00
|
|
|
lage = QVBoxLayout(kasten)
|
|
|
|
|
lage.setSpacing(6)
|
2026-09-08 09:32:56 +02:00
|
|
|
# Jede Sammlung kostet beim Anlegen rund eine halbe Sekunde (der Provider fragt
|
|
|
|
|
# Sammlung, Schema, Stil, OPTIONS und die erste Seite ab). Wer nur einen Teil braucht,
|
2026-09-08 10:06:45 +02:00
|
|
|
# ist mit der Auswahl deutlich schneller: 27 Layer ~14 s, die Vorerhebung allein ~2 s.
|
2026-09-08 09:32:56 +02:00
|
|
|
lage.addWidget(_klein(QLabel("Was geladen wird:")))
|
|
|
|
|
self.gruppen_haken = {}
|
|
|
|
|
for schluessel, beschriftung in (("Verfahren", "Verfahren (Umring, Plan 41, KAS …)"),
|
|
|
|
|
("Vorerhebung", "Vorerhebung"),
|
|
|
|
|
("Maßnahmenblätter", "Maßnahmenblätter")):
|
|
|
|
|
haken = QCheckBox(beschriftung)
|
|
|
|
|
haken.setChecked(True)
|
|
|
|
|
lage.addWidget(haken)
|
|
|
|
|
self.gruppen_haken[schluessel] = haken
|
|
|
|
|
self.ogc_knopf = QPushButton("Layer laden")
|
2026-09-08 10:06:45 +02:00
|
|
|
self.ogc_knopf.setToolTip("Die angehakten Sammlungen als editierbare Layer der OGC API")
|
2026-09-08 08:28:11 +02:00
|
|
|
self.ogc_knopf.clicked.connect(lambda: self._ruf("load_ogc"))
|
|
|
|
|
linie = QFrame()
|
|
|
|
|
linie.setFrameShape(QFrame.Shape.HLine)
|
|
|
|
|
linie.setFrameShadow(QFrame.Shadow.Sunken)
|
2026-09-08 10:06:45 +02:00
|
|
|
self.speichern_knopf = QPushButton("Änderungen speichern")
|
|
|
|
|
self.speichern_knopf.setToolTip("Schließt die offenen Bearbeitungssitzungen aller "
|
|
|
|
|
"geladenen VLN-Layer; jede Änderung geht einzeln an "
|
|
|
|
|
"die API")
|
|
|
|
|
self.speichern_knopf.clicked.connect(lambda: self._ruf("save_edits"))
|
|
|
|
|
lage.addWidget(self.ogc_knopf)
|
2026-09-08 08:28:11 +02:00
|
|
|
lage.addWidget(linie)
|
2026-09-08 10:06:45 +02:00
|
|
|
lage.addWidget(self.speichern_knopf)
|
|
|
|
|
lage.addWidget(_klein(QLabel("Bearbeiten wie gewohnt über den Stift; Speichern schreibt "
|
|
|
|
|
"jede Änderung sofort zurück.")))
|
|
|
|
|
return kasten
|
|
|
|
|
|
|
|
|
|
def _bau_darstellung(self):
|
|
|
|
|
kasten = QGroupBox("Darstellung")
|
|
|
|
|
lage = QVBoxLayout(kasten)
|
|
|
|
|
lage.setSpacing(6)
|
|
|
|
|
self.stil_knopf = QPushButton("Stil des aktiven Layers speichern")
|
|
|
|
|
self.stil_knopf.setToolTip("Den Stil dieses Verfahrens darf jeder speichern, der das "
|
|
|
|
|
"Verfahren sieht")
|
|
|
|
|
self.stil_knopf.clicked.connect(lambda: self._ruf("save_style"))
|
|
|
|
|
self.vorlage_haken = QCheckBox("als Vorlage für alle Verfahren")
|
|
|
|
|
self.vorlage_haken.setToolTip("Gilt dann für diese Art in jedem Verfahren, z. B. für "
|
|
|
|
|
"jede Karte alter Stand — nur für die Gruppe "
|
|
|
|
|
"MODUL_KARTENSTIL")
|
|
|
|
|
lage.addWidget(self.stil_knopf)
|
|
|
|
|
lage.addWidget(self.vorlage_haken)
|
2026-09-08 08:28:11 +02:00
|
|
|
return kasten
|
|
|
|
|
|
|
|
|
|
def _bau_status(self):
|
|
|
|
|
self.status_zeile = _klein(QLabel(""))
|
|
|
|
|
self.status_zeile.setToolTip("letzte Aktion")
|
|
|
|
|
return self.status_zeile
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Zustand (vom Plugin gesetzt)
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def set_angemeldet(self, angemeldet, wer=None):
|
|
|
|
|
self.status_label.setText(("Angemeldet: %s" % wer) if angemeldet and wer
|
|
|
|
|
else ("Angemeldet" if angemeldet else "Nicht angemeldet"))
|
|
|
|
|
self.login_knopf.setVisible(not angemeldet)
|
|
|
|
|
self.logout_knopf.setVisible(angemeldet)
|
2026-09-08 10:06:45 +02:00
|
|
|
for widget in (self.filter_feld, self.combo, self.ogc_knopf, self.speichern_knopf,
|
|
|
|
|
self.stil_knopf, self.vorlage_haken, *self.gruppen_haken.values()):
|
2026-09-08 08:28:11 +02:00
|
|
|
widget.setEnabled(angemeldet)
|
|
|
|
|
if not angemeldet:
|
|
|
|
|
self._stumm = True
|
|
|
|
|
self._verfahren = []
|
|
|
|
|
self.combo.clear()
|
|
|
|
|
self.combo.addItem("— bitte anmelden —", None)
|
|
|
|
|
self.filter_feld.clear()
|
|
|
|
|
self._stumm = False
|
|
|
|
|
|
|
|
|
|
def set_verfahren(self, verfahren):
|
|
|
|
|
"""``verfahren``: Liste von ``{"vkz":…, "name":…}`` aus der API."""
|
|
|
|
|
self._verfahren = [
|
|
|
|
|
("%s — %s" % (v["vkz"], v["name"]) if v.get("name") else str(v["vkz"]), v["vkz"])
|
|
|
|
|
for v in verfahren
|
|
|
|
|
]
|
|
|
|
|
self._liste_fuellen()
|
|
|
|
|
|
|
|
|
|
def set_status(self, text):
|
|
|
|
|
self.status_zeile.setText(text or "")
|
|
|
|
|
|
|
|
|
|
def aktuelles_vkz(self):
|
|
|
|
|
return self.combo.currentData()
|
|
|
|
|
|
|
|
|
|
def aktueller_text(self):
|
|
|
|
|
return self.combo.currentText()
|
|
|
|
|
|
|
|
|
|
def als_vorlage(self):
|
|
|
|
|
return self.vorlage_haken.isChecked()
|
|
|
|
|
|
2026-09-08 09:32:56 +02:00
|
|
|
def gewaehlte_gruppen(self):
|
|
|
|
|
"""Namen der angehakten Layer-Gruppen (wie in ``ogc_layers.GRUPPEN``)."""
|
|
|
|
|
return {name for name, haken in self.gruppen_haken.items() if haken.isChecked()}
|
|
|
|
|
|
2026-09-08 08:28:11 +02:00
|
|
|
def waehle_vkz(self, vkz):
|
|
|
|
|
"""Setzt die Auswahl ohne Rückruf (Wiederherstellen aus der Projektdatei)."""
|
|
|
|
|
self._stumm = True
|
|
|
|
|
index = self.combo.findData(vkz) if vkz else -1
|
|
|
|
|
self.combo.setCurrentIndex(index if index >= 0 else 0)
|
|
|
|
|
self._stumm = False
|
|
|
|
|
|
|
|
|
|
def anzahl_eintraege(self):
|
|
|
|
|
return self.combo.count()
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# Innenleben
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def _liste_fuellen(self, muster=""):
|
|
|
|
|
"""Füllt die Auswahlliste, gefiltert; die bisherige Auswahl bleibt wenn möglich."""
|
|
|
|
|
vorher = self.combo.currentData()
|
|
|
|
|
muster = (muster or "").strip().lower()
|
|
|
|
|
treffer = [(t, v) for t, v in self._verfahren if muster in t.lower()]
|
|
|
|
|
self._stumm = True
|
|
|
|
|
self.combo.clear()
|
|
|
|
|
if not self._verfahren:
|
|
|
|
|
self.combo.addItem("— keine Verfahren —", None)
|
|
|
|
|
elif not treffer:
|
|
|
|
|
self.combo.addItem("— kein Treffer —", None)
|
|
|
|
|
else:
|
|
|
|
|
self.combo.addItem("— Verfahren wählen —", None)
|
|
|
|
|
for beschriftung, vkz in treffer:
|
|
|
|
|
self.combo.addItem(beschriftung, vkz)
|
|
|
|
|
index = self.combo.findData(vorher) if vorher else -1
|
|
|
|
|
self.combo.setCurrentIndex(index if index >= 0 else 0)
|
|
|
|
|
self._stumm = False
|
|
|
|
|
if index < 0 and vorher:
|
|
|
|
|
self._auswahl_geaendert(0)
|
|
|
|
|
|
|
|
|
|
def _filter_geaendert(self, text):
|
|
|
|
|
self._liste_fuellen(text)
|
|
|
|
|
|
|
|
|
|
def _auswahl_geaendert(self, _index):
|
|
|
|
|
if not self._stumm:
|
|
|
|
|
self._ruf("verfahren_gewaehlt", self.combo.currentData())
|
|
|
|
|
|
|
|
|
|
def _ruf(self, name, *args):
|
|
|
|
|
rueckruf = self._cb.get(name)
|
|
|
|
|
if rueckruf:
|
|
|
|
|
rueckruf(*args)
|