1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-05 00:12:49 +01:00

feat: add procedure panel to IDE

This commit is contained in:
2025-11-03 13:49:50 +01:00
parent d322d3c26c
commit 15a9967cab
4 changed files with 106 additions and 72 deletions

View File

@@ -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_())

View File

@@ -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):

View File

@@ -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

View File

@@ -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)