Compare commits

...

9 Commits

Author SHA1 Message Date
Michael Otto
6a4c699940 Fehler beim Entladen / Update behoben. 2025-11-17 12:48:26 +01:00
Michael Otto
6a2ac6640d Menü und Symbolleiste überarbeitet. 2025-11-17 12:23:19 +01:00
Michael Otto
71f6faf915 Aufgeräumt. 2025-11-17 11:29:40 +01:00
Michael Otto
dac87fd1ba Refactoring Aufgrund Fehler beim Beenden. 2025-11-17 10:06:00 +01:00
Michael Otto
a1cbf92640 Anpassung an Qt6, Fehler beim Beenden behoben 2025-11-13 09:32:29 +01:00
350154ac0c Merge pull request 'Navigation in sn_basis ausgelagert' (#2) from refactor/navigation into main
Reviewed-on: #2
2025-10-09 13:57:50 +02:00
Michael Otto
fa0fc55699 Navigation in sn_basis ausgelagert 2025-10-09 13:57:01 +02:00
7adbbe07e4 Merge pull request 'Dockmanager in sn_basis ausgelagert' (#1) from refactor/dockmanager into main
Reviewed-on: #1
2025-10-09 12:57:23 +02:00
Michael Otto
8ac1ae07f4 Dockmanager in sn_basis ausgelagert 2025-10-09 12:52:06 +02:00
19 changed files with 73 additions and 438 deletions

View File

@@ -1,31 +1,3 @@
# -*- coding: utf-8 -*-
# import debugpy
# _debugger_started = False
def classFactory(iface):
from .main import Plan41
# start_debugger()
return Plan41(iface)
# def start_debugger():
# global _debugger_started
# if _debugger_started:
# return # Schon gestartet nichts tun
# try:
# debugpy.listen(5678)
# _debugger_started = True
# print("Debugger wartet auf Verbindung...")
# except RuntimeError:
# print("Debugger läuft bereits Verbindung wird erwartet...")
# if debugpy.is_client_connected():
# print("Debugger verbunden Plugin läuft")
# else:
# try:
# debugpy.wait_for_client()
# print("Debugger verbunden Plugin läuft")
# except RuntimeError as e:
# print(f"Fehler beim Warten auf Debugger: {e}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

101
main.py
View File

@@ -1,81 +1,52 @@
# Import grundlegender Qt- und QGIS-Komponenten
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication, Qt
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QMessageBox
from qgis.utils import plugins
from sn_basis.ui.dockmanager import DockManager
from .ui.dockwidget import DockWidget
# Import der generierten Ressourcen (z.B. Icons)
from .resources import *
# Import der gemeinsamen UI-Klasse für Menü und Symbolleiste
from .shared import UI
from .shared import DockManager
# Import des Dockwidgets, das beim Ausführen des Plugins angezeigt wird
from .ui.sn_plan41_dockwidget import Plan41DockWidget
import os.path
class Plan41:
"""
Hauptklasse des Plugins. Verwaltet die Initialisierung, GUI-Integration und das Dockwidget.
"""
def __init__(self, iface):
# iface: QGIS-Schnittstelle zur Interaktion mit der Anwendung
self.iface = iface
self.plugin_dir = os.path.dirname(__file__) # Pfad zum Plugin-Verzeichnis
self.action = None
self.dockwidget = None
self.actions = [] # Platzhalter für spätere Aktionsverwaltung (optional)
self.pluginIsActive = False # Statusflag, ob das Plugin aktiv ist
self.dockwidget = None # Referenz auf das Dockwidget
# Namen automatisch aus Klassennamen ableiten
self.plugin_name = self.__class__.__name__
self.dock_name = f"sn_dock_{self.plugin_name.lower()}"
def initGui(self):
"""
Wird beim Laden des Plugins aufgerufen. Fügt Menüeintrag und Symbolleistenaktion hinzu.
"""
self.action_text = "Plan 41" # Einheitlicher Text für Menü und Toolbar
icon = QIcon(":/sn_plugin1/icons/icon.png") # Icon aus Ressourcen laden
self.ui = UI() # Gemeinsame UI-Instanz für Menü und Toolbar
self.ui.add_action(self.action_text, self.run, icon, tooltip="Öffnet Plan41")
def onClosePlugin(self):
"""
Wird aufgerufen, wenn das Dockwidget geschlossen wird. Setzt den Aktivitätsstatus zurück.
"""
self.dockwidget.closingPlugin.disconnect(self.onClosePlugin)
self.pluginIsActive = False
basis = plugins.get("sn_basis")
if basis and basis.ui:
self.action = basis.ui.add_action(
self.plugin_name,
self.run,
tooltip=f"Öffnet {self.plugin_name}",
priority=20
)
basis.ui.finalize_menu_and_toolbar()
def unload(self):
"""
Wird beim Deaktivieren des Plugins aufgerufen. Entfernt Menü- und Toolbar-Eintrag.
"""
self.ui.remove_action(self.action_text)
if self.dockwidget:
self.iface.removeDockWidget(self.dockwidget)
self.dockwidget.deleteLater()
self.dockwidget = None
if self.action:
basis = plugins.get("sn_basis")
if basis and basis.ui:
# Action aus Menü und Toolbar entfernen
basis.ui.remove_action(self.action)
self.action = None
def run(self):
"""
Wird beim Klick auf Menüeintrag oder Symbolleistenaktion ausgeführt.
Zeigt das Dockwidget an.
"""
self.dockwidget = DockWidget(self.iface.mainWindow(), subtitle=self.plugin_name)
self.dockwidget.setObjectName(self.dock_name)
if 'sn_basis' not in plugins:
QMessageBox.warning(None, "Abhängigkeit fehlt",
"Das Plugin 'LNO Sachsen | Basisfunktionen' ist nicht installiert oder nicht aktiviert.\nBitte installieren und aktivieren, um fortzufahren.")
return # Plugin nicht starten
# Prüfen, ob das eigene Dockwidget existiert und aktuell sichtbar ist
dock_visible = self.dockwidget is not None and self.dockwidget.isVisible()
# Action-Referenz im Dock speichern
self.dockwidget.action = self.action
if not dock_visible:
# Pluginstatus setzen, damit z.B. beim Schließen korrekt zurückgesetzt werden kann
self.pluginIsActive = True
DockManager.show(self.dockwidget)
# Falls noch kein Dockwidget existiert, wird es jetzt erzeugt
if self.dockwidget is None:
self.dockwidget = Plan41DockWidget()
self.dockwidget.closingPlugin.connect(self.onClosePlugin)
# Dock anzeigen und ggf. andere Docks schließen
from .shared.dockmanager import DockManager
DockManager.show(self.dockwidget)
else:
# Falls das eigene Dock bereits sichtbar ist, wird keine Aktion ausgeführt
pass
# Toolbar-Button als aktiv markieren
basis = plugins.get("sn_basis")
if basis and basis.ui:
basis.ui.set_active_plugin(self.action)

View File

@@ -1,21 +1,13 @@
[general]
name=LNO Sachsen | Plan41
qgisMinimumVersion=3.0
description=Dieses Plugin ist ein Test
version=25.10.1
description=Plugin zum Erzeugen der Pläne nach §38 und §41
version=25.11.3
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 zum Erzeugen der Pläne nach §38 und §41
category=Plugins
icon=icon.png
experimental=True
deprecated=False
server=False
homepage=https://entwicklung.vln-sn.de/AG_QGIS/Plugin_SN_Plan41
repository=https://entwicklung.vln-sn.de/AG_QGIS/Repository
supportsQt6=true
experimental=true

View File

@@ -1,101 +0,0 @@
# -*- coding: utf-8 -*-
# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore
qt_resource_data = b"\
\x00\x00\x02\x5f\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\
\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\
\x01\xc7\x6f\xa8\x64\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\xec\x49\x44\
\x41\x54\x58\x85\xed\x96\x4f\x4b\x54\x51\x18\xc6\x7f\x8f\xdc\x60\
\x5c\x95\x21\x8c\x90\xe4\xd4\xa2\x75\x9b\x5c\xe7\x4a\x23\x62\x08\
\xdc\xda\xa6\x0f\x20\x41\x0b\x21\x68\x53\x8b\x96\x2e\x12\xa1\x6d\
\xa0\x5f\x20\x12\xbf\x80\x6e\x5a\x24\x09\x6e\x24\xff\x20\xa1\x09\
\xb3\xc9\x9c\x19\x32\xde\x16\xf7\x1d\x3a\x5c\xef\xf5\xde\x1c\x1d\
\x25\xe6\x85\xcb\xb9\xf7\x37\xef\xb9\xef\x73\xce\x79\xce\x99\x2b\
\xe0\x07\x50\xe2\x62\xa2\x1e\x01\x57\x80\x3e\xa0\xd9\xe1\xe2\x25\
\xe0\x7b\xe4\x0f\x47\x66\xf6\xab\x93\xd5\x25\x45\x00\x3d\x9d\x2c\
\x9a\x16\x5d\x01\x97\x5f\x80\xa4\x3e\x49\x6f\x25\x55\x03\x56\x96\
\x34\x23\x69\xec\x84\x7e\x77\x24\xcd\x4a\x7a\x93\x57\xa3\x01\x94\
\xcc\x8c\xb4\x0b\xb8\x0f\x18\xf0\x22\x60\x0f\x9d\x3d\x4b\xc9\x8f\
\x80\x49\xe0\xc0\x73\xf6\x33\xde\xdb\x0b\xd4\x8b\x2c\xc1\x2d\x6f\
\x37\x02\x36\xe4\xed\x66\x62\xd4\x77\x81\x4f\xc0\x34\xf0\xb9\xc0\
\xbb\x89\xf2\x53\xa8\x14\x15\x00\xbc\x06\xae\x02\x8f\x81\x65\x60\
\xf7\xd4\x02\x24\x8d\x00\xa3\x40\x6b\x9d\x9f\x04\x3e\x78\xe0\xed\
\x53\x49\x4b\x66\x36\xe7\xcf\x2f\x81\x35\x33\xab\x4b\x2a\xe7\x15\
\x6f\x45\xaa\x07\x80\x57\x40\x0d\x38\x02\x7e\xfb\x7d\x2d\x85\x7d\
\xc8\x58\xe3\x32\x05\x3c\x90\x29\x20\x48\xdc\xf4\x51\x85\xec\x1b\
\xb0\x92\xd5\xe7\x5f\x04\x9c\x68\x42\x3f\xaf\x6f\x00\x5b\x01\x2b\
\x01\x03\x21\x6b\x27\xf2\x76\xc1\x20\xb1\x4f\xb6\x03\x56\x01\x94\
\x60\xa7\x8e\x54\x13\x4a\xea\xf1\x42\xf7\x1c\xfd\x94\x74\xdb\xef\
\x87\x03\x36\x64\x66\x6d\xcf\xc4\x31\x0f\x78\x61\x2b\x78\x55\xda\
\xf1\x40\xd6\x36\xfc\x02\x8c\x03\x13\x40\x15\x98\x02\xbe\xfa\x6f\
\x13\xc0\x23\xe0\x39\xb1\x41\x77\xce\x7c\x06\x02\x95\xef\x7d\x14\
\x03\x01\x9b\x77\x76\xfd\xdc\x77\x01\xf1\x89\xd7\x04\xf6\x12\xec\
\xc0\xcc\x6a\xed\x8e\x1c\xf2\x77\xc1\x4d\x60\xdb\x5c\x72\x20\x20\
\xd5\x78\x92\xde\x49\x32\x49\xc6\xdf\x63\xb8\xbf\xc5\x24\x35\x25\
\xf5\x86\x7d\xf2\xfe\x0b\x16\x80\xf5\x04\xfb\x08\xac\x66\xe4\x2f\
\x12\x4f\x7b\x56\x1c\x92\xf8\xf8\x15\xb1\x07\xae\x99\x59\x23\x47\
\xcc\x99\x86\xcf\x44\xed\xf2\x7f\x11\x75\x05\x74\x05\xfc\xf7\x02\
\x5a\x07\x51\x5d\xd2\x45\xd4\x6f\xfc\x01\xf3\xb6\x83\x46\x54\x23\
\xdb\x20\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
"
qt_resource_name = b"\
\x00\x0a\
\x07\x34\x98\xd1\
\x00\x73\
\x00\x6e\x00\x5f\x00\x70\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x31\
\x00\x05\
\x00\x6f\xa6\x53\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x73\
\x00\x08\
\x0a\x61\x5a\xa7\
\x00\x69\
\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
"
qt_resource_struct_v1 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
"
qt_resource_struct_v2 = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x99\x28\x6e\x23\x10\
"
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
if qt_version < [5, 8, 0]:
rcc_version = 1
qt_resource_struct = qt_resource_struct_v1
else:
rcc_version = 2
qt_resource_struct = qt_resource_struct_v2
def qInitResources():
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()

View File

@@ -1,5 +0,0 @@
<RCC>
<qresource prefix="/sn_plugin1" >
<file>icons/icon.png</file>
</qresource>
</RCC>

View File

@@ -1,11 +0,0 @@
@echo off
REM Wechsle ins Plugin-Hauptverzeichnis (eine Ebene über /scripts)
cd /d %~dp0\..
call "C:\Program Files\QGIS 3.34.5\bin\o4w_env.bat"
REM Kompiliere die Ressourcen-Datei
pyrcc5 resources.qrc -o resources.py
@echo on
echo Ressourcen wurden erfolgreich kompiliert.

View File

@@ -1,2 +0,0 @@
from .ui import UI
from .dockmanager import DockManager

View File

@@ -1,60 +0,0 @@
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
@classmethod
def show(cls, dock_widget, area=None):
# Falls kein Bereich übergeben wurde, verwende den Standardwert
if area is None:
area = 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)
# 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()

View File

@@ -1,75 +0,0 @@
from qgis.PyQt.QtWidgets import QMenu, QToolBar, QAction
from qgis.PyQt.QtGui import QIcon
from qgis.utils import iface
_shared_toolbar = None # globale Toolbar-Instanz
_shared_menu = None # globale Menü-Instanz
class UI:
TITLE = "LNO Sachsen"
def __init__(self):
self.menu = self._get_or_create_menu()
self.toolbar = self._get_or_create_toolbar()
def _get_or_create_menu(self):
global _shared_menu
if _shared_menu:
return _shared_menu
menubar = iface.mainWindow().menuBar()
for action in menubar.actions():
if action.menu() and action.text() == self.TITLE:
_shared_menu = action.menu()
return _shared_menu
menu = QMenu(self.TITLE, iface.mainWindow())
menu.setObjectName(self.TITLE)
menubar.addMenu(menu)
_shared_menu = menu
return menu
def _get_or_create_toolbar(self):
global _shared_toolbar
if _shared_toolbar:
return _shared_toolbar
main_window = iface.mainWindow()
toolbar = main_window.findChild(QToolBar, self.TITLE)
if not toolbar:
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)
# Symbolleistenaktion
if not any(a.text() == text for a in self.toolbar.actions()):
action = QAction(icon, text, iface.mainWindow()) if icon else QAction(text, iface.mainWindow())
if tooltip:
action.setToolTip(tooltip)
action.triggered.connect(callback)
self.toolbar.addAction(action)
def remove_action(self, text):
# Menüeintrag entfernen
for act in self.menu.actions():
if act.text() == text:
self.menu.removeAction(act)
break
# Symbolleistenaktion entfernen
for act in self.toolbar.actions():
if act.text() == text:
self.toolbar.removeAction(act)
break

