0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

refactor: BECWidget is a mixin based on BECConnector, for each QWidget in BEC

Handles closeEvent() and RPC registering/unregistering
This commit is contained in:
2024-07-16 16:36:46 +02:00
parent d64758f268
commit c7feb6952d
22 changed files with 62 additions and 103 deletions

View File

@ -1,6 +1,7 @@
from __future__ import annotations
from bec_widgets.utils import BECConnector, ConnectionConfig
from bec_widgets.utils import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget
class DeviceInputConfig(ConnectionConfig):
@ -9,7 +10,7 @@ class DeviceInputConfig(ConnectionConfig):
arg_name: str | None = None
class DeviceInputBase(BECConnector):
class DeviceInputBase(BECWidget):
"""
Mixin class for device input widgets. This class provides methods to get the device list and device object based
on the current text of the widget.
@ -120,6 +121,3 @@ class DeviceInputBase(BECConnector):
"""
if device not in self.get_device_list(self.config.device_filter):
raise ValueError(f"Device {device} is not valid.")
def cleanup(self):
super().cleanup()

View File

@ -2,10 +2,11 @@ from bec_lib.endpoints import MessageEndpoints
from qtpy.QtCore import Qt, Slot
from qtpy.QtWidgets import QHeaderView, QTableWidget, QTableWidgetItem, QWidget
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget
class BECQueue(BECConnector, QTableWidget):
class BECQueue(BECWidget, QTableWidget):
"""
Widget to display the BEC queue.
"""

View File

@ -13,7 +13,7 @@ from bec_lib.utils.import_utils import lazy_import_from
from qtpy.QtCore import QObject, QTimer, Signal, Slot
from qtpy.QtWidgets import QHBoxLayout, QTreeWidget, QTreeWidgetItem, QWidget
from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme
from bec_widgets.widgets.bec_status_box.status_item import StatusItem
@ -57,7 +57,7 @@ class BECServiceStatusMixin(QObject):
self.services_update.emit(self.client._services_info, self.client._services_metric)
class BECStatusBox(BECConnector, QWidget):
class BECStatusBox(BECWidget, QWidget):
"""An autonomous widget to display the status of BEC services.
Args:
@ -290,15 +290,6 @@ class BECStatusBox(BECConnector, QWidget):
if objects["item"] == item:
objects["widget"].show_popup()
def closeEvent(self, event):
"""Upon closing the widget, clean up the BECStatusBox and the QWidget.
Args:
event: The close event.
"""
super().cleanup()
super().closeEvent(event)
def main():
"""Main method to run the BECStatusBox widget."""

View File

@ -8,11 +8,11 @@ from qtpy.QtGui import QDoubleValidator
from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme
class DeviceBox(BECConnector, QWidget):
class DeviceBox(BECWidget, QWidget):
device_changed = Signal(str, str)
def __init__(self, parent=None, device=None, *args, **kwargs):

View File

@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
from qtpy.QtWidgets import QComboBox
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase, DeviceInputConfig
if TYPE_CHECKING:
@ -82,11 +83,3 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
if device_obj is None:
raise ValueError(f"Device {device_name} is not found.")
return device_obj
def cleanup(self):
"""Cleanup the widget."""
super().cleanup()
def closeEvent(self, event):
super().cleanup()
return QComboBox.closeEvent(self, event)

View File

@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
from qtpy.QtCore import QSize
from qtpy.QtWidgets import QCompleter, QLineEdit, QSizePolicy
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase, DeviceInputConfig
if TYPE_CHECKING:
@ -33,8 +34,8 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
default: str | None = None,
arg_name: str | None = None,
):
super().__init__(client=client, config=config, gui_id=gui_id)
QLineEdit.__init__(self, parent=parent)
DeviceInputBase.__init__(self, client=client, config=config, gui_id=gui_id)
self.completer = QCompleter(self)
self.setCompleter(self.completer)
@ -94,11 +95,3 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
if device_obj is None:
raise ValueError(f"Device {device_name} is not found.")
return device_obj
def cleanup(self):
"""Cleanup the widget."""
super().cleanup()
def closeEvent(self, event):
super().cleanup()
return QLineEdit.closeEvent(self, event)

View File

@ -6,7 +6,8 @@ from pydantic import Field
from pyqtgraph.dockarea import Dock
from bec_widgets.cli.rpc_wigdet_handler import widget_handler
from bec_widgets.utils import BECConnector, ConnectionConfig, GridLayoutManager
from bec_widgets.utils import ConnectionConfig, GridLayoutManager
from bec_widgets.utils.bec_widget import BECWidget
if TYPE_CHECKING:
from qtpy.QtWidgets import QWidget
@ -24,7 +25,7 @@ class DockConfig(ConnectionConfig):
)
class BECDock(BECConnector, Dock):
class BECDock(BECWidget, Dock):
USER_ACCESS = [
"_config_dict",
"_rpc_id",
@ -91,7 +92,7 @@ class BECDock(BECConnector, Dock):
super().float()
@property
def widget_list(self) -> list[BECConnector]:
def widget_list(self) -> list[BECWidget]:
"""
Get the widgets in the dock.
@ -101,7 +102,7 @@ class BECDock(BECConnector, Dock):
return self.widgets
@widget_list.setter
def widget_list(self, value: list[BECConnector]):
def widget_list(self, value: list[BECWidget]):
self.widgets = value
def hide_title_bar(self):
@ -153,13 +154,13 @@ class BECDock(BECConnector, Dock):
def add_widget(
self,
widget: BECConnector | str,
widget: BECWidget | str,
row=None,
col=0,
rowspan=1,
colspan=1,
shift: Literal["down", "up", "left", "right"] = "down",
) -> BECConnector:
) -> BECWidget:
"""
Add a widget to the dock.
@ -236,8 +237,8 @@ class BECDock(BECConnector, Dock):
Clean up the dock, including all its widgets.
"""
for widget in self.widgets:
if hasattr(widget, "cleanup"):
widget.cleanup()
widget.cleanup()
self.widgets.clear()
super().cleanup()
def close(self):

