From 99383b77150ca7c74c19c899a0e6a7879b770376 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Tue, 8 Apr 2025 18:01:27 +0200 Subject: [PATCH] refactor(launcher,main_window): launcher window moved to inherit from BECMainWindow --- bec_widgets/applications/launch_window.py | 130 ++------- .../containers/main_window/example_app.ui | 42 --- .../containers/main_window/general_app.ui | 262 ------------------ .../containers/main_window/main_window.py | 217 ++++----------- .../scatter_waveform/scatter_waveform.py | 6 +- 5 files changed, 83 insertions(+), 574 deletions(-) delete mode 100644 bec_widgets/widgets/containers/main_window/example_app.ui delete mode 100644 bec_widgets/widgets/containers/main_window/general_app.ui diff --git a/bec_widgets/applications/launch_window.py b/bec_widgets/applications/launch_window.py index 6c249d24..6fcdc7c1 100644 --- a/bec_widgets/applications/launch_window.py +++ b/bec_widgets/applications/launch_window.py @@ -13,116 +13,30 @@ from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.container_utils import WidgetContainerUtils from bec_widgets.widgets.containers.dock.dock_area import BECDockArea from bec_widgets.widgets.containers.main_window.addons.web_links import BECWebLinksMixin +from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow logger = bec_logger.logger MODULE_PATH = os.path.dirname(bec_widgets.__file__) -class LaunchWindow(BECWidget, QMainWindow): - def __init__(self, parent=None, gui_id: str = None, *args, **kwargs): - super().__init__(parent=parent, gui_id=gui_id, **kwargs) +class LaunchWindow(BECMainWindow): + RPC = True + + def __init__( + self, parent=None, gui_id: str = None, window_title="BEC Launcher", *args, **kwargs + ): + super().__init__(parent=parent, gui_id=gui_id, window_title=window_title, **kwargs) self.app = QApplication.instance() self.resize(500, 300) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self._init_ui() - - def _init_ui(self): - # Set the window title - self.setWindowTitle("BEC Launcher") - - # Load ui file ui_file_path = os.path.join(MODULE_PATH, "applications/launch_dialog.ui") self.load_ui(ui_file_path) - - # Set Menu and Status bar - self._setup_menu_bar() - - # BEC Specific UI - self._init_bec_specific_ui() - - # TODO can be implemented for toolbar - def load_ui(self, ui_file): - loader = UILoader(self) - self.ui = loader.loader(ui_file) - self.setCentralWidget(self.ui) self.ui.open_dock_area.setText("Open Dock Area") self.ui.open_dock_area.clicked.connect(lambda: self.launch("dock_area")) - def _init_bec_specific_ui(self): - if getattr(self.app, "gui_id", None): - self.statusBar().showMessage(f"App ID: {self.app.gui_id}") - else: - logger.warning( - "Application is not a BECApplication instance. Status bar will not show App ID. Please initialize the application with BECApplication." - ) - - def list_app_hierarchy(self): - """ - List the hierarchy of the application. - """ - self.app.list_hierarchy() - - def _setup_menu_bar(self): - """ - Setup the menu bar for the main window. - """ - menu_bar = self.menuBar() - - ######################################## - # Theme menu - theme_menu = menu_bar.addMenu("Theme") - - theme_group = QActionGroup(self) - light_theme_action = QAction("Light Theme", self, checkable=True) - dark_theme_action = QAction("Dark Theme", self, checkable=True) - theme_group.addAction(light_theme_action) - theme_group.addAction(dark_theme_action) - theme_group.setExclusive(True) - - theme_menu.addAction(light_theme_action) - theme_menu.addAction(dark_theme_action) - - # Connect theme actions - light_theme_action.triggered.connect(lambda: self.change_theme("light")) - dark_theme_action.triggered.connect(lambda: self.change_theme("dark")) - - # Set the default theme - # TODO can be fetched from app - dark_theme_action.setChecked(True) - - ######################################## - # Help menu - help_menu = menu_bar.addMenu("Help") - - help_icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxQuestion) - bug_icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxInformation) - - bec_docs = QAction("BEC Docs", self) - bec_docs.setIcon(help_icon) - widgets_docs = QAction("BEC Widgets Docs", self) - widgets_docs.setIcon(help_icon) - bug_report = QAction("Bug Report", self) - bug_report.setIcon(bug_icon) - - bec_docs.triggered.connect(BECWebLinksMixin.open_bec_docs) - widgets_docs.triggered.connect(BECWebLinksMixin.open_bec_widgets_docs) - bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report) - - help_menu.addAction(bec_docs) - help_menu.addAction(widgets_docs) - help_menu.addAction(bug_report) - - debug_bar = menu_bar.addMenu(f"DEBUG {self.__class__.__name__}") - list_hierarchy = QAction("List App Hierarchy", self) - list_hierarchy.triggered.connect(self.list_app_hierarchy) - debug_bar.addAction(list_hierarchy) - - def change_theme(self, theme): - apply_theme(theme) - def launch( self, launch_script: str, @@ -137,7 +51,7 @@ class LaunchWindow(BECWidget, QMainWindow): Returns: BECDockArea: The newly created dock area. """ - from bec_widgets.applications.bw_launch import dock_area + from bec_widgets.applications import bw_launch with RPCRegister.delayed_broadcast() as rpc_register: existing_dock_areas = rpc_register.get_names_of_rpc_by_class_type(BECDockArea) @@ -149,16 +63,30 @@ class LaunchWindow(BECWidget, QMainWindow): else: name = "dock_area" name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas) - dock_area = dock_area(name) # BECDockArea(name=name) - dock_area.resize(dock_area.minimumSizeHint()) + + if launch_script is None: + launch_script = "dock_area" + if not isinstance(launch_script, str): + raise ValueError(f"Launch script must be a string, but got {type(launch_script)}.") + launch = getattr(bw_launch, launch_script, None) + if launch is None: + raise ValueError(f"Launch script {launch_script} not found.") + + result_widget = launch(name) + result_widget.resize(result_widget.minimumSizeHint()) # TODO Should we simply use the specified name as title here? - dock_area.window().setWindowTitle(f"BEC - {name}") + result_widget.window().setWindowTitle(f"BEC - {name}") logger.info(f"Created new dock area: {name}") logger.info(f"Existing dock areas: {geometry}") if geometry is not None: - dock_area.setGeometry(*geometry) - dock_area.show() - return dock_area + result_widget.setGeometry(*geometry) + if isinstance(result_widget, BECMainWindow): + result_widget.show() + else: + window = BECMainWindow() + window.setCentralWidget(result_widget) + window.show() + return result_widget def show_launcher(self): self.show() diff --git a/bec_widgets/widgets/containers/main_window/example_app.ui b/bec_widgets/widgets/containers/main_window/example_app.ui deleted file mode 100644 index 972d5c30..00000000 --- a/bec_widgets/widgets/containers/main_window/example_app.ui +++ /dev/null @@ -1,42 +0,0 @@ - - - Form - - - - 0 - 0 - 824 - 1234 - - - - Form - - - - - - - - - - - - - - - - BECDockArea - QWidget -
dock_area
-
- - Waveform - QWidget -
waveform
-
-
- - -
diff --git a/bec_widgets/widgets/containers/main_window/general_app.ui b/bec_widgets/widgets/containers/main_window/general_app.ui deleted file mode 100644 index 3a70bc77..00000000 --- a/bec_widgets/widgets/containers/main_window/general_app.ui +++ /dev/null @@ -1,262 +0,0 @@ - - - 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 373900cd..cb76703e 100644 --- a/bec_widgets/widgets/containers/main_window/main_window.py +++ b/bec_widgets/widgets/containers/main_window/main_window.py @@ -1,65 +1,74 @@ import os -from typing import TYPE_CHECKING -from bec_lib.logger import bec_logger -from qtpy.QtGui import QAction, QActionGroup +from qtpy.QtCore import QSize +from qtpy.QtGui import QAction, QActionGroup, QIcon from qtpy.QtWidgets import QApplication, QMainWindow, QStyle -from bec_widgets.cli.rpc.rpc_register import RPCRegister +import bec_widgets from bec_widgets.utils import UILoader -from bec_widgets.utils.bec_qapp import BECApplication from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.container_utils import WidgetContainerUtils +from bec_widgets.utils.error_popups import SafeSlot +from bec_widgets.utils.widget_io import WidgetHierarchy from bec_widgets.widgets.containers.main_window.addons.web_links import BECWebLinksMixin -if TYPE_CHECKING: - from bec_widgets.widgets.containers.dock.dock_area import BECDockArea - -logger = bec_logger.logger +MODULE_PATH = os.path.dirname(bec_widgets.__file__) class BECMainWindow(BECWidget, QMainWindow): - def __init__(self, parent=None, gui_id: str = None, *args, **kwargs): + RPC = False + + def __init__( + self, + parent=None, + gui_id: str = None, + client=None, + window_title: str = "BEC", + *args, + **kwargs, + ): super().__init__(parent=parent, gui_id=gui_id, **kwargs) self.app = QApplication.instance() - - # self._upgrade_qapp() #TODO consider to make upgrade function to any QApplication to BECQApplication + self.setWindowTitle(window_title) self._init_ui() def _init_ui(self): - # Set the window title - self.setWindowTitle("BEC") + + # Set the icon + self._init_bec_icon() # Set Menu and Status bar self._setup_menu_bar() # BEC Specific UI - self._init_bec_specific_ui() - # self.ui = UILoader - # ui_file_path = os.path.join(os.path.dirname(__file__), "general_app.ui") - # self.load_ui(ui_file_path) + self.display_app_id() + + def _init_bec_icon(self): + icon = self.app.windowIcon() + if icon.isNull(): + print("No icon is set, setting default icon") + icon = QIcon() + icon.addFile( + os.path.join(MODULE_PATH, "assets", "app_icons", "bec_widgets_icon.png"), + size=QSize(48, 48), + ) + self.app.setWindowIcon(icon) + else: + print("An icon is set") - # TODO can be implemented for toolbar def load_ui(self, ui_file): loader = UILoader(self) self.ui = loader.loader(ui_file) self.setCentralWidget(self.ui) - def _init_bec_specific_ui(self): - if getattr(self.app, "is_bec_app", False): - self.statusBar().showMessage(f"App ID: {self.app.gui_id}") - else: - logger.warning( - "Application is not a BECApplication instance. Status bar will not show App ID. Please initialize the application with BECApplication." - ) + def display_app_id(self): + server_id = self.bec_dispatcher.cli_server.gui_id + self.statusBar().showMessage(f"App ID: {server_id}") - def list_app_hierarchy(self): - """ - List the hierarchy of the application. - """ - self.app.list_hierarchy() + def _fetch_theme(self) -> str: + return self.app.theme.theme def _setup_menu_bar(self): """ @@ -86,8 +95,11 @@ class BECMainWindow(BECWidget, QMainWindow): dark_theme_action.triggered.connect(lambda: self.change_theme("dark")) # Set the default theme - # TODO can be fetched from app - dark_theme_action.setChecked(True) + theme = self.app.theme.theme + if theme == "light": + light_theme_action.setChecked(True) + elif theme == "dark": + dark_theme_action.setChecked(True) ######################################## # Help menu @@ -111,139 +123,12 @@ class BECMainWindow(BECWidget, QMainWindow): help_menu.addAction(widgets_docs) help_menu.addAction(bug_report) - debug_bar = menu_bar.addMenu(f"DEBUG {self.__class__.__name__}") - list_hierarchy = QAction("List App Hierarchy", self) - list_hierarchy.triggered.connect(self.list_app_hierarchy) - debug_bar.addAction(list_hierarchy) - - def change_theme(self, theme): + @SafeSlot(str) + def change_theme(self, theme: str): apply_theme(theme) - def _dump(self): - """Return a dictionary with informations about the application state, for use in tests""" - # TODO: ModularToolBar and something else leak top-level widgets (3 or 4 QMenu + 2 QWidget); - # so, a filtering based on title is applied here, but the solution is to not have those widgets - # as top-level (so for now, a window with no title does not appear in _dump() result) - - # NOTE: the main window itself is excluded, since we want to dump dock areas - info = { - tlw.gui_id: { - "title": tlw.windowTitle(), - "visible": tlw.isVisible(), - "class": str(type(tlw)), - } - for tlw in QApplication.instance().topLevelWidgets() - if tlw is not self and tlw.windowTitle() - } - # Add the main window dock area - info[self.centralWidget().gui_id] = { - "title": self.windowTitle(), - "visible": self.isVisible(), - "class": str(type(self.centralWidget())), - } - return info - - 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. - """ - from bec_widgets.widgets.containers.dock.dock_area import BECDockArea - - with RPCRegister.delayed_broadcast() as rpc_register: - existing_dock_areas = rpc_register.get_names_of_rpc_by_class_type(BECDockArea) - if name is not None: - if name in existing_dock_areas: - raise ValueError( - f"Name {name} must be unique for dock areas, but already exists: {existing_dock_areas}." - ) - else: - name = "dock_area" - name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas) - dock_area = WindowWithUi() # BECDockArea(name=name) - dock_area.resize(dock_area.minimumSizeHint()) - # TODO Should we simply use the specified name as title here? - dock_area.window().setWindowTitle(f"BEC - {name}") - logger.info(f"Created new dock area: {name}") - logger.info(f"Existing dock areas: {geometry}") - if geometry is not None: - dock_area.setGeometry(*geometry) - dock_area.show() - return dock_area - def cleanup(self): - super().close() - - -class WindowWithUi(BECMainWindow): - """ - This is just testing app wiht UI file which could be connected to RPC. - - """ - - USER_ACCESS = [ - "new_dock_area", - "all_connections", - "change_theme", - "dock_area", - "register_all_rpc", - "widget_list", - "list_app_hierarchy", - ] - - def __init__(self, *args, name: str = None, **kwargs): - super().__init__(*args, **kwargs) - if name is None: - name = self.__class__.__name__ - else: - if not WidgetContainerUtils.has_name_valid_chars(name): - raise ValueError(f"Name {name} contains invalid characters.") - self._name = name if name else self.__class__.__name__ - ui_file_path = os.path.join(os.path.dirname(__file__), "example_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) - - # TODO actually these propertiers are not much exposed now in the real CLI - @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__": - import sys - - app = QApplication(sys.argv) - print(id(app)) - # app = BECApplication(sys.argv) - # print(id(app)) - main_window = WindowWithUi() - main_window.show() - sys.exit(app.exec()) + central_widget = self.centralWidget() + central_widget.close() + central_widget.deleteLater() + super().cleanup() diff --git a/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py b/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py index 5057c74a..45005792 100644 --- a/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +++ b/bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py @@ -14,7 +14,7 @@ from bec_widgets.utils.colors import set_theme from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.toolbar import MaterialIconAction -from bec_widgets.widgets.plots.plot_base import PlotBase +from bec_widgets.widgets.plots.plot_base import PlotBase, UIMode from bec_widgets.widgets.plots.scatter_waveform.scatter_curve import ( ScatterCurve, ScatterCurveConfig, @@ -129,7 +129,8 @@ class ScatterWaveform(PlotBase): self.proxy_update_sync = pg.SignalProxy( self.sync_signal_update, rateLimit=25, slot=self.update_sync_curves ) - self._init_scatter_curve_settings() + if self.ui_mode == UIMode.SIDE: + self._init_scatter_curve_settings() self.update_with_scan_history(-1) ################################################################################ @@ -512,7 +513,6 @@ class ScatterWaveform(PlotBase): self.scatter_dialog.deleteLater() if self.scatter_curve_settings is not None: self.scatter_curve_settings.cleanup() - print("scatter_curve_settings celanup called") self.bec_dispatcher.disconnect_slot(self.on_scan_status, MessageEndpoints.scan_status()) self.bec_dispatcher.disconnect_slot(self.on_scan_progress, MessageEndpoints.scan_progress()) self.plot_item.removeItem(self._main_curve)