diff --git a/bec_widgets/cli/client.py b/bec_widgets/cli/client.py index 7040dc71..428e6dbc 100644 --- a/bec_widgets/cli/client.py +++ b/bec_widgets/cli/client.py @@ -335,6 +335,13 @@ class BECDock(RPCBase): class BECDockArea(RPCBase): + @property + @rpc_call + def all_connections(self) -> "list": + """ + None + """ + @rpc_call def new( self, @@ -468,6 +475,21 @@ class BECDockArea(RPCBase): extra(str): Extra docks that are in the dockarea but that are not mentioned in state will be added to the bottom of the dockarea, unless otherwise specified by the extra argument. """ + @rpc_call + def list_all_rpc(self) -> "dict": + """ + List all the registered RPC objects. + Returns: + dict: A dictionary containing all the registered RPC objects. + """ + + @property + @rpc_call + def widget_list(self) -> "list": + """ + Return a list of all widgets in the application. + """ + class BECFigure(RPCBase): @property @@ -1198,9 +1220,32 @@ class BECImageShow(RPCBase): class BECMainWindow(RPCBase): @rpc_call - def remove(self): + def new_dock_area( + self, name: "str | None" = None, geometry: "tuple[int, int, int, int] | None" = None + ) -> "BECDockArea": """ - Cleanup the BECConnector + Create a new dock area. + + Args: + name(str): The name of the dock area. + geometry(tuple): The geometry parameters to be passed to the dock area. + Returns: + BECDockArea: The newly created dock area. + """ + + @rpc_call + def change_theme(self, theme): + """ + None + """ + + @rpc_call + def list_all_rpc(self) -> "list": + """ + List all the registered RPC objects. + + Returns: + dict: A dictionary containing all the registered RPC objects. """ @@ -4956,3 +5001,63 @@ class WebsiteWidget(RPCBase): """ Go forward in the history """ + + +class WindowWithUi(RPCBase): + """A class that represents a window with a user interface.""" + + @rpc_call + def new_dock_area( + self, name: "str | None" = None, geometry: "tuple[int, int, int, int] | None" = None + ) -> "BECDockArea": + """ + Create a new dock area. + + Args: + name(str): The name of the dock area. + geometry(tuple): The geometry parameters to be passed to the dock area. + Returns: + BECDockArea: The newly created dock area. + """ + + @property + @rpc_call + def all_connections(self) -> "list": + """ + None + """ + + @rpc_call + def change_theme(self, theme): + """ + None + """ + + @rpc_call + def list_all_rpc(self) -> "list": + """ + List all the registered RPC objects. + + Returns: + dict: A dictionary containing all the registered RPC objects. + """ + + @property + @rpc_call + def dock_area(self): + """ + None + """ + + @rpc_call + def register_all_rpc(self): + """ + None + """ + + @property + @rpc_call + def widget_list(self) -> "list": + """ + Return a list of all widgets in the application. + """ diff --git a/bec_widgets/cli/client_utils.py b/bec_widgets/cli/client_utils.py index ef8551d9..66e8b4a5 100644 --- a/bec_widgets/cli/client_utils.py +++ b/bec_widgets/cli/client_utils.py @@ -249,6 +249,12 @@ class BECGuiClient(RPCBase): raise RuntimeError("No GUI connected. Call 'connect_gui(gui_id)' first.") return self._run_rpc(action, *(args or []), timeout=timeout, **(kwargs or {})) + # @property + # def all_connections(self): + # """Get all connections to the GUI.""" + # all_connections = self. + # # pylint: disable=protected-access + # return client.RPCRegister()._rpc_register @property def windows(self) -> dict: """Dictionary with dock areas in the GUI.""" @@ -444,6 +450,11 @@ class BECGuiClient(RPCBase): self._top_level[widget.widget_name] = widget return widget + def list_all_rpc(self) -> dict: + """List all RPC connections.""" + rpc_client = RPCBase(gui_id=f"{self._gui_id}:window", parent=self) + list = rpc_client._run_rpc("list_all_rpc") # pylint: disable=protected-access + def delete(self, name: str) -> None: """Delete a dock area. diff --git a/bec_widgets/cli/server.py b/bec_widgets/cli/server.py index 38da85e2..872bcde5 100644 --- a/bec_widgets/cli/server.py +++ b/bec_widgets/cli/server.py @@ -23,7 +23,7 @@ from bec_widgets.utils import BECDispatcher from bec_widgets.utils.bec_connector import BECConnector from bec_widgets.widgets.containers.dock import BECDockArea from bec_widgets.widgets.containers.figure import BECFigure -from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow +from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow, WindowWithUi messages = lazy_import("bec_lib.messages") logger = bec_logger.logger @@ -60,7 +60,7 @@ class BECWidgetsCLIServer: dispatcher: BECDispatcher = None, client=None, config=None, - gui_class: Union[BECFigure, BECDockArea] = BECDockArea, + gui_class: Union[BECFigure, BECDockArea, WindowWithUi] = BECDockArea, gui_class_id: str = "bec", ) -> None: self.status = messages.BECStatus.BUSY @@ -191,7 +191,7 @@ class SimpleFileLikeFromLogOutputFunc: def _start_server( gui_id: str, - gui_class: Union[BECFigure, BECDockArea], + gui_class: Union[BECFigure, BECDockArea, BECMainWindow], gui_class_id: str = "bec", config: str | None = None, ): @@ -255,12 +255,14 @@ def main(): gui_class = BECDockArea elif args.gui_class == "BECFigure": gui_class = BECFigure + elif args.gui_class == "MainWindow": + gui_class = WindowWithUi else: print( "Please specify a valid gui_class to run. Use -h for help." "\n Starting with default gui_class BECFigure." ) - gui_class = BECDockArea + gui_class = WindowWithUi with redirect_stdout(SimpleFileLikeFromLogOutputFunc(logger.info)): with redirect_stderr(SimpleFileLikeFromLogOutputFunc(logger.error)): diff --git a/bec_widgets/examples/jupyter_console/jupyter_console_window.py b/bec_widgets/examples/jupyter_console/jupyter_console_window.py index d4837d0c..3f36f379 100644 --- a/bec_widgets/examples/jupyter_console/jupyter_console_window.py +++ b/bec_widgets/examples/jupyter_console/jupyter_console_window.py @@ -3,6 +3,8 @@ import os import numpy as np import pyqtgraph as pg from bec_qthemes import material_icon +from bec_widgets.examples.qapp_custom.bec_qapp import BECQApplication +from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow from qtpy.QtWidgets import ( QApplication, QGroupBox, @@ -240,7 +242,7 @@ if __name__ == "__main__": # pragma: no cover module_path = os.path.dirname(bec_widgets.__file__) - app = QApplication(sys.argv) + app = BECQApplication(sys.argv) app.setApplicationName("Jupyter Console") app.setApplicationDisplayName("Jupyter Console") icon = material_icon("terminal", color=(255, 255, 255, 255), filled=True) @@ -250,7 +252,9 @@ if __name__ == "__main__": # pragma: no cover client = bec_dispatcher.client client.start() - win = JupyterConsoleWindow() + win = BECMainWindow() + widget = JupyterConsoleWindow() + win.setCentralWidget(widget) win.show() win.resize(1500, 800) diff --git a/bec_widgets/examples/qapp_custom/bec_qapp.py b/bec_widgets/examples/qapp_custom/bec_qapp.py index 4ea918a9..cdb9b300 100644 --- a/bec_widgets/examples/qapp_custom/bec_qapp.py +++ b/bec_widgets/examples/qapp_custom/bec_qapp.py @@ -1,5 +1,6 @@ import os +from bec_widgets.utils.bec_widget import BECWidget from qtpy.QtCore import QSize from qtpy.QtGui import QIcon from qtpy.QtWidgets import QApplication @@ -55,6 +56,25 @@ class BECQApplication(QApplication): self.setWindowIcon(icon) print("[BECQApplication]: Window icon set.") + def register_all(self): + widgets = self.allWidgets() + all_connections = self.rpc_register.list_all_connections() + for widget in widgets: + gui_id = getattr(widget, "gui_id", None) + if gui_id and widget not in all_connections: + self.rpc_register.add_rpc(widget) + print( + f"[BECQApplication]: Registered widget {widget.__class__} with GUI ID: {gui_id}" + ) + + def list_all_bec_widgets(self): + widgets = self.allWidgets() + bec_widgets = [] + for widget in widgets: + if isinstance(widget, BECWidget): + bec_widgets.append(widget) + return bec_widgets + def shutdown(self): self.dispatcher.disconnect_all() super().shutdown() diff --git a/bec_widgets/widgets/containers/dock/dock_area.py b/bec_widgets/widgets/containers/dock/dock_area.py index 60ddc641..7f7079c1 100644 --- a/bec_widgets/widgets/containers/dock/dock_area.py +++ b/bec_widgets/widgets/containers/dock/dock_area.py @@ -49,6 +49,7 @@ class DockAreaConfig(ConnectionConfig): class BECDockArea(BECWidget, QWidget): PLUGIN = True USER_ACCESS = [ + "all_connections", "new", "show", "hide", @@ -62,6 +63,8 @@ class BECDockArea(BECWidget, QWidget): "selected_device", "save_state", "restore_state", + "list_all_rpc", + "widget_list", ] def __init__( @@ -249,6 +252,12 @@ class BECDockArea(BECWidget, QWidget): except AttributeError: return None + @property + def all_connections(self) -> list: + all_connections = self.rpc_register.list_all_connections() + all_connections_keys = list(all_connections.keys()) + return all_connections_keys + @property def panels(self) -> dict[str, BECDock]: """ @@ -272,6 +281,13 @@ class BECDockArea(BECWidget, QWidget): """ return list(self.dock_area.docks.values()) + @property + def widget_list(self) -> list: + """Return a list of all widgets in the application.""" + app = QApplication.instance() + all_widgets = app.list_all_bec_widgets() + return all_widgets + @property def temp_areas(self) -> list: """ @@ -476,6 +492,16 @@ class BECDockArea(BECWidget, QWidget): raise ValueError(f"Dock with name {dock_name} does not exist.") # self._broadcast_update() + def list_all_rpc(self) -> dict: + """ + List all the registered RPC objects. + Returns: + dict: A dictionary containing all the registered RPC objects. + """ + rpc_register = RPCRegister() + all_connections = rpc_register.list_all_connections() + return all_connections + def remove(self) -> None: """Remove the dock area.""" self.close() diff --git a/bec_widgets/widgets/containers/main_window/general_app.ui b/bec_widgets/widgets/containers/main_window/general_app.ui new file mode 100644 index 00000000..3a70bc77 --- /dev/null +++ b/bec_widgets/widgets/containers/main_window/general_app.ui @@ -0,0 +1,262 @@ + + + MainWindow + + + + 0 + 0 + 1718 + 1139 + + + + MainWindow + + + QTabWidget::TabShape::Rounded + + + + + + + 0 + + + + Dock Area + + + + 2 + + + 1 + + + 2 + + + 2 + + + + + + + + + + + + Visual Studio Code + + + + 2 + + + 1 + + + 2 + + + 2 + + + + + + + + + + + + + + 0 + 0 + 1718 + 31 + + + + + Help + + + + + + + + Theme + + + + + + + + + + + Scan Control + + + 2 + + + + + + + + + + + + BEC Service Status + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + Scan Queue + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + BEC Docs + + + + + + + + BEC Widgets Docs + + + + + + + + Bug Report + + + + + true + + + Light + + + + + true + + + Dark + + + + + + WebsiteWidget + QWebEngineView +
website_widget
+
+ + BECQueue + QTableWidget +
bec_queue
+
+ + ScanControl + QWidget +
scan_control
+
+ + VSCodeEditor + WebsiteWidget +
vs_code_editor
+
+ + BECStatusBox + QWidget +
bec_status_box
+
+ + BECDockArea + QWidget +
dock_area
+
+ + QWebEngineView + +
QtWebEngineWidgets/QWebEngineView
+
+
+ + +
diff --git a/bec_widgets/widgets/containers/main_window/main_window.py b/bec_widgets/widgets/containers/main_window/main_window.py index 8e7cd122..a8d8f744 100644 --- a/bec_widgets/widgets/containers/main_window/main_window.py +++ b/bec_widgets/widgets/containers/main_window/main_window.py @@ -1,10 +1,13 @@ +from __future__ import annotations import os +import sys from bec_lib.logger import bec_logger from PySide6.QtCore import QSize from PySide6.QtGui import QAction, QActionGroup, QIcon from PySide6.QtWidgets import QFileDialog, QMessageBox, QStyle, QWidget -from bec_widgets.examples.qapp_custom.bec_qapp import upgrade_to_becqapp +from bec_widgets.examples.qapp_custom.bec_qapp import upgrade_to_becqapp, BECQApplication +from bec_widgets.utils import UILoader from qtpy.QtCore import Qt from qtpy.QtWidgets import QApplication, QMainWindow @@ -21,10 +24,12 @@ logger = bec_logger.logger class BECMainWindow(BECWidget, QMainWindow): - USER_ACCESS = ["new_dock_area", "change_theme", "show_gui_id"] + USER_ACCESS = ["new_dock_area", "change_theme", "list_all_rpc"] - def __init__(self, gui_id: str = None, default_widget=QWidget, *args, **kwargs): - BECWidget.__init__(self, gui_id=gui_id, **kwargs) + def __init__( + self, gui_id: str = None, name: str = None, default_widget=QWidget, *args, **kwargs + ): + BECWidget.__init__(self, gui_id=gui_id, name=name, **kwargs) QMainWindow.__init__(self, *args, **kwargs) # Upgrade qApp if necessary self.app = QApplication.instance() @@ -160,5 +165,75 @@ class BECMainWindow(BECWidget, QMainWindow): new_q_main_window.show() return dock_area + def list_all_rpc(self) -> list: + """ + List all the registered RPC objects. + + Returns: + dict: A dictionary containing all the registered RPC objects. + """ + all_connections = self.rpc_register.list_all_connections() + all_connections_keys = list(all_connections.keys()) + return all_connections_keys + def cleanup(self): super().close() + + +class WindowWithUi(BECMainWindow): + """ + A class that represents a window with a user interface. + It inherits from BECMainWindow and provides additional functionality. + """ + + USER_ACCESS = [ + "new_dock_area", + "all_connections", + "change_theme", + "list_all_rpc", + "dock_area", + "register_all_rpc", + "widget_list", + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ui_file_path = os.path.join(os.path.dirname(__file__), "general_app.ui") + self.load_ui(ui_file_path) + + def load_ui(self, ui_file): + loader = UILoader(self) + self.ui = loader.loader(ui_file) + self.setCentralWidget(self.ui) + + @property + def dock_area(self): + dock_area = self.ui.dock_area + return dock_area + + @property + def all_connections(self) -> list: + all_connections = self.rpc_register.list_all_connections() + all_connections_keys = list(all_connections.keys()) + return all_connections_keys + + def register_all_rpc(self): + app = QApplication.instance() + app.register_all() + + @property + def widget_list(self) -> list: + """Return a list of all widgets in the application.""" + app = QApplication.instance() + all_widgets = app.list_all_bec_widgets() + return all_widgets + + +if __name__ == "__main__": + app = BECQApplication(sys.argv) + + window = WindowWithUi() + window.resize(1280, 720) + window.show() + sys.exit(app.exec())