View File

@ -9,7 +9,8 @@ from qtpy.QtCore import Qt
from qtpy.QtGui import QPainter, QPaintEvent
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
from bec_widgets.utils.bec_widget import BECWidget
from .dock import BECDock, DockConfig
@ -21,7 +22,7 @@ class DockAreaConfig(ConnectionConfig):
)
class BECDockArea(BECConnector, DockArea):
class BECDockArea(BECWidget, DockArea):
USER_ACCESS = [
"_config_dict",
"panels",
@ -227,6 +228,7 @@ class BECDockArea(BECConnector, DockArea):
self.attach_all()
for dock in dict(self.docks).values():
dock.remove()
self.docks.clear()
def cleanup(self):
"""

View File

@ -12,7 +12,8 @@ from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtWidgets import QWidget
from typeguard import typechecked
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme
from bec_widgets.widgets.figure.plots.image.image import BECImageShow, ImageConfig
from bec_widgets.widgets.figure.plots.motor_map.motor_map import BECMotorMap, MotorMapConfig
@ -108,7 +109,7 @@ class WidgetHandler:
return widget
class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
USER_ACCESS = [
"_rpc_id",
"_config_dict",
@ -728,14 +729,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
"""Clear all widgets from the figure and reset to default state"""
for widget in list(self._widgets.values()):
widget.remove()
# self.clear()
self._widgets = defaultdict(dict)
self._widgets.clear()
self.grid = []
theme = self.config.theme
self.config = FigureConfig(
widget_class=self.__class__.__name__, gui_id=self.gui_id, theme=theme
)
# def cleanup(self):
# self.clear_all()
# super().cleanup()

View File

@ -596,7 +596,4 @@ class BECImageShow(BECPlotBase):
self.bec_dispatcher.disconnect_slot(
self.on_image_update, MessageEndpoints.device_monitor(monitor)
)
for image in self.images:
image.cleanup()
super().cleanup()
self.images.clear()

View File

@ -518,4 +518,3 @@ class BECMotorMap(BECPlotBase):
def cleanup(self):
"""Cleanup the widget."""
self._disconnect_current_motors()
super().cleanup()

View File

@ -296,9 +296,4 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
def remove(self):
"""Remove the plot widget from the figure."""
if self.figure is not None:
self.cleanup()
self.figure.remove(widget_id=self.gui_id)
def cleanup(self):
"""Cleanup the plot widget."""
super().cleanup()

View File

@ -1368,6 +1368,4 @@ class BECWaveform(BECPlotBase):
self.on_async_readback,
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
)
for curve in self.curves:
curve.cleanup()
super().cleanup()
self.curves.clear()

View File

@ -262,4 +262,4 @@ class BECCurve(BECConnector, pg.PlotDataItem):
"""Remove the curve from the plot."""
# self.parent_item.removeItem(self)
self.parent_item.remove_curve(self.name())
self.cleanup()
self.rpc_register.remove_rpc(self)

View File

@ -6,7 +6,7 @@ from qtpy.QtWidgets import QVBoxLayout, QWidget
from bec_widgets.qt_utils.settings_dialog import SettingsDialog
from bec_widgets.qt_utils.toolbar import ModularToolBar
from bec_widgets.utils import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.figure.plots.motor_map.motor_map import MotorMapConfig
from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_settings import MotorMapSettings
@ -18,7 +18,7 @@ from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_toolbar import (
)
class BECMotorMapWidget(BECConnector, QWidget):
class BECMotorMapWidget(BECWidget, QWidget):
USER_ACCESS = [
"change_motors",
"set_max_points",
@ -208,10 +208,6 @@ class BECMotorMapWidget(BECConnector, QWidget):
self.toolbar.widgets["motor_y"].device_combobox.cleanup()
return super().cleanup()
def closeEvent(self, event):
self.cleanup()
QWidget().closeEvent(event)
def main(): # pragma: no cover
from qtpy.QtWidgets import QApplication

View File

@ -288,7 +288,3 @@ class Ring(BECConnector):
value = msg.get("signals").get(device).get("value")
self.set_value(value)
self.parent_progress_widget.update()
def cleanup(self):
self.reset_connection()
super().cleanup()

View File

@ -10,7 +10,8 @@ from qtpy import QtCore, QtGui
from qtpy.QtCore import QSize, Slot
from qtpy.QtWidgets import QSizePolicy, QWidget
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
from bec_widgets.utils import Colors, ConnectionConfig, EntryValidator
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.ring_progress_bar.ring import Ring, RingConfig
@ -66,7 +67,7 @@ class RingProgressBarConfig(ConnectionConfig):
_validate_colormap = field_validator("color_map")(Colors.validate_color_map)
class RingProgressBar(BECConnector, QWidget):
class RingProgressBar(BECWidget, QWidget):
USER_ACCESS = [
"_get_all_rpc",
"_rpc_id",
@ -208,7 +209,7 @@ class RingProgressBar(BECConnector, QWidget):
index(int): Index of the progress bar to remove.
"""
ring = self._find_ring_by_index(index)
ring.cleanup()
ring.reset_connection()
self._rings.remove(ring)
self.config.rings.remove(ring.config)
self.config.num_bars -= 1
@ -622,9 +623,8 @@ class RingProgressBar(BECConnector, QWidget):
def clear_all(self):
for ring in self._rings:
ring.cleanup()
del ring
self._rings = []
ring.reset_connection()
self._rings.clear()
self.update()
self.initialize_bars()
@ -633,6 +633,6 @@ class RingProgressBar(BECConnector, QWidget):
self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
)
for ring in self._rings:
ring.cleanup()
del ring
ring.reset_connection()
self._rings.clear()
super().cleanup()

