mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
fix(widgets)!: BECConnector resolves hierarchy including objectName, parent, parent_id upon init; all widgets adjusted
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
|
from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
|
||||||
|
|
||||||
|
|
||||||
def dock_area(name: str | None = None):
|
def dock_area(object_name: str | None = None):
|
||||||
_dock_area = BECDockArea(name=name)
|
_dock_area = BECDockArea(object_name=object_name)
|
||||||
return _dock_area
|
return _dock_area
|
||||||
|
@ -19,9 +19,8 @@ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
|||||||
|
|
||||||
|
|
||||||
class LaunchWindow(BECWidget, QMainWindow):
|
class LaunchWindow(BECWidget, QMainWindow):
|
||||||
def __init__(self, gui_id: str = None, *args, **kwargs):
|
def __init__(self, parent=None, gui_id: str = None, *args, **kwargs):
|
||||||
BECWidget.__init__(self, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, gui_id=gui_id, **kwargs)
|
||||||
QMainWindow.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
self.app = QApplication.instance()
|
self.app = QApplication.instance()
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import TYPE_CHECKING, Literal, TypeAlias
|
from typing import TYPE_CHECKING, Literal, TypeAlias, cast
|
||||||
|
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
@ -22,9 +22,9 @@ import bec_widgets.cli.client as client
|
|||||||
from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCReference
|
from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCReference
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from bec_lib.redis_connector import StreamMessage
|
from bec_lib.messages import GUIRegistryStateMessage
|
||||||
else:
|
else:
|
||||||
StreamMessage = lazy_import_from("bec_lib.redis_connector", ("StreamMessage",))
|
GUIRegistryStateMessage = lazy_import_from("bec_lib.messages", "GUIRegistryStateMessage")
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
@ -71,7 +71,11 @@ def _get_output(process, logger) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _start_plot_process(
|
def _start_plot_process(
|
||||||
gui_id: str, gui_class_id: str, config: dict | str, gui_class: str = "launcher", logger=None
|
gui_id: str,
|
||||||
|
gui_class_id: str,
|
||||||
|
config: dict | str,
|
||||||
|
gui_class: str = "dock_area",
|
||||||
|
logger=None, # FIXME change gui_class back to "launcher" later
|
||||||
) -> tuple[subprocess.Popen[str], threading.Thread | None]:
|
) -> tuple[subprocess.Popen[str], threading.Thread | None]:
|
||||||
"""
|
"""
|
||||||
Start the plot in a new process.
|
Start the plot in a new process.
|
||||||
@ -199,7 +203,7 @@ class BECGuiClient(RPCBase):
|
|||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
self._default_dock_name = "bec"
|
self._anchor_widget = "launcher"
|
||||||
self._auto_updates_enabled = True
|
self._auto_updates_enabled = True
|
||||||
self._auto_updates = None
|
self._auto_updates = None
|
||||||
self._killed = False
|
self._killed = False
|
||||||
@ -220,7 +224,7 @@ class BECGuiClient(RPCBase):
|
|||||||
@property
|
@property
|
||||||
def launcher(self) -> RPCBase:
|
def launcher(self) -> RPCBase:
|
||||||
"""The launcher object."""
|
"""The launcher object."""
|
||||||
return RPCBase(gui_id=f"{self._gui_id}:launcher", parent=self, name="launcher")
|
return RPCBase(gui_id=f"{self._gui_id}:launcher", parent=self, object_name="launcher")
|
||||||
|
|
||||||
def connect_to_gui_server(self, gui_id: str) -> None:
|
def connect_to_gui_server(self, gui_id: str) -> None:
|
||||||
"""Connect to a GUI server"""
|
"""Connect to a GUI server"""
|
||||||
@ -247,7 +251,7 @@ class BECGuiClient(RPCBase):
|
|||||||
@property
|
@property
|
||||||
def windows(self) -> dict:
|
def windows(self) -> dict:
|
||||||
"""Dictionary with dock areas in the GUI."""
|
"""Dictionary with dock areas in the GUI."""
|
||||||
return {widget._name: widget for widget in self._top_level.values()}
|
return {widget.object_name: widget for widget in self._top_level.values()}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def window_list(self) -> list:
|
def window_list(self) -> list:
|
||||||
@ -365,7 +369,7 @@ class BECGuiClient(RPCBase):
|
|||||||
# After 60s timeout. Should this raise an exception on timeout?
|
# After 60s timeout. Should this raise an exception on timeout?
|
||||||
while time.time() < time.time() + timeout:
|
while time.time() < time.time() + timeout:
|
||||||
if len(list(self._server_registry.keys())) < 2 or not hasattr(
|
if len(list(self._server_registry.keys())) < 2 or not hasattr(
|
||||||
self, self._default_dock_name
|
self, self._anchor_widget
|
||||||
):
|
):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
@ -383,7 +387,7 @@ class BECGuiClient(RPCBase):
|
|||||||
self._gui_started_event.clear()
|
self._gui_started_event.clear()
|
||||||
self._process, self._process_output_processing_thread = _start_plot_process(
|
self._process, self._process_output_processing_thread = _start_plot_process(
|
||||||
self._gui_id,
|
self._gui_id,
|
||||||
gui_class_id=self._default_dock_name,
|
gui_class_id="bec",
|
||||||
config=self._client._service_config.config, # pylint: disable=protected-access
|
config=self._client._service_config.config, # pylint: disable=protected-access
|
||||||
logger=logger,
|
logger=logger,
|
||||||
)
|
)
|
||||||
@ -413,11 +417,13 @@ class BECGuiClient(RPCBase):
|
|||||||
return self._start_server(wait=wait)
|
return self._start_server(wait=wait)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_registry_update(msg: StreamMessage, parent: BECGuiClient) -> None:
|
def _handle_registry_update(
|
||||||
|
msg: dict[str, GUIRegistryStateMessage], parent: BECGuiClient
|
||||||
|
) -> None:
|
||||||
# This was causing a deadlock during shutdown, not sure why.
|
# This was causing a deadlock during shutdown, not sure why.
|
||||||
# with self._lock:
|
# with self._lock:
|
||||||
self = parent
|
self = parent
|
||||||
self._server_registry = msg["data"].state
|
self._server_registry = cast(dict[str, RegistryState], msg["data"].state)
|
||||||
self._update_dynamic_namespace(self._server_registry)
|
self._update_dynamic_namespace(self._server_registry)
|
||||||
|
|
||||||
def _do_show_all(self):
|
def _do_show_all(self):
|
||||||
@ -460,12 +466,11 @@ class BECGuiClient(RPCBase):
|
|||||||
for gui_id, widget in self._ipython_registry.items():
|
for gui_id, widget in self._ipython_registry.items():
|
||||||
if gui_id not in server_registry:
|
if gui_id not in server_registry:
|
||||||
remove_from_registry.append(gui_id)
|
remove_from_registry.append(gui_id)
|
||||||
widget._refresh_references()
|
|
||||||
for gui_id in remove_from_registry:
|
for gui_id in remove_from_registry:
|
||||||
self._ipython_registry.pop(gui_id)
|
self._ipython_registry.pop(gui_id)
|
||||||
|
|
||||||
removed_widgets = [
|
removed_widgets = [
|
||||||
widget._name for widget in self._top_level.values() if widget._is_deleted()
|
widget.object_name for widget in self._top_level.values() if widget._is_deleted()
|
||||||
]
|
]
|
||||||
|
|
||||||
for widget_name in removed_widgets:
|
for widget_name in removed_widgets:
|
||||||
@ -475,10 +480,13 @@ class BECGuiClient(RPCBase):
|
|||||||
delattr(self, widget_name)
|
delattr(self, widget_name)
|
||||||
|
|
||||||
for gui_id, widget_ref in top_level_widgets.items():
|
for gui_id, widget_ref in top_level_widgets.items():
|
||||||
setattr(self, widget_ref._name, widget_ref)
|
setattr(self, widget_ref.object_name, widget_ref)
|
||||||
|
|
||||||
self._top_level = top_level_widgets
|
self._top_level = top_level_widgets
|
||||||
|
|
||||||
|
for widget in self._ipython_registry.values():
|
||||||
|
widget._refresh_references()
|
||||||
|
|
||||||
def _add_widget(self, state: dict, parent: object) -> RPCReference | None:
|
def _add_widget(self, state: dict, parent: object) -> RPCReference | None:
|
||||||
"""Add a widget to the namespace
|
"""Add a widget to the namespace
|
||||||
|
|
||||||
@ -486,7 +494,7 @@ class BECGuiClient(RPCBase):
|
|||||||
state (dict): The state of the widget from the _server_registry.
|
state (dict): The state of the widget from the _server_registry.
|
||||||
parent (object): The parent object.
|
parent (object): The parent object.
|
||||||
"""
|
"""
|
||||||
name = state["name"]
|
object_name = state["object_name"]
|
||||||
gui_id = state["gui_id"]
|
gui_id = state["gui_id"]
|
||||||
if state["widget_class"] in IGNORE_WIDGETS:
|
if state["widget_class"] in IGNORE_WIDGETS:
|
||||||
return
|
return
|
||||||
@ -495,7 +503,7 @@ class BECGuiClient(RPCBase):
|
|||||||
return
|
return
|
||||||
obj = self._ipython_registry.get(gui_id)
|
obj = self._ipython_registry.get(gui_id)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
widget = widget_class(gui_id=gui_id, name=name, parent=parent)
|
widget = widget_class(gui_id=gui_id, object_name=object_name, parent=parent)
|
||||||
self._ipython_registry[gui_id] = widget
|
self._ipython_registry[gui_id] = widget
|
||||||
else:
|
else:
|
||||||
widget = obj
|
widget = obj
|
||||||
|
@ -4,7 +4,7 @@ import inspect
|
|||||||
import threading
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from bec_lib.client import BECClient
|
from bec_lib.client import BECClient
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
@ -41,7 +41,7 @@ def rpc_call(func):
|
|||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
# we could rely on a strict type check here, but this is more flexible
|
# we could rely on a strict type check here, but this is more flexible
|
||||||
# moreover, it would anyway crash for objects...
|
# moreover, it would anyway crash for objects...
|
||||||
caller_frame = inspect.currentframe().f_back
|
caller_frame = inspect.currentframe().f_back # type: ignore
|
||||||
while caller_frame:
|
while caller_frame:
|
||||||
if "jedi" in caller_frame.f_globals:
|
if "jedi" in caller_frame.f_globals:
|
||||||
# Jedi module is present, likely tab completion
|
# Jedi module is present, likely tab completion
|
||||||
@ -91,7 +91,7 @@ class RPCReference:
|
|||||||
def __init__(self, registry: dict, gui_id: str) -> None:
|
def __init__(self, registry: dict, gui_id: str) -> None:
|
||||||
self._registry = registry
|
self._registry = registry
|
||||||
self._gui_id = gui_id
|
self._gui_id = gui_id
|
||||||
self._name = self._registry[self._gui_id]._name
|
self.object_name = self._registry[self._gui_id].object_name
|
||||||
|
|
||||||
@check_for_deleted_widget
|
@check_for_deleted_widget
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
@ -134,13 +134,13 @@ class RPCBase:
|
|||||||
self,
|
self,
|
||||||
gui_id: str | None = None,
|
gui_id: str | None = None,
|
||||||
config: dict | None = None,
|
config: dict | None = None,
|
||||||
name: str | None = None,
|
object_name: str | None = None,
|
||||||
parent=None,
|
parent=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._client = BECClient() # BECClient is a singleton; here, we simply get the instance
|
self._client = BECClient() # BECClient is a singleton; here, we simply get the instance
|
||||||
self._config = config if config is not None else {}
|
self._config = config if config is not None else {}
|
||||||
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())[:5]
|
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())[:5]
|
||||||
self._name = name if name is not None else str(uuid.uuid4())[:5]
|
self.object_name = object_name if object_name is not None else str(uuid.uuid4())[:5]
|
||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._msg_wait_event = threading.Event()
|
self._msg_wait_event = threading.Event()
|
||||||
self._rpc_response = None
|
self._rpc_response = None
|
||||||
@ -163,7 +163,7 @@ class RPCBase:
|
|||||||
"""
|
"""
|
||||||
Get the widget name.
|
Get the widget name.
|
||||||
"""
|
"""
|
||||||
return self._name
|
return self.object_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _root(self) -> BECGuiClient:
|
def _root(self) -> BECGuiClient:
|
||||||
@ -175,7 +175,7 @@ class RPCBase:
|
|||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
while parent._parent is not None:
|
while parent._parent is not None:
|
||||||
parent = parent._parent
|
parent = parent._parent
|
||||||
return parent
|
return parent # type: ignore
|
||||||
|
|
||||||
def _run_rpc(self, method, *args, wait_for_rpc_response=True, timeout=300, **kwargs) -> Any:
|
def _run_rpc(self, method, *args, wait_for_rpc_response=True, timeout=300, **kwargs) -> Any:
|
||||||
"""
|
"""
|
||||||
@ -219,7 +219,11 @@ class RPCBase:
|
|||||||
self._client.connector.unregister(
|
self._client.connector.unregister(
|
||||||
MessageEndpoints.gui_instruction_response(request_id), cb=self._on_rpc_response
|
MessageEndpoints.gui_instruction_response(request_id), cb=self._on_rpc_response
|
||||||
)
|
)
|
||||||
# get class name
|
|
||||||
|
# we can assume that the response is a RequestResponseMessage, updated by
|
||||||
|
# the _on_rpc_response method
|
||||||
|
assert isinstance(self._rpc_response, messages.RequestResponseMessage)
|
||||||
|
|
||||||
if not self._rpc_response.accepted:
|
if not self._rpc_response.accepted:
|
||||||
raise ValueError(self._rpc_response.message["error"])
|
raise ValueError(self._rpc_response.message["error"])
|
||||||
msg_result = self._rpc_response.message.get("result")
|
msg_result = self._rpc_response.message.get("result")
|
||||||
@ -227,8 +231,8 @@ class RPCBase:
|
|||||||
return self._create_widget_from_msg_result(msg_result)
|
return self._create_widget_from_msg_result(msg_result)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _on_rpc_response(msg: MessageObject, parent: RPCBase) -> None:
|
def _on_rpc_response(msg_obj: MessageObject, parent: RPCBase) -> None:
|
||||||
msg = msg.value
|
msg = cast(messages.RequestResponseMessage, msg_obj.value)
|
||||||
parent._msg_wait_event.set()
|
parent._msg_wait_event.set()
|
||||||
parent._rpc_response = msg
|
parent._rpc_response = msg
|
||||||
|
|
||||||
@ -283,12 +287,17 @@ class RPCBase:
|
|||||||
for key, val in self._root._server_registry.items():
|
for key, val in self._root._server_registry.items():
|
||||||
parent_id = val["config"].get("parent_id")
|
parent_id = val["config"].get("parent_id")
|
||||||
if parent_id == self._gui_id:
|
if parent_id == self._gui_id:
|
||||||
references[key] = {"gui_id": val["config"]["gui_id"], "name": val["name"]}
|
references[key] = {
|
||||||
|
"gui_id": val["config"]["gui_id"],
|
||||||
|
"object_name": val["object_name"],
|
||||||
|
}
|
||||||
removed_references = set(self._rpc_references.keys()) - set(references.keys())
|
removed_references = set(self._rpc_references.keys()) - set(references.keys())
|
||||||
for key in removed_references:
|
for key in removed_references:
|
||||||
delattr(self, self._rpc_references[key]["name"])
|
delattr(self, self._rpc_references[key]["object_name"])
|
||||||
self._rpc_references = references
|
self._rpc_references = references
|
||||||
for key, val in references.items():
|
for key, val in references.items():
|
||||||
setattr(
|
setattr(
|
||||||
self, val["name"], RPCReference(self._root._ipython_registry, val["gui_id"])
|
self,
|
||||||
|
val["object_name"],
|
||||||
|
RPCReference(self._root._ipython_registry, val["gui_id"]),
|
||||||
)
|
)
|
||||||
|
@ -123,7 +123,7 @@ class RPCRegister:
|
|||||||
# This retrieves any rpc objects that are subclass of BECWidget,
|
# This retrieves any rpc objects that are subclass of BECWidget,
|
||||||
# i.e. curve and image items are excluded
|
# i.e. curve and image items are excluded
|
||||||
widgets = [rpc for rpc in self._rpc_register.values() if isinstance(rpc, cls)]
|
widgets = [rpc for rpc in self._rpc_register.values() if isinstance(rpc, cls)]
|
||||||
return [widget._name for widget in widgets]
|
return [widget.object_name for widget in widgets]
|
||||||
|
|
||||||
def broadcast(self):
|
def broadcast(self):
|
||||||
"""
|
"""
|
||||||
@ -172,6 +172,6 @@ class RPCRegisterBroadcast:
|
|||||||
"""Exit the context manager"""
|
"""Exit the context manager"""
|
||||||
|
|
||||||
self._call_depth -= 1 # Remove nested calls
|
self._call_depth -= 1 # Remove nested calls
|
||||||
if self._call_depth == 0: # Last one to exit is repsonsible for broadcasting
|
if self._call_depth == 0: # The Last one to exit is responsible for broadcasting
|
||||||
self.rpc_register._skip_broadcast = False
|
self.rpc_register._skip_broadcast = False
|
||||||
self.rpc_register.broadcast()
|
self.rpc_register.broadcast()
|
||||||
|
@ -36,7 +36,7 @@ class RPCWidgetHandler:
|
|||||||
cls.__name__: cls for cls in clss.widgets if cls.__name__ not in IGNORE_WIDGETS
|
cls.__name__: cls for cls in clss.widgets if cls.__name__ not in IGNORE_WIDGETS
|
||||||
}
|
}
|
||||||
|
|
||||||
def create_widget(self, widget_type, name: str | None = None, **kwargs) -> BECWidget:
|
def create_widget(self, widget_type, **kwargs) -> BECWidget:
|
||||||
"""
|
"""
|
||||||
Create a widget from an RPC message.
|
Create a widget from an RPC message.
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class RPCWidgetHandler:
|
|||||||
"""
|
"""
|
||||||
widget_class = self.widget_classes.get(widget_type) # type: ignore
|
widget_class = self.widget_classes.get(widget_type) # type: ignore
|
||||||
if widget_class:
|
if widget_class:
|
||||||
return widget_class(name=name, **kwargs)
|
return widget_class(**kwargs)
|
||||||
raise ValueError(f"Unknown widget type: {widget_type}")
|
raise ValueError(f"Unknown widget type: {widget_type}")
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,8 +111,8 @@ class GUIServer:
|
|||||||
self.setup_bec_icon()
|
self.setup_bec_icon()
|
||||||
|
|
||||||
service_config = self._get_service_config()
|
service_config = self._get_service_config()
|
||||||
self.dispatcher = BECDispatcher(config=service_config)
|
self.dispatcher = BECDispatcher(config=service_config, gui_id=self.gui_id)
|
||||||
self.dispatcher.start_cli_server(gui_id=self.gui_id)
|
# self.dispatcher.start_cli_server(gui_id=self.gui_id)
|
||||||
|
|
||||||
self.launcher_window = LaunchWindow(gui_id=f"{self.gui_id}:launcher")
|
self.launcher_window = LaunchWindow(gui_id=f"{self.gui_id}:launcher")
|
||||||
self.launcher_window.setAttribute(Qt.WA_ShowWithoutActivating) # type: ignore
|
self.launcher_window.setAttribute(Qt.WA_ShowWithoutActivating) # type: ignore
|
||||||
@ -139,8 +139,6 @@ class GUIServer:
|
|||||||
if self.app:
|
if self.app:
|
||||||
self.app.quit()
|
self.app.quit()
|
||||||
|
|
||||||
# gui.bec.close()
|
|
||||||
# win.shutdown()
|
|
||||||
signal.signal(signal.SIGINT, sigint_handler)
|
signal.signal(signal.SIGINT, sigint_handler)
|
||||||
signal.signal(signal.SIGTERM, sigint_handler)
|
signal.signal(signal.SIGTERM, sigint_handler)
|
||||||
|
|
||||||
|
@ -10,21 +10,23 @@ from typing import TYPE_CHECKING, Optional
|
|||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from bec_lib.utils.import_utils import lazy_import_from
|
from bec_lib.utils.import_utils import lazy_import_from
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from qtpy.QtCore import QObject, QRunnable, QThreadPool, Signal
|
from qtpy.QtCore import QObject, QRunnable, Qt, QThreadPool, Signal
|
||||||
from qtpy.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
||||||
from bec_widgets.utils.container_utils import WidgetContainerUtils
|
from bec_widgets.utils.container_utils import WidgetContainerUtils
|
||||||
from bec_widgets.utils.error_popups import ErrorPopupUtility
|
from bec_widgets.utils.error_popups import ErrorPopupUtility
|
||||||
from bec_widgets.utils.error_popups import SafeSlot as pyqtSlot
|
from bec_widgets.utils.error_popups import SafeSlot as pyqtSlot
|
||||||
|
from bec_widgets.utils.widget_io import WidgetHierarchy
|
||||||
from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
|
from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||||
from bec_widgets.widgets.containers.dock import BECDock
|
from bec_widgets.widgets.containers.dock import BECDock
|
||||||
|
else:
|
||||||
|
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionConfig(BaseModel):
|
class ConnectionConfig(BaseModel):
|
||||||
@ -82,14 +84,21 @@ class BECConnector:
|
|||||||
client=None,
|
client=None,
|
||||||
config: ConnectionConfig | None = None,
|
config: ConnectionConfig | None = None,
|
||||||
gui_id: str | None = None,
|
gui_id: str | None = None,
|
||||||
name: str | None = None,
|
object_name: str | None = None,
|
||||||
parent_dock: BECDock | None = None,
|
parent_dock: BECDock | None = None, # TODO should go away -> issue created #473
|
||||||
parent_id: str | None = None,
|
parent_id: str | None = None,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
# Extract object_name from kwargs to not pass it to Qt class
|
||||||
|
object_name = object_name or kwargs.pop("objectName", None)
|
||||||
|
# Ensure the parent is always the first argument for QObject
|
||||||
|
parent = kwargs.pop("parent", None)
|
||||||
|
# This initializes the QObject or any qt related class
|
||||||
|
super().__init__(parent=parent, **kwargs)
|
||||||
# BEC related connections
|
# BEC related connections
|
||||||
self.bec_dispatcher = BECDispatcher(client=client)
|
self.bec_dispatcher = BECDispatcher(client=client)
|
||||||
self.client = self.bec_dispatcher.client if client is None else client
|
self.client = self.bec_dispatcher.client if client is None else client
|
||||||
self._parent_dock = parent_dock
|
self._parent_dock = parent_dock # TODO also remove at some point -> issue created #473
|
||||||
|
|
||||||
if not self.client in BECConnector.EXIT_HANDLERS:
|
if not self.client in BECConnector.EXIT_HANDLERS:
|
||||||
# register function to clean connections at exit;
|
# register function to clean connections at exit;
|
||||||
@ -122,12 +131,24 @@ class BECConnector:
|
|||||||
self.gui_id: str = gui_id # Keep namespace in sync
|
self.gui_id: str = gui_id # Keep namespace in sync
|
||||||
else:
|
else:
|
||||||
self.gui_id: str = self.config.gui_id # type: ignore
|
self.gui_id: str = self.config.gui_id # type: ignore
|
||||||
if name is None:
|
|
||||||
name = self.__class__.__name__
|
# TODO Hierarchy can be refreshed upon creation -> also registry should be notified if objectName changes -> issue #472
|
||||||
else:
|
if object_name is not None:
|
||||||
if not WidgetContainerUtils.has_name_valid_chars(name):
|
self.setObjectName(object_name)
|
||||||
raise ValueError(f"Name {name} contains invalid characters.")
|
|
||||||
self._name = name if name else self.__class__.__name__
|
# 1) If no objectName is set, set the initial name
|
||||||
|
if not self.objectName():
|
||||||
|
self.setObjectName(self.__class__.__name__)
|
||||||
|
self.object_name = self.objectName()
|
||||||
|
|
||||||
|
# 2) Enforce unique objectName among siblings with the same BECConnector parent
|
||||||
|
self.setParent(parent)
|
||||||
|
if parent_id is None:
|
||||||
|
connector_parent = WidgetHierarchy._get_becwidget_ancestor(self)
|
||||||
|
if connector_parent is not None:
|
||||||
|
self.parent_id = connector_parent.gui_id
|
||||||
|
|
||||||
|
self._enforce_unique_sibling_name()
|
||||||
self.rpc_register = RPCRegister()
|
self.rpc_register = RPCRegister()
|
||||||
self.rpc_register.add_rpc(self)
|
self.rpc_register.add_rpc(self)
|
||||||
|
|
||||||
@ -138,6 +159,49 @@ class BECConnector:
|
|||||||
# Store references to running workers so they're not garbage collected prematurely.
|
# Store references to running workers so they're not garbage collected prematurely.
|
||||||
self._workers = []
|
self._workers = []
|
||||||
|
|
||||||
|
def _enforce_unique_sibling_name(self):
|
||||||
|
"""
|
||||||
|
Enforce that this BECConnector has a unique objectName among its siblings.
|
||||||
|
|
||||||
|
Sibling logic:
|
||||||
|
- If there's a nearest BECConnector parent, only compare with children of that parent.
|
||||||
|
- If parent is None (i.e., top-level object), compare with all other top-level BECConnectors.
|
||||||
|
"""
|
||||||
|
parent_bec = WidgetHierarchy._get_becwidget_ancestor(self)
|
||||||
|
|
||||||
|
if parent_bec:
|
||||||
|
# We have a parent => only compare with siblings under that parent
|
||||||
|
siblings = parent_bec.findChildren(BECConnector)
|
||||||
|
else:
|
||||||
|
# No parent => treat all top-level BECConnectors as siblings
|
||||||
|
# 1) Gather all BECConnectors from QApplication
|
||||||
|
all_widgets = QApplication.allWidgets()
|
||||||
|
all_bec = [w for w in all_widgets if isinstance(w, BECConnector)]
|
||||||
|
# 2) "Top-level" means closest BECConnector parent is None
|
||||||
|
top_level_bec = [
|
||||||
|
w for w in all_bec if WidgetHierarchy._get_becwidget_ancestor(w) is None
|
||||||
|
]
|
||||||
|
# 3) We are among these top-level siblings
|
||||||
|
siblings = top_level_bec
|
||||||
|
|
||||||
|
# Collect used names among siblings
|
||||||
|
used_names = {sib.objectName() for sib in siblings if sib is not self}
|
||||||
|
|
||||||
|
base_name = self.objectName()
|
||||||
|
if base_name not in used_names:
|
||||||
|
# Name is already unique among siblings
|
||||||
|
return
|
||||||
|
|
||||||
|
# Need a suffix to avoid collision
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
trial_name = f"{base_name}_{counter}"
|
||||||
|
if trial_name not in used_names:
|
||||||
|
self.setObjectName(trial_name)
|
||||||
|
self.object_name = trial_name
|
||||||
|
break
|
||||||
|
counter += 1
|
||||||
|
|
||||||
def submit_task(self, fn, *args, on_complete: pyqtSlot = None, **kwargs) -> Worker:
|
def submit_task(self, fn, *args, on_complete: pyqtSlot = None, **kwargs) -> Worker:
|
||||||
"""
|
"""
|
||||||
Submit a task to run in a separate thread. The task will run the specified
|
Submit a task to run in a separate thread. The task will run the specified
|
||||||
@ -316,8 +380,9 @@ class BECConnector:
|
|||||||
def remove(self):
|
def remove(self):
|
||||||
"""Cleanup the BECConnector"""
|
"""Cleanup the BECConnector"""
|
||||||
# If the widget is attached to a dock, remove it from the dock.
|
# If the widget is attached to a dock, remove it from the dock.
|
||||||
|
# TODO this should be handled by dock and dock are not by BECConnector
|
||||||
if self._parent_dock is not None:
|
if self._parent_dock is not None:
|
||||||
self._parent_dock.delete(self._name)
|
self._parent_dock.delete(self.object_name)
|
||||||
# If the widget is from Qt, trigger its close method.
|
# If the widget is from Qt, trigger its close method.
|
||||||
elif hasattr(self, "close"):
|
elif hasattr(self, "close"):
|
||||||
self.close()
|
self.close()
|
||||||
|
@ -80,13 +80,21 @@ class BECDispatcher:
|
|||||||
client: BECClient
|
client: BECClient
|
||||||
cli_server: CLIServer | None = None
|
cli_server: CLIServer | None = None
|
||||||
|
|
||||||
def __new__(cls, client=None, config: str | ServiceConfig | None = None, *args, **kwargs):
|
# TODO add custom gui id for server
|
||||||
|
def __new__(
|
||||||
|
cls,
|
||||||
|
client=None,
|
||||||
|
config: str | ServiceConfig | None = None,
|
||||||
|
gui_id: str = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super(BECDispatcher, cls).__new__(cls)
|
cls._instance = super(BECDispatcher, cls).__new__(cls)
|
||||||
cls._initialized = False
|
cls._initialized = False
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, client=None, config: str | ServiceConfig | None = None):
|
def __init__(self, client=None, config: str | ServiceConfig | None = None, gui_id: str = None):
|
||||||
if self._initialized:
|
if self._initialized:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -114,6 +122,8 @@ class BECDispatcher:
|
|||||||
logger.warning("Could not connect to Redis, skipping start of BECClient.")
|
logger.warning("Could not connect to Redis, skipping start of BECClient.")
|
||||||
|
|
||||||
logger.success("Initialized BECDispatcher")
|
logger.success("Initialized BECDispatcher")
|
||||||
|
|
||||||
|
self.start_cli_server(gui_id=gui_id)
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -207,7 +217,7 @@ class BECDispatcher:
|
|||||||
logger.error("Cannot start CLI server without a running client")
|
logger.error("Cannot start CLI server without a running client")
|
||||||
return
|
return
|
||||||
self.cli_server = CLIServer(gui_id, dispatcher=self, client=self.client)
|
self.cli_server = CLIServer(gui_id, dispatcher=self, client=self.client)
|
||||||
logger.success("Started CLI server with gui_id: {gui_id}")
|
logger.success(f"Started CLI server with gui_id: {gui_id}")
|
||||||
|
|
||||||
def stop_cli_server(self):
|
def stop_cli_server(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import darkdetect
|
import darkdetect
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from qtpy.QtCore import Slot
|
from qtpy.QtCore import QObject, Slot
|
||||||
from qtpy.QtWidgets import QApplication, QWidget
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
||||||
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
||||||
@ -32,8 +32,7 @@ class BECWidget(BECConnector):
|
|||||||
config: ConnectionConfig = None,
|
config: ConnectionConfig = None,
|
||||||
gui_id: str | None = None,
|
gui_id: str | None = None,
|
||||||
theme_update: bool = False,
|
theme_update: bool = False,
|
||||||
name: str | None = None,
|
parent_dock: BECDock | None = None, # TODO should go away -> issue created #473
|
||||||
parent_dock: BECDock | None = None,
|
|
||||||
parent_id: str | None = None,
|
parent_id: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
@ -54,16 +53,17 @@ class BECWidget(BECConnector):
|
|||||||
theme_update(bool, optional): Whether to subscribe to theme updates. Defaults to False. When set to True, the
|
theme_update(bool, optional): Whether to subscribe to theme updates. Defaults to False. When set to True, the
|
||||||
widget's apply_theme method will be called when the theme changes.
|
widget's apply_theme method will be called when the theme changes.
|
||||||
"""
|
"""
|
||||||
if not isinstance(self, QWidget):
|
|
||||||
raise RuntimeError(f"{repr(self)} is not a subclass of QWidget")
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
client=client,
|
client=client,
|
||||||
config=config,
|
config=config,
|
||||||
gui_id=gui_id,
|
gui_id=gui_id,
|
||||||
name=name,
|
|
||||||
parent_dock=parent_dock,
|
parent_dock=parent_dock,
|
||||||
parent_id=parent_id,
|
parent_id=parent_id,
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
if not isinstance(self, QObject):
|
||||||
|
raise RuntimeError(f"{repr(self)} is not a subclass of QWidget")
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
if not hasattr(app, "theme"):
|
if not hasattr(app, "theme"):
|
||||||
# DO NOT SET THE THEME TO AUTO! Otherwise, the qwebengineview will segfault
|
# DO NOT SET THE THEME TO AUTO! Otherwise, the qwebengineview will segfault
|
||||||
|
@ -11,12 +11,15 @@ from bec_lib.endpoints import MessageEndpoints
|
|||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from bec_lib.utils.import_utils import lazy_import
|
from bec_lib.utils.import_utils import lazy_import
|
||||||
from qtpy.QtCore import QTimer
|
from qtpy.QtCore import QTimer
|
||||||
|
from qtpy.QtWidgets import QApplication
|
||||||
from redis.exceptions import RedisError
|
from redis.exceptions import RedisError
|
||||||
|
|
||||||
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
||||||
from bec_widgets.utils import BECDispatcher
|
from bec_widgets.utils import BECDispatcher
|
||||||
from bec_widgets.utils.bec_connector import BECConnector
|
from bec_widgets.utils.bec_connector import BECConnector
|
||||||
from bec_widgets.utils.error_popups import ErrorPopupUtility
|
from bec_widgets.utils.error_popups import ErrorPopupUtility
|
||||||
|
from bec_widgets.utils.widget_io import WidgetHierarchy
|
||||||
|
from bec_widgets.widgets.plots.plot_base import PlotBase
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bec_lib import messages
|
from bec_lib import messages
|
||||||
@ -77,6 +80,7 @@ class CLIServer:
|
|||||||
self._heartbeat_timer = QTimer()
|
self._heartbeat_timer = QTimer()
|
||||||
self._heartbeat_timer.timeout.connect(self.emit_heartbeat)
|
self._heartbeat_timer.timeout.connect(self.emit_heartbeat)
|
||||||
self._heartbeat_timer.start(200)
|
self._heartbeat_timer.start(200)
|
||||||
|
self._registry_update_callbacks = []
|
||||||
|
|
||||||
self.status = messages.BECStatus.RUNNING
|
self.status = messages.BECStatus.RUNNING
|
||||||
logger.success(f"Server started with gui_id: {self.gui_id}")
|
logger.success(f"Server started with gui_id: {self.gui_id}")
|
||||||
@ -141,17 +145,10 @@ class CLIServer:
|
|||||||
|
|
||||||
def serialize_object(self, obj):
|
def serialize_object(self, obj):
|
||||||
if isinstance(obj, BECConnector):
|
if isinstance(obj, BECConnector):
|
||||||
config = obj.config.model_dump()
|
# Respect RPC = False
|
||||||
config["parent_id"] = obj.parent_id # add parent_id to config
|
if hasattr(obj, "RPC") and obj.RPC is False:
|
||||||
return {
|
return None
|
||||||
"gui_id": obj.gui_id,
|
return self._serialize_bec_connector(obj)
|
||||||
"name": (
|
|
||||||
obj._name if hasattr(obj, "_name") else obj.__class__.__name__
|
|
||||||
), # pylint: disable=protected-access
|
|
||||||
"widget_class": obj.__class__.__name__,
|
|
||||||
"config": config,
|
|
||||||
"__rpc__": True,
|
|
||||||
}
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def emit_heartbeat(self):
|
def emit_heartbeat(self):
|
||||||
@ -166,19 +163,57 @@ class CLIServer:
|
|||||||
logger.error(f"Error while emitting heartbeat: {exc}")
|
logger.error(f"Error while emitting heartbeat: {exc}")
|
||||||
|
|
||||||
def broadcast_registry_update(self, connections: dict):
|
def broadcast_registry_update(self, connections: dict):
|
||||||
"""
|
data = {}
|
||||||
Broadcast the updated registry to all clients.
|
for key, val in connections.items():
|
||||||
"""
|
if not isinstance(val, BECConnector):
|
||||||
|
continue
|
||||||
|
if not getattr(val, "RPC", True):
|
||||||
|
continue
|
||||||
|
data[key] = self._serialize_bec_connector(val)
|
||||||
|
|
||||||
# We only need to broadcast the dock areas
|
|
||||||
data = {key: self.serialize_object(val) for key, val in connections.items()}
|
|
||||||
logger.info(f"Broadcasting registry update: {data} for {self.gui_id}")
|
logger.info(f"Broadcasting registry update: {data} for {self.gui_id}")
|
||||||
self.client.connector.xadd(
|
self.client.connector.xadd(
|
||||||
MessageEndpoints.gui_registry_state(self.gui_id),
|
MessageEndpoints.gui_registry_state(self.gui_id),
|
||||||
msg_dict={"data": messages.GUIRegistryStateMessage(state=data)},
|
msg_dict={"data": messages.GUIRegistryStateMessage(state=data)},
|
||||||
max_size=1, # only single message in stream
|
max_size=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _serialize_bec_connector(self, connector: BECConnector) -> dict:
|
||||||
|
"""
|
||||||
|
Create the serialization dict for a single BECConnector,
|
||||||
|
setting 'parent_id' via the real nearest BECConnector parent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
config_dict = connector.config.model_dump()
|
||||||
|
config_dict["parent_id"] = getattr(connector, "parent_id", None)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"gui_id": connector.gui_id,
|
||||||
|
"object_name": connector.object_name or connector.__class__.__name__,
|
||||||
|
"widget_class": connector.__class__.__name__,
|
||||||
|
"config": config_dict,
|
||||||
|
"__rpc__": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_becwidget_ancestor(widget):
|
||||||
|
"""
|
||||||
|
Traverse up the parent chain to find the nearest BECConnector.
|
||||||
|
Returns None if none is found.
|
||||||
|
"""
|
||||||
|
from bec_widgets.utils import BECConnector
|
||||||
|
|
||||||
|
parent = widget.parent()
|
||||||
|
while parent is not None:
|
||||||
|
if isinstance(parent, BECConnector):
|
||||||
|
return parent
|
||||||
|
parent = parent.parent()
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Suppose clients register callbacks to receive updates
|
||||||
|
def add_registry_update_callback(self, cb):
|
||||||
|
self._registry_update_callbacks.append(cb)
|
||||||
|
|
||||||
def shutdown(self): # TODO not sure if needed when cleanup is done at level of BECConnector
|
def shutdown(self): # TODO not sure if needed when cleanup is done at level of BECConnector
|
||||||
self.status = messages.BECStatus.IDLE
|
self.status = messages.BECStatus.IDLE
|
||||||
self._heartbeat_timer.stop()
|
self._heartbeat_timer.stop()
|
||||||
|
@ -22,10 +22,10 @@ class PaletteViewer(BECWidget, QWidget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
ICON_NAME = "palette"
|
ICON_NAME = "palette"
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, *args, parent=None, **kwargs):
|
def __init__(self, *args, parent=None, **kwargs):
|
||||||
super().__init__(*args, theme_update=True, **kwargs)
|
super().__init__(parent=parent, theme_update=True, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
self.setFixedSize(400, 600)
|
self.setFixedSize(400, 600)
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
dark_mode_button = DarkModeButton(self)
|
dark_mode_button = DarkModeButton(self)
|
||||||
|
@ -35,7 +35,6 @@ class SidePanel(QWidget):
|
|||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
self.setProperty("skip_settings", True)
|
self.setProperty("skip_settings", True)
|
||||||
self.setObjectName("SidePanel")
|
|
||||||
|
|
||||||
self._orientation = orientation
|
self._orientation = orientation
|
||||||
self._panel_max_width = panel_max_width
|
self._panel_max_width = panel_max_width
|
||||||
|
@ -31,7 +31,6 @@ if PYSIDE6:
|
|||||||
f"Custom widget {class_name} does not have a parent_id argument. "
|
f"Custom widget {class_name} does not have a parent_id argument. "
|
||||||
)
|
)
|
||||||
widget = self.custom_widgets[class_name](self.baseinstance)
|
widget = self.custom_widgets[class_name](self.baseinstance)
|
||||||
widget.setObjectName(name)
|
|
||||||
return widget
|
return widget
|
||||||
return super().createWidget(class_name, self.baseinstance, name)
|
return super().createWidget(class_name, self.baseinstance, name)
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ from qtpy.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from bec_widgets.utils.bec_widget import BECWidget
|
|
||||||
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
|
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
|
||||||
|
|
||||||
|
|
||||||
@ -291,62 +290,141 @@ class WidgetHierarchy:
|
|||||||
only_bec_widgets(bool, optional): Whether to print only widgets that are instances of BECWidget.
|
only_bec_widgets(bool, optional): Whether to print only widgets that are instances of BECWidget.
|
||||||
show_parent(bool, optional): Whether to display which BECWidget is the parent of each discovered BECWidget.
|
show_parent(bool, optional): Whether to display which BECWidget is the parent of each discovered BECWidget.
|
||||||
"""
|
"""
|
||||||
# Decide if this particular widget is to be printed
|
from bec_widgets.utils import BECConnector
|
||||||
is_bec = isinstance(widget, BECWidget)
|
from bec_widgets.widgets.plots.waveform.waveform import Waveform
|
||||||
print_this = (not only_bec_widgets) or is_bec
|
|
||||||
|
|
||||||
# If it is a BECWidget and we're showing the parent, climb the chain to find the nearest BECWidget ancestor
|
# 1) Filter out widgets that are not BECConnectors (if 'only_bec_widgets' is True)
|
||||||
|
is_bec = isinstance(widget, BECConnector)
|
||||||
|
if only_bec_widgets and not is_bec:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2) Determine and print the parent's info (closest BECConnector)
|
||||||
|
parent_info = ""
|
||||||
if show_parent and is_bec:
|
if show_parent and is_bec:
|
||||||
ancestor = WidgetHierarchy._get_becwidget_ancestor(widget)
|
ancestor = WidgetHierarchy._get_becwidget_ancestor(widget)
|
||||||
if ancestor is not None:
|
if ancestor:
|
||||||
parent_info = f" parent={ancestor.__class__.__name__}"
|
parent_label = ancestor.objectName() or ancestor.__class__.__name__
|
||||||
|
parent_info = f" parent={parent_label}"
|
||||||
else:
|
else:
|
||||||
parent_info = " parent=None"
|
parent_info = " parent=None"
|
||||||
else:
|
|
||||||
parent_info = ""
|
|
||||||
|
|
||||||
if print_this:
|
widget_info = f"{widget.__class__.__name__} ({widget.objectName()}){parent_info}"
|
||||||
widget_info = f"{widget.__class__.__name__} ({widget.objectName()}){parent_info}"
|
print(prefix + widget_info)
|
||||||
if grab_values:
|
|
||||||
value = WidgetIO.get_value(widget, ignore_errors=True)
|
|
||||||
value_str = f" [value: {value}]" if value is not None else ""
|
|
||||||
widget_info += value_str
|
|
||||||
print(prefix + widget_info)
|
|
||||||
|
|
||||||
# Always recurse so we can discover deeper BECWidgets even if the current widget is not a BECWidget
|
# 3) If it's a Waveform, explicitly print the curves
|
||||||
children = widget.children()
|
if isinstance(widget, Waveform):
|
||||||
for i, child in enumerate(children):
|
for curve in widget.curves:
|
||||||
# Possibly skip known internal child widgets of a QComboBox
|
curve_prefix = prefix + " └─ "
|
||||||
if (
|
print(
|
||||||
exclude_internal_widgets
|
f"{curve_prefix}{curve.__class__.__name__} ({curve.objectName()}) "
|
||||||
and isinstance(widget, QComboBox)
|
f"parent={widget.objectName()}"
|
||||||
and child.__class__.__name__ in ["QFrame", "QBoxLayout", "QListView"]
|
)
|
||||||
):
|
|
||||||
|
# 4) Recursively handle each child if:
|
||||||
|
# - It's a QWidget
|
||||||
|
# - It is a BECConnector (or we don't care about filtering)
|
||||||
|
# - Its closest BECConnector parent is the current widget
|
||||||
|
for child in widget.findChildren(QWidget):
|
||||||
|
if only_bec_widgets and not isinstance(child, BECConnector):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
child_prefix = prefix + " "
|
# if WidgetHierarchy._get_becwidget_ancestor(child) == widget:
|
||||||
arrow = "├─ " if child != children[-1] else "└─ "
|
child_prefix = prefix + " └─ "
|
||||||
|
|
||||||
# Regardless of whether child is BECWidget or not, keep recursing, or we might miss deeper BECWidgets
|
|
||||||
WidgetHierarchy.print_widget_hierarchy(
|
WidgetHierarchy.print_widget_hierarchy(
|
||||||
child,
|
child,
|
||||||
indent + 1,
|
indent=indent + 1,
|
||||||
grab_values=grab_values,
|
grab_values=grab_values,
|
||||||
prefix=child_prefix + arrow,
|
prefix=child_prefix,
|
||||||
exclude_internal_widgets=exclude_internal_widgets,
|
exclude_internal_widgets=exclude_internal_widgets,
|
||||||
only_bec_widgets=only_bec_widgets,
|
only_bec_widgets=only_bec_widgets,
|
||||||
show_parent=show_parent,
|
show_parent=show_parent,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def print_becconnector_hierarchy_from_app():
|
||||||
|
"""
|
||||||
|
Enumerate ALL BECConnector objects in the QApplication.
|
||||||
|
Also detect if a widget is a PlotBase, and add any data items
|
||||||
|
(PlotDataItem-like) that are also BECConnector objects.
|
||||||
|
|
||||||
|
Build a parent->children graph where each child's 'parent'
|
||||||
|
is its closest BECConnector ancestor. Print the entire hierarchy
|
||||||
|
from the root(s).
|
||||||
|
|
||||||
|
The result is a single, consolidated tree for your entire
|
||||||
|
running GUI, including PlotBase data items that are BECConnector.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
|
from bec_widgets.utils import BECConnector
|
||||||
|
from bec_widgets.widgets.plots.plot_base import PlotBase
|
||||||
|
|
||||||
|
# 1) Gather ALL QWidget-based BECConnector objects
|
||||||
|
all_qwidgets = QApplication.allWidgets()
|
||||||
|
bec_widgets = set(w for w in all_qwidgets if isinstance(w, BECConnector))
|
||||||
|
|
||||||
|
# 2) Also gather any BECConnector-based data items from PlotBase widgets
|
||||||
|
for w in all_qwidgets:
|
||||||
|
if isinstance(w, PlotBase) and hasattr(w, "plot_item"):
|
||||||
|
plot_item = w.plot_item
|
||||||
|
if hasattr(plot_item, "listDataItems"):
|
||||||
|
for data_item in plot_item.listDataItems():
|
||||||
|
if isinstance(data_item, BECConnector):
|
||||||
|
bec_widgets.add(data_item)
|
||||||
|
|
||||||
|
# 3) Build a map of (closest BECConnector parent) -> list of children
|
||||||
|
parent_map = defaultdict(list)
|
||||||
|
for w in bec_widgets:
|
||||||
|
parent_bec = WidgetHierarchy._get_becwidget_ancestor(w)
|
||||||
|
parent_map[parent_bec].append(w)
|
||||||
|
|
||||||
|
# 4) Define a recursive printer to show each object's children
|
||||||
|
def print_tree(parent, prefix=""):
|
||||||
|
children = parent_map[parent]
|
||||||
|
for i, child in enumerate(children):
|
||||||
|
connector_class = child.__class__.__name__
|
||||||
|
connector_name = child.objectName() or connector_class
|
||||||
|
|
||||||
|
if parent is None:
|
||||||
|
parent_label = "None"
|
||||||
|
else:
|
||||||
|
parent_label = parent.objectName() or parent.__class__.__name__
|
||||||
|
|
||||||
|
line = f"{connector_class} ({connector_name}) parent={parent_label}"
|
||||||
|
# Determine tree-branch symbols
|
||||||
|
is_last = i == len(children) - 1
|
||||||
|
branch_str = "└─ " if is_last else "├─ "
|
||||||
|
print(prefix + branch_str + line)
|
||||||
|
|
||||||
|
# Recurse deeper
|
||||||
|
next_prefix = prefix + (" " if is_last else "│ ")
|
||||||
|
print_tree(child, prefix=next_prefix)
|
||||||
|
|
||||||
|
# 5) Print top-level items (roots) whose BECConnector parent is None
|
||||||
|
roots = parent_map[None]
|
||||||
|
for r_i, root in enumerate(roots):
|
||||||
|
root_class = root.__class__.__name__
|
||||||
|
root_name = root.objectName() or root_class
|
||||||
|
line = f"{root_class} ({root_name}) parent=None"
|
||||||
|
is_last_root = r_i == len(roots) - 1
|
||||||
|
print(line)
|
||||||
|
# Recurse into its children
|
||||||
|
print_tree(root, prefix=" ")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_becwidget_ancestor(widget):
|
def _get_becwidget_ancestor(widget):
|
||||||
"""
|
"""
|
||||||
Climb the parent chain to find the nearest BECWidget above this widget.
|
Traverse up the parent chain to find the nearest BECConnector.
|
||||||
Returns None if none is found.
|
Returns None if none is found.
|
||||||
"""
|
"""
|
||||||
|
from bec_widgets.utils import BECConnector
|
||||||
|
|
||||||
parent = widget.parent()
|
parent = widget.parent()
|
||||||
while parent is not None:
|
while parent is not None:
|
||||||
if isinstance(parent, BECWidget):
|
if isinstance(parent, BECConnector):
|
||||||
return parent
|
return parent
|
||||||
parent = parent.parent()
|
parent = parent.parent()
|
||||||
return None
|
return None
|
||||||
|
@ -133,6 +133,7 @@ class BECDock(BECWidget, Dock):
|
|||||||
parent_id: str | None = None,
|
parent_id: str | None = None,
|
||||||
config: DockConfig | None = None,
|
config: DockConfig | None = None,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
|
object_name: str | None = None,
|
||||||
client=None,
|
client=None,
|
||||||
gui_id: str | None = None,
|
gui_id: str | None = None,
|
||||||
closable: bool = True,
|
closable: bool = True,
|
||||||
@ -148,12 +149,17 @@ class BECDock(BECWidget, Dock):
|
|||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
config = DockConfig(**config)
|
config = DockConfig(**config)
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(
|
|
||||||
client=client, config=config, gui_id=gui_id, name=name, parent_id=parent_id
|
|
||||||
) # Name was checked and created in BEC Widget
|
|
||||||
label = CustomDockLabel(text=name, closable=closable)
|
label = CustomDockLabel(text=name, closable=closable)
|
||||||
Dock.__init__(self, name=name, label=label, parent=self, **kwargs)
|
super().__init__(
|
||||||
# Dock.__init__(self, name=name, **kwargs)
|
parent=parent_dock_area,
|
||||||
|
name=name,
|
||||||
|
object_name=object_name,
|
||||||
|
client=client,
|
||||||
|
gui_id=gui_id,
|
||||||
|
config=config,
|
||||||
|
label=label,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
self.parent_dock_area = parent_dock_area
|
self.parent_dock_area = parent_dock_area
|
||||||
# Layout Manager
|
# Layout Manager
|
||||||
@ -193,7 +199,7 @@ class BECDock(BECWidget, Dock):
|
|||||||
widgets(dict): The widgets in the dock.
|
widgets(dict): The widgets in the dock.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
return dict((widget._name, widget) for widget in self.element_list)
|
return dict((widget.object_name, widget) for widget in self.element_list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def element_list(self) -> list[BECWidget]:
|
def element_list(self) -> list[BECWidget]:
|
||||||
@ -296,27 +302,11 @@ class BECDock(BECWidget, Dock):
|
|||||||
shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied.
|
shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied.
|
||||||
"""
|
"""
|
||||||
if row is None:
|
if row is None:
|
||||||
# row = cast(int, self.layout.rowCount()) # type:ignore
|
|
||||||
row = self.layout.rowCount()
|
row = self.layout.rowCount()
|
||||||
# row = cast(int, row)
|
|
||||||
|
|
||||||
if self.layout_manager.is_position_occupied(row, col):
|
if self.layout_manager.is_position_occupied(row, col):
|
||||||
self.layout_manager.shift_widgets(shift, start_row=row)
|
self.layout_manager.shift_widgets(shift, start_row=row)
|
||||||
|
|
||||||
existing_widgets_parent_dock = self._get_list_of_widget_name_of_parent_dock_area()
|
|
||||||
|
|
||||||
if name is not None: # Name is provided
|
|
||||||
if name in existing_widgets_parent_dock:
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
raise ValueError(
|
|
||||||
f"Name {name} must be unique for widgets, but already exists in DockArea "
|
|
||||||
f"with name: {self.parent_dock_area._name} and id {self.parent_dock_area.gui_id}."
|
|
||||||
)
|
|
||||||
else: # Name is not provided
|
|
||||||
widget_class_name = widget if isinstance(widget, str) else widget.__class__.__name__
|
|
||||||
name = WidgetContainerUtils.generate_unique_name(
|
|
||||||
name=widget_class_name, list_of_names=existing_widgets_parent_dock
|
|
||||||
)
|
|
||||||
# Check that Widget is not BECDock or BECDockArea
|
# Check that Widget is not BECDock or BECDockArea
|
||||||
widget_class_name = widget if isinstance(widget, str) else widget.__class__.__name__
|
widget_class_name = widget if isinstance(widget, str) else widget.__class__.__name__
|
||||||
if widget_class_name in IGNORE_WIDGETS:
|
if widget_class_name in IGNORE_WIDGETS:
|
||||||
@ -326,16 +316,20 @@ class BECDock(BECWidget, Dock):
|
|||||||
widget = cast(
|
widget = cast(
|
||||||
BECWidget,
|
BECWidget,
|
||||||
widget_handler.create_widget(
|
widget_handler.create_widget(
|
||||||
widget_type=widget, name=name, parent_dock=self, parent_id=self.gui_id
|
widget_type=widget,
|
||||||
|
object_name=name,
|
||||||
|
parent_dock=self,
|
||||||
|
parent_id=self.gui_id,
|
||||||
|
parent=self,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
widget._name = name # pylint: disable=protected-access
|
widget.object_name = name
|
||||||
|
|
||||||
self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
|
self.addWidget(widget, row=row, col=col, rowspan=rowspan, colspan=colspan)
|
||||||
if hasattr(widget, "config"):
|
if hasattr(widget, "config"):
|
||||||
widget.config.gui_id = widget.gui_id
|
widget.config.gui_id = widget.gui_id
|
||||||
self.config.widgets[widget._name] = widget.config # pylint: disable=protected-access
|
self.config.widgets[widget.object_name] = widget.config
|
||||||
return widget
|
return widget
|
||||||
|
|
||||||
def move_widget(self, widget: QWidget, new_row: int, new_col: int):
|
def move_widget(self, widget: QWidget, new_row: int, new_col: int):
|
||||||
@ -365,7 +359,7 @@ class BECDock(BECWidget, Dock):
|
|||||||
"""
|
"""
|
||||||
Remove the dock from the parent dock area.
|
Remove the dock from the parent dock area.
|
||||||
"""
|
"""
|
||||||
self.parent_dock_area.delete(self._name)
|
self.parent_dock_area.delete(self.object_name)
|
||||||
|
|
||||||
def delete(self, widget_name: str) -> None:
|
def delete(self, widget_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
@ -375,7 +369,7 @@ class BECDock(BECWidget, Dock):
|
|||||||
widget_name(str): Delete the widget with the given name.
|
widget_name(str): Delete the widget with the given name.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
widgets = [widget for widget in self.widgets if widget._name == widget_name]
|
widgets = [widget for widget in self.widgets if widget.object_name == widget_name]
|
||||||
if len(widgets) == 0:
|
if len(widgets) == 0:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Widget with name {widget_name} not found in dock {self.name()}. "
|
f"Widget with name {widget_name} not found in dock {self.name()}. "
|
||||||
@ -391,7 +385,7 @@ class BECDock(BECWidget, Dock):
|
|||||||
else:
|
else:
|
||||||
widget = widgets[0]
|
widget = widgets[0]
|
||||||
self.layout.removeWidget(widget)
|
self.layout.removeWidget(widget)
|
||||||
self.config.widgets.pop(widget._name, None)
|
self.config.widgets.pop(widget.object_name, None)
|
||||||
if widget in self.widgets:
|
if widget in self.widgets:
|
||||||
self.widgets.remove(widget)
|
self.widgets.remove(widget)
|
||||||
widget.close()
|
widget.close()
|
||||||
@ -401,7 +395,7 @@ class BECDock(BECWidget, Dock):
|
|||||||
Remove all widgets from the dock.
|
Remove all widgets from the dock.
|
||||||
"""
|
"""
|
||||||
for widget in self.widgets:
|
for widget in self.widgets:
|
||||||
self.delete(widget._name) # pylint: disable=protected-access
|
self.delete(widget.object_name)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""
|
"""
|
||||||
|
@ -21,6 +21,7 @@ from bec_widgets.utils.toolbar import (
|
|||||||
ModularToolBar,
|
ModularToolBar,
|
||||||
SeparatorAction,
|
SeparatorAction,
|
||||||
)
|
)
|
||||||
|
from bec_widgets.utils.widget_io import WidgetHierarchy
|
||||||
from bec_widgets.widgets.containers.dock.dock import BECDock, DockConfig
|
from bec_widgets.widgets.containers.dock.dock import BECDock, DockConfig
|
||||||
from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox
|
from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox
|
||||||
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
|
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
|
||||||
@ -73,7 +74,7 @@ class BECDockArea(BECWidget, QWidget):
|
|||||||
config: DockAreaConfig | None = None,
|
config: DockAreaConfig | None = None,
|
||||||
client=None,
|
client=None,
|
||||||
gui_id: str = None,
|
gui_id: str = None,
|
||||||
name: str | None = None,
|
object_name: str = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
if config is None:
|
if config is None:
|
||||||
@ -82,9 +83,15 @@ class BECDockArea(BECWidget, QWidget):
|
|||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
config = DockAreaConfig(**config)
|
config = DockAreaConfig(**config)
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, name=name, **kwargs)
|
super().__init__(
|
||||||
QWidget.__init__(self, parent=parent)
|
parent=parent,
|
||||||
self._parent = parent
|
object_name=object_name,
|
||||||
|
client=client,
|
||||||
|
gui_id=gui_id,
|
||||||
|
config=config,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
self._parent = parent # TODO probably not needed
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
self.layout.setSpacing(5)
|
self.layout.setSpacing(5)
|
||||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||||
@ -354,17 +361,26 @@ class BECDockArea(BECWidget, QWidget):
|
|||||||
Returns:
|
Returns:
|
||||||
BECDock: The created dock.
|
BECDock: The created dock.
|
||||||
"""
|
"""
|
||||||
dock_names = [dock._name for dock in self.panel_list] # pylint: disable=protected-access
|
dock_names = [
|
||||||
|
dock.object_name for dock in self.panel_list
|
||||||
|
] # pylint: disable=protected-access
|
||||||
if name is not None: # Name is provided
|
if name is not None: # Name is provided
|
||||||
if name in dock_names:
|
if name in dock_names:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Name {name} must be unique for docks, but already exists in DockArea "
|
f"Name {name} must be unique for docks, but already exists in DockArea "
|
||||||
f"with name: {self._name} and id {self.gui_id}."
|
f"with name: {self.object_name} and id {self.gui_id}."
|
||||||
)
|
)
|
||||||
else: # Name is not provided
|
else: # Name is not provided
|
||||||
name = WidgetContainerUtils.generate_unique_name(name="dock", 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, parent_id=self.gui_id, closable=closable)
|
dock = BECDock(
|
||||||
|
parent=self,
|
||||||
|
name=name, # this is dock name pyqtgraph property, this is displayed on label
|
||||||
|
object_name=name, # this is a real qt object name passed to BECConnector
|
||||||
|
parent_dock_area=self,
|
||||||
|
parent_id=self.gui_id,
|
||||||
|
closable=closable,
|
||||||
|
)
|
||||||
dock.config.position = position
|
dock.config.position = position
|
||||||
self.config.docks[dock.name()] = dock.config
|
self.config.docks[dock.name()] = dock.config
|
||||||
# The dock.name is equal to the name passed to BECDock
|
# The dock.name is equal to the name passed to BECDock
|
||||||
@ -499,11 +515,13 @@ if __name__ == "__main__": # pragma: no cover
|
|||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
set_theme("auto")
|
set_theme("auto")
|
||||||
dock_area = BECDockArea()
|
dock_area = BECDockArea()
|
||||||
dock_1 = dock_area.new(name="dock_0", widget="Waveform")
|
dock_1 = dock_area.new(name="dock_0", widget="DarkModeButton")
|
||||||
|
dock_1.new(widget="DarkModeButton")
|
||||||
# dock_1 = dock_area.new(name="dock_0", widget="Waveform")
|
# dock_1 = dock_area.new(name="dock_0", widget="Waveform")
|
||||||
dock_area.new(widget="Waveform")
|
dock_area.new(widget="DarkModeButton")
|
||||||
dock_area.show()
|
dock_area.show()
|
||||||
dock_area.setGeometry(100, 100, 800, 600)
|
dock_area.setGeometry(100, 100, 800, 600)
|
||||||
app.topLevelWidgets()
|
app.topLevelWidgets()
|
||||||
|
WidgetHierarchy.print_becconnector_hierarchy_from_app()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
@ -34,7 +34,6 @@ class LayoutManagerWidget(QWidget):
|
|||||||
|
|
||||||
def __init__(self, parent=None, auto_reindex=True):
|
def __init__(self, parent=None, auto_reindex=True):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setObjectName("LayoutManagerWidget")
|
|
||||||
self.layout = QGridLayout(self)
|
self.layout = QGridLayout(self)
|
||||||
self.auto_reindex = auto_reindex
|
self.auto_reindex = auto_reindex
|
||||||
|
|
||||||
|
@ -20,9 +20,8 @@ logger = bec_logger.logger
|
|||||||
|
|
||||||
|
|
||||||
class BECMainWindow(BECWidget, QMainWindow):
|
class BECMainWindow(BECWidget, QMainWindow):
|
||||||
def __init__(self, gui_id: str = None, *args, **kwargs):
|
def __init__(self, parent=None, gui_id: str = None, *args, **kwargs):
|
||||||
BECWidget.__init__(self, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, gui_id=gui_id, **kwargs)
|
||||||
QMainWindow.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
self.app = QApplication.instance()
|
self.app = QApplication.instance()
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ class AbortButton(BECWidget, QWidget):
|
|||||||
|
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "cancel"
|
ICON_NAME = "cancel"
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -22,9 +23,7 @@ class AbortButton(BECWidget, QWidget):
|
|||||||
scan_id=None,
|
scan_id=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
self.layout = QHBoxLayout(self)
|
self.layout = QHBoxLayout(self)
|
||||||
|
@ -11,11 +11,10 @@ class ResetButton(BECWidget, QWidget):
|
|||||||
|
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "restart_alt"
|
ICON_NAME = "restart_alt"
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
self.layout = QHBoxLayout(self)
|
self.layout = QHBoxLayout(self)
|
||||||
|
@ -11,10 +11,10 @@ class ResumeButton(BECWidget, QWidget):
|
|||||||
|
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "resume"
|
ICON_NAME = "resume"
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ class StopButton(BECWidget, QWidget):
|
|||||||
|
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "dangerous"
|
ICON_NAME = "dangerous"
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None, toolbar=False, **kwargs):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ class PositionIndicator(BECWidget, QWidget):
|
|||||||
ICON_NAME = "horizontal_distribute"
|
ICON_NAME = "horizontal_distribute"
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
self.position = 50
|
self.position = 50
|
||||||
self.min_value = 0
|
self.min_value = 0
|
||||||
self.max_value = 100
|
self.max_value = 100
|
||||||
|
@ -47,6 +47,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
|||||||
|
|
||||||
current_path = ""
|
current_path = ""
|
||||||
ICON_NAME = "switch_right"
|
ICON_NAME = "switch_right"
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, parent=None, **kwargs):
|
def __init__(self, parent=None, **kwargs):
|
||||||
"""Initialize the PositionerBox widget.
|
"""Initialize the PositionerBox widget.
|
||||||
@ -55,8 +56,7 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
|||||||
parent: The parent widget.
|
parent: The parent widget.
|
||||||
device (Positioner): The device to control.
|
device (Positioner): The device to control.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(parent=parent, layout=QVBoxLayout, **kwargs)
|
||||||
CompactPopupWidget.__init__(self, parent=parent, layout=QVBoxLayout)
|
|
||||||
self._dialog = None
|
self._dialog = None
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
|
@ -69,8 +69,7 @@ class PositionerGroup(BECWidget, QWidget):
|
|||||||
Args:
|
Args:
|
||||||
parent: The parent widget.
|
parent: The parent widget.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(parent=parent, **kwargs)
|
||||||
QWidget.__init__(self, parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ class DeviceSignalInputBase(BECWidget):
|
|||||||
signal object based on the current text of the widget.
|
signal object based on the current text of the widget.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
RPC = False
|
||||||
_filter_handler = {
|
_filter_handler = {
|
||||||
Kind.hinted: "include_hinted_signals",
|
Kind.hinted: "include_hinted_signals",
|
||||||
Kind.normal: "include_normal_signals",
|
Kind.normal: "include_normal_signals",
|
||||||
|
@ -47,8 +47,7 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
|
|||||||
arg_name: str | None = None,
|
arg_name: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QComboBox.__init__(self, parent=parent)
|
|
||||||
if arg_name is not None:
|
if arg_name is not None:
|
||||||
self.config.arg_name = arg_name
|
self.config.arg_name = arg_name
|
||||||
self.arg_name = arg_name
|
self.arg_name = arg_name
|
||||||
|
@ -53,8 +53,7 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
|||||||
self._callback_id = None
|
self._callback_id = None
|
||||||
self._is_valid_input = False
|
self._is_valid_input = False
|
||||||
self._accent_colors = get_accent_colors()
|
self._accent_colors = get_accent_colors()
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QLineEdit.__init__(self, parent=parent)
|
|
||||||
self.completer = QCompleter(self)
|
self.completer = QCompleter(self)
|
||||||
self.setCompleter(self.completer)
|
self.setCompleter(self.completer)
|
||||||
|
|
||||||
|
@ -40,8 +40,7 @@ class SignalComboBox(DeviceSignalInputBase, QComboBox):
|
|||||||
arg_name: str | None = None,
|
arg_name: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QComboBox.__init__(self, parent=parent)
|
|
||||||
if arg_name is not None:
|
if arg_name is not None:
|
||||||
self.config.arg_name = arg_name
|
self.config.arg_name = arg_name
|
||||||
self.arg_name = arg_name
|
self.arg_name = arg_name
|
||||||
|
@ -42,8 +42,7 @@ class SignalLineEdit(DeviceSignalInputBase, QLineEdit):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
self._is_valid_input = False
|
self._is_valid_input = False
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QLineEdit.__init__(self, parent=parent)
|
|
||||||
self._accent_colors = get_accent_colors()
|
self._accent_colors = get_accent_colors()
|
||||||
self.completer = QCompleter(self)
|
self.completer = QCompleter(self)
|
||||||
self.setCompleter(self.completer)
|
self.setCompleter(self.completer)
|
||||||
|
@ -65,8 +65,7 @@ class ScanControl(BECWidget, QWidget):
|
|||||||
config = ScanControlConfig(
|
config = ScanControlConfig(
|
||||||
widget_class=self.__class__.__name__, allowed_scans=allowed_scans
|
widget_class=self.__class__.__name__, allowed_scans=allowed_scans
|
||||||
)
|
)
|
||||||
super().__init__(client=client, gui_id=gui_id, config=config, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self._hide_add_remove_buttons = False
|
self._hide_add_remove_buttons = False
|
||||||
|
|
||||||
|
@ -44,8 +44,7 @@ class DapComboBox(BECWidget, QWidget):
|
|||||||
default_fit: str | None = None,
|
default_fit: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
self.fit_model_combobox = QComboBox(self)
|
self.fit_model_combobox = QComboBox(self)
|
||||||
self.layout.addWidget(self.fit_model_combobox)
|
self.layout.addWidget(self.fit_model_combobox)
|
||||||
|
@ -17,6 +17,7 @@ class LMFitDialog(BECWidget, QWidget):
|
|||||||
|
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "monitoring"
|
ICON_NAME = "monitoring"
|
||||||
|
RPC = False
|
||||||
# Signal to emit the currently selected fit curve_id
|
# Signal to emit the currently selected fit curve_id
|
||||||
selected_fit = Signal(str)
|
selected_fit = Signal(str)
|
||||||
# Signal to emit a move action in form of a tuple (param_name, value)
|
# Signal to emit a move action in form of a tuple (param_name, value)
|
||||||
@ -43,10 +44,8 @@ class LMFitDialog(BECWidget, QWidget):
|
|||||||
gui_id (str): GUI ID.
|
gui_id (str): GUI ID.
|
||||||
ui_file (str): The UI file to be loaded.
|
ui_file (str): The UI file to be loaded.
|
||||||
"""
|
"""
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
self.setProperty("skip_settings", True)
|
self.setProperty("skip_settings", True)
|
||||||
self.setObjectName("LMFitDialog")
|
|
||||||
self._ui_file = ui_file
|
self._ui_file = ui_file
|
||||||
self.target_widget = target_widget
|
self.target_widget = target_widget
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ class ScanMetadata(BECWidget, QWidget):
|
|||||||
|
|
||||||
metadata_updated = Signal(dict)
|
metadata_updated = Signal(dict)
|
||||||
metadata_cleared = Signal(NoneType)
|
metadata_cleared = Signal(NoneType)
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -54,8 +55,7 @@ class ScanMetadata(BECWidget, QWidget):
|
|||||||
initial_extras: list[list[str]] | None = None,
|
initial_extras: list[list[str]] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, **kwargs)
|
super().__init__(parent=parent, client=client, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.set_schema(scan_name)
|
self.set_schema(scan_name)
|
||||||
|
|
||||||
|
@ -49,8 +49,7 @@ class TextBox(BECWidget, QWidget):
|
|||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
config = TextBoxConfig(**config)
|
config = TextBoxConfig(**config)
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent)
|
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
self.text_box_text_edit = QTextEdit(parent=self)
|
self.text_box_text_edit = QTextEdit(parent=self)
|
||||||
self.layout.addWidget(self.text_box_text_edit)
|
self.layout.addWidget(self.text_box_text_edit)
|
||||||
|
@ -26,8 +26,7 @@ class WebsiteWidget(BECWidget, QWidget):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, parent=None, url: str = None, config=None, client=None, gui_id=None, **kwargs
|
self, parent=None, url: str = None, config=None, client=None, gui_id=None, **kwargs
|
||||||
):
|
):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.website = QWebEngineView()
|
self.website = QWebEngineView()
|
||||||
|
@ -144,10 +144,10 @@ class Minesweeper(BECWidget, QWidget):
|
|||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
ICON_NAME = "videogame_asset"
|
ICON_NAME = "videogame_asset"
|
||||||
USER_ACCESS = []
|
USER_ACCESS = []
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, parent=None, *args, **kwargs):
|
def __init__(self, parent=None, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(parent=parent, *args, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self._ui_initialised = False
|
self._ui_initialised = False
|
||||||
self._timer_start_num_seconds = 0
|
self._timer_start_num_seconds = 0
|
||||||
|
@ -125,16 +125,15 @@ class Image(PlotBase):
|
|||||||
popups: bool = True,
|
popups: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
self._main_image = ImageItem(parent_image=self)
|
|
||||||
self._color_bar = None
|
|
||||||
if config is None:
|
if config is None:
|
||||||
config = ImageConfig(widget_class=self.__class__.__name__)
|
config = ImageConfig(widget_class=self.__class__.__name__)
|
||||||
|
self.gui_id = config.gui_id
|
||||||
|
self._color_bar = None
|
||||||
|
self._main_image = ImageItem()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
||||||
)
|
)
|
||||||
|
self._main_image.parent_image = self
|
||||||
# For PropertyManager identification
|
|
||||||
self.setObjectName("Image")
|
|
||||||
|
|
||||||
self.plot_item.addItem(self._main_image)
|
self.plot_item.addItem(self._main_image)
|
||||||
self.scan_id = None
|
self.scan_id = None
|
||||||
|
@ -82,10 +82,12 @@ class ImageItem(BECConnector, pg.ImageItem):
|
|||||||
self.config = config
|
self.config = config
|
||||||
else:
|
else:
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(config=config, gui_id=gui_id)
|
if parent_image is not None:
|
||||||
pg.ImageItem.__init__(self)
|
self.set_parent(parent_image)
|
||||||
|
else:
|
||||||
self.parent_image = parent_image
|
self.parent_image = None
|
||||||
|
self.parent_id = None
|
||||||
|
super().__init__(config=config, gui_id=gui_id, **kwargs)
|
||||||
|
|
||||||
self.raw_data = None
|
self.raw_data = None
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
@ -94,6 +96,13 @@ class ImageItem(BECConnector, pg.ImageItem):
|
|||||||
# Image processor will handle any setting of data
|
# Image processor will handle any setting of data
|
||||||
self._image_processor = ImageProcessor(config=self.config.processing)
|
self._image_processor = ImageProcessor(config=self.config.processing)
|
||||||
|
|
||||||
|
def set_parent(self, parent: BECConnector):
|
||||||
|
self.parent_image = parent
|
||||||
|
self.parent_id = parent.gui_id
|
||||||
|
|
||||||
|
def parent(self):
|
||||||
|
return self.parent_image
|
||||||
|
|
||||||
def set_data(self, data: np.ndarray):
|
def set_data(self, data: np.ndarray):
|
||||||
self.raw_data = data
|
self.raw_data = data
|
||||||
self._process_image()
|
self._process_image()
|
||||||
|
@ -161,9 +161,6 @@ class MotorMap(PlotBase):
|
|||||||
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
# For PropertyManager identification
|
|
||||||
self.setObjectName("MotorMap")
|
|
||||||
|
|
||||||
# Default values for PlotBase
|
# Default values for PlotBase
|
||||||
self.x_grid = True
|
self.x_grid = True
|
||||||
self.y_grid = True
|
self.y_grid = True
|
||||||
|
@ -20,7 +20,6 @@ class MotorMapSettings(SettingWidget):
|
|||||||
super().__init__(parent=parent, *args, **kwargs)
|
super().__init__(parent=parent, *args, **kwargs)
|
||||||
|
|
||||||
self.setProperty("skip_settings", True)
|
self.setProperty("skip_settings", True)
|
||||||
self.setObjectName("MotorMapSettings")
|
|
||||||
current_path = os.path.dirname(__file__)
|
current_path = os.path.dirname(__file__)
|
||||||
|
|
||||||
form = UILoader().load_ui(os.path.join(current_path, "motor_map_settings.ui"), self)
|
form = UILoader().load_ui(os.path.join(current_path, "motor_map_settings.ui"), self)
|
||||||
|
@ -126,9 +126,6 @@ class MultiWaveform(PlotBase):
|
|||||||
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
# For PropertyManager identification
|
|
||||||
self.setObjectName("MultiWaveform")
|
|
||||||
|
|
||||||
# Scan Data
|
# Scan Data
|
||||||
self.old_scan_id = None
|
self.old_scan_id = None
|
||||||
self.scan_id = None
|
self.scan_id = None
|
||||||
|
@ -20,7 +20,6 @@ class MultiWaveformControlPanel(SettingWidget):
|
|||||||
super().__init__(parent=parent, *args, **kwargs)
|
super().__init__(parent=parent, *args, **kwargs)
|
||||||
|
|
||||||
self.setProperty("skip_settings", True)
|
self.setProperty("skip_settings", True)
|
||||||
self.setObjectName("MultiWaveformControlPanel")
|
|
||||||
current_path = os.path.dirname(__file__)
|
current_path = os.path.dirname(__file__)
|
||||||
|
|
||||||
form = UILoader().load_ui(os.path.join(current_path, "multi_waveform_controls.ui"), self)
|
form = UILoader().load_ui(os.path.join(current_path, "multi_waveform_controls.ui"), self)
|
||||||
|
@ -74,11 +74,9 @@ class PlotBase(BECWidget, QWidget):
|
|||||||
) -> None:
|
) -> None:
|
||||||
if config is None:
|
if config is None:
|
||||||
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
||||||
super().__init__(client=client, gui_id=gui_id, config=config, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
# For PropertyManager identification
|
# For PropertyManager identification
|
||||||
self.setObjectName("PlotBase")
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
|
|
||||||
# Layout Management
|
# Layout Management
|
||||||
@ -1018,7 +1016,7 @@ if __name__ == "__main__": # pragma: no cover:
|
|||||||
from qtpy.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
window = DemoPlotBase()
|
window = PlotBase()
|
||||||
window.show()
|
window.show()
|
||||||
|
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
@ -77,13 +77,16 @@ class ScatterCurve(BECConnector, pg.PlotDataItem):
|
|||||||
else:
|
else:
|
||||||
self.config = config
|
self.config = config
|
||||||
name = config.label
|
name = config.label
|
||||||
super().__init__(config=config, gui_id=gui_id)
|
|
||||||
pg.PlotDataItem.__init__(self, name=name)
|
|
||||||
|
|
||||||
self.parent_item = parent_item
|
self.parent_item = parent_item
|
||||||
|
self.parent_id = self.parent_item.gui_id
|
||||||
|
super().__init__(name=name, config=config, gui_id=gui_id, **kwargs)
|
||||||
|
|
||||||
self.data_z = None # color scaling needs to be cashed for changing colormap
|
self.data_z = None # color scaling needs to be cashed for changing colormap
|
||||||
self.apply_config()
|
self.apply_config()
|
||||||
|
|
||||||
|
def parent(self):
|
||||||
|
return self.parent_item
|
||||||
|
|
||||||
def apply_config(self, config: dict | ScatterCurveConfig | None = None, **kwargs) -> None:
|
def apply_config(self, config: dict | ScatterCurveConfig | None = None, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Apply the configuration to the curve.
|
Apply the configuration to the curve.
|
||||||
|
@ -111,8 +111,6 @@ class ScatterWaveform(PlotBase):
|
|||||||
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
||||||
)
|
)
|
||||||
self._main_curve = ScatterCurve(parent_item=self)
|
self._main_curve = ScatterCurve(parent_item=self)
|
||||||
# For PropertyManager identification
|
|
||||||
self.setObjectName("ScatterWaveform")
|
|
||||||
|
|
||||||
# Specific GUI elements
|
# Specific GUI elements
|
||||||
self.scatter_dialog = None
|
self.scatter_dialog = None
|
||||||
|
@ -15,7 +15,6 @@ class ScatterCurveSettings(SettingWidget):
|
|||||||
# and should mirror what is in the target widget.
|
# and should mirror what is in the target widget.
|
||||||
# Saving settings for this widget could result in recursively setting the target widget.
|
# Saving settings for this widget could result in recursively setting the target widget.
|
||||||
self.setProperty("skip_settings", True)
|
self.setProperty("skip_settings", True)
|
||||||
self.setObjectName("ScatterCurveSettings")
|
|
||||||
|
|
||||||
current_path = os.path.dirname(__file__)
|
current_path = os.path.dirname(__file__)
|
||||||
if popup:
|
if popup:
|
||||||
|
@ -16,7 +16,6 @@ class AxisSettings(SettingWidget):
|
|||||||
# and should mirror what is in the target widget.
|
# and should mirror what is in the target widget.
|
||||||
# Saving settings for this widget could result in recursively setting the target widget.
|
# Saving settings for this widget could result in recursively setting the target widget.
|
||||||
self.setProperty("skip_settings", True)
|
self.setProperty("skip_settings", True)
|
||||||
self.setObjectName("AxisSettings")
|
|
||||||
current_path = os.path.dirname(__file__)
|
current_path = os.path.dirname(__file__)
|
||||||
if popup:
|
if popup:
|
||||||
form = UILoader().load_ui(
|
form = UILoader().load_ui(
|
||||||
|
@ -91,10 +91,10 @@ class Curve(BECConnector, pg.PlotDataItem):
|
|||||||
self.config = config
|
self.config = config
|
||||||
else:
|
else:
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(config=config, gui_id=gui_id)
|
|
||||||
pg.PlotDataItem.__init__(self, name=name)
|
|
||||||
|
|
||||||
self.parent_item = parent_item
|
self.parent_item = parent_item
|
||||||
|
self.parent_id = self.parent_item.gui_id
|
||||||
|
super().__init__(name=name, config=config, gui_id=gui_id, **kwargs)
|
||||||
|
|
||||||
self.apply_config()
|
self.apply_config()
|
||||||
self.dap_params = None
|
self.dap_params = None
|
||||||
self.dap_summary = None
|
self.dap_summary = None
|
||||||
@ -104,6 +104,9 @@ class Curve(BECConnector, pg.PlotDataItem):
|
|||||||
# Activate setClipToView, to boost performance for large datasets per default
|
# Activate setClipToView, to boost performance for large datasets per default
|
||||||
self.setClipToView(True)
|
self.setClipToView(True)
|
||||||
|
|
||||||
|
def parent(self):
|
||||||
|
return self.parent_item
|
||||||
|
|
||||||
def apply_config(self, config: dict | CurveConfig | None = None, **kwargs) -> None:
|
def apply_config(self, config: dict | CurveConfig | None = None, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Apply the configuration to the curve.
|
Apply the configuration to the curve.
|
||||||
|
@ -334,11 +334,11 @@ class CurveTree(BECWidget, QWidget):
|
|||||||
client=None,
|
client=None,
|
||||||
gui_id: str | None = None,
|
gui_id: str | None = None,
|
||||||
waveform: Waveform | None = None,
|
waveform: Waveform | None = None,
|
||||||
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
if config is None:
|
if config is None:
|
||||||
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
||||||
super().__init__(client=client, gui_id=gui_id, config=config)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.waveform = waveform
|
self.waveform = waveform
|
||||||
if self.waveform and hasattr(self.waveform, "color_palette"):
|
if self.waveform and hasattr(self.waveform, "color_palette"):
|
||||||
|
@ -9,8 +9,15 @@ import pyqtgraph as pg
|
|||||||
from bec_lib import bec_logger, messages
|
from bec_lib import bec_logger, messages
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
from pydantic import Field, ValidationError, field_validator
|
from pydantic import Field, ValidationError, field_validator
|
||||||
from qtpy.QtCore import QTimer, Signal, Slot
|
from qtpy.QtCore import QTimer, Signal
|
||||||
from qtpy.QtWidgets import QDialog, QHBoxLayout, QMainWindow, QVBoxLayout, QWidget
|
from qtpy.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
|
QDialog,
|
||||||
|
QHBoxLayout,
|
||||||
|
QMainWindow,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
from bec_widgets.utils import ConnectionConfig
|
from bec_widgets.utils import ConnectionConfig
|
||||||
from bec_widgets.utils.bec_signal_proxy import BECSignalProxy
|
from bec_widgets.utils.bec_signal_proxy import BECSignalProxy
|
||||||
@ -18,7 +25,6 @@ from bec_widgets.utils.colors import Colors, set_theme
|
|||||||
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
||||||
from bec_widgets.utils.settings_dialog import SettingsDialog
|
from bec_widgets.utils.settings_dialog import SettingsDialog
|
||||||
from bec_widgets.utils.toolbar import MaterialIconAction
|
from bec_widgets.utils.toolbar import MaterialIconAction
|
||||||
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow
|
|
||||||
from bec_widgets.widgets.dap.lmfit_dialog.lmfit_dialog import LMFitDialog
|
from bec_widgets.widgets.dap.lmfit_dialog.lmfit_dialog import LMFitDialog
|
||||||
from bec_widgets.widgets.plots.plot_base import PlotBase
|
from bec_widgets.widgets.plots.plot_base import PlotBase
|
||||||
from bec_widgets.widgets.plots.waveform.curve import Curve, CurveConfig, DeviceSignal
|
from bec_widgets.widgets.plots.waveform.curve import Curve, CurveConfig, DeviceSignal
|
||||||
@ -129,9 +135,6 @@ class Waveform(PlotBase):
|
|||||||
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
# For PropertyManager identification
|
|
||||||
self.setObjectName("Waveform")
|
|
||||||
|
|
||||||
# Curve data
|
# Curve data
|
||||||
self._sync_curves = []
|
self._sync_curves = []
|
||||||
self._async_curves = []
|
self._async_curves = []
|
||||||
@ -1737,12 +1740,12 @@ class Waveform(PlotBase):
|
|||||||
super().cleanup()
|
super().cleanup()
|
||||||
|
|
||||||
|
|
||||||
class DemoApp(BECMainWindow): # pragma: no cover
|
class DemoApp(QMainWindow): # pragma: no cover
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle("Waveform Demo")
|
self.setWindowTitle("Waveform Demo")
|
||||||
self.resize(800, 600)
|
self.resize(800, 600)
|
||||||
self.main_widget = QWidget()
|
self.main_widget = QWidget(self)
|
||||||
self.layout = QHBoxLayout(self.main_widget)
|
self.layout = QHBoxLayout(self.main_widget)
|
||||||
self.setCentralWidget(self.main_widget)
|
self.setCentralWidget(self.main_widget)
|
||||||
|
|
||||||
@ -1760,9 +1763,7 @@ class DemoApp(BECMainWindow): # pragma: no cover
|
|||||||
if __name__ == "__main__": # pragma: no cover
|
if __name__ == "__main__": # pragma: no cover
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from bec_widgets.utils.bec_qapp import BECApplication
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
app = BECApplication(sys.argv)
|
|
||||||
set_theme("dark")
|
set_theme("dark")
|
||||||
widget = DemoApp()
|
widget = DemoApp()
|
||||||
widget.show()
|
widget.show()
|
||||||
|
@ -25,8 +25,7 @@ class BECProgressBar(BECWidget, QWidget):
|
|||||||
ICON_NAME = "page_control"
|
ICON_NAME = "page_control"
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
accent_colors = get_accent_colors()
|
accent_colors = get_accent_colors()
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from bec_lib.endpoints import EndpointInfo, MessageEndpoints
|
|||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from pydantic_core import PydanticCustomError
|
from pydantic_core import PydanticCustomError
|
||||||
from qtpy import QtGui
|
from qtpy import QtGui
|
||||||
|
from qtpy.QtCore import QObject
|
||||||
|
|
||||||
from bec_widgets.utils import BECConnector, ConnectionConfig
|
from bec_widgets.utils import BECConnector, ConnectionConfig
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ class RingConfig(ProgressbarConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Ring(BECConnector):
|
class Ring(BECConnector, QObject):
|
||||||
USER_ACCESS = [
|
USER_ACCESS = [
|
||||||
"_get_all_rpc",
|
"_get_all_rpc",
|
||||||
"_rpc_id",
|
"_rpc_id",
|
||||||
@ -108,7 +109,7 @@ class Ring(BECConnector):
|
|||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
config = RingConfig(**config)
|
config = RingConfig(**config)
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, config=config, gui_id=gui_id, **kwargs)
|
||||||
|
|
||||||
self.parent_progress_widget = parent_progress_widget
|
self.parent_progress_widget = parent_progress_widget
|
||||||
self.color = None
|
self.color = None
|
||||||
|
@ -110,8 +110,7 @@ class RingProgressBar(BECWidget, QWidget):
|
|||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
config = RingProgressBarConfig(**config, widget_class=self.__class__.__name__)
|
config = RingProgressBarConfig(**config, widget_class=self.__class__.__name__)
|
||||||
self.config = config
|
self.config = config
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
self.entry_validator = EntryValidator(self.dev)
|
self.entry_validator = EntryValidator(self.dev)
|
||||||
|
@ -44,8 +44,9 @@ class BECQueue(BECWidget, CompactPopupWidget):
|
|||||||
refresh_upon_start: bool = True,
|
refresh_upon_start: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(
|
||||||
CompactPopupWidget.__init__(self, parent=parent, layout=QVBoxLayout)
|
parent=parent, layout=QVBoxLayout, client=client, gui_id=gui_id, config=config, **kwargs
|
||||||
|
)
|
||||||
self.layout.setSpacing(0)
|
self.layout.setSpacing(0)
|
||||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
@ -89,8 +89,7 @@ class BECStatusBox(BECWidget, CompactPopupWidget):
|
|||||||
gui_id: str = None,
|
gui_id: str = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(client=client, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, layout=QHBoxLayout, client=client, gui_id=gui_id, **kwargs)
|
||||||
CompactPopupWidget.__init__(self, parent=parent, layout=QHBoxLayout)
|
|
||||||
|
|
||||||
self.box_name = box_name
|
self.box_name = box_name
|
||||||
self.status_container = defaultdict(lambda: {"info": None, "item": None, "widget": None})
|
self.status_container = defaultdict(lambda: {"info": None, "item": None, "widget": None})
|
||||||
|
@ -25,8 +25,7 @@ class DeviceBrowser(BECWidget, QWidget):
|
|||||||
gui_id: Optional[str] = None,
|
gui_id: Optional[str] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent)
|
|
||||||
|
|
||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
self.ui = None
|
self.ui = None
|
||||||
|
@ -103,8 +103,8 @@ class BecLogsQueue:
|
|||||||
self._display_queue.append(self._line_formatter(_msg))
|
self._display_queue.append(self._line_formatter(_msg))
|
||||||
if self._new_message_signal:
|
if self._new_message_signal:
|
||||||
self._new_message_signal.emit()
|
self._new_message_signal.emit()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.warning(f"Error in LogPanel incoming message callback: {e}")
|
pass
|
||||||
|
|
||||||
def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format):
|
def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format):
|
||||||
self._line_formatter: LineFormatter = line_formatter
|
self._line_formatter: LineFormatter = line_formatter
|
||||||
|
@ -30,10 +30,8 @@ class BECSpinBox(BECWidget, QDoubleSpinBox):
|
|||||||
) -> None:
|
) -> None:
|
||||||
if config is None:
|
if config is None:
|
||||||
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
||||||
super().__init__(client=client, gui_id=gui_id, config=config, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QDoubleSpinBox.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.setObjectName("BECSpinBox")
|
|
||||||
# Make the widget as compact as possible horizontally.
|
# Make the widget as compact as possible horizontally.
|
||||||
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||||
self.setAlignment(Qt.AlignHCenter)
|
self.setAlignment(Qt.AlignHCenter)
|
||||||
|
@ -9,13 +9,11 @@ from bec_widgets.utils.bec_widget import BECWidget
|
|||||||
class BECColorMapWidget(BECWidget, QWidget):
|
class BECColorMapWidget(BECWidget, QWidget):
|
||||||
colormap_changed_signal = Signal(str)
|
colormap_changed_signal = Signal(str)
|
||||||
ICON_NAME = "palette"
|
ICON_NAME = "palette"
|
||||||
USER_ACCESS = ["colormap"]
|
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(self, parent=None, cmap: str = "magma", **kwargs):
|
def __init__(self, parent=None, cmap: str = "magma", **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(parent=parent, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
# Create the ColorMapButton
|
# Create the ColorMapButton
|
||||||
self.button = ColorMapButton()
|
self.button = ColorMapButton()
|
||||||
|
|
||||||
|
@ -9,10 +9,10 @@ from bec_widgets.utils.colors import set_theme
|
|||||||
|
|
||||||
|
|
||||||
class DarkModeButton(BECWidget, QWidget):
|
class DarkModeButton(BECWidget, QWidget):
|
||||||
USER_ACCESS = ["toggle_dark_mode"]
|
|
||||||
|
|
||||||
ICON_NAME = "dark_mode"
|
ICON_NAME = "dark_mode"
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
|
RPC = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -22,8 +22,7 @@ class DarkModeButton(BECWidget, QWidget):
|
|||||||
toolbar: bool = False,
|
toolbar: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(client=client, gui_id=gui_id, theme_update=True, **kwargs)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, theme_update=True, **kwargs)
|
||||||
QWidget.__init__(self, parent)
|
|
||||||
|
|
||||||
self._dark_mode_enabled = False
|
self._dark_mode_enabled = False
|
||||||
self.layout = QHBoxLayout(self)
|
self.layout = QHBoxLayout(self)
|
||||||
@ -99,9 +98,6 @@ class DarkModeButton(BECWidget, QWidget):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from qtpy.QtWidgets import QApplication
|
|
||||||
|
|
||||||
from bec_widgets.utils.colors import set_theme
|
|
||||||
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
set_theme("auto")
|
set_theme("auto")
|
||||||
|
@ -130,11 +130,11 @@ class ExamplePlotWidget(BECWidget, QWidget):
|
|||||||
config: ConnectionConfig | None = None,
|
config: ConnectionConfig | None = None,
|
||||||
client=None,
|
client=None,
|
||||||
gui_id: str | None = None,
|
gui_id: str | None = None,
|
||||||
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
if config is None:
|
if config is None:
|
||||||
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
||||||
super().__init__(client=client, gui_id=gui_id, config=config)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
self.layout = QVBoxLayout(self)
|
self.layout = QVBoxLayout(self)
|
||||||
self.glw = pg.GraphicsLayoutWidget()
|
self.glw = pg.GraphicsLayoutWidget()
|
||||||
|
@ -17,9 +17,8 @@ from .conftest import create_widget
|
|||||||
class DeviceInputWidget(DeviceInputBase, QWidget):
|
class DeviceInputWidget(DeviceInputBase, QWidget):
|
||||||
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
|
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None, **kwargs):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -22,8 +22,7 @@ class DeviceInputWidget(DeviceSignalInputBase, QWidget):
|
|||||||
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
|
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
|
||||||
|
|
||||||
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
||||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
||||||
QWidget.__init__(self, parent=parent)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -5,7 +5,7 @@ from bec_widgets.cli.rpc.rpc_base import DeletedWidgetError, RPCBase, RPCReferen
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def rpc_base():
|
def rpc_base():
|
||||||
yield RPCBase(gui_id="rpc_base_test", name="test")
|
yield RPCBase(gui_id="rpc_base_test", object_name="test")
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_base(rpc_base):
|
def test_rpc_base(rpc_base):
|
||||||
|
Reference in New Issue
Block a user