0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

feat(launch_window): enhance auto update functionality with selector and dynamic loading

This commit is contained in:
2025-04-15 11:44:26 +02:00
parent 778230b5ed
commit 29653239c5

View File

@ -9,11 +9,13 @@ from qtpy.QtCore import Qt, Signal
from qtpy.QtGui import QPainter, QPainterPath, QPixmap from qtpy.QtGui import QPainter, QPainterPath, QPixmap
from qtpy.QtWidgets import ( from qtpy.QtWidgets import (
QApplication, QApplication,
QComboBox,
QFileDialog, QFileDialog,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
QPushButton, QPushButton,
QSizePolicy, QSizePolicy,
QSpacerItem,
QWidget, QWidget,
) )
@ -21,9 +23,11 @@ import bec_widgets
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 SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.plugin_utils import get_plugin_auto_updates
from bec_widgets.utils.round_frame import RoundedFrame from bec_widgets.utils.round_frame import RoundedFrame
from bec_widgets.utils.toolbar import ModularToolBar from bec_widgets.utils.toolbar import ModularToolBar
from bec_widgets.utils.ui_loader import UILoader from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
from bec_widgets.widgets.containers.dock.dock_area import BECDockArea from bec_widgets.widgets.containers.dock.dock_area import BECDockArea
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow, UILaunchWindow from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow, UILaunchWindow
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
@ -45,6 +49,7 @@ class LaunchTile(RoundedFrame):
top_label: str | None = None, top_label: str | None = None,
main_label: str | None = None, main_label: str | None = None,
description: str | None = None, description: str | None = None,
show_selector: bool = False,
): ):
super().__init__(parent=parent, orientation="vertical") super().__init__(parent=parent, orientation="vertical")
@ -86,12 +91,25 @@ class LaunchTile(RoundedFrame):
self.main_label.setAlignment(Qt.AlignCenter) self.main_label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.main_label) self.layout.addWidget(self.main_label)
self.spacer_top = QSpacerItem(0, 10, QSizePolicy.Fixed, QSizePolicy.Fixed)
self.layout.addItem(self.spacer_top)
# Description # Description
self.description_label = QLabel(description) self.description_label = QLabel(description)
self.description_label.setWordWrap(True) self.description_label.setWordWrap(True)
self.description_label.setAlignment(Qt.AlignCenter) self.description_label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.description_label) self.layout.addWidget(self.description_label)
# Selector
if show_selector:
self.selector = QComboBox(self)
self.layout.addWidget(self.selector)
else:
self.selector = None
self.spacer_bottom = QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Expanding)
self.layout.addItem(self.spacer_bottom)
# Action button # Action button
self.action_button = QPushButton("Open") self.action_button = QPushButton("Open")
self.action_button.setStyleSheet( self.action_button.setStyleSheet(
@ -114,6 +132,7 @@ class LaunchTile(RoundedFrame):
class LaunchWindow(BECMainWindow): class LaunchWindow(BECMainWindow):
RPC = True RPC = True
TILE_SIZE = (250, 300)
def __init__( def __init__(
self, parent=None, gui_id: str = None, window_title="BEC Launcher", *args, **kwargs self, parent=None, gui_id: str = None, window_title="BEC Launcher", *args, **kwargs
@ -142,15 +161,16 @@ class LaunchWindow(BECMainWindow):
main_label="BEC Dock Area", main_label="BEC Dock Area",
description="Highly flexible and customizable dock area application with modular widgets.", description="Highly flexible and customizable dock area application with modular widgets.",
) )
self.tile_dock_area.setFixedSize(250, 300) self.tile_dock_area.setFixedSize(*self.TILE_SIZE)
self.tile_auto_update = LaunchTile( self.tile_auto_update = LaunchTile(
icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "auto_update.png"), icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "auto_update.png"),
top_label="Get automated", top_label="Get automated",
main_label="BEC Auto Update Dock Area", main_label="BEC Auto Update Dock Area",
description="Dock area with auto update functionality for BEC widgets plotting.", description="Dock area with auto update functionality for BEC widgets plotting.",
show_selector=True,
) )
self.tile_auto_update.setFixedSize(250, 300) self.tile_auto_update.setFixedSize(*self.TILE_SIZE)
self.tile_ui_file = LaunchTile( self.tile_ui_file = LaunchTile(
icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "ui_loader_tile.png"), icon_path=os.path.join(MODULE_PATH, "assets", "app_icons", "ui_loader_tile.png"),
@ -158,7 +178,7 @@ class LaunchWindow(BECMainWindow):
main_label="Launch Custom UI File", main_label="Launch Custom UI File",
description="GUI application with custom UI file.", description="GUI application with custom UI file.",
) )
self.tile_ui_file.setFixedSize(250, 300) self.tile_ui_file.setFixedSize(*self.TILE_SIZE)
# Add tiles to the main layout # Add tiles to the main layout
self.central_widget.layout.addWidget(self.tile_dock_area) self.central_widget.layout.addWidget(self.tile_dock_area)
@ -170,12 +190,19 @@ class LaunchWindow(BECMainWindow):
# Connect signals # Connect signals
self.tile_dock_area.action_button.clicked.connect(lambda: self.launch("dock_area")) self.tile_dock_area.action_button.clicked.connect(lambda: self.launch("dock_area"))
self.tile_auto_update.action_button.clicked.connect( self.tile_auto_update.action_button.clicked.connect(self._open_auto_update)
lambda: self.launch("auto_update_dock_area", "auto_updates")
)
self.tile_ui_file.action_button.clicked.connect(self._open_custom_ui_file) self.tile_ui_file.action_button.clicked.connect(self._open_custom_ui_file)
self._update_theme() self._update_theme()
# Auto updates
self.available_auto_updates: dict[str, type[AutoUpdates]] = (
self._update_available_auto_updates()
)
if self.tile_auto_update.selector is not None:
self.tile_auto_update.selector.addItems(
list(self.available_auto_updates.keys()) + ["Default"]
)
def launch( def launch(
self, self,
launch_script: str, launch_script: str,
@ -213,41 +240,18 @@ class LaunchWindow(BECMainWindow):
raise ValueError(f"Launch script must be a string, but got {type(launch_script)}.") raise ValueError(f"Launch script must be a string, but got {type(launch_script)}.")
if launch_script == "custom_ui_file": if launch_script == "custom_ui_file":
# Load the custom UI file
ui_file = kwargs.pop("ui_file", None) ui_file = kwargs.pop("ui_file", None)
if ui_file is None: return self._launch_custom_ui_file(ui_file)
raise ValueError("UI file must be provided for custom UI file launch.")
filename = os.path.basename(ui_file).split(".")[0]
tree = ET.parse(ui_file) if launch_script == "auto_update":
root = tree.getroot() auto_update = kwargs.pop("auto_update", None)
# Check if the top-level widget is a QMainWindow return self._launch_auto_update(auto_update)
widget = root.find("widget")
if widget is None:
raise ValueError("No widget found in the UI file.")
if widget.attrib.get("class") == "QMainWindow": launch = getattr(bw_launch, launch_script, None)
raise ValueError( if launch is None:
"Loading a QMainWindow from a UI file is currently not supported." raise ValueError(f"Launch script {launch_script} not found.")
"If you need this, please contact the BEC team or create a ticket on gitlab.psi.ch/bec/bec_widgets."
)
window = UILaunchWindow(object_name=filename) result_widget = launch(name)
QApplication.processEvents()
result_widget = UILoader(window).loader(ui_file)
window.setCentralWidget(result_widget)
window.setWindowTitle(f"BEC - {window.object_name}")
window.show()
logger.info(
f"Object name of new instance: {result_widget.objectName()}, {window.gui_id}"
)
return window
else:
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()) result_widget.resize(result_widget.minimumSizeHint())
# TODO Should we simply use the specified name as title here? # TODO Should we simply use the specified name as title here?
result_widget.window().setWindowTitle(f"BEC - {name}") result_widget.window().setWindowTitle(f"BEC - {name}")
@ -263,6 +267,49 @@ class LaunchWindow(BECMainWindow):
window.show() window.show()
return result_widget return result_widget
def _launch_custom_ui_file(self, ui_file: str | None) -> BECMainWindow:
# Load the custom UI file
if ui_file is None:
raise ValueError("UI file must be provided for custom UI file launch.")
filename = os.path.basename(ui_file).split(".")[0]
tree = ET.parse(ui_file)
root = tree.getroot()
# Check if the top-level widget is a QMainWindow
widget = root.find("widget")
if widget is None:
raise ValueError("No widget found in the UI file.")
if widget.attrib.get("class") == "QMainWindow":
raise ValueError(
"Loading a QMainWindow from a UI file is currently not supported."
"If you need this, please contact the BEC team or create a ticket on gitlab.psi.ch/bec/bec_widgets."
)
window = UILaunchWindow(object_name=filename)
QApplication.processEvents()
result_widget = UILoader(window).loader(ui_file)
window.setCentralWidget(result_widget)
window.setWindowTitle(f"BEC - {window.object_name}")
window.show()
logger.info(f"Object name of new instance: {result_widget.objectName()}, {window.gui_id}")
return window
def _launch_auto_update(self, auto_update: str) -> AutoUpdates:
if auto_update in self.available_auto_updates:
auto_update_cls = self.available_auto_updates[auto_update]
window = auto_update_cls()
else:
auto_update = "auto_updates"
window = AutoUpdates()
window.resize(window.minimumSizeHint())
QApplication.processEvents()
window.setWindowTitle(f"BEC - {window.objectName()}")
window.show()
return window
def apply_theme(self, theme: str): def apply_theme(self, theme: str):
""" """
Change the theme of the application. Change the theme of the application.
@ -272,6 +319,18 @@ class LaunchWindow(BECMainWindow):
super().apply_theme(theme) super().apply_theme(theme)
def _open_auto_update(self):
"""
Open the auto update window.
"""
if self.tile_auto_update.selector is None:
auto_update = None
else:
auto_update = self.tile_auto_update.selector.currentText()
if auto_update == "Default":
auto_update = None
self.launch("auto_update", auto_update=auto_update)
@SafeSlot(popup_error=True) @SafeSlot(popup_error=True)
def _open_custom_ui_file(self): def _open_custom_ui_file(self):
""" """
@ -282,6 +341,19 @@ class LaunchWindow(BECMainWindow):
) )
self.launch("custom_ui_file", ui_file=ui_file) self.launch("custom_ui_file", ui_file=ui_file)
@staticmethod
def _update_available_auto_updates() -> dict[str, type[AutoUpdates]]:
"""
Load all available auto updates from the plugin repository.
"""
try:
auto_updates = get_plugin_auto_updates()
logger.info(f"Available auto updates: {auto_updates.keys()}")
except Exception as exc:
logger.error(f"Failed to load auto updates: {exc}")
return {}
return auto_updates
def show_launcher(self): def show_launcher(self):
self.show() self.show()