View File

@ -10,13 +10,13 @@ from qtpy.QtWidgets import (
QWidget,
)
from bec_widgets.utils import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme
from bec_widgets.widgets.scan_control.scan_group_box import ScanGroupBox
from bec_widgets.widgets.stop_button.stop_button import StopButton
class ScanControl(BECConnector, QWidget):
class ScanControl(BECWidget, QWidget):
def __init__(
self, parent=None, client=None, gui_id: str | None = None, allowed_scans: list | None = None
@ -196,10 +196,6 @@ class ScanControl(BECConnector, QWidget):
widget.cleanup()
super().cleanup()
def closeEvent(self, event):
self.cleanup()
return QWidget.closeEvent(self, event)
# Application example
if __name__ == "__main__": # pragma: no cover

View File

@ -1,10 +1,10 @@
from qtpy.QtCore import Slot
from qtpy.QtWidgets import QPushButton
from bec_widgets.utils import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
class StopButton(BECConnector, QPushButton):
class StopButton(BECWidget, QPushButton):
"""A button that stops the current scan."""
def __init__(self, parent=None, client=None, config=None, gui_id=None):

View File

@ -3,7 +3,8 @@ import re
from pydantic import Field, field_validator
from qtpy.QtWidgets import QTextEdit
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import Colors
@ -27,7 +28,7 @@ class TextBoxConfig(ConnectionConfig):
_validate_background_color = field_validator("background_color")(Colors.validate_color)
class TextBox(BECConnector, QTextEdit):
class TextBox(BECWidget, QTextEdit):
USER_ACCESS = ["set_color", "set_text", "set_font_size"]

View File

@ -2,7 +2,7 @@ from qtpy.QtCore import QUrl, qInstallMessageHandler
from qtpy.QtWebEngineWidgets import QWebEngineView
from qtpy.QtWidgets import QApplication
from bec_widgets.utils import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
def suppress_qt_messages(type_, context, msg):
@ -14,7 +14,7 @@ def suppress_qt_messages(type_, context, msg):
qInstallMessageHandler(suppress_qt_messages)
class WebsiteWidget(BECConnector, QWebEngineView):
class WebsiteWidget(BECWidget, QWebEngineView):
"""
A simple widget to display a website
"""

View File

@ -1,13 +1,19 @@
import pytest
from qtpy.QtWidgets import QWidget
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase
from .client_mocks import mocked_client
# DeviceInputBase is meant to be mixed in a QWidget
class DeviceInputWidget(DeviceInputBase, QWidget):
pass
@pytest.fixture
def device_input_base(mocked_client):
widget = DeviceInputBase(client=mocked_client)
widget = DeviceInputWidget(client=mocked_client)
yield widget
@ -15,7 +21,7 @@ def test_device_input_base_init(device_input_base):
assert device_input_base is not None
assert device_input_base.client is not None
assert isinstance(device_input_base, DeviceInputBase)
assert device_input_base.config.widget_class == "DeviceInputBase"
assert device_input_base.config.widget_class == "DeviceInputWidget"
assert device_input_base.config.device_filter is None
assert device_input_base.config.default is None
assert device_input_base.devices == []
@ -23,12 +29,12 @@ def test_device_input_base_init(device_input_base):
def test_device_input_base_init_with_config(mocked_client):
config = {
"widget_class": "DeviceInputBase",
"widget_class": "DeviceInputWidget",
"gui_id": "test_gui_id",
"device_filter": "FakePositioner",
"default": "samx",
}
widget = DeviceInputBase(client=mocked_client, config=config)
widget = DeviceInputWidget(client=mocked_client, config=config)
assert widget.config.gui_id == "test_gui_id"
assert widget.config.device_filter == "FakePositioner"
assert widget.config.default == "samx"