mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-11-09 02:54:05 +01:00
refactor(widgets): move _call_later from PlotBase to BECWidget
This commit is contained in:
1 parent
a1f3730c60
commit
fd8707d7f5
4 files changed
+68
-41
No files matched your search
@@ -2,13 +2,14 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import shiboken6
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import QBuffer, QByteArray, QIODevice, QObject, Qt
|
||||
from qtpy.QtCore import QBuffer, QByteArray, QIODevice, QObject, Qt, QTimer
|
||||
from qtpy.QtGui import QFont, QPixmap
|
||||
from qtpy.QtWidgets import QApplication, QFileDialog, QLabel, QVBoxLayout, QWidget
|
||||
|
||||
@@ -128,6 +129,27 @@ class BECWidget(BECConnector):
|
||||
self._update_overlay_theme(theme)
|
||||
self.apply_theme(theme)
|
||||
|
||||
def _call_later(self, msec: int, callback: Callable[[], object]) -> None:
|
||||
"""
|
||||
Call ``callback`` once after ``msec`` milliseconds, but only while the widget is in use.
|
||||
|
||||
Use this instead of a bare ``QTimer.singleShot`` for deferred work that touches the
|
||||
widget: the timer is bound to the widget, so the call is dropped if the widget is
|
||||
deleted first, and it is skipped if the widget has been closed (cleaned up) but not
|
||||
yet deleted.
|
||||
|
||||
Args:
|
||||
msec(int): Delay in milliseconds.
|
||||
callback(Callable[[], object]): Called without arguments.
|
||||
"""
|
||||
|
||||
def _call_unless_cleaned_up():
|
||||
if self._destroyed:
|
||||
return
|
||||
callback()
|
||||
|
||||
QTimer.singleShot(msec, self, _call_unless_cleaned_up)
|
||||
|
||||
def create_busy_state_widget(self) -> QWidget:
|
||||
"""
|
||||
Method to create a custom busy state widget to be shown in the busy overlay.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
from collections.abc import Iterable, Mapping
|
||||
from enum import Enum
|
||||
from typing import Literal, TypeAlias
|
||||
|
||||
@@ -9,7 +9,7 @@ import pyqtgraph as pg
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.scan_data_container import ScanDataContainer
|
||||
from bec_lib.scan_items import ScanItem
|
||||
from qtpy.QtCore import QPoint, QPointF, Qt, QTimer, Signal
|
||||
from qtpy.QtCore import QPoint, QPointF, Qt, Signal
|
||||
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.bec_connector import ConnectionConfig
|
||||
@@ -1375,27 +1375,6 @@ class PlotBase(BECWidget, QWidget):
|
||||
self.crosshair.clear_markers()
|
||||
self.crosshair.update_markers()
|
||||
|
||||
def _call_later(self, msec: int, callback: Callable[[], object]) -> None:
|
||||
"""
|
||||
Call ``callback`` once after ``msec`` milliseconds, but only while the widget is in use.
|
||||
|
||||
Use this instead of a bare ``QTimer.singleShot`` for deferred work that touches the
|
||||
widget: the timer is bound to the widget, so the call is dropped if the widget is
|
||||
deleted first, and it is skipped if the widget has been closed (cleaned up) but not
|
||||
yet deleted.
|
||||
|
||||
Args:
|
||||
msec(int): Delay in milliseconds.
|
||||
callback(Callable[[], object]): Called without arguments.
|
||||
"""
|
||||
|
||||
def _call_unless_cleaned_up():
|
||||
if self._destroyed:
|
||||
return
|
||||
callback()
|
||||
|
||||
QTimer.singleShot(msec, self, _call_unless_cleaned_up)
|
||||
|
||||
def cleanup(self):
|
||||
self.toolbar.cleanup()
|
||||
self.unhook_crosshair(preserve_pin=False)
|
||||
|
||||
@@ -243,3 +243,46 @@ def test_parent_destruction_purges_dead_dispatcher_slots(qtbot, mocked_client, b
|
||||
for slot in bec_dispatcher._registered_slots.values():
|
||||
owner = getattr(slot.cb, "__self__", None)
|
||||
assert not isinstance(owner, QObject) or shiboken6.isValid(owner)
|
||||
|
||||
|
||||
def test_call_later_runs_only_while_widget_is_open(qtbot, mocked_client):
|
||||
"""
|
||||
_call_later runs the callback while the widget is open and skips it once the widget has
|
||||
been closed (cleaned up) or deleted before the delay elapsed.
|
||||
"""
|
||||
open_widget = LifecycleWidget(client=mocked_client)
|
||||
closed_widget = LifecycleWidget(client=mocked_client)
|
||||
deleted_widget = LifecycleWidget(client=mocked_client)
|
||||
calls = []
|
||||
|
||||
open_widget._call_later(10, lambda: calls.append("open"))
|
||||
closed_widget._call_later(10, lambda: calls.append("closed"))
|
||||
deleted_widget._call_later(10, lambda: calls.append("deleted"))
|
||||
closed_widget.close() # closeEvent -> cleanup() before the callback is due
|
||||
deleted_widget.deleteLater()
|
||||
_flush_deferred_deletes(qtbot)
|
||||
|
||||
qtbot.wait(100)
|
||||
assert calls == ["open"]
|
||||
|
||||
for widget in (open_widget, closed_widget):
|
||||
widget.close()
|
||||
widget.deleteLater()
|
||||
_flush_deferred_deletes(qtbot)
|
||||
|
||||
|
||||
def test_call_later_dropped_when_parent_destruction_deletes_widget(qtbot, mocked_client):
|
||||
"""
|
||||
Destroying a plain parent deletes the BECWidget child without a close event, so the
|
||||
cleaned-up flag is never set; the timer is bound to the widget and Qt drops the call.
|
||||
"""
|
||||
parent = QWidget()
|
||||
child = LifecycleWidget(parent=parent, client=mocked_client)
|
||||
calls = []
|
||||
|
||||
child._call_later(10, lambda: calls.append("child"))
|
||||
parent.deleteLater()
|
||||
_flush_deferred_deletes(qtbot)
|
||||
|
||||
qtbot.wait(100)
|
||||
assert calls == []
|
||||
@@ -792,20 +792,3 @@ def test_limits_accept_fractional_values(qtbot, mocked_client):
|
||||
pb.y_limits = (-1.75, 3.5)
|
||||
assert (pb.x_limits.x(), pb.x_limits.y()) == (0.5, 9.25)
|
||||
assert (pb.y_limits.x(), pb.y_limits.y()) == (-1.75, 3.5)
|
||||
|
||||
|
||||
def test_call_later_skips_callback_once_widget_is_closed(qtbot, mocked_client):
|
||||
"""
|
||||
PlotBase._call_later runs the deferred callback while the widget is open and skips it
|
||||
when the widget has been closed (cleaned up) before the delay elapsed.
|
||||
"""
|
||||
open_pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
closed_pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
calls = []
|
||||
|
||||
open_pb._call_later(10, lambda: calls.append("open"))
|
||||
closed_pb._call_later(10, lambda: calls.append("closed"))
|
||||
closed_pb.close() # closeEvent -> cleanup() before the callback is due
|
||||
|
||||
qtbot.wait(100)
|
||||
assert calls == ["open"]
|
||||
Reference in new issue
Block a user