mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 19:21:50 +02:00
fix: ensure provided dock and dock_area names are valid and defaults are snake_case
This commit is contained in:
@ -231,6 +231,10 @@ class LaunchWindow(BECMainWindow):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Name {name} must be unique for dock areas, but already exists: {existing_dock_areas}."
|
f"Name {name} must be unique for dock areas, but already exists: {existing_dock_areas}."
|
||||||
)
|
)
|
||||||
|
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||||
|
raise ValueError(
|
||||||
|
f"Name {name} contains invalid characters. Only alphanumeric characters, underscores, and dashes are allowed."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
name = "dock_area"
|
name = "dock_area"
|
||||||
name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas)
|
name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas)
|
||||||
|
@ -5,6 +5,8 @@ from typing import NamedTuple
|
|||||||
|
|
||||||
from qtpy.QtCore import QObject
|
from qtpy.QtCore import QObject
|
||||||
|
|
||||||
|
from bec_widgets.utils.name_utils import pascal_to_snake
|
||||||
|
|
||||||
EXCLUDED_PLUGINS = ["BECConnector", "BECDockArea", "BECDock", "BECFigure"]
|
EXCLUDED_PLUGINS = ["BECConnector", "BECDockArea", "BECDock", "BECFigure"]
|
||||||
|
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ class DesignerPluginInfo:
|
|||||||
def __init__(self, plugin_class):
|
def __init__(self, plugin_class):
|
||||||
self.plugin_class = plugin_class
|
self.plugin_class = plugin_class
|
||||||
self.plugin_name_pascal = plugin_class.__name__
|
self.plugin_name_pascal = plugin_class.__name__
|
||||||
self.plugin_name_snake = self.pascal_to_snake(self.plugin_name_pascal)
|
self.plugin_name_snake = pascal_to_snake(self.plugin_name_pascal)
|
||||||
self.widget_import = f"from {plugin_class.__module__} import {self.plugin_name_pascal}"
|
self.widget_import = f"from {plugin_class.__module__} import {self.plugin_name_pascal}"
|
||||||
plugin_module = (
|
plugin_module = (
|
||||||
".".join(plugin_class.__module__.split(".")[:-1]) + f".{self.plugin_name_snake}_plugin"
|
".".join(plugin_class.__module__.split(".")[:-1]) + f".{self.plugin_name_snake}_plugin"
|
||||||
@ -38,21 +40,6 @@ class DesignerPluginInfo:
|
|||||||
|
|
||||||
self.base_path = os.path.dirname(inspect.getfile(plugin_class))
|
self.base_path = os.path.dirname(inspect.getfile(plugin_class))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pascal_to_snake(name: str) -> str:
|
|
||||||
"""
|
|
||||||
Convert PascalCase to snake_case.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): The name to be converted.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The converted name.
|
|
||||||
"""
|
|
||||||
s1 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name)
|
|
||||||
s2 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s1)
|
|
||||||
return s2.lower()
|
|
||||||
|
|
||||||
|
|
||||||
class DesignerPluginGenerator:
|
class DesignerPluginGenerator:
|
||||||
def __init__(self, widget: type):
|
def __init__(self, widget: type):
|
||||||
|
16
bec_widgets/utils/name_utils.py
Normal file
16
bec_widgets/utils/name_utils.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def pascal_to_snake(name: str) -> str:
|
||||||
|
"""
|
||||||
|
Convert PascalCase to snake_case.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name to be converted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The converted name.
|
||||||
|
"""
|
||||||
|
s1 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name)
|
||||||
|
s2 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s1)
|
||||||
|
return s2.lower()
|
@ -14,6 +14,7 @@ from bec_widgets.cli.rpc.rpc_register import RPCRegister
|
|||||||
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
|
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
|
||||||
from bec_widgets.utils.bec_widget import BECWidget
|
from bec_widgets.utils.bec_widget import BECWidget
|
||||||
from bec_widgets.utils.error_popups import SafeSlot
|
from bec_widgets.utils.error_popups import SafeSlot
|
||||||
|
from bec_widgets.utils.name_utils import pascal_to_snake
|
||||||
from bec_widgets.utils.toolbar import (
|
from bec_widgets.utils.toolbar import (
|
||||||
ExpandableMenuAction,
|
ExpandableMenuAction,
|
||||||
MaterialIconAction,
|
MaterialIconAction,
|
||||||
@ -242,7 +243,8 @@ class BECDockArea(BECWidget, QWidget):
|
|||||||
def _create_widget_from_toolbar(self, widget_name: str) -> None:
|
def _create_widget_from_toolbar(self, widget_name: str) -> None:
|
||||||
# Run with RPC broadcast to namespace of all widgets
|
# Run with RPC broadcast to namespace of all widgets
|
||||||
with RPCRegister.delayed_broadcast():
|
with RPCRegister.delayed_broadcast():
|
||||||
dock_name = WidgetContainerUtils.generate_unique_name(widget_name, self.panels.keys())
|
name = pascal_to_snake(widget_name)
|
||||||
|
dock_name = WidgetContainerUtils.generate_unique_name(name, self.panels.keys())
|
||||||
self.new(name=dock_name, widget=widget_name)
|
self.new(name=dock_name, widget=widget_name)
|
||||||
|
|
||||||
def paintEvent(self, event: QPaintEvent): # TODO decide if we want any default instructions
|
def paintEvent(self, event: QPaintEvent): # TODO decide if we want any default instructions
|
||||||
@ -362,6 +364,11 @@ class BECDockArea(BECWidget, QWidget):
|
|||||||
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.object_name} and id {self.gui_id}."
|
f"with name: {self.object_name} and id {self.gui_id}."
|
||||||
)
|
)
|
||||||
|
if not WidgetContainerUtils.has_name_valid_chars(name):
|
||||||
|
raise ValueError(
|
||||||
|
f"Name {name} contains invalid characters. "
|
||||||
|
f"Only alphanumeric characters and underscores are allowed."
|
||||||
|
)
|
||||||
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)
|
||||||
|
|
||||||
|
@ -94,41 +94,45 @@ def test_undock_and_dock_docks(bec_dock_area, qtbot):
|
|||||||
###################################
|
###################################
|
||||||
def test_toolbar_add_plot_waveform(bec_dock_area):
|
def test_toolbar_add_plot_waveform(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["waveform"].trigger()
|
bec_dock_area.toolbar.widgets["menu_plots"].widgets["waveform"].trigger()
|
||||||
assert "Waveform_0" in bec_dock_area.panels
|
assert "waveform_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["Waveform_0"].widgets[0].config.widget_class == "Waveform"
|
assert bec_dock_area.panels["waveform_0"].widgets[0].config.widget_class == "Waveform"
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_plot_scatter_waveform(bec_dock_area):
|
def test_toolbar_add_plot_scatter_waveform(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["scatter_waveform"].trigger()
|
bec_dock_area.toolbar.widgets["menu_plots"].widgets["scatter_waveform"].trigger()
|
||||||
assert "ScatterWaveform_0" in bec_dock_area.panels
|
assert "scatter_waveform_0" in bec_dock_area.panels
|
||||||
assert (
|
assert (
|
||||||
bec_dock_area.panels["ScatterWaveform_0"].widgets[0].config.widget_class
|
bec_dock_area.panels["scatter_waveform_0"].widgets[0].config.widget_class
|
||||||
== "ScatterWaveform"
|
== "ScatterWaveform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_plot_image(bec_dock_area):
|
def test_toolbar_add_plot_image(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["image"].trigger()
|
bec_dock_area.toolbar.widgets["menu_plots"].widgets["image"].trigger()
|
||||||
assert "Image_0" in bec_dock_area.panels
|
assert "image_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["Image_0"].widgets[0].config.widget_class == "Image"
|
assert bec_dock_area.panels["image_0"].widgets[0].config.widget_class == "Image"
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_plot_motor_map(bec_dock_area):
|
def test_toolbar_add_plot_motor_map(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["motor_map"].trigger()
|
bec_dock_area.toolbar.widgets["menu_plots"].widgets["motor_map"].trigger()
|
||||||
assert "MotorMap_0" in bec_dock_area.panels
|
assert "motor_map_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["MotorMap_0"].widgets[0].config.widget_class == "MotorMap"
|
assert bec_dock_area.panels["motor_map_0"].widgets[0].config.widget_class == "MotorMap"
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_multi_waveform(bec_dock_area):
|
def test_toolbar_add_multi_waveform(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["multi_waveform"].trigger()
|
bec_dock_area.toolbar.widgets["menu_plots"].widgets["multi_waveform"].trigger()
|
||||||
assert "MultiWaveform_0" in bec_dock_area.panels
|
assert "multi_waveform_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["MultiWaveform_0"].widgets[0].config.widget_class == "MultiWaveform"
|
assert (
|
||||||
|
bec_dock_area.panels["multi_waveform_0"].widgets[0].config.widget_class == "MultiWaveform"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_device_positioner_box(bec_dock_area):
|
def test_toolbar_add_device_positioner_box(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_devices"].widgets["positioner_box"].trigger()
|
bec_dock_area.toolbar.widgets["menu_devices"].widgets["positioner_box"].trigger()
|
||||||
assert "PositionerBox_0" in bec_dock_area.panels
|
assert "positioner_box_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["PositionerBox_0"].widgets[0].config.widget_class == "PositionerBox"
|
assert (
|
||||||
|
bec_dock_area.panels["positioner_box_0"].widgets[0].config.widget_class == "PositionerBox"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_utils_queue(bec_dock_area, bec_queue_msg_full):
|
def test_toolbar_add_utils_queue(bec_dock_area, bec_queue_msg_full):
|
||||||
@ -136,20 +140,20 @@ def test_toolbar_add_utils_queue(bec_dock_area, bec_queue_msg_full):
|
|||||||
MessageEndpoints.scan_queue_status(), bec_queue_msg_full
|
MessageEndpoints.scan_queue_status(), bec_queue_msg_full
|
||||||
)
|
)
|
||||||
bec_dock_area.toolbar.widgets["menu_utils"].widgets["queue"].trigger()
|
bec_dock_area.toolbar.widgets["menu_utils"].widgets["queue"].trigger()
|
||||||
assert "BECQueue_0" in bec_dock_area.panels
|
assert "bec_queue_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["BECQueue_0"].widgets[0].config.widget_class == "BECQueue"
|
assert bec_dock_area.panels["bec_queue_0"].widgets[0].config.widget_class == "BECQueue"
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_utils_status(bec_dock_area):
|
def test_toolbar_add_utils_status(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_utils"].widgets["status"].trigger()
|
bec_dock_area.toolbar.widgets["menu_utils"].widgets["status"].trigger()
|
||||||
assert "BECStatusBox_0" in bec_dock_area.panels
|
assert "bec_status_box_0" in bec_dock_area.panels
|
||||||
assert bec_dock_area.panels["BECStatusBox_0"].widgets[0].config.widget_class == "BECStatusBox"
|
assert bec_dock_area.panels["bec_status_box_0"].widgets[0].config.widget_class == "BECStatusBox"
|
||||||
|
|
||||||
|
|
||||||
def test_toolbar_add_utils_progress_bar(bec_dock_area):
|
def test_toolbar_add_utils_progress_bar(bec_dock_area):
|
||||||
bec_dock_area.toolbar.widgets["menu_utils"].widgets["progress_bar"].trigger()
|
bec_dock_area.toolbar.widgets["menu_utils"].widgets["progress_bar"].trigger()
|
||||||
assert "RingProgressBar_0" in bec_dock_area.panels
|
assert "ring_progress_bar_0" in bec_dock_area.panels
|
||||||
assert (
|
assert (
|
||||||
bec_dock_area.panels["RingProgressBar_0"].widgets[0].config.widget_class
|
bec_dock_area.panels["ring_progress_bar_0"].widgets[0].config.widget_class
|
||||||
== "RingProgressBar"
|
== "RingProgressBar"
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user