mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-09 18:27:52 +01:00
refactor: fix cleanup, add remove methods, improve docs
This commit is contained in:
@@ -50,27 +50,12 @@ class Widgets(str, enum.Enum):
|
||||
|
||||
|
||||
class AbortButton(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""A button that abort the scan."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
@@ -263,7 +248,8 @@ class BECDock(RPCBase):
|
||||
Add a widget to the dock.
|
||||
|
||||
Args:
|
||||
widget(QWidget): The widget to add.
|
||||
widget(QWidget): The widget to add. It can not be BECDock or BECDockArea.
|
||||
name(str): The name of the widget.
|
||||
row(int): The row to add the widget to. If None, the widget will be added to the next available row.
|
||||
col(int): The column to add the widget to.
|
||||
rowspan(int): The number of rows the widget should span.
|
||||
@@ -313,17 +299,6 @@ class BECDock(RPCBase):
|
||||
list: The list of eligible widgets.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def move_widget(self, widget: "QWidget", new_row: "int", new_col: "int"):
|
||||
"""
|
||||
Move a widget to a new position in the layout.
|
||||
|
||||
Args:
|
||||
widget(QWidget): The widget to move.
|
||||
new_row(int): The new row to move the widget to.
|
||||
new_col(int): The new column to move the widget to.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def delete(self, widget_name: "str") -> "None":
|
||||
"""
|
||||
@@ -339,6 +314,12 @@ class BECDock(RPCBase):
|
||||
Remove all widgets from the dock.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def remove(self):
|
||||
"""
|
||||
Remove the dock from the parent dock area.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def attach(self):
|
||||
"""
|
||||
@@ -433,6 +414,12 @@ class BECDockArea(RPCBase):
|
||||
Delete all docks.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def remove(self) -> "None":
|
||||
"""
|
||||
Remove the dock area.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def detach_dock(self, dock_name: "str") -> "BECDock":
|
||||
"""
|
||||
@@ -2214,6 +2201,8 @@ class BECPlotBase(RPCBase):
|
||||
|
||||
|
||||
class BECProgressBar(RPCBase):
|
||||
"""A custom progress bar with smooth transitions. The displayed text can be customized using a template."""
|
||||
|
||||
@rpc_call
|
||||
def set_value(self, value):
|
||||
"""
|
||||
@@ -2265,52 +2254,22 @@ class BECProgressBar(RPCBase):
|
||||
|
||||
|
||||
class BECQueue(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Widget to display the BEC queue."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class BECStatusBox(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""An autonomous widget to display the status of BEC services."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
@@ -2829,6 +2788,8 @@ class Curve(RPCBase):
|
||||
|
||||
|
||||
class DapComboBox(RPCBase):
|
||||
"""The DAPComboBox widget is an extension to the QComboBox with all avaialble DAP model from BEC."""
|
||||
|
||||
@rpc_call
|
||||
def select_y_axis(self, y_axis: str):
|
||||
"""
|
||||
@@ -2867,156 +2828,66 @@ class DarkModeButton(RPCBase):
|
||||
|
||||
|
||||
class DeviceBrowser(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class DeviceComboBox(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Combobox widget for device input with autocomplete for device names."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class DeviceInputBase(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Mixin base class for device input widgets."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class DeviceLineEdit(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Line edit widget for device input with autocomplete for device names."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class DeviceSignalInputBase(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Mixin base class for device signal input widgets."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class LMFitDialog(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Dialog for displaying the fit summary and params for LMFit DAP processes"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class LogPanel(RPCBase):
|
||||
"""Displays a log panel"""
|
||||
|
||||
@rpc_call
|
||||
def set_plain_text(self, text: str) -> None:
|
||||
"""
|
||||
@@ -3079,6 +2950,8 @@ class PositionIndicator(RPCBase):
|
||||
|
||||
|
||||
class PositionerBox(RPCBase):
|
||||
"""Simple Widget to control a positioner in box form"""
|
||||
|
||||
@rpc_call
|
||||
def set_positioner(self, positioner: "str | Positioner"):
|
||||
"""
|
||||
@@ -3090,6 +2963,8 @@ class PositionerBox(RPCBase):
|
||||
|
||||
|
||||
class PositionerBox2D(RPCBase):
|
||||
"""Simple Widget to control two positioners in box form"""
|
||||
|
||||
@rpc_call
|
||||
def set_positioner_hor(self, positioner: "str | Positioner"):
|
||||
"""
|
||||
@@ -3110,31 +2985,18 @@ class PositionerBox2D(RPCBase):
|
||||
|
||||
|
||||
class PositionerBoxBase(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Contains some core logic for positioner box widgets"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class PositionerControlLine(RPCBase):
|
||||
"""A widget that controls a single device."""
|
||||
|
||||
@rpc_call
|
||||
def set_positioner(self, positioner: "str | Positioner"):
|
||||
"""
|
||||
@@ -3146,6 +3008,8 @@ class PositionerControlLine(RPCBase):
|
||||
|
||||
|
||||
class PositionerGroup(RPCBase):
|
||||
"""Simple Widget to control a positioner in box form"""
|
||||
|
||||
@rpc_call
|
||||
def set_positioners(self, device_names: "str"):
|
||||
"""
|
||||
@@ -3156,52 +3020,22 @@ class PositionerGroup(RPCBase):
|
||||
|
||||
|
||||
class ResetButton(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""A button that resets the scan queue."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class ResumeButton(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""A button that continue scan queue."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
@@ -3485,131 +3319,56 @@ class RingProgressBar(RPCBase):
|
||||
|
||||
|
||||
class ScanControl(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class ScanMetadata(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Dynamically generates a form for inclusion of metadata for a scan. Uses the"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class SignalComboBox(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Line edit widget for device input with autocomplete for device names."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class SignalLineEdit(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""Line edit widget for device input with autocomplete for device names."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class StopButton(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
"""A button that stops the current scan."""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
def remove(self):
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def _rpc_id(self) -> "str":
|
||||
"""
|
||||
Get the RPC ID of the widget.
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
|
||||
|
||||
class TextBox(RPCBase):
|
||||
"""A widget that displays text in plain and HTML format"""
|
||||
|
||||
@rpc_call
|
||||
def set_plain_text(self, text: str) -> None:
|
||||
"""
|
||||
@@ -3629,7 +3388,10 @@ class TextBox(RPCBase):
|
||||
"""
|
||||
|
||||
|
||||
class VSCodeEditor(RPCBase): ...
|
||||
class VSCodeEditor(RPCBase):
|
||||
"""A widget to display the VSCode editor."""
|
||||
|
||||
...
|
||||
|
||||
|
||||
class Waveform(RPCBase):
|
||||
@@ -4044,6 +3806,8 @@ class Waveform(RPCBase):
|
||||
|
||||
|
||||
class WebsiteWidget(RPCBase):
|
||||
"""A simple widget to display a website"""
|
||||
|
||||
@rpc_call
|
||||
def set_url(self, url: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
""" Client utilities for the BEC GUI. """
|
||||
"""Client utilities for the BEC GUI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -16,6 +16,8 @@ from typing import TYPE_CHECKING
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_lib.utils.import_utils import lazy_import, lazy_import_from
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
import bec_widgets.cli.client as client
|
||||
from bec_widgets.cli.auto_updates import AutoUpdates
|
||||
@@ -155,7 +157,40 @@ def wait_for_server(client: BECGuiClient):
|
||||
|
||||
|
||||
class WidgetNameSpace:
|
||||
pass
|
||||
def __repr__(self):
|
||||
console = Console()
|
||||
table = Table(title="Available widgets for BEC CLI usage")
|
||||
table.add_column("Widget Name", justify="left", style="magenta")
|
||||
table.add_column("Description", justify="left")
|
||||
for attr, value in self.__dict__.items():
|
||||
docs = value.__doc__
|
||||
docs = docs if docs else "No description available"
|
||||
table.add_row(attr, docs)
|
||||
console.print(table)
|
||||
return f""
|
||||
|
||||
|
||||
class AvailableWidgetsNamespace:
|
||||
"""Namespace for available widgets in the BEC GUI."""
|
||||
|
||||
def __init__(self):
|
||||
for widget in client.Widgets:
|
||||
name = widget.value
|
||||
if name in ["BECDockArea", "BECDock"]:
|
||||
continue
|
||||
setattr(self, name, name)
|
||||
|
||||
def __repr__(self):
|
||||
console = Console()
|
||||
table = Table(title="Available widgets for BEC CLI usage")
|
||||
table.add_column("Widget Name", justify="left", style="magenta")
|
||||
table.add_column("Description", justify="left")
|
||||
for attr_name, _ in self.__dict__.items():
|
||||
docs = getattr(client, attr_name).__doc__
|
||||
docs = docs if docs else "No description available"
|
||||
table.add_row(attr_name, docs if len(docs.strip()) > 0 else "No description available")
|
||||
console.print(table)
|
||||
return "" # f"<{self.__class__.__name__}>"
|
||||
|
||||
|
||||
class BECDockArea(client.BECDockArea):
|
||||
@@ -166,6 +201,17 @@ class BECDockArea(client.BECDockArea):
|
||||
# Add namespaces for DockArea
|
||||
self.elements = WidgetNameSpace()
|
||||
|
||||
def delete(self, dock_name):
|
||||
# Don't close the bec dock area
|
||||
if dock_name == "bec":
|
||||
raise ValueError("Cannot delete the bec dock area")
|
||||
super().delete(dock_name)
|
||||
|
||||
def remove(self):
|
||||
if self._name == "bec":
|
||||
raise ValueError("Cannot delete the bec dock area")
|
||||
super().remove()
|
||||
|
||||
|
||||
class BECGuiClient(RPCBase):
|
||||
"""BEC GUI client class. Container for GUI applications within Python."""
|
||||
@@ -177,6 +223,7 @@ class BECGuiClient(RPCBase):
|
||||
self._default_dock_name = "bec"
|
||||
self._auto_updates_enabled = True
|
||||
self._auto_updates = None
|
||||
self._killed = False
|
||||
self._startup_timeout = 0
|
||||
self._gui_started_timer = None
|
||||
self._gui_started_event = threading.Event()
|
||||
@@ -184,7 +231,7 @@ class BECGuiClient(RPCBase):
|
||||
self._process_output_processing_thread = None
|
||||
self._exposed_dock_areas = []
|
||||
self._registry_state = {}
|
||||
self.available_widgets = client.Widgets
|
||||
self.available_widgets = AvailableWidgetsNamespace()
|
||||
|
||||
def connect_to_gui_server(self, gui_id: str) -> None:
|
||||
"""Connect to a GUI server"""
|
||||
@@ -336,6 +383,7 @@ class BECGuiClient(RPCBase):
|
||||
return rpc_client._run_rpc("_dump")
|
||||
|
||||
def _start(self):
|
||||
self._killed = False
|
||||
self._client.connector.register(
|
||||
MessageEndpoints.gui_registry_state(self._gui_id), cb=self._handle_registry_update
|
||||
)
|
||||
@@ -365,8 +413,12 @@ class BECGuiClient(RPCBase):
|
||||
with wait_for_server(self):
|
||||
rpc_client = RPCBase(gui_id=f"{self._gui_id}:window", parent=self)
|
||||
rpc_client._run_rpc("hide") # pylint: disable=protected-access
|
||||
for window in self._top_level.values():
|
||||
window.hide()
|
||||
# because of the registry callbacks, we may have
|
||||
# dock areas that are already killed, but not yet
|
||||
# removed from the registry state
|
||||
if not self._killed:
|
||||
for window in self._top_level.values():
|
||||
window.hide()
|
||||
|
||||
def show(self):
|
||||
"""Show the GUI window."""
|
||||
@@ -399,6 +451,22 @@ class BECGuiClient(RPCBase):
|
||||
widget = rpc_client._run_rpc("new_dock_area", name) # pylint: disable=protected-access
|
||||
return widget
|
||||
|
||||
def delete(self, name: str) -> None:
|
||||
"""Delete a dock area.
|
||||
|
||||
Args:
|
||||
name(str): The name of the dock area.
|
||||
"""
|
||||
widget = self.windows.get(name)
|
||||
if widget is None:
|
||||
raise ValueError(f"Dock area {name} not found.")
|
||||
widget._run_rpc("close") # pylint: disable=protected-access
|
||||
|
||||
def delete_all(self) -> None:
|
||||
"""Delete all dock areas."""
|
||||
for widget_name in self.windows.keys():
|
||||
self.delete(widget_name)
|
||||
|
||||
def _clear_top_level_widgets(self):
|
||||
self._top_level.clear()
|
||||
for widget_id in self._exposed_dock_areas:
|
||||
@@ -447,19 +515,14 @@ class BECGuiClient(RPCBase):
|
||||
self._add_dock_areas_from_registry()
|
||||
|
||||
def close(self):
|
||||
"""Deprecated. Use kill() instead."""
|
||||
"""Deprecated. Use kill_server() instead."""
|
||||
# FIXME, deprecated in favor of kill, will be removed in the future
|
||||
self.kill()
|
||||
self.kill_server()
|
||||
|
||||
def kill(self) -> None:
|
||||
def kill_server(self) -> None:
|
||||
"""Kill the GUI server."""
|
||||
self._close()
|
||||
|
||||
def _close(self) -> None:
|
||||
"""
|
||||
Close the gui window.
|
||||
"""
|
||||
self._top_level.clear()
|
||||
self._killed = True
|
||||
|
||||
if self._gui_started_timer is not None:
|
||||
self._gui_started_timer.cancel()
|
||||
|
||||
@@ -95,9 +95,21 @@ class {class_name}(RPCBase):"""
|
||||
self.content += f"""
|
||||
class {class_name}(RPCBase):"""
|
||||
|
||||
if cls.__doc__:
|
||||
# We only want the first line of the docstring
|
||||
# But skip the first line if it's a blank line
|
||||
first_line = cls.__doc__.split("\n")[0]
|
||||
if first_line:
|
||||
class_docs = first_line
|
||||
else:
|
||||
class_docs = cls.__doc__.split("\n")[1]
|
||||
self.content += f"""
|
||||
\"\"\"{class_docs}\"\"\"
|
||||
"""
|
||||
if not cls.USER_ACCESS:
|
||||
self.content += """...
|
||||
"""
|
||||
|
||||
for method in cls.USER_ACCESS:
|
||||
is_property_setter = False
|
||||
obj = getattr(cls, method, None)
|
||||
|
||||
@@ -81,7 +81,20 @@ class RPCBase:
|
||||
def __repr__(self):
|
||||
type_ = type(self)
|
||||
qualname = type_.__qualname__
|
||||
return f"<{qualname} object at {hex(id(self))}>"
|
||||
return f"<{qualname} with name: {self.widget_name}>"
|
||||
|
||||
def remove(self):
|
||||
"""
|
||||
Remove the widget.
|
||||
"""
|
||||
self._run_rpc("remove")
|
||||
|
||||
@property
|
||||
def widget_name(self):
|
||||
"""
|
||||
Get the widget name.
|
||||
"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def _root(self):
|
||||
|
||||
@@ -33,9 +33,14 @@ class RPCWidgetHandler:
|
||||
from bec_widgets.utils.plugin_utils import get_custom_classes
|
||||
|
||||
clss = get_custom_classes("bec_widgets")
|
||||
self._widget_classes = {cls.__name__: cls for cls in clss.widgets}
|
||||
self._widget_classes = {
|
||||
cls.__name__: cls
|
||||
for cls in clss.widgets
|
||||
if cls.__name__
|
||||
not in ["BECDockArea", "BECDock"] # Exclude these classes due to hierarchy
|
||||
}
|
||||
|
||||
def create_widget(self, widget_type, name: str, **kwargs) -> BECWidget:
|
||||
def create_widget(self, widget_type, name: str | None = None, **kwargs) -> BECWidget:
|
||||
"""
|
||||
Create a widget from an RPC message.
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ class BECWidgetsCLIServer:
|
||||
for key, val in connections.items()
|
||||
if val.__class__.__name__ == "BECDockArea"
|
||||
}
|
||||
logger.info(f"Broadcasting registry update: {data}")
|
||||
logger.info(f"All registered connections: {list(connections.keys())}")
|
||||
self.client.connector.xadd(
|
||||
MessageEndpoints.gui_registry_state(self.gui_id),
|
||||
msg_dict={"data": messages.GUIRegistryStateMessage(state=data)},
|
||||
|
||||
@@ -209,6 +209,7 @@ class BECConnector:
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
# FIXME some thoughts are required to decide how thhis should work with rpc registry
|
||||
def apply_config(self, config: dict, generate_new_id: bool = True) -> None:
|
||||
"""
|
||||
Apply the configuration to the widget.
|
||||
@@ -221,11 +222,12 @@ class BECConnector:
|
||||
if generate_new_id is True:
|
||||
gui_id = str(uuid.uuid4())
|
||||
self.rpc_register.remove_rpc(self)
|
||||
self.set_gui_id(gui_id)
|
||||
self._set_gui_id(gui_id)
|
||||
self.rpc_register.add_rpc(self)
|
||||
else:
|
||||
self.gui_id = self.config.gui_id
|
||||
|
||||
# FIXME some thoughts are required to decide how thhis should work with rpc registry
|
||||
def load_config(self, path: str | None = None, gui: bool = False):
|
||||
"""
|
||||
Load the configuration of the widget from YAML.
|
||||
@@ -262,8 +264,8 @@ class BECConnector:
|
||||
file_path = os.path.join(path, f"{self.__class__.__name__}_config.yaml")
|
||||
save_yaml(file_path, self._config_dict)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_gui_id(self, gui_id: str) -> None:
|
||||
# @pyqtSlot(str)
|
||||
def _set_gui_id(self, gui_id: str) -> None:
|
||||
"""
|
||||
Set the GUI ID for the widget.
|
||||
|
||||
@@ -309,6 +311,15 @@ class BECConnector:
|
||||
if gui_id and config.gui_id != gui_id: # Recreating config should not overwrite the gui_id
|
||||
self.config.gui_id = gui_id
|
||||
|
||||
def remove(self):
|
||||
"""Cleanup the BECConnector"""
|
||||
if hasattr(self, "close"):
|
||||
self.close()
|
||||
if hasattr(self, "deleteLater"):
|
||||
self.deleteLater()
|
||||
else:
|
||||
self.rpc_register.remove_rpc(self)
|
||||
|
||||
def get_config(self, dict_output: bool = True) -> dict | BaseModel:
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import darkdetect
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import Slot
|
||||
@@ -9,6 +11,9 @@ from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
||||
from bec_widgets.utils.colors import set_theme
|
||||
from bec_widgets.utils.container_utils import WidgetContainerUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_widgets.widgets.containers.dock import BECDock
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
@@ -18,7 +23,9 @@ class BECWidget(BECConnector):
|
||||
# The icon name is the name of the icon in the icon theme, typically a name taken
|
||||
# from fonts.google.com/icons. Override this in subclasses to set the icon name.
|
||||
ICON_NAME = "widgets"
|
||||
USER_ACCESS = ["remove"]
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(
|
||||
self,
|
||||
client=None,
|
||||
@@ -26,6 +33,7 @@ class BECWidget(BECConnector):
|
||||
gui_id: str | None = None,
|
||||
theme_update: bool = False,
|
||||
name: str | None = None,
|
||||
parent_dock: BECDock | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@@ -55,6 +63,7 @@ class BECWidget(BECConnector):
|
||||
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||
raise ValueError(f"Name {name} contains invalid characters.")
|
||||
super().__init__(client=client, config=config, gui_id=gui_id, name=name)
|
||||
self._parent_dock = parent_dock
|
||||
app = QApplication.instance()
|
||||
if not hasattr(app, "theme"):
|
||||
# DO NOT SET THE THEME TO AUTO! Otherwise, the qwebengineview will segfault
|
||||
@@ -95,10 +104,13 @@ class BECWidget(BECConnector):
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup the widget."""
|
||||
# needed here instead of closeEvent, to be checked why
|
||||
# However, all widgets need to call super().cleanup() in their cleanup method
|
||||
self.rpc_register.remove_rpc(self)
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.rpc_register.remove_rpc(self)
|
||||
"""Wrap the close even to ensure the rpc_register is cleaned up."""
|
||||
try:
|
||||
self.cleanup()
|
||||
finally:
|
||||
super().closeEvent(event)
|
||||
super().closeEvent(event) # pylint: disable=no-member
|
||||
|
||||
@@ -121,6 +121,7 @@ class BECDock(BECWidget, Dock):
|
||||
"available_widgets",
|
||||
"delete",
|
||||
"delete_all",
|
||||
"remove",
|
||||
"attach",
|
||||
"detach",
|
||||
]
|
||||
@@ -136,9 +137,11 @@ class BECDock(BECWidget, Dock):
|
||||
closable: bool = True,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
|
||||
if config is None:
|
||||
config = DockConfig(
|
||||
widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id
|
||||
widget_class=self.__class__.__name__,
|
||||
parent_dock_area=parent_dock_area.gui_id if parent_dock_area else None,
|
||||
)
|
||||
else:
|
||||
if isinstance(config, dict):
|
||||
@@ -148,7 +151,7 @@ class BECDock(BECWidget, Dock):
|
||||
client=client, config=config, gui_id=gui_id, name=name
|
||||
) # Name was checked and created in BEC Widget
|
||||
label = CustomDockLabel(text=name, closable=closable)
|
||||
Dock.__init__(self, name=name, label=label, **kwargs)
|
||||
Dock.__init__(self, name=name, label=label, parent=self, **kwargs)
|
||||
# Dock.__init__(self, name=name, **kwargs)
|
||||
|
||||
self.parent_dock_area = parent_dock_area
|
||||
@@ -282,7 +285,8 @@ class BECDock(BECWidget, Dock):
|
||||
Add a widget to the dock.
|
||||
|
||||
Args:
|
||||
widget(QWidget): The widget to add.
|
||||
widget(QWidget): The widget to add. It can not be BECDock or BECDockArea.
|
||||
name(str): The name of the widget.
|
||||
row(int): The row to add the widget to. If None, the widget will be added to the next available row.
|
||||
col(int): The column to add the widget to.
|
||||
rowspan(int): The number of rows the widget should span.
|
||||
@@ -311,8 +315,16 @@ class BECDock(BECWidget, Dock):
|
||||
name = WidgetContainerUtils.generate_unique_name(
|
||||
name=widget_class_name, list_of_names=existing_widgets_parent_dock
|
||||
)
|
||||
# Check that Widget is not BECDock or BECDockArea
|
||||
widget_class_name = widget if isinstance(widget, str) else widget.__class__.__name__
|
||||
if widget_class_name in ["BECDock", "BECDockArea"]:
|
||||
raise ValueError(f"Widget {widget} can not be added to dock.")
|
||||
|
||||
if isinstance(widget, str):
|
||||
widget = cast(BECWidget, widget_handler.create_widget(widget_type=widget, name=name))
|
||||
widget = cast(
|
||||
BECWidget,
|
||||
widget_handler.create_widget(widget_type=widget, name=name, parent_dock=self),
|
||||
)
|
||||
else:
|
||||
widget._name = name # pylint: disable=protected-access
|
||||
|
||||
@@ -354,7 +366,7 @@ class BECDock(BECWidget, Dock):
|
||||
"""
|
||||
Remove the dock from the parent dock area.
|
||||
"""
|
||||
self.parent_dock_area.delete(self.gui_id)
|
||||
self.parent_dock_area.delete(self._name)
|
||||
|
||||
def delete(self, widget_name: str) -> None:
|
||||
"""
|
||||
@@ -364,20 +376,21 @@ class BECDock(BECWidget, Dock):
|
||||
widget_name(str): Delete the widget with the given name.
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
widget = [widget for widget in self.widgets if widget._name == widget_name]
|
||||
if not widget:
|
||||
widgets = [widget for widget in self.widgets if widget._name == widget_name]
|
||||
if len(widgets) == 0:
|
||||
logger.warning(
|
||||
f"Widget with name {widget_name} not found in dock {self.name()}. "
|
||||
f"Checking if gui_id was passed as widget_name."
|
||||
)
|
||||
# Try to find the widget in the RPC register
|
||||
# Try to find the widget in the RPC register, maybe the gui_id was passed as widget_name
|
||||
widget = self.rpc_register.get_rpc_by_id(widget_name)
|
||||
if widget is None:
|
||||
logger.warning(
|
||||
f"Widget not found for name or gui_id: {widget_name} in dock {self.name()}"
|
||||
)
|
||||
return
|
||||
widget = widget[0]
|
||||
else:
|
||||
widget = widgets[0]
|
||||
self.layout.removeWidget(widget)
|
||||
self.config.widgets.pop(widget._name, None)
|
||||
if widget in self.widgets:
|
||||
@@ -396,12 +409,25 @@ class BECDock(BECWidget, Dock):
|
||||
"""
|
||||
Clean up the dock, including all its widgets.
|
||||
"""
|
||||
# Remove the dock from the parent dock area
|
||||
if self.parent_dock_area:
|
||||
self.parent_dock_area.dock_area.docks.pop(self.name(), None)
|
||||
self.parent_dock_area.config.docks.pop(self.name(), None)
|
||||
self.delete_all()
|
||||
self.widgets.clear()
|
||||
self.label.close()
|
||||
self.label.deleteLater()
|
||||
super().cleanup()
|
||||
|
||||
# def closeEvent(self, event): # pylint: disable=uselsess-parent-delegation
|
||||
# """Close Event for dock and cleanup.
|
||||
|
||||
# This wrapper ensures that the BECWidget close event is triggered.
|
||||
# If removed, the closeEvent from pyqtgraph will be triggered, which
|
||||
# is not calling super().closeEvent(event) and will not trigger the BECWidget close event.
|
||||
# """
|
||||
# return super().closeEvent(event)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the dock area and cleanup.
|
||||
@@ -409,13 +435,15 @@ class BECDock(BECWidget, Dock):
|
||||
"""
|
||||
self.cleanup()
|
||||
super().close()
|
||||
self.parent_dock_area.dock_area.docks.pop(self.name(), None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication([])
|
||||
dock = BECDock(name="dock")
|
||||
dock.show()
|
||||
app.exec_()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@@ -55,6 +55,7 @@ class BECDockArea(BECWidget, QWidget):
|
||||
"panel_list",
|
||||
"delete",
|
||||
"delete_all",
|
||||
"remove",
|
||||
"detach_dock",
|
||||
"attach_all",
|
||||
"selected_device",
|
||||
@@ -79,6 +80,7 @@ class BECDockArea(BECWidget, QWidget):
|
||||
self.config = config
|
||||
super().__init__(client=client, config=config, gui_id=gui_id, name=name, **kwargs)
|
||||
QWidget.__init__(self, parent=parent)
|
||||
self._parent = parent
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.setSpacing(5)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -346,9 +348,7 @@ class BECDockArea(BECWidget, QWidget):
|
||||
f"with name: {self._name} and id {self.gui_id}."
|
||||
)
|
||||
else: # Name is not provided
|
||||
name = WidgetContainerUtils.generate_unique_name(
|
||||
name=BECDock.__name__, list_of_names=dock_names
|
||||
)
|
||||
name = WidgetContainerUtils.generate_unique_name(name="dock", list_of_names=dock_names)
|
||||
|
||||
dock = BECDock(name=name, parent_dock_area=self, closable=closable)
|
||||
dock.config.position = position
|
||||
@@ -430,25 +430,6 @@ class BECDockArea(BECWidget, QWidget):
|
||||
self.dock_area.deleteLater()
|
||||
super().cleanup()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.parent() is None:
|
||||
# we are at top-level (independent window)
|
||||
if self.isVisible():
|
||||
# we are visible => user clicked on [X]
|
||||
# (when closeEvent is called from shutdown procedure,
|
||||
# everything is hidden first)
|
||||
# so, let's ignore "close", and do hide instead
|
||||
event.ignore()
|
||||
self.setVisible(False)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the dock area and cleanup.
|
||||
Has to be implemented to overwrite pyqtgraph event accept in Container close.
|
||||
"""
|
||||
self.cleanup()
|
||||
super().close()
|
||||
|
||||
def show(self):
|
||||
"""Show all windows including floating docks."""
|
||||
super().show()
|
||||
@@ -494,6 +475,10 @@ class BECDockArea(BECWidget, QWidget):
|
||||
raise ValueError(f"Dock with name {dock_name} does not exist.")
|
||||
self._broadcast_update()
|
||||
|
||||
def remove(self) -> None:
|
||||
"""Remove the dock area."""
|
||||
self.close()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
|
||||
@@ -78,13 +78,7 @@ class WidgetHandler:
|
||||
}
|
||||
|
||||
def create_widget(
|
||||
self,
|
||||
widget_type: str,
|
||||
widget_id: str,
|
||||
parent_figure,
|
||||
parent_id: str,
|
||||
config: dict = None,
|
||||
**axis_kwargs,
|
||||
self, widget_type: str, parent_figure, parent_id: str, config: dict = None, **axis_kwargs
|
||||
) -> BECPlotBase:
|
||||
"""
|
||||
Create and configure a widget based on its type.
|
||||
@@ -109,7 +103,6 @@ class WidgetHandler:
|
||||
widget_config_dict = {
|
||||
"widget_class": widget_class.__name__,
|
||||
"parent_id": parent_id,
|
||||
"gui_id": widget_id,
|
||||
**(config if config is not None else {}),
|
||||
}
|
||||
widget_config = config_class(**widget_config_dict)
|
||||
@@ -568,13 +561,13 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
|
||||
|
||||
widget = self.widget_handler.create_widget(
|
||||
widget_type=widget_type,
|
||||
widget_id=widget_id,
|
||||
parent_figure=self,
|
||||
parent_id=self.gui_id,
|
||||
config=config,
|
||||
**axis_kwargs,
|
||||
)
|
||||
widget.set_gui_id(widget_id)
|
||||
widget_id = widget.gui_id
|
||||
|
||||
widget.config.row = row
|
||||
widget.config.col = col
|
||||
|
||||
|
||||
@@ -942,7 +942,7 @@ class PlotBase(BECWidget, QWidget):
|
||||
self.axis_settings_dialog.close()
|
||||
self.axis_settings_dialog = None
|
||||
self.cleanup_pyqtgraph()
|
||||
self.rpc_register.remove_rpc(self)
|
||||
super().cleanup()
|
||||
|
||||
def cleanup_pyqtgraph(self):
|
||||
"""Cleanup pyqtgraph items."""
|
||||
|
||||
Reference in New Issue
Block a user