Ein langer Tab (Druck-Tab mit aufgeklapptem Basemap-Bereich) schob den unteren Rand des QGIS-Fensters aus dem Bildschirm, weil Qt für das Dock die Höhe des höchsten Tabs verlangt. BaseDockWidget.setWidget packt jetzt jeden Tab eines QTabWidget in einen QScrollArea; das wirkt auch für Fachplugins, die ihre Tabs selbst aufbauen. QScrollArea/QFrame im qt_wrapper ergänzt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
169 lines
5.8 KiB
Python
169 lines
5.8 KiB
Python
"""Tests für sn_basis/ui/base_dockwidget.py: closeEvent (Issue #65) und Scroll-Bereich je Tab (Basis #104)."""
|
|
|
|
import unittest
|
|
|
|
from sn_basis.functions.qt_wrapper import QScrollArea, QTabWidget
|
|
from sn_basis.ui.base_dockwidget import BaseDockWidget
|
|
|
|
|
|
class _FakeTab:
|
|
def __init__(self, mit_cleanup: bool = True) -> None:
|
|
self.cleanup_aufgerufen = False
|
|
if mit_cleanup:
|
|
self.cleanup = self._cleanup
|
|
|
|
def _cleanup(self) -> None:
|
|
self.cleanup_aufgerufen = True
|
|
|
|
|
|
class _FehlerhafterTab:
|
|
def cleanup(self) -> None:
|
|
raise RuntimeError("cleanup kaputt")
|
|
|
|
|
|
class _FakeTabWidget:
|
|
def __init__(self, tabs) -> None:
|
|
self._tabs = tabs
|
|
|
|
def count(self) -> int:
|
|
return len(self._tabs)
|
|
|
|
def widget(self, index: int):
|
|
return self._tabs[index]
|
|
|
|
|
|
class _FakeAction:
|
|
def __init__(self) -> None:
|
|
self.checked = None
|
|
|
|
def setChecked(self, value: bool) -> None:
|
|
self.checked = value
|
|
|
|
|
|
def _neues_dock() -> BaseDockWidget:
|
|
"""Erzeugt ein BaseDockWidget ohne den echten __init__ (keine Qt-Tab-Erzeugung nötig für closeEvent)."""
|
|
dock = BaseDockWidget.__new__(BaseDockWidget)
|
|
dock.action = None
|
|
return dock
|
|
|
|
|
|
class CloseEventTests(unittest.TestCase):
|
|
def test_ruft_cleanup_aller_tabs_auf(self):
|
|
"""Prüft, dass closeEvent cleanup() auf allen Tabs des Dock-Widgets aufruft."""
|
|
dock = _neues_dock()
|
|
tab1, tab2 = _FakeTab(), _FakeTab()
|
|
dock.widget = lambda: _FakeTabWidget([tab1, tab2])
|
|
|
|
dock.closeEvent(None)
|
|
|
|
self.assertTrue(tab1.cleanup_aufgerufen)
|
|
self.assertTrue(tab2.cleanup_aufgerufen)
|
|
|
|
def test_ignoriert_tabs_ohne_cleanup(self):
|
|
"""Prüft, dass ein Tab ohne cleanup()-Methode nicht zu einem Fehler führt."""
|
|
dock = _neues_dock()
|
|
dock.widget = lambda: _FakeTabWidget([_FakeTab(mit_cleanup=False)])
|
|
|
|
dock.closeEvent(None) # darf keine Ausnahme werfen
|
|
|
|
def test_fehler_in_cleanup_wird_abgefangen_und_andere_tabs_laufen_weiter(self):
|
|
"""Prüft, dass ein Fehler in cleanup() eines Tabs nicht die übrigen Tabs verhindert."""
|
|
dock = _neues_dock()
|
|
funktionierender_tab = _FakeTab()
|
|
dock.widget = lambda: _FakeTabWidget([_FehlerhafterTab(), funktionierender_tab])
|
|
|
|
dock.closeEvent(None)
|
|
|
|
self.assertTrue(funktionierender_tab.cleanup_aufgerufen)
|
|
|
|
def test_setzt_toolbar_action_zurueck(self):
|
|
"""Prüft, dass closeEvent die verknüpfte Toolbar-Action auf 'nicht gedrückt' zurücksetzt."""
|
|
dock = _neues_dock()
|
|
dock.widget = lambda: _FakeTabWidget([])
|
|
action = _FakeAction()
|
|
dock.action = action
|
|
|
|
dock.closeEvent(None)
|
|
|
|
self.assertFalse(action.checked)
|
|
|
|
def test_ohne_action_wird_nichts_zurueckgesetzt(self):
|
|
"""Prüft, dass closeEvent ohne verknüpfte Toolbar-Action keine Ausnahme wirft."""
|
|
dock = _neues_dock()
|
|
dock.widget = lambda: _FakeTabWidget([])
|
|
dock.action = None
|
|
|
|
dock.closeEvent(None) # darf keine Ausnahme werfen
|
|
|
|
|
|
class _TabA:
|
|
tab_title = "Daten"
|
|
|
|
|
|
class _TabB:
|
|
tab_title = "Druck"
|
|
|
|
|
|
class _Dock(BaseDockWidget):
|
|
tabs = [_TabA, _TabB]
|
|
|
|
|
|
class ScrollbereichTests(unittest.TestCase):
|
|
"""Lange Tabs dürfen das QGIS-Fenster nicht über den Bildschirmrand schieben (Basis #104)."""
|
|
|
|
def test_jeder_tab_liegt_in_einem_scrollbereich(self):
|
|
"""Prüft, dass BaseDockWidget jeden Tab in einen mitwachsenden Scroll-Bereich packt, damit ein langer Tab scrollt, statt das Fenster zu verlängern."""
|
|
reiter = _Dock().widget()
|
|
self.assertEqual(reiter.count(), 2)
|
|
for index, klasse in enumerate((_TabA, _TabB)):
|
|
bereich = reiter.widget(index)
|
|
self.assertIsInstance(bereich, QScrollArea)
|
|
self.assertTrue(bereich.widgetResizable())
|
|
self.assertIsInstance(bereich.widget(), klasse)
|
|
self.assertEqual(reiter.tabText(index), klasse.tab_title)
|
|
|
|
def test_fachplugin_mit_eigenem_tabaufbau_bekommt_scrollbereich(self):
|
|
"""Prüft, dass auch ein Fachplugin, das seine Tabs selbst aufbaut und per setWidget einsetzt (wie Plan41, Verfahrensgebiet), Scroll-Bereiche bekommt."""
|
|
dock = _Dock()
|
|
eigene = QTabWidget()
|
|
tab = _TabA()
|
|
eigene.addTab(tab, "Eigener Tab")
|
|
eigene.setCurrentIndex(0)
|
|
dock.setWidget(eigene)
|
|
self.assertIs(dock.widget(), eigene)
|
|
self.assertIsInstance(eigene.widget(0), QScrollArea)
|
|
self.assertIs(eigene.widget(0).widget(), tab)
|
|
self.assertEqual(eigene.tabText(0), "Eigener Tab")
|
|
self.assertEqual(eigene.currentIndex(), 0)
|
|
|
|
def test_scrollbereich_wird_nicht_doppelt_angelegt(self):
|
|
"""Prüft, dass ein Tab, der schon in einem Scroll-Bereich liegt, beim erneuten Einsetzen nicht ein zweites Mal verpackt wird."""
|
|
dock = _Dock()
|
|
reiter = dock.widget()
|
|
bereich = reiter.widget(0)
|
|
dock.setWidget(reiter)
|
|
self.assertIs(reiter.widget(0), bereich)
|
|
|
|
def test_setwidget_none_und_andere_widgets_bleiben_unveraendert(self):
|
|
"""Prüft, dass setWidget(None) und Widgets ohne Tabs ohne Fehler und unverändert eingesetzt werden."""
|
|
dock = _Dock()
|
|
dock.setWidget(None)
|
|
self.assertIsNone(dock.widget())
|
|
einfach = _TabA()
|
|
dock.setWidget(einfach)
|
|
self.assertIs(dock.widget(), einfach)
|
|
|
|
def test_cleanup_erreicht_tabs_im_scrollbereich(self):
|
|
"""Prüft, dass closeEvent cleanup() auch für Tabs aufruft, die in einem Scroll-Bereich liegen."""
|
|
dock = _neues_dock()
|
|
tab = _FakeTab()
|
|
bereich = QScrollArea()
|
|
bereich.setWidget(tab)
|
|
dock.widget = lambda: _FakeTabWidget([bereich])
|
|
dock.closeEvent(None)
|
|
self.assertTrue(tab.cleanup_aufgerufen)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|