Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab51838db5 | ||
|
|
e3e858233d | ||
|
|
cc2d5c6200 | ||
|
|
b2ee167132 | ||
|
|
600a3c7aff | ||
|
|
8f9cf4c9fb | ||
|
|
18573cb3be | ||
|
|
6b0d94c54e |
+14
@@ -1,3 +1,17 @@
|
||||
"""sn_plan41 – QGIS-Plugin für thematische Linklisten und Datenquellen.
|
||||
Verwaltet Excel-basierte Link-Listen und Fachdatenabruf.
|
||||
"""
|
||||
|
||||
|
||||
def classFactory(iface):
|
||||
"""Factory-Funktion für QGIS.
|
||||
Wird von QGIS aufgerufen, um die Plugin-Instanz zu erstellen.
|
||||
|
||||
Args:
|
||||
iface: QGIS-Schnittstellenobjekt
|
||||
|
||||
Returns:
|
||||
Plan41: Instanziiertes Plugin
|
||||
"""
|
||||
from .main import Plan41
|
||||
return Plan41(iface)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
---
|
||||
Version 26.4.2-testing:
|
||||
---
|
||||
Version 26.04.1-unstable:
|
||||
---
|
||||
Version 26.3.3-unstable:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# sn_plan41/main.py
|
||||
"""sn_plan41/main.py – Einstiegspunkt des sn_plan41 Plugins.
|
||||
Orchestriert die UI und den Datenabruf für Thematische Linklisten.
|
||||
"""
|
||||
|
||||
from qgis.utils import plugins
|
||||
from sn_basis.ui.dockmanager import DockManager
|
||||
@@ -8,6 +10,9 @@ from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||
|
||||
|
||||
class Plan41:
|
||||
"""Einstiegspunkt des sn_plan41-Plugins.
|
||||
Verwaltet die DockWidget-Initialisierung und den Datenflusss.
|
||||
"""
|
||||
def __init__(self, iface):
|
||||
self.iface = iface
|
||||
self.pruefmanager=Pruefmanager(ui_modus="qgis")
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
[general]
|
||||
version=26.04.1-unstable
|
||||
version=26.4.2-unstable
|
||||
name=LNO Sachsen | Plan41
|
||||
description=Plugin zum Erzeugen der Pläne nach §38 und §41
|
||||
author=Daniel Helbig, Michael Otto
|
||||
homepage=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Plan41
|
||||
tracker=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_SN_Plan41/issues
|
||||
repository=https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Plugin_Test_Action/src/branch/unstable/
|
||||
|
||||
QGISMaximumVersion=4.99
|
||||
qgisMinimumVersion=3.40
|
||||
experimental=true
|
||||
deprecated=false
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#sn_plan41/modules/listenauswerter.py
|
||||
"""sn_plan41/modules/listenauswerter.py – Validierung von Linklisten.
|
||||
Prüft Zeilen aus DataDict auf Vollständigkeit und Konsistenz.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
||||
from collections.abc import Mapping as _Mapping
|
||||
# Prüfer-Typen (werden als Instanzen erwartet)
|
||||
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
#Testordner
|
||||
"""sn_plan41/tests – Test-Suite für das sn_plan41 Plugin.
|
||||
Enthält Unit-Tests und Integrationstests für Tab-Logik und Datengrabber.
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import unittest
|
||||
from typing import Any
|
||||
|
||||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||
from sn_plan41.modules.listenauswerter import Listenauswerter
|
||||
|
||||
|
||||
class _DummyPruefmanager:
|
||||
def __init__(self) -> None:
|
||||
self.seen = []
|
||||
|
||||
def verarbeite(self, ergebnis: Any):
|
||||
self.seen.append(ergebnis)
|
||||
return ergebnis
|
||||
|
||||
|
||||
class _DummyStilPruefer:
|
||||
def __init__(self, ok: bool = True) -> None:
|
||||
self.ok = ok
|
||||
|
||||
def pruefe(self, _path: str) -> pruef_ergebnis:
|
||||
if self.ok:
|
||||
return pruef_ergebnis(ok=True, meldung="ok", aktion="ok", kontext=None)
|
||||
return pruef_ergebnis(ok=False, meldung="stil fehler", aktion="falsche_endung", kontext=None)
|
||||
|
||||
|
||||
class TestListenauswerter(unittest.TestCase):
|
||||
|
||||
def test_validate_rows_accepts_valid_row_and_normalizes_provider(self):
|
||||
manager = _DummyPruefmanager()
|
||||
validator = Listenauswerter(manager, _DummyStilPruefer(ok=True))
|
||||
|
||||
data = {"rows": [{"ident": "A1", "Inhalt": "Test", "Link": "http://x", "Provider": "wfs"}]}
|
||||
validated, processed = validator.validate_rows(data)
|
||||
|
||||
self.assertEqual(len(validated["rows"]), 1)
|
||||
self.assertEqual(validated["rows"][0]["Provider"], "WFS")
|
||||
self.assertEqual(processed, [])
|
||||
|
||||
def test_validate_rows_rejects_missing_required_fields(self):
|
||||
manager = _DummyPruefmanager()
|
||||
validator = Listenauswerter(manager, _DummyStilPruefer(ok=True))
|
||||
|
||||
data = {"rows": [{"ident": "", "Link": "", "Provider": ""}]}
|
||||
validated, processed = validator.validate_rows(data)
|
||||
|
||||
self.assertEqual(validated["rows"], [])
|
||||
self.assertEqual(len(processed), 1)
|
||||
self.assertEqual(processed[0].aktion, "pflichtfelder_fehlen")
|
||||
|
||||
def test_validate_rows_sets_stildatei_none_on_style_failure(self):
|
||||
manager = _DummyPruefmanager()
|
||||
validator = Listenauswerter(manager, _DummyStilPruefer(ok=False))
|
||||
|
||||
data = {
|
||||
"rows": [
|
||||
{
|
||||
"ident": "A1",
|
||||
"Inhalt": "Thema",
|
||||
"Link": "http://x",
|
||||
"Provider": "WMS",
|
||||
"Stildatei": "broken.qml",
|
||||
}
|
||||
]
|
||||
}
|
||||
validated, processed = validator.validate_rows(data)
|
||||
|
||||
self.assertEqual(len(validated["rows"]), 1)
|
||||
self.assertIsNone(validated["rows"][0]["stildatei"])
|
||||
self.assertEqual(len(processed), 1)
|
||||
self.assertEqual(processed[0].aktion, "falsche_endung")
|
||||
|
||||
def test_validate_rows_handles_non_mapping_rows_edge_case(self):
|
||||
manager = _DummyPruefmanager()
|
||||
validator = Listenauswerter(manager, _DummyStilPruefer(ok=True))
|
||||
|
||||
validated, processed = validator.validate_rows({"rows": ["invalid", 123, None]})
|
||||
|
||||
self.assertEqual(validated, {"rows": []})
|
||||
self.assertEqual(processed, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+83
-73
@@ -1,99 +1,109 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Any, cast
|
||||
from unittest.mock import patch
|
||||
|
||||
from sn_plan41.ui.tab_a_logic import TabALogic # type: ignore
|
||||
from sn_basis.functions.variable_wrapper import get_variable # type: ignore
|
||||
from sn_basis.functions.sys_wrapper import file_exists # type: ignore
|
||||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||
from sn_basis.functions.variable_wrapper import get_variable
|
||||
from sn_plan41.ui.tab_a_logic import TabALogic
|
||||
|
||||
|
||||
class _DummyPruefmanager:
|
||||
def verarbeite(self, ergebnis):
|
||||
return ergebnis
|
||||
|
||||
|
||||
class TestTabALogic(unittest.TestCase):
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 1. Verfahrens-DB setzen und laden
|
||||
# -----------------------------------------------------
|
||||
def test_verfahrens_db_set_and_load(self):
|
||||
logic = TabALogic()
|
||||
def _make_logic(self) -> TabALogic:
|
||||
return TabALogic(
|
||||
pruefmanager=cast(Any, _DummyPruefmanager()),
|
||||
link_pruefer=cast(Any, object()),
|
||||
stil_pruefer=cast(Any, object()),
|
||||
)
|
||||
|
||||
with TemporaryDirectory() as tmp:
|
||||
db_path = Path(tmp) / "test.gpkg"
|
||||
db_path.write_text("")
|
||||
def test_verfahrens_db_set_and_load_roundtrip(self):
|
||||
logic = self._make_logic()
|
||||
logic.set_verfahrens_db("C:/tmp/v_db.gpkg")
|
||||
self.assertEqual(logic.load_verfahrens_db(), "C:/tmp/v_db.gpkg")
|
||||
|
||||
logic.set_verfahrens_db(str(db_path))
|
||||
|
||||
stored = get_variable("verfahrens_db", scope="project")
|
||||
self.assertEqual(stored, str(db_path))
|
||||
|
||||
loaded = logic.load_verfahrens_db()
|
||||
self.assertEqual(loaded, str(db_path))
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 2. Verfahrens-DB löschen
|
||||
# -----------------------------------------------------
|
||||
def test_verfahrens_db_clear(self):
|
||||
logic = TabALogic()
|
||||
|
||||
logic = self._make_logic()
|
||||
logic.set_verfahrens_db(None)
|
||||
self.assertEqual(get_variable("verfahrens_db", scope="project"), "")
|
||||
|
||||
stored = get_variable("verfahrens_db", scope="project")
|
||||
self.assertEqual(stored, "")
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 3. Neue Verfahrens-DB anlegen
|
||||
# -----------------------------------------------------
|
||||
def test_create_new_verfahrens_db(self):
|
||||
logic = TabALogic()
|
||||
|
||||
with TemporaryDirectory() as tmp:
|
||||
db_path = Path(tmp) / "neu.gpkg"
|
||||
|
||||
result = logic.create_new_verfahrens_db(str(db_path))
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(file_exists(db_path))
|
||||
|
||||
stored = get_variable("verfahrens_db", scope="project")
|
||||
self.assertEqual(stored, str(db_path))
|
||||
|
||||
def test_create_new_verfahrens_db_with_none_path(self):
|
||||
logic = TabALogic()
|
||||
result = logic.create_new_verfahrens_db(None)
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 4. Linkliste setzen und laden
|
||||
# -----------------------------------------------------
|
||||
def test_linkliste_set_and_load(self):
|
||||
logic = TabALogic()
|
||||
|
||||
def test_linkliste_set_and_load_existing_path(self):
|
||||
logic = self._make_logic()
|
||||
with TemporaryDirectory() as tmp:
|
||||
link_path = Path(tmp) / "links.xlsx"
|
||||
link_path.write_text("dummy")
|
||||
link_path.write_text("x", encoding="utf-8")
|
||||
|
||||
logic.set_linkliste(str(link_path))
|
||||
self.assertEqual(logic.load_linkliste(), str(link_path))
|
||||
|
||||
stored = get_variable("linkliste", scope="project")
|
||||
self.assertEqual(stored, str(link_path))
|
||||
def test_linkliste_load_returns_none_when_file_missing(self):
|
||||
logic = self._make_logic()
|
||||
logic.set_linkliste("C:/tmp/missing.xlsx")
|
||||
self.assertIsNone(logic.load_linkliste())
|
||||
|
||||
loaded = logic.load_linkliste()
|
||||
self.assertEqual(loaded, str(link_path))
|
||||
@patch("sn_plan41.ui.tab_a_logic.get_plugin_root", return_value="C:/plugins")
|
||||
@patch("sn_plan41.ui.tab_a_logic.join_path", return_value="C:/plugins/sn_plan41/assets/Linkliste.xlsx")
|
||||
def test_resolve_linkliste_empty_uses_standard_and_persists(self, _mock_join_path, _mock_root):
|
||||
logic = self._make_logic()
|
||||
resolved = logic._resolve_linkliste(None)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 5. Linkliste löschen
|
||||
# -----------------------------------------------------
|
||||
def test_linkliste_clear(self):
|
||||
logic = TabALogic()
|
||||
self.assertEqual(resolved, "C:/plugins/sn_plan41/assets/Linkliste.xlsx")
|
||||
self.assertEqual(get_variable("linkliste", scope="project"), "C:/plugins/sn_plan41/assets/Linkliste.xlsx")
|
||||
|
||||
logic.set_linkliste(None)
|
||||
@patch("sn_plan41.ui.tab_a_logic.Dateipruefer")
|
||||
def test_resolve_linkliste_returns_none_when_pruefmanager_rejects(self, mock_dateipruefer):
|
||||
logic = self._make_logic()
|
||||
mock_dateipruefer.return_value.pruefe.return_value = pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung="Ungültig",
|
||||
aktion="datei_nicht_gefunden",
|
||||
kontext=None,
|
||||
)
|
||||
|
||||
stored = get_variable("linkliste", scope="project")
|
||||
self.assertEqual(stored, "")
|
||||
resolved = logic._resolve_linkliste("C:/tmp/missing.xlsx")
|
||||
self.assertIsNone(resolved)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 6. Layer-ID speichern
|
||||
# -----------------------------------------------------
|
||||
def test_resolve_raumfilter_without_filter_returns_none(self):
|
||||
logic = self._make_logic()
|
||||
result = logic._resolve_raumfilter("ohne", "C:/tmp/v_db.gpkg")
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch.object(TabALogic, "_get_verfahrensgebiet_layer", return_value="layer_obj")
|
||||
def test_resolve_raumfilter_verfahrensgebiet_uses_layer_lookup(self, mock_get_layer):
|
||||
logic = self._make_logic()
|
||||
result = logic._resolve_raumfilter("Verfahrensgebiet", "C:/tmp/v_db.gpkg")
|
||||
|
||||
self.assertEqual(result, "layer_obj")
|
||||
mock_get_layer.assert_called_once()
|
||||
|
||||
def test_extract_datenabruf_error_maps_merges_reason_maps(self):
|
||||
logic = self._make_logic()
|
||||
results = [
|
||||
pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung="Head failed",
|
||||
aktion="url_nicht_erreichbar",
|
||||
kontext={"ident": "A1", "thema": "Dienst A", "error": "Timeout"},
|
||||
),
|
||||
pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung="Fetch failed",
|
||||
aktion="datenabruf",
|
||||
kontext={"fehler": {"Dienst B": "HTTP 500", "Dienst A": "ignored"}},
|
||||
),
|
||||
]
|
||||
|
||||
merged = logic._extract_datenabruf_error_maps(results)
|
||||
|
||||
self.assertEqual(merged["by_ident"]["A1"], "Timeout")
|
||||
self.assertEqual(merged["by_thema"]["Dienst A"], "Timeout")
|
||||
self.assertEqual(merged["by_thema"]["Dienst B"], "HTTP 500")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+94
-37
@@ -1,56 +1,113 @@
|
||||
"""
|
||||
Smoke-Tests für TabA UI (sn_plan41/ui/tab_a_ui.py)
|
||||
|
||||
Ziel:
|
||||
- UI kann erstellt werden
|
||||
- Callbacks crashen nicht
|
||||
- Keine Qt-Verhaltensprüfung
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from sn_plan41.ui.tab_a_ui import TabA #type:ignore
|
||||
from sn_basis.functions.variable_wrapper import get_variable
|
||||
from sn_plan41.ui.tab_a_ui import TabA
|
||||
|
||||
|
||||
class _DummySignal:
|
||||
def connect(self, _callback):
|
||||
return None
|
||||
|
||||
|
||||
class _DummyToggleButton:
|
||||
def __init__(self):
|
||||
self.arrow = None
|
||||
self.style = ""
|
||||
|
||||
def setArrowType(self, arrow):
|
||||
self.arrow = arrow
|
||||
|
||||
def setStyleSheet(self, style):
|
||||
self.style = style
|
||||
|
||||
|
||||
class _DummyWidget:
|
||||
def __init__(self):
|
||||
self.visible = None
|
||||
|
||||
def setVisible(self, visible):
|
||||
self.visible = visible
|
||||
|
||||
|
||||
class _DummyFileWidget:
|
||||
def __init__(self):
|
||||
self._path = ""
|
||||
|
||||
def setFilePath(self, path):
|
||||
self._path = path
|
||||
|
||||
def filePath(self):
|
||||
return self._path
|
||||
|
||||
|
||||
class _DummyCombo:
|
||||
def __init__(self, value="Pufferlayer"):
|
||||
self._value = value
|
||||
|
||||
def currentText(self):
|
||||
return self._value
|
||||
|
||||
|
||||
class _DummyPruefmanager:
|
||||
pass
|
||||
|
||||
|
||||
class _DummyDataGrabber:
|
||||
pass
|
||||
|
||||
|
||||
class TestTabAUI(unittest.TestCase):
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 1. UI kann erstellt werden
|
||||
# -----------------------------------------------------
|
||||
def test_tab_a_ui_can_be_created(self):
|
||||
tab = TabA(parent=None,build_ui=False)
|
||||
def _make_tab_without_qt(self):
|
||||
tab = TabA.__new__(TabA)
|
||||
tab.group_button = _DummyToggleButton()
|
||||
tab.group_content = _DummyWidget()
|
||||
tab.optional_button = _DummyToggleButton()
|
||||
tab.optional_content = _DummyWidget()
|
||||
tab.file_widget = _DummyFileWidget()
|
||||
tab.linkliste_widget = _DummyFileWidget()
|
||||
tab._raumfilter_combo = _DummyCombo("ohne")
|
||||
tab.verfahrens_db = None
|
||||
tab.lokale_linkliste = None
|
||||
tab.logic = None
|
||||
tab.pruefmanager = None
|
||||
tab.data_grabber = None
|
||||
return tab
|
||||
|
||||
self.assertIsNotNone(tab)
|
||||
self.assertEqual(tab.tab_title, "Daten")
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 2. Toggle-Callbacks crashen nicht
|
||||
# -----------------------------------------------------
|
||||
def test_tab_a_toggle_callbacks_do_not_crash(self):
|
||||
tab = TabA(parent=None,build_ui=False)
|
||||
def test_tab_a_callbacks_can_be_called_without_crash(self):
|
||||
tab = self._make_tab_without_qt()
|
||||
|
||||
tab._toggle_group(True)
|
||||
tab._toggle_group(False)
|
||||
|
||||
tab._toggle_optional(True)
|
||||
tab._toggle_optional(False)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 3. Datei-Callbacks akzeptieren leere Eingaben
|
||||
# -----------------------------------------------------
|
||||
def test_tab_a_file_callbacks_accept_empty_input(self):
|
||||
tab = TabA(parent=None,build_ui=False)
|
||||
|
||||
tab._on_verfahrens_db_changed("")
|
||||
tab._on_linkliste_changed("")
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 4. Layer-Callback akzeptiert None
|
||||
# -----------------------------------------------------
|
||||
def test_tab_a_layer_callback_accepts_none(self):
|
||||
tab = TabA(parent=None,build_ui=False)
|
||||
self.assertEqual(tab.group_content.visible, False)
|
||||
self.assertEqual(tab.optional_content.visible, False)
|
||||
|
||||
tab._on_layer_changed(None)
|
||||
def test_tab_a_raumfilter_callback_persists_value(self):
|
||||
tab = self._make_tab_without_qt()
|
||||
|
||||
tab._on_raumfilter_changed("ohne")
|
||||
self.assertEqual(get_variable("Raumfilter", scope="project"), "ohne")
|
||||
|
||||
@patch("sn_plan41.ui.tab_a_ui.TabALogic")
|
||||
def test_set_services_initializes_logic_and_injects_data_grabber(self, mock_logic_cls):
|
||||
tab = self._make_tab_without_qt()
|
||||
mock_logic = mock_logic_cls.return_value
|
||||
|
||||
pm = _DummyPruefmanager()
|
||||
dg = _DummyDataGrabber()
|
||||
|
||||
tab.set_services(pruefmanager=pm, data_grabber=dg)
|
||||
|
||||
self.assertIs(tab.pruefmanager, pm)
|
||||
self.assertIs(tab.data_grabber, dg)
|
||||
self.assertIs(tab.logic, mock_logic)
|
||||
self.assertIs(tab.logic.data_grabber, dg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+20
-1
@@ -1,4 +1,7 @@
|
||||
#sn_plan41/ui/dockwidget.py
|
||||
"""sn_plan41/ui/dockwidget.py – Haupt-DockWidget für sn_plan41.
|
||||
Verwaltet die Tabs für Linkvalidierung, Druck und Einstellungen.
|
||||
"""
|
||||
|
||||
from sn_basis.ui.tabs.settings_tab import SettingsTab
|
||||
from sn_plan41.ui.tab_a_ui import TabA
|
||||
from sn_basis.ui.tabs.print_tab import PrintTab
|
||||
@@ -8,9 +11,25 @@ from sn_basis.functions.message_wrapper import error
|
||||
|
||||
|
||||
class DockWidget(BaseDockWidget):
|
||||
"""Haupt-DockWidget für sn_plan41 mit Tab-Verwaltung.
|
||||
|
||||
Attributes:
|
||||
tabs: Liste von Tab-Klassen (TabA, PrintTab, SettingsTab)
|
||||
pruefmanager: Prüfmanager-Instanz für Validierung
|
||||
data_grabber: DataGrabber-Instanz für Fachdatenabruf
|
||||
"""
|
||||
|
||||
tabs = [TabA, PrintTab, SettingsTab]
|
||||
|
||||
def __init__(self, parent=None, subtitle="", pruefmanager=None, data_grabber=None):
|
||||
"""Initialisiert das DockWidget mit Services und Tabs.
|
||||
|
||||
Args:
|
||||
parent: Übergeordnetes Widget
|
||||
subtitle: Untertitel des DockWidgets
|
||||
pruefmanager: Prüfmanager-Instanz
|
||||
data_grabber: DataGrabber-Instanz
|
||||
"""
|
||||
super().__init__(parent, subtitle)
|
||||
|
||||
# Services als Attribute speichern
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
"""sn_plan41/ui/tabs/tab_b.py – Beispiel-Tab für zusätzliche Funktionen.
|
||||
Ein Tab für zukünftige Erweiterungen.
|
||||
"""
|
||||
|
||||
from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QTextEdit
|
||||
|
||||
class TabB(QWidget):
|
||||
"""Beispiel-Tab für sn_plan41.
|
||||
|
||||
Attributes:
|
||||
tab_title: Anzeigetitel des Tabs ("Tab B")
|
||||
"""
|
||||
|
||||
tab_title = "Tab B"
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialisiert den Tab mit Beispielinhalt.
|
||||
|
||||
Args:
|
||||
parent: Übergeordnetes Widget
|
||||
"""
|
||||
super().__init__(parent)
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(QLabel("Plugin2 – Tab B"))
|
||||
|
||||
Reference in New Issue
Block a user