bec_widgets.utils ================= .. py:module:: bec_widgets.utils Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/bec_widgets/utils/bec_connector/index /autoapi/bec_widgets/utils/bec_designer/index /autoapi/bec_widgets/utils/bec_dispatcher/index /autoapi/bec_widgets/utils/bec_list/index /autoapi/bec_widgets/utils/bec_login/index /autoapi/bec_widgets/utils/bec_plugin_helper/index /autoapi/bec_widgets/utils/bec_plugin_manager/index /autoapi/bec_widgets/utils/bec_signal_proxy/index /autoapi/bec_widgets/utils/bec_table/index /autoapi/bec_widgets/utils/bec_widget/index /autoapi/bec_widgets/utils/busy_loader/index /autoapi/bec_widgets/utils/clickable_label/index /autoapi/bec_widgets/utils/collapsible_panel_manager/index /autoapi/bec_widgets/utils/colors/index /autoapi/bec_widgets/utils/compact_popup/index /autoapi/bec_widgets/utils/container_utils/index /autoapi/bec_widgets/utils/crosshair/index /autoapi/bec_widgets/utils/entry_validator/index /autoapi/bec_widgets/utils/error_popups/index /autoapi/bec_widgets/utils/expandable_frame/index /autoapi/bec_widgets/utils/filter_io/index /autoapi/bec_widgets/utils/forms_from_types/index /autoapi/bec_widgets/utils/fps_counter/index /autoapi/bec_widgets/utils/fuzzy_search/index /autoapi/bec_widgets/utils/generate_designer_plugin/index /autoapi/bec_widgets/utils/guided_tour/index /autoapi/bec_widgets/utils/help_inspector/index /autoapi/bec_widgets/utils/layout_manager/index /autoapi/bec_widgets/utils/linear_region_selector/index /autoapi/bec_widgets/utils/list_of_expandable_frames/index /autoapi/bec_widgets/utils/name_utils/index /autoapi/bec_widgets/utils/ophyd_kind_util/index /autoapi/bec_widgets/utils/palette_viewer/index /autoapi/bec_widgets/utils/plot_indicator_items/index /autoapi/bec_widgets/utils/plugin_utils/index /autoapi/bec_widgets/utils/property_editor/index /autoapi/bec_widgets/utils/redis_message_waiter/index /autoapi/bec_widgets/utils/reference_utils/index /autoapi/bec_widgets/utils/round_frame/index /autoapi/bec_widgets/utils/rpc_decorator/index /autoapi/bec_widgets/utils/rpc_server/index /autoapi/bec_widgets/utils/screen_utils/index /autoapi/bec_widgets/utils/serialization/index /autoapi/bec_widgets/utils/settings_dialog/index /autoapi/bec_widgets/utils/side_panel/index /autoapi/bec_widgets/utils/thread_checker/index /autoapi/bec_widgets/utils/ui_loader/index /autoapi/bec_widgets/utils/validator_delegate/index /autoapi/bec_widgets/utils/widget_highlighter/index /autoapi/bec_widgets/utils/widget_io/index /autoapi/bec_widgets/utils/widget_state_manager/index /autoapi/bec_widgets/utils/yaml_dialog/index Classes ------- .. autoapisummary:: bec_widgets.utils.BECConnector bec_widgets.utils.BECDispatcher bec_widgets.utils.BECTable bec_widgets.utils.Colors bec_widgets.utils.ConnectionConfig bec_widgets.utils.Crosshair bec_widgets.utils.DoubleValidationDelegate bec_widgets.utils.EntryValidator bec_widgets.utils.GridLayoutManager bec_widgets.utils.UILoader bec_widgets.utils.WidgetContainerUtils Functions --------- .. autoapisummary:: bec_widgets.utils.register_rpc_methods bec_widgets.utils.rpc_public Package Contents ---------------- .. py:class:: BECConnector(client=None, config: ConnectionConfig | None = None, gui_id: str | None = None, object_name: str | None = None, root_widget: bool = False, rpc_exposed: bool = True, rpc_passthrough_children: bool = True, **kwargs) Connection mixin class to handle BEC client and device manager BECConnector mixin class to handle BEC client and device manager. :param client: The BEC client. :type client: BECClient, optional :param config: The connection configuration with specific gui id. :type config: ConnectionConfig, optional :param gui_id: The GUI ID. :type gui_id: str, optional :param object_name: The object name. :type object_name: str, optional :param root_widget: If set to True, the parent_id will be always set to None, thus enforcing that the widget is accessible as a root widget of the BECGuiClient object. :type root_widget: bool, optional :param rpc_exposed: If set to False, this instance is excluded from RPC registry broadcast and CLI namespace discovery. :type rpc_exposed: bool, optional :param rpc_passthrough_children: Only relevant when ``rpc_exposed=False``. If True, RPC-visible children rebind to the next visible ancestor. If False (default), children stay hidden behind this widget. :type rpc_passthrough_children: bool, optional :param \*\*kwargs: .. py:method:: _enforce_unique_sibling_name() 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. .. py:method:: _get_all_rpc() -> dict Get all registered RPC objects. .. py:method:: _get_bec_meta_objects() -> dict Get BEC meta objects for the widget. :returns: BEC meta objects. :rtype: dict .. py:method:: _get_rpc_parent_ancestor() -> BECConnector | None Find the nearest ancestor that is RPC-addressable. Rules: - If an ancestor has ``rpc_exposed=False``, it is an explicit visibility boundary unless ``rpc_passthrough_children=True``. - If an ancestor has ``RPC=False`` (but remains rpc_exposed), it is treated as structural and children continue to the next ancestor. - Lookup always happens through ``WidgetHierarchy.get_becwidget_ancestor`` so plain ``QWidget`` nodes between connectors are ignored. .. py:method:: _set_gui_id(gui_id: str) -> None Set the GUI ID for the widget. :param gui_id: GUI ID. :type gui_id: str .. py:method:: _update_object_name() -> None Enforce a unique object name among siblings and register the object for RPC. This method is called through a single shot timer kicked off in the constructor. .. py:method:: apply_config(config: dict, generate_new_id: bool = True) -> None Apply the configuration to the widget. :param config: Configuration settings. :type config: dict :param generate_new_id: If True, generate a new GUI ID for the widget. :type generate_new_id: bool .. py:method:: change_object_name(name: str) -> None Change the object name of the widget. Unregister old name and register the new one. :param name: The new object name. :type name: str .. py:method:: export_settings() -> dict Export the settings of the widget as dict. :returns: The exported settings of the widget. :rtype: dict .. py:method:: get_bec_shortcuts() Get BEC shortcuts for the widget. .. py:method:: get_config(dict_output: bool = True) -> dict | pydantic.BaseModel Get the configuration of the widget. :param dict_output: If True, return the configuration as a dictionary. If False, return the configuration as a pydantic model. :type dict_output: bool :returns: The configuration of the widget. :rtype: dict | BaseModel .. py:method:: get_obj_by_id(obj_id: str) .. py:method:: load_config(path: str | None = None, gui: bool = False) Load the configuration of the widget from YAML. :param path: Path to the configuration file for non-GUI dialog mode. :type path: str | None :param gui: If True, use the GUI dialog to load the configuration file. :type gui: bool .. py:method:: load_settings(settings: dict) -> None Load the settings of the widget from dict. :param settings: The settings to load into the widget. :type settings: dict .. py:method:: on_config_update(config: ConnectionConfig | dict) -> None Update the configuration for the widget. :param config: Configuration settings. :type config: ConnectionConfig | dict .. py:method:: remove() Cleanup the BECConnector .. py:method:: save_config(path: str | None = None, gui: bool = False) Save the configuration of the widget to YAML. :param path: Path to save the configuration file for non-GUI dialog mode. :type path: str | None :param gui: If True, use the GUI dialog to save the configuration file. :type gui: bool .. py:method:: setObjectName(name: str) -> None Set the object name of the widget. :param name: The new object name. :type name: str .. py:method:: submit_task(fn, *args, on_complete: bec_widgets.utils.error_popups.SafeSlot = None, **kwargs) -> Worker Submit a task to run in a separate thread. The task will run the specified function with the provided arguments and emit the completed signal when done. Use this method if you want to wait for a task to complete without blocking the main thread. :param fn: Function to run in a separate thread. :param \*args: Arguments for the function. :param on_complete: Slot to run when the task is complete. :param \*\*kwargs: Keyword arguments for the function. :returns: The worker object that will run the task. :rtype: worker .. rubric:: Examples >>> def my_function(a, b): >>> print(a + b) >>> self.submit_task(my_function, 1, 2) >>> def my_function(a, b): >>> print(a + b) >>> def on_complete(): >>> print("Task complete") >>> self.submit_task(my_function, 1, 2, on_complete=on_complete) .. py:method:: update_client(client) -> None Update the client and device manager from BEC and create object for BEC shortcuts. :param client: BEC client. .. py:attribute:: EXIT_HANDLERS .. py:attribute:: USER_ACCESS :value: ['_config_dict', '_get_all_rpc', '_rpc_id'] .. py:property:: _config_dict :type: dict Get the configuration of the widget. :returns: The configuration of the widget. :rtype: dict .. py:property:: _rpc_id :type: str Get the RPC ID of the widget. .. py:attribute:: bec_dispatcher .. py:attribute:: client .. py:attribute:: error_utility :value: None .. py:attribute:: name_established .. py:attribute:: object_name .. py:property:: parent_id :type: str | None .. py:attribute:: root_widget :value: False .. py:attribute:: rpc_exposed :value: True .. py:attribute:: rpc_passthrough_children :value: True .. py:attribute:: rpc_register .. py:attribute:: widget_removed .. py:class:: BECDispatcher(client=None, config: str | bec_lib.service_config.ServiceConfig | None = None, gui_id: str = None) Utility class to keep track of slots connected to a particular redis connector .. py:method:: connect_slot(slot: collections.abc.Callable, topics: bec_lib.endpoints.EndpointInfo | str | list[bec_lib.endpoints.EndpointInfo] | list[str], cb_info: dict | None = None, **kwargs) -> None Connect widget's qt slot, so that it is called on new pub/sub topic message. :param slot: A slot method/function that accepts two inputs: content and metadata of the corresponding pub/sub message :type slot: Callable :param topics EndpointInfo | str | list[EndpointInfo] | list[str]: A topic or list of topics that can typically be acquired via bec_lib.MessageEndpoints :param cb_info: A dictionary containing information about the callback. Defaults to None. :type cb_info: dict | None .. py:method:: disconnect_all(*args, **kwargs) Disconnect all slots from all topics. :param \*args: Arbitrary positional arguments :param \*\*kwargs: Arbitrary keyword arguments .. py:method:: disconnect_slot(slot: collections.abc.Callable, topics: bec_lib.endpoints.EndpointInfo | str | list[bec_lib.endpoints.EndpointInfo] | list[str]) Disconnect a slot from a topic. :param slot: The slot to disconnect :type slot: Callable :param topics EndpointInfo | str | list[EndpointInfo] | list[str]: A topic or list of topics to unsub from. .. py:method:: disconnect_topics(topics: Union[str, list]) Disconnect all slots from a topic. :param topics: The topic(s) to disconnect from :type topics: Union[str, list] .. py:method:: generate_unique_identifier(length: int = 4) -> str :staticmethod: Generate a unique identifier for the application. :param length: The length of the identifier. Defaults to 4. :returns: The unique identifier. :rtype: str .. py:method:: reset_singleton() :classmethod: Reset the singleton instance of the BECDispatcher. .. py:method:: start_cli_server(gui_id: str | None = None) Start the CLI server. :param gui_id: The GUI ID. Defaults to None. If None, a unique identifier will be generated. :type gui_id: str, optional .. py:method:: stop_cli_server() Stop the CLI server. .. py:attribute:: cli_server :type: bec_widgets.utils.rpc_server.RPCServer | None :value: None .. py:attribute:: client :type: bec_lib.client.BECClient .. py:class:: BECTable Bases: :py:obj:`qtpy.QtWidgets.QTableWidget` Table widget with custom keyPressEvent to delete rows with backspace or delete key .. py:method:: keyPressEvent(event) -> None Delete selected rows with backspace or delete key :param event: keyPressEvent .. py:class:: Colors .. py:method:: _blend(background: qtpy.QtGui.QColor, accent: qtpy.QtGui.QColor, t: float) -> qtpy.QtGui.QColor :staticmethod: Blend two colors based on a tint strength t. .. py:method:: _get_colormap_cached(name: str) -> pyqtgraph.ColorMap :staticmethod: .. py:method:: _tint_strength(accent: qtpy.QtGui.QColor, background: qtpy.QtGui.QColor, min_tint: float = 0.06, max_tint: float = 0.18) -> float :staticmethod: Calculate the tint strength based on the contrast between the accent and background colors. min_tint and max_tint define the range of tint strength and are empirically chosen. :param accent: The accent color. :type accent: QColor :param background: The background color. :type background: QColor :param min_tint: The minimum tint strength. :type min_tint: float :param max_tint: The maximum tint strength. :type max_tint: float :returns: The tint strength between 0 and 1. :rtype: float .. py:method:: canonical_colormap_name(color_map: str) -> str :staticmethod: Return an available colormap/preset name if a case-insensitive match exists. .. py:method:: evenly_spaced_colors(colormap: str, num: int, format: Literal['QColor', 'HEX', 'RGB'] = 'QColor', theme_offset=0.2, theme: Literal['light', 'dark'] | None = None) -> list :staticmethod: Extract `num` colors from the specified colormap, evenly spaced along its range, and return them in the specified format. :param colormap: Name of the colormap. :type colormap: str :param num: Number of requested colors. :type num: int :param format: The format of the returned colors ('RGB', 'HEX', 'QColor'). :type format: Literal["QColor","HEX","RGB"] :param theme_offset: Has to be between 0-1. Offset to avoid colors too close to white or black with light or dark theme respectively for pyqtgraph plot background. :type theme_offset: float :param theme: The theme to be applied. Overrides the QApplication theme if specified. :type theme: Literal['light', 'dark'] | None :returns: List of colors in the specified format. :rtype: list :raises ValueError: If theme_offset is not between 0 and 1. .. py:method:: get_colormap(color_map: str) -> pyqtgraph.ColorMap :staticmethod: Resolve a string into a `pg.ColorMap` using either: - the `pg.colormap` registry (optionally including matplotlib/colorcet backends), or - `GradientEditorItem` presets (HistogramLUT right-click menu). .. py:method:: golden_angle_color(colormap: str, num: int, format: Literal['QColor', 'HEX', 'RGB'] = 'QColor', theme_offset=0.2, theme: Literal['dark', 'light'] | None = None) -> list :staticmethod: Extract num colors from the specified colormap following golden angle distribution and return them in the specified format. :param colormap: Name of the colormap. :type colormap: str :param num: Number of requested colors. :type num: int :param format: The format of the returned colors ('RGB', 'HEX', 'QColor'). :type format: Literal["QColor","HEX","RGB"] :param theme_offset: Has to be between 0-1. Offset to avoid colors too close to white or black with light or dark theme respectively for pyqtgraph plot background. :type theme_offset: float :returns: List of colors in the specified format. :rtype: list :raises ValueError: If theme_offset is not between 0 and 1. .. py:method:: golden_ratio(num: int) -> list :staticmethod: Calculate the golden ratio for a given number of angles. :param num: Number of angles :type num: int :returns: List of angles calculated using the golden ratio. :rtype: list .. py:method:: hex_to_rgba(hex_color: str, alpha=255) -> tuple :staticmethod: Convert HEX color to RGBA. :param hex_color: HEX color string. :type hex_color: str :param alpha: Alpha value (0-255). Default is 255 (opaque). :type alpha: int :returns: RGBA color tuple (r, g, b, a). :rtype: tuple .. py:method:: list_available_colormaps() -> list[str] :staticmethod: List colormap names available via the pyqtgraph colormap registry. Note: This does not include `GradientEditorItem` presets (used by HistogramLUT menus). .. py:method:: list_available_gradient_presets() -> list[str] :staticmethod: List `GradientEditorItem` preset names (HistogramLUT right-click menu entries). .. py:method:: relative_luminance(color: qtpy.QtGui.QColor) -> float :staticmethod: Calculate the relative luminance of a QColor according to WCAG 2.0 standards. See https://www.w3.org/TR/WCAG21/#dfn-relative-luminance. :param color: The color to calculate the relative luminance for. :type color: QColor :returns: The relative luminance of the color. :rtype: float .. py:method:: rgba_to_hex(r: int, g: int, b: int, a: int = 255) -> str :staticmethod: Convert RGBA color to HEX. :param r: Red value (0-255). :type r: int :param g: Green value (0-255). :type g: int :param b: Blue value (0-255). :type b: int :param a: Alpha value (0-255). Default is 255 (opaque). :type a: int :returns: HEX color string. :rtype: hec_color(str) .. py:method:: set_theme_offset(theme: Literal['light', 'dark'] | None = None, offset=0.2) -> tuple :staticmethod: Set the theme offset to avoid colors too close to white or black with light or dark theme respectively for pyqtgraph plot background. :param theme: The theme to be applied. :type theme: str :param offset: Offset to avoid colors too close to white or black with light or dark theme respectively for pyqtgraph plot background. :type offset: float :returns: Tuple of min_pos and max_pos. :rtype: tuple :raises ValueError: If theme_offset is not between 0 and 1. .. py:method:: subtle_background_color(accent: qtpy.QtGui.QColor, background: qtpy.QtGui.QColor) -> qtpy.QtGui.QColor :staticmethod: Generate a subtle, contrast-safe background color derived from an accent color. :param accent: The accent color. :type accent: QColor :param background: The background color. :type background: QColor :returns: The generated subtle background color. :rtype: QColor .. py:method:: validate_color(color: tuple | str) -> tuple | str :staticmethod: Validate the color input if it is HEX or RGBA compatible. Can be used in any pydantic model as a field validator. :param color: The color to be validated. Can be a tuple of RGBA values or a HEX string. :type color: tuple|str :returns: The validated color. :rtype: tuple|str .. py:method:: validate_color_map(color_map: str, return_error: bool = True) -> str | bool :staticmethod: Validate the colormap input if it is supported by pyqtgraph. Can be used in any pydantic model as a field validator. If validation fails it prints all available colormaps from pyqtgraph instance. :param color_map: The colormap to be validated. :type color_map: str :returns: The validated colormap, if colormap is valid. bool: False, if colormap is invalid. :rtype: str :raises PydanticCustomError: If colormap is invalid. .. py:class:: ConnectionConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Configuration for BECConnector mixin class Create a new model by parsing and validating input data from keyword arguments. Raises [`ValidationError`][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model. `self` is explicitly positional-only to allow `self` as a field name. .. py:method:: generate_gui_id(v, values) :classmethod: Generate a GUI ID if none is provided. .. py:attribute:: gui_id :type: Optional[str] :value: None .. py:attribute:: model_config :type: dict Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. .. py:attribute:: widget_class :type: str :value: None .. py:class:: Crosshair(plot_item: pyqtgraph.PlotItem, precision: int | None = None, *, min_precision: int = 2, parent=None) Bases: :py:obj:`qtpy.QtCore.QObject` Crosshair for 1D and 2D plots. :param plot_item: The plot item to which the crosshair will be attached. :type plot_item: pyqtgraph.PlotItem :param precision: Fixed number of decimal places to display. If *None*, precision is chosen dynamically from the current view range. :type precision: int | None, optional :param min_precision: The lower bound (in decimal places) used when dynamic precision is enabled. Defaults to 2. :type min_precision: int, optional :param parent: Parent object for the QObject. Defaults to None. :type parent: QObject, optional .. py:method:: _connect_to_theme_change() Connect to the theme change signal. .. py:method:: _current_precision() -> int Get the current precision based on the view range or fixed precision. .. py:method:: _get_transformed_position(x: float, y: float, transform: qtpy.QtGui.QTransform) -> tuple[qtpy.QtCore.QPointF, qtpy.QtCore.QPointF] Maps the given x and y coordinates to the transformed position using the provided transform. :param x: The x-coordinate to transform. :type x: float :param y: The y-coordinate to transform. :type y: float :param transform: The transformation to apply. :type transform: QTransform .. py:method:: _update_theme(theme: str | None = None) Update the theme. .. py:method:: apply_theme(theme: str) Apply the theme to the plot. .. py:method:: check_derivatives() Checks if the derivatives are enabled and updates the internal state accordingly. .. py:method:: check_log() Checks if the x or y axis is in log scale and updates the internal state accordingly. .. py:method:: cleanup() .. py:method:: clear_markers() Clears the markers from the plot. .. py:method:: closest_x_y_value(input_x: float, list_x: list, list_y: list) -> tuple Find the closest x and y value to the input value. :param input_x: Input value :type input_x: float :param list_x: List of x values :type list_x: list :param list_y: List of y values :type list_y: list :returns: Closest x and y value :rtype: tuple .. py:method:: mouse_clicked(event) Handles the mouse clicked event, updating the crosshair position and emitting signals. :param event: The mouse clicked event .. py:method:: mouse_moved(event=None, manual_pos=None) Handles the mouse moved event, updating the crosshair position and emitting signals. :param event: The mouse moved event, which contains the scene position. :type event: object :param manual_pos: A tuple containing the (x, y) coordinates to manually set the crosshair position. :type manual_pos: tuple, optional .. py:method:: reset() Resets the crosshair to its initial state. .. py:method:: scale_emitted_coordinates(x, y) Scales the emitted coordinates if the axes are in log scale. :param x: The x-coordinate :type x: float :param y: The y-coordinate :type y: float :returns: The scaled x and y coordinates :rtype: tuple .. py:method:: snap_to_data(x: float, y: float) -> tuple[None, None] | tuple[collections.defaultdict[Any, list], collections.defaultdict[Any, list]] Finds the nearest data points to the given x and y coordinates. :param x: The x-coordinate of the mouse cursor :type x: float :param y: The y-coordinate of the mouse cursor :type y: float :returns: x and y values snapped to the nearest data :rtype: tuple .. py:method:: update_coord_label(pos: tuple) Updates the coordinate label based on the crosshair position and axis scales. :param pos: The (x, y) position of the crosshair. :type pos: tuple .. py:method:: update_highlighted_curve(curve_index: int) Update the highlighted curve in the case of multiple curves in a plot item. :param curve_index: The index of curve to highlight :type curve_index: int .. py:method:: update_markers() Update the markers for the crosshair, creating new ones if necessary. .. py:method:: update_markers_on_image_change() Update markers when the image changes, e.g. when the image shape or transformation changes. .. py:attribute:: coord_label .. py:attribute:: coordinatesChanged1D .. py:attribute:: coordinatesChanged2D .. py:attribute:: coordinatesClicked1D .. py:attribute:: coordinatesClicked2D .. py:attribute:: crosshairChanged .. py:attribute:: crosshairClicked .. py:attribute:: h_line .. py:attribute:: highlighted_curve_index :value: None .. py:attribute:: is_derivative :value: None .. py:attribute:: is_log_x :value: None .. py:attribute:: is_log_y :value: None .. py:attribute:: items :value: [] .. py:attribute:: marker_2d_col :value: None .. py:attribute:: marker_2d_row :value: None .. py:attribute:: marker_clicked_1d .. py:attribute:: marker_moved_1d .. py:property:: min_precision :type: int Lower bound on decimals when dynamic precision is used. .. py:attribute:: plot_item .. py:attribute:: positionChanged .. py:attribute:: positionClicked .. py:property:: precision :type: int | None Fixed number of decimals; ``None`` enables dynamic mode. .. py:attribute:: proxy .. py:attribute:: v_line .. py:class:: DoubleValidationDelegate Bases: :py:obj:`qtpy.QtWidgets.QStyledItemDelegate` .. py:method:: createEditor(parent, option, index) .. py:class:: EntryValidator(devices) .. py:method:: validate_monitor(monitor: str) -> str Validate a monitor entry for a given device. :param monitor: Monitor entry :type monitor: str :returns: Monitor entry :rtype: str .. py:method:: validate_signal(name: str, entry: str = None) -> str Validate a signal entry for a given device. If the entry is not provided, the first signal entry will be used from the device hints. :param name: Device name :type name: str :param entry: Signal entry :type entry: str :returns: Signal entry :rtype: str .. py:attribute:: devices .. py:class:: GridLayoutManager(layout: qtpy.QtWidgets.QGridLayout) GridLayoutManager class is used to manage widgets in a QGridLayout and extend its functionality. The GridLayoutManager class provides methods to add, move, and check the position of widgets in a QGridLayout. It also provides a method to get the positions of all widgets in the layout. :param layout: The layout to manage. :type layout: QGridLayout .. py:method:: add_widget(widget: qtpy.QtWidgets.QWidget, row=None, col=0, rowspan=1, colspan=1, shift: Literal['down', 'up', 'left', 'right'] = 'down') Add a widget to the layout at the specified position. :param widget: The widget to add. :type widget: QWidget :param row: The row to add the widget to. If None, the widget will be added to the next available row. :type row: int :param col: The column to add the widget to. Default is 0. :type col: int :param rowspan: The number of rows the widget will span. Default is 1. :type rowspan: int :param colspan: The number of columns the widget will span. Default is 1. :type colspan: int :param shift: The direction to shift the widgets if the position is occupied. Can be "down", "up", "left", or "right". :type shift: str .. py:method:: get_widgets_positions() -> dict Get the positions of all widgets in the layout. :returns: A dictionary with the positions of the widgets in the layout. :rtype: dict .. py:method:: is_position_occupied(row: int, col: int) -> bool Check if the position in the layout is occupied by a widget. :param row: The row to check. :type row: int :param col: The column to check. :type col: int :returns: True if the position is occupied, False otherwise. :rtype: bool .. py:method:: move_widget(widget: qtpy.QtWidgets.QWidget, new_row: int, new_col: int) Move a widget to a new position in the layout. :param widget: The widget to move. :type widget: QWidget :param new_row: The new row to move the widget to. :type new_row: int :param new_col: The new column to move the widget to. :type new_col: int .. py:method:: shift_widgets(direction: Literal['down', 'up', 'left', 'right'] = 'down', start_row: int = 0, start_col: int = 0) Shift widgets in the layout in the specified direction starting from the specified position. :param direction: The direction to shift the widgets. Can be "down", "up", "left", or "right". :type direction: str :param start_row: The row to start shifting from. Default is 0. :type start_row: int :param start_col: The column to start shifting from. Default is 0. :type start_col: int .. py:attribute:: layout .. py:class:: UILoader(parent=None) Universal UI loader for PyQt6 and PySide6. .. py:method:: load_ui(ui_file, parent=None) Universal UI loader method. :param ui_file: Path to the .ui file. :type ui_file: str :param parent: Parent widget. :type parent: QWidget :returns: The loaded widget. :rtype: QWidget .. py:method:: load_ui_pyqt6(ui_file, parent=None) Specific loader for PyQt6 using loadUi. :param ui_file: Path to the .ui file. :type ui_file: str :param parent: Parent widget. :type parent: QWidget :returns: The loaded widget. :rtype: QWidget .. py:method:: load_ui_pyside6(ui_file, parent=None) Specific loader for PySide6 using QUiLoader. :param ui_file: Path to the .ui file. :type ui_file: str :param parent: Parent widget. :type parent: QWidget :returns: The loaded widget. :rtype: QWidget .. py:attribute:: custom_widgets .. py:attribute:: parent :value: None .. py:class:: WidgetContainerUtils .. py:method:: find_first_widget_by_class(container: dict, widget_class: Type[qtpy.QtWidgets.QWidget], can_fail: bool = True) -> qtpy.QtWidgets.QWidget | None :staticmethod: Find the first widget of a given class in the figure. :param container: The container of widgets. :type container: dict :param widget_class: The class of the widget to find. :type widget_class: Type :param can_fail: If True, the method will return None if no widget is found. If False, it will raise an error. :type can_fail: bool :returns: The widget of the given class. :rtype: widget .. py:method:: generate_unique_name(name: str, list_of_names: list[str] | None = None) -> str :staticmethod: Generate a unique ID. :param name: The name of the widget. :type name: str :returns: The unique name :rtype: tuple (str) .. py:method:: has_name_valid_chars(name: str) -> bool :staticmethod: Check if the name is valid. :param name: The name to be checked. :type name: str :returns: True if the name is valid, False otherwise. :rtype: bool .. py:method:: name_is_protected(name: str, container: Any = None) -> bool :staticmethod: Check if the name is not protected. :param name: The name to be checked. :type name: str :returns: True if the name is not protected, False otherwise. :rtype: bool .. py:method:: raise_for_invalid_name(name: str, container: Any = None) -> None :staticmethod: Check if the name is valid. If not, raise a ValueError. :param name: The name to be checked. :type name: str :raises ValueError: If the name is not valid. .. py:function:: register_rpc_methods(cls) Class decorator to scan for rpc_public methods and add them to USER_ACCESS. .. py:function:: rpc_public(func)