Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bec_widgets/utils/forms_from_types/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ def populate(self):

def _add_griditem(self, item: FormItemSpec, row: int):
grid = self._form_grid.layout()
label = QLabel(parent=self._form_grid, text=item.name)
# Use title from FieldInfo if available, otherwise use the property name
label_text = item.info.title if item.info.title else item.name
label = QLabel(parent=self._form_grid, text=label_text)
label.setProperty("_model_field_name", item.name)
label.setToolTip(item.info.description or item.name)
grid.addWidget(label, row, 0)
Expand Down
2 changes: 2 additions & 0 deletions bec_widgets/utils/forms_from_types/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ class StrFormItem(DynamicFormItem):
def __init__(self, parent: QWidget | None = None, *, spec: FormItemSpec) -> None:
super().__init__(parent=parent, spec=spec)
self._main_widget.textChanged.connect(self._value_changed)
if spec.info.description:
self._main_widget.setPlaceholderText(spec.info.description)

def _add_main_widget(self) -> None:
self._main_widget = QLineEdit()
Expand Down
9 changes: 8 additions & 1 deletion bec_widgets/widgets/control/scan_control/scan_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from qtpy.QtWidgets import (
QApplication,
QComboBox,
QGroupBox,
QHBoxLayout,
QLabel,
QPushButton,
Expand Down Expand Up @@ -171,7 +172,13 @@ def _init_UI(self):
self.layout.addStretch()

def _add_metadata_form(self):
self.layout.addWidget(self._metadata_form)
# Wrap metadata form in a group box
self._metadata_group = QGroupBox("Scan Metadata", self)
self._metadata_group.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
metadata_layout = QVBoxLayout(self._metadata_group)
metadata_layout.addWidget(self._metadata_form)

self.layout.addWidget(self._metadata_group)
self._metadata_form.update_with_new_scan(self.comboBox_scan_selection.currentText())
self.scan_selected.connect(self._metadata_form.update_with_new_scan)
self._metadata_form.form_data_updated.connect(self.update_scan_metadata)
Expand Down
19 changes: 18 additions & 1 deletion bec_widgets/widgets/editors/scan_metadata/scan_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def __init__(

super().__init__(parent=parent, data_model=self._md_schema, client=client, **kwargs)

self._layout.setContentsMargins(0, 0, 0, 0)
self._form_grid_container.layout().setContentsMargins(0, 0, 0, 0)
self._layout.addWidget(self._additional_md_box)
self._additional_md_box_layout.addWidget(self._additional_metadata)

Expand All @@ -78,12 +80,27 @@ def hide_optional_metadata(self, hide: bool):

def get_form_data(self):
"""Get the entered metadata as a dict"""
return self._additional_metadata.dump_dict() | self._dict_from_grid()
form_data = self._additional_metadata.dump_dict() | self._dict_from_grid()

# If scan_name is empty, set it to the current scan
if "scan_name" in form_data and not form_data["scan_name"]:
form_data["scan_name"] = self._scan_name

return form_data

def populate(self):
self._additional_metadata.update_disallowed_keys(list(self._md_schema.model_fields.keys()))
super().populate()

# Set scan_name field to current scan if it exists and is empty
if "scan_name" not in self.widget_dict:
return
scan_name_widget = self.widget_dict["scan_name"]
if not hasattr(scan_name_widget, "getValue") or scan_name_widget.getValue():
return
if hasattr(scan_name_widget, "setValue"):
scan_name_widget.setValue(self._scan_name)

def set_schema_from_scan(self, scan_name: str | None):
self._scan_name = scan_name or ""
self.set_schema(get_metadata_schema_for_scan(self._scan_name))
Expand Down
59 changes: 47 additions & 12 deletions bec_widgets/widgets/services/bec_queue/bec_queue.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import json

from bec_lib import messages
from bec_lib.endpoints import MessageEndpoints
from bec_qthemes import material_icon
Expand Down Expand Up @@ -59,8 +61,10 @@ def __init__(
# Set up the table
self.table = QTableWidget(self)
# self.layout.addWidget(self.table)
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["Scan Number", "Type", "Status", "Cancel"])
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels(
["Scan Number", "Scan Name", "Type", "Status", "Cancel"]
)

header = self.table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Stretch)
Expand Down Expand Up @@ -169,25 +173,42 @@ def update_queue(self, content, _metadata):
blocks = item.request_blocks
scan_types = []
scan_numbers = []
scan_names = []
scan_ids = []
user_metadatas = []
status = item.status
for request_block in blocks:
scan_type = request_block.msg.scan_type
user_metadata = request_block.msg.metadata.get("user_metadata", {})
scan_name = user_metadata.get("scan_name", scan_type)
if scan_type:
scan_types.append(scan_type)
scan_number = request_block.scan_number
if scan_number:
scan_numbers.append(str(scan_number))
if scan_name:
scan_names.append(scan_name)
if user_metadata:
user_metadatas.append(user_metadata)
scan_id = request_block.scan_id
if scan_id:
scan_ids.append(scan_id)
if scan_types:
scan_types = ", ".join(scan_types)
if scan_numbers:
scan_numbers = ", ".join(scan_numbers)
if scan_names:
scan_names = ", ".join(scan_names)
# Pretty print user metadata as tooltip
tooltip = ""
if user_metadatas:
if len(user_metadatas) == 1:
tooltip = json.dumps(user_metadatas[0], indent=2)
else:
tooltip = json.dumps(user_metadatas, indent=2)
if scan_ids:
scan_ids = ", ".join(scan_ids)
self.set_row(index, scan_numbers, scan_types, status, scan_ids)
self.set_row(index, scan_numbers, scan_names, scan_types, status, scan_ids, tooltip)
busy = (
False
if all(item.status in ("STOPPED", "COMPLETED", "IDLE") for item in queue_info)
Expand All @@ -196,21 +217,22 @@ def update_queue(self, content, _metadata):
self.set_global_state("warning" if busy else "default")
self.queue_busy.emit(busy)

def format_item(self, content: str, status=False) -> QTableWidgetItem:
def format_item(self, content: str, status=False, tooltip: str = "") -> QTableWidgetItem:
"""
Format the content of the table item.

Args:
content (str): The content to be formatted.

tooltip (str): Optional tooltip to display.
Returns:
QTableWidgetItem: The formatted item.
"""
if not content or not isinstance(content, str):
content = ""
item = QTableWidgetItem(content)
item.setTextAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
# item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
if tooltip:
item.setToolTip(tooltip)

if status:
try:
Expand All @@ -220,23 +242,36 @@ def format_item(self, content: str, status=False) -> QTableWidgetItem:
return item
return item

def set_row(self, index: int, scan_number: str, scan_type: str, status: str, scan_id: str):
def set_row(
self,
index: int,
scan_number: str,
scan_name: str,
scan_type: str,
status: str,
scan_id: str,
tooltip: str = "",
):
"""
Set the row of the table.

Args:
index (int): The index of the row.
scan_number (str): The scan number.
scan_name (str): The scan name.
scan_type (str): The scan type.
status (str): The status.
scan_id (str): The scan id.
tooltip (str): Optional tooltip to display (pretty-printed user metadata).
"""
abort_button = self._create_abort_button(scan_id)
abort_button.button.clicked.connect(self.delete_selected_row)

self.table.setItem(index, 0, self.format_item(scan_number))
self.table.setItem(index, 1, self.format_item(scan_type))
self.table.setItem(index, 2, self.format_item(status, status=True))
self.table.setCellWidget(index, 3, abort_button)
self.table.setItem(index, 0, self.format_item(scan_number, tooltip=tooltip))
self.table.setItem(index, 1, self.format_item(scan_name, tooltip=tooltip))
self.table.setItem(index, 2, self.format_item(scan_type, tooltip=tooltip))
self.table.setItem(index, 3, self.format_item(status, status=True, tooltip=tooltip))
self.table.setCellWidget(index, 4, abort_button)

def _create_abort_button(self, scan_id: str) -> AbortButton:
"""
Expand Down Expand Up @@ -279,7 +314,7 @@ def reset_content(self):
"""

self.table.setRowCount(1)
self.set_row(0, "", "", "", "")
self.set_row(0, "", "", "", "", "")


if __name__ == "__main__": # pragma: no cover
Expand Down
19 changes: 12 additions & 7 deletions tests/unit_tests/test_bec_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def test_bec_queue(bec_queue, bec_queue_msg_full):
assert bec_queue.table.rowCount() == 1
assert bec_queue.table.item(0, 0).text() == "1289"
assert bec_queue.table.item(0, 1).text() == "line_scan"
assert bec_queue.table.item(0, 2).text() == "COMPLETED"
assert bec_queue.table.item(0, 2).text() == "line_scan"
assert bec_queue.table.item(0, 3).text() == "COMPLETED"


def test_bec_queue_empty(bec_queue):
Expand All @@ -109,6 +110,7 @@ def test_bec_queue_empty(bec_queue):
assert bec_queue.table.item(0, 0).text() == ""
assert bec_queue.table.item(0, 1).text() == ""
assert bec_queue.table.item(0, 2).text() == ""
assert bec_queue.table.item(0, 3).text() == ""


def test_queue_abort(bec_queue, bec_queue_msg_full):
Expand All @@ -118,27 +120,30 @@ def test_queue_abort(bec_queue, bec_queue_msg_full):
assert bec_queue.table.rowCount() == 1
assert bec_queue.table.item(0, 0).text() == "1289"
assert bec_queue.table.item(0, 1).text() == "line_scan"
assert bec_queue.table.item(0, 2).text() == "COMPLETED"
assert bec_queue.table.item(0, 2).text() == "line_scan"
assert bec_queue.table.item(0, 3).text() == "COMPLETED"

abort_button = bec_queue.table.cellWidget(0, 3)
abort_button = bec_queue.table.cellWidget(0, 4)
abort_button.button.click()

bec_queue.update_queue(bec_queue_msg_full.content, {})

assert bec_queue.table.rowCount() == 1
assert bec_queue.table.item(0, 0).text() == "1289"
assert bec_queue.table.item(0, 1).text() == "line_scan"
assert bec_queue.table.item(0, 2).text() == "COMPLETED"
assert bec_queue.table.item(0, 2).text() == "line_scan"
assert bec_queue.table.item(0, 3).text() == "COMPLETED"

abort_button = bec_queue.table.cellWidget(0, 3)
abort_button = bec_queue.table.cellWidget(0, 4)
abort_button.button.click()

bec_queue.update_queue(bec_queue_msg_full.content, {})

assert bec_queue.table.rowCount() == 1
assert bec_queue.table.item(0, 0).text() == "1289"
assert bec_queue.table.item(0, 1).text() == "line_scan"
assert bec_queue.table.item(0, 2).text() == "COMPLETED"
assert bec_queue.table.item(0, 2).text() == "line_scan"
assert bec_queue.table.item(0, 3).text() == "COMPLETED"

abort_button = bec_queue.table.cellWidget(0, 3)
abort_button = bec_queue.table.cellWidget(0, 4)
abort_button.button.click()
30 changes: 18 additions & 12 deletions tests/unit_tests/test_scan_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class ExampleSchema(BasicScanMetadata):


TEST_DICT = {
"scan_name": "",
"comment": "",
"sample_name": "test name",
"str_optional": None,
"str_required": "something",
Expand Down Expand Up @@ -75,22 +77,26 @@ def metadata_widget(empty_metadata_widget: ScanMetadata):
widget._md_schema = ExampleSchema
widget.populate()

sample_name = widget._form_grid.layout().itemAtPosition(0, 1).widget()
str_optional = widget._form_grid.layout().itemAtPosition(1, 1).widget()
str_required = widget._form_grid.layout().itemAtPosition(2, 1).widget()
bool_optional = widget._form_grid.layout().itemAtPosition(3, 1).widget()
bool_required_default = widget._form_grid.layout().itemAtPosition(4, 1).widget()
bool_required_nodefault = widget._form_grid.layout().itemAtPosition(5, 1).widget()
int_default = widget._form_grid.layout().itemAtPosition(6, 1).widget()
int_nodefault_optional = widget._form_grid.layout().itemAtPosition(7, 1).widget()
float_nodefault = widget._form_grid.layout().itemAtPosition(8, 1).widget()
decimal_dp_limits_nodefault = widget._form_grid.layout().itemAtPosition(9, 1).widget()
dict_default = widget._form_grid.layout().itemAtPosition(10, 1).widget()
unsupported_class = widget._form_grid.layout().itemAtPosition(11, 1).widget()
scan_name = widget._form_grid.layout().itemAtPosition(0, 1).widget()
comment = widget._form_grid.layout().itemAtPosition(1, 1).widget()
sample_name = widget._form_grid.layout().itemAtPosition(2, 1).widget()
str_optional = widget._form_grid.layout().itemAtPosition(3, 1).widget()
str_required = widget._form_grid.layout().itemAtPosition(4, 1).widget()
bool_optional = widget._form_grid.layout().itemAtPosition(5, 1).widget()
bool_required_default = widget._form_grid.layout().itemAtPosition(6, 1).widget()
bool_required_nodefault = widget._form_grid.layout().itemAtPosition(7, 1).widget()
int_default = widget._form_grid.layout().itemAtPosition(8, 1).widget()
int_nodefault_optional = widget._form_grid.layout().itemAtPosition(9, 1).widget()
float_nodefault = widget._form_grid.layout().itemAtPosition(10, 1).widget()
decimal_dp_limits_nodefault = widget._form_grid.layout().itemAtPosition(11, 1).widget()
dict_default = widget._form_grid.layout().itemAtPosition(12, 1).widget()
unsupported_class = widget._form_grid.layout().itemAtPosition(13, 1).widget()

yield (
widget,
{
"scan_name": scan_name,
"comment": comment,
"sample_name": sample_name,
"str_optional": str_optional,
"str_required": str_required,
Expand Down
Loading