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:
@ -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()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user