From 15a9967cabb3bde662bc7640e63a4d79545d80e7 Mon Sep 17 00:00:00 2001 From: David Perl Date: Mon, 3 Nov 2025 13:49:50 +0100 Subject: [PATCH] feat: add procedure panel to IDE --- .../views/developer_view/developer_view.py | 8 -- .../views/developer_view/developer_widget.py | 78 ++++++++++++------- .../procedure_control/procedure_control.py | 49 +++++++++++- .../procedure_control/procedure_panel.py | 43 ++-------- 4 files changed, 106 insertions(+), 72 deletions(-) diff --git a/bec_widgets/applications/views/developer_view/developer_view.py b/bec_widgets/applications/views/developer_view/developer_view.py index 6f177c75..a3ace52c 100644 --- a/bec_widgets/applications/views/developer_view/developer_view.py +++ b/bec_widgets/applications/views/developer_view/developer_view.py @@ -48,13 +48,5 @@ if __name__ == "__main__": height = int(width / (16 / 9)) _app.resize(width, height) - developer_view = DeveloperView() - _app.add_view( - icon="code_blocks", title="IDE", widget=developer_view, id="developer_view", exclusive=True - ) _app.show() - # developer_view.show() - # developer_view.setWindowTitle("Developer View") - # developer_view.resize(1920, 1080) - # developer_view.set_stretch(horizontal=[1, 3, 2], vertical=[5, 5]) #can be set during runtime sys.exit(app.exec_()) diff --git a/bec_widgets/applications/views/developer_view/developer_widget.py b/bec_widgets/applications/views/developer_view/developer_widget.py index ce7030c3..eccb56cb 100644 --- a/bec_widgets/applications/views/developer_view/developer_widget.py +++ b/bec_widgets/applications/views/developer_view/developer_widget.py @@ -2,6 +2,7 @@ import re import markdown from bec_lib.endpoints import MessageEndpoints +from bec_lib.messages import ProcedureRequestMessage from bec_lib.script_executor import upload_script from bec_qthemes import material_icon from qtpy.QtGui import QKeySequence, QShortcut @@ -13,6 +14,7 @@ from bec_widgets.utils.toolbars.bundles import ToolbarBundle from bec_widgets.utils.toolbars.toolbar import ModularToolBar from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import AdvancedDockArea from bec_widgets.widgets.containers.advanced_dock_area.basic_dock_area import DockAreaWidget +from bec_widgets.widgets.control.procedure_control.procedure_panel import ProcedurePanel from bec_widgets.widgets.editors.monaco.monaco_dock import MonacoDock from bec_widgets.widgets.editors.monaco.monaco_widget import MonacoWidget from bec_widgets.widgets.editors.web_console.web_console import WebConsole @@ -119,6 +121,9 @@ class DeveloperWidget(DockAreaWidget): self._current_script_id: str | None = None self.script_editor_tab = None + self.procedures = ProcedurePanel(self) + self.procedures.setObjectName("Procedure Control") + self._initialize_layout() # Connect editor signals @@ -176,24 +181,16 @@ class DeveloperWidget(DockAreaWidget): ) # Plotting area on the right with signature help tabbed alongside - self.plotting_ads_dock = self.new( - self.plotting_ads, - where="right", - closable=False, - floatable=False, - movable=False, - return_dock=True, - title_buttons={"float": True}, - ) - self.signature_dock = self.new( - self.signature_help, - closable=False, - floatable=False, - movable=False, - tab_with=self.plotting_ads_dock, - return_dock=True, - title_buttons={"float": False, "close": False}, - ) + _r_panel = { + "closable": False, + "floatable": False, + "movable": False, + "return_dock": True, + "title_buttons": {"float": True}, + } + self.plotting_dock = self.new(self.plotting_ads, where="right", **_r_panel) + self.signature_dock = self.new(self.signature_help, **_r_panel, tab_with=self.plotting_dock) + self.procedure_dock = self.new(self.procedures, **_r_panel, tab_with=self.plotting_dock) self.set_layout_ratios(horizontal=[2, 5, 3], vertical=[7, 3]) @@ -226,6 +223,16 @@ class DeveloperWidget(DockAreaWidget): run_action.action.triggered.connect(self.on_execute) self.toolbar.components.add_safe("run", run_action) + submit_action = MaterialIconAction( + icon_name="animated_images", + tooltip="Run current file as a BEC procedure", + label_text="Run on server", + filled=True, + parent=self, + ) + submit_action.action.triggered.connect(self.on_submit_procedure) + self.toolbar.components.add_safe("run_proc", submit_action) + stop_action = MaterialIconAction( icon_name="stop", tooltip="Stop current execution", @@ -239,6 +246,7 @@ class DeveloperWidget(DockAreaWidget): execution_bundle = ToolbarBundle("execution", self.toolbar.components) execution_bundle.add_action("run") execution_bundle.add_action("stop") + execution_bundle.add_action("run_proc") self.toolbar.add_bundle(execution_bundle) vim_action = MaterialIconAction( @@ -295,18 +303,34 @@ class DeveloperWidget(DockAreaWidget): self.toolbar.components.get_action("save").action.setEnabled(enabled) self.toolbar.components.get_action("save_as").action.setEnabled(enabled) + def _try_upload(self) -> str | None: + self.script_editor_tab = self.monaco.last_focused_editor + if not self.script_editor_tab: + return None + if not isinstance(widget := self.script_editor_tab.widget(), MonacoWidget): + return None + return upload_script(self.client.connector, widget.get_text()) + @SafeSlot() def on_execute(self): """Upload and run the currently focused script in the Monaco editor.""" - self.script_editor_tab = self.monaco.last_focused_editor - if not self.script_editor_tab: - return - widget = self.script_editor_tab.widget() - if not isinstance(widget, MonacoWidget): - return - self.current_script_id = upload_script(self.client.connector, widget.get_text()) - self.console.write(f'bec._run_script("{self.current_script_id}")') - print(f"Uploaded script with ID: {self.current_script_id}") + if (script_id := self._try_upload()) is not None: + self.current_script_id = script_id + self.console.write(f'bec._run_script("{self.current_script_id}")') + print(f"Uploaded script with ID: {self.current_script_id}") + + @SafeSlot() + def on_submit_procedure(self): + """Upload and run the currently focused script in the Monaco editor as a procedure.""" + if (script_id := self._try_upload()) is not None: + self.current_script_id = script_id + print(f"Uploaded script with ID: {self.current_script_id}") + self.client.connector.xadd( + MessageEndpoints.procedure_request(), + ProcedureRequestMessage( + identifier="run_script", args_kwargs=((self.current_script_id,), {}) + ).model_dump(), + ) @SafeSlot() def on_stop(self): diff --git a/bec_widgets/widgets/control/procedure_control/procedure_control.py b/bec_widgets/widgets/control/procedure_control/procedure_control.py index dc0c53b2..c223742d 100644 --- a/bec_widgets/widgets/control/procedure_control/procedure_control.py +++ b/bec_widgets/widgets/control/procedure_control/procedure_control.py @@ -12,9 +12,12 @@ from bec_lib.messages import ( from bec_qthemes._icon.material_icons import material_icon from bec_server.scan_server.procedures.helper import FrontendProcedureHelper from pydantic import BaseModel, ConfigDict -from qtpy.QtCore import Signal +from qtpy.QtCore import QSize, Qt, Signal from qtpy.QtWidgets import ( + QDialog, + QDialogButtonBox, QHBoxLayout, + QPushButton, QToolButton, QTreeWidget, QTreeWidgetItem, @@ -212,6 +215,7 @@ class CategoryItem(QTreeWidgetItem): self._queues[queue] = QueueItem( self, [queue], _QueueConfig(base=self._config, queue=queue, msgs=msgs) ) + self._queues[queue].setExpanded(True) class ProcedureControl(BECWidget, QWidget): @@ -266,6 +270,7 @@ class ProcedureControl(BECWidget, QWidget): config(actions={"abort"}, child_actions={"abort"}, active_queue=True), ) self._content.addTopLevelItem(self._active_queues) + self._active_queues.setExpanded(True) self._unhandled_queues = CategoryItem( self._content, @@ -273,6 +278,7 @@ class ProcedureControl(BECWidget, QWidget): config(actions={"delete"}, child_actions={"delete", "resubmit"}), ) self._content.addTopLevelItem(self._unhandled_queues) + self._active_queues.setExpanded(True) def _init_queues(self): for queue in self._helper.get.active_and_pending_queue_names(): @@ -281,6 +287,47 @@ class ProcedureControl(BECWidget, QWidget): self._unhandled_queues.update(queue, self._helper.get.unhandled_queue(queue)) +class ProcedureSubmissionOptionsDialog(QDialog): + """ + Dialog to customize procedure options + """ + + def __init__(self, parent=None, client=None): + super().__init__(parent) + self.setWindowTitle("Procedure execution options") + + self._setup_ui() + + def sizeHint(self) -> QSize: + return QSize(600, 800) + + def _setup_ui(self): + """Setup the dialog UI with ScanControl widget and buttons.""" + layout = QVBoxLayout(self) + + # Create the scan control widget + + # Create dialog buttons + button_box = QDialogButtonBox(Qt.Orientation.Horizontal, self) + + # Create custom buttons with appropriate text + insert_button = QPushButton("Insert") + cancel_button = QPushButton("Cancel") + + button_box.addButton(insert_button, QDialogButtonBox.ButtonRole.AcceptRole) + button_box.addButton(cancel_button, QDialogButtonBox.ButtonRole.RejectRole) + + layout.addWidget(button_box) + + # Connect button signals + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + def accept(self): + """Override accept to generate code before closing.""" + super().accept() + + if __name__ == "__main__": import sys diff --git a/bec_widgets/widgets/control/procedure_control/procedure_panel.py b/bec_widgets/widgets/control/procedure_control/procedure_panel.py index 9d66d7d4..5b3550bd 100644 --- a/bec_widgets/widgets/control/procedure_control/procedure_panel.py +++ b/bec_widgets/widgets/control/procedure_control/procedure_panel.py @@ -1,49 +1,20 @@ -from qtpy.QtWidgets import QVBoxLayout, QWidget - -from bec_widgets import BECWidget -from bec_widgets.utils.toolbars.toolbar import ModularToolBar -from bec_widgets.widgets.containers.ads import CDockManager, CDockWidget, DockWidgetArea -from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import AdvancedDockArea +from bec_widgets.widgets.containers.advanced_dock_area.basic_dock_area import DockAreaWidget from bec_widgets.widgets.control.procedure_control.procedure_control import ProcedureControl from bec_widgets.widgets.control.procedure_control.procedure_logs import ProcedureLogs -class ProcedurePanel(BECWidget, QWidget): +class ProcedurePanel(DockAreaWidget): def __init__(self, parent=None, **kwargs): super().__init__(parent=parent, **kwargs) - - # Top-level layout hosting a toolbar and the dock manager - self._root_layout = QVBoxLayout(self) - self._root_layout.setContentsMargins(0, 0, 0, 0) - self._root_layout.setSpacing(0) - self.toolbar = ModularToolBar(self) - self._root_layout.addWidget(self.toolbar) - - self.dock_manager = CDockManager(self) - self.dock_manager.setStyleSheet("") - self._root_layout.addWidget(self.dock_manager) - self.procedure_control = ProcedureControl(parent=self) + self.procedure_control.setObjectName("Procedure Queue Control") self.procedure_logs = ProcedureLogs(parent=self) + self.procedure_logs.setObjectName("Procedure Logs") - self.procedure_control_dock = CDockWidget("Procedure Control", self) - self.procedure_control_dock.setWidget(self.procedure_control) - - self.procedure_logs_dock = CDockWidget("Procedure Logs", self) - self.procedure_logs_dock.setWidget(self.procedure_logs) - - self.dock_manager.addDockWidget( - DockWidgetArea.TopDockWidgetArea, self.procedure_control_dock - ) - self.dock_manager.addDockWidget( - DockWidgetArea.BottomDockWidgetArea, self.procedure_logs_dock - ) - - for dock in self.dock_manager.dockWidgets(): - dock.setFeature(CDockWidget.DockWidgetFeature.DockWidgetClosable, False) - dock.setFeature(CDockWidget.DockWidgetFeature.DockWidgetFloatable, True) - dock.setFeature(CDockWidget.DockWidgetFeature.DockWidgetMovable, False) + _dock_kwargs = {"closable": False, "movable": False, "floatable": False} + self.new(self.procedure_control, **_dock_kwargs) + self.new(self.procedure_logs, where="bottom", **_dock_kwargs) self.procedure_control.queue_selected.connect(self.procedure_logs.set_queue)