View File

@@ -1 +0,0 @@
from .tab_plan41 import TabPlan41Widget

7
ui/dockwidget.py Normal file
View File

@@ -0,0 +1,7 @@
from sn_basis.ui.tabs.settings_tab import SettingsTab
from sn_plan41.ui.tabs.tab_a import TabA
from sn_plan41.ui.tabs.tab_b import TabB
from sn_basis.ui.base_dockwidget import BaseDockWidget
class DockWidget(BaseDockWidget):
tabs = [TabA, TabB, SettingsTab]

View File

@@ -1,31 +0,0 @@
from qgis.PyQt.QtWidgets import QDockWidget, QTabWidget, QVBoxLayout, QWidget
from qgis.PyQt.QtCore import pyqtSignal
from sn_basis.ui.tab_projekt import TabProjektWidget
from ..ui import TabPlan41Widget
class Plan41DockWidget(QDockWidget):
closingPlugin = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("LNO Sachsen | Plan 41")
container = QWidget()
layout = QVBoxLayout(container)
self.tabWidget = QTabWidget()
layout.addWidget(self.tabWidget)
self.setWidget(container)
# Tabs hinzufügen
self.tabWidget.addTab(TabPlan41Widget(self), "Plan 41")
self.tabWidget.addTab(TabProjektWidget(self), "Projekt")
def closeEvent(self, event):
self.closingPlugin.emit()
event.accept()

View File

@@ -1,12 +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_plan41.ui'))
class TabPlan41Widget(QWidget, FORM_CLASS):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)

View File

@@ -1,32 +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>538</width>
<height>295</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>40</x>
<y>50</y>
<width>47</width>
<height>13</height>
</rect>
</property>
<property name="text">
<string>Plan41</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

12
ui/tabs/tab_a.py Normal file
View File

@@ -0,0 +1,12 @@
from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit
class TabA(QWidget):
tab_title = "Tab A"
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout()
layout.addWidget(QLabel("Plugin2 Tab A"))
layout.addWidget(QLineEdit("Feld A1"))
layout.addWidget(QLineEdit("Feld A2"))
self.setLayout(layout)

11
ui/tabs/tab_b.py Normal file
View File

@@ -0,0 +1,11 @@
from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QTextEdit
class TabB(QWidget):
tab_title = "Tab B"
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout()
layout.addWidget(QLabel("Plugin2 Tab B"))
layout.addWidget(QTextEdit("Mehrzeiliger Text für Plugin2"))
self.setLayout(layout)