mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
fix: scan_control.py kwargs and args are added to the correct layouts
This commit is contained in:
@ -15,8 +15,9 @@ from PyQt5.QtWidgets import (
|
|||||||
QFrame,
|
QFrame,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QGridLayout,
|
QGridLayout,
|
||||||
|
QLayout,
|
||||||
)
|
)
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
import msgpack
|
import msgpack
|
||||||
|
|
||||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||||
@ -30,6 +31,32 @@ class ScanArgType:
|
|||||||
BOOL = "bool"
|
BOOL = "bool"
|
||||||
|
|
||||||
|
|
||||||
|
class BundleGroup(QWidget):
|
||||||
|
WIDGET_HANDLER = {
|
||||||
|
ScanArgType.DEVICE: QLineEdit,
|
||||||
|
ScanArgType.FLOAT: QDoubleSpinBox,
|
||||||
|
ScanArgType.INT: QSpinBox,
|
||||||
|
ScanArgType.BOOL: QCheckBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, arg_input, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.arg_input = arg_input
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
self.layout = QHBoxLayout(self)
|
||||||
|
for param, param_type in self.arg_input.items():
|
||||||
|
widget_class = self.WIDGET_HANDLER.get(param_type)
|
||||||
|
if widget_class:
|
||||||
|
widget = widget_class(self)
|
||||||
|
if isinstance(widget, QDoubleSpinBox):
|
||||||
|
widget.setAlignment(Qt.AlignLeft) # Align to left
|
||||||
|
self.layout.addWidget(widget)
|
||||||
|
else:
|
||||||
|
print(f"Unsupported annotation '{param_type}' for parameter '{param}'")
|
||||||
|
|
||||||
|
|
||||||
class ScanControl(QWidget):
|
class ScanControl(QWidget):
|
||||||
WIDGET_HANDLER = {
|
WIDGET_HANDLER = {
|
||||||
ScanArgType.DEVICE: QLineEdit,
|
ScanArgType.DEVICE: QLineEdit,
|
||||||
@ -52,9 +79,6 @@ class ScanControl(QWidget):
|
|||||||
# Populate scans to ComboBox for scan selection
|
# Populate scans to ComboBox for scan selection
|
||||||
self.populate_scans()
|
self.populate_scans()
|
||||||
|
|
||||||
# Connect signals #TODO so far not useful
|
|
||||||
# self.comboBox_scan_selection.currentIndexChanged.connect(self.on_scan_selected)
|
|
||||||
|
|
||||||
def _init_UI(self):
|
def _init_UI(self):
|
||||||
self.verticalLayout = QVBoxLayout(self)
|
self.verticalLayout = QVBoxLayout(self)
|
||||||
|
|
||||||
@ -63,7 +87,7 @@ class ScanControl(QWidget):
|
|||||||
|
|
||||||
self.comboBox_scan_selection = QComboBox(self.scan_selection_group)
|
self.comboBox_scan_selection = QComboBox(self.scan_selection_group)
|
||||||
self.scan_selection_layout.addWidget(self.comboBox_scan_selection)
|
self.scan_selection_layout.addWidget(self.comboBox_scan_selection)
|
||||||
self.scan_selection_layout.addWidget(QPushButton("Connect", self.scan_selection_group))
|
# self.scan_selection_layout.addWidget(QPushButton("Connect", self.scan_selection_group)) #TODO button probably not needed
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.scan_selection_group)
|
self.verticalLayout.addWidget(self.scan_selection_group)
|
||||||
|
|
||||||
@ -71,28 +95,45 @@ class ScanControl(QWidget):
|
|||||||
self.scan_control_layout = QVBoxLayout(self.scan_control_group)
|
self.scan_control_layout = QVBoxLayout(self.scan_control_group)
|
||||||
self.verticalLayout.addWidget(self.scan_control_group)
|
self.verticalLayout.addWidget(self.scan_control_group)
|
||||||
|
|
||||||
self.bundle_spinBox = QSpinBox(self.scan_control_group)
|
# Kwargs layout
|
||||||
self.bundle_spinBox.setValue(1) # default value
|
self.kwargs_layout = QVBoxLayout()
|
||||||
self.bundle_spinBox.setMinimum(1)
|
|
||||||
self.bundle_layout = QHBoxLayout()
|
|
||||||
self.bundle_layout.addWidget(QLabel("Bundle Size:", self.scan_control_group))
|
|
||||||
self.bundle_layout.addWidget(self.bundle_spinBox)
|
|
||||||
self.scan_control_layout.addLayout(self.bundle_layout)
|
|
||||||
|
|
||||||
self.kwargs_layout = QGridLayout()
|
|
||||||
self.scan_control_layout.addLayout(self.kwargs_layout)
|
self.scan_control_layout.addLayout(self.kwargs_layout)
|
||||||
|
|
||||||
self.separator = QFrame(self.scan_control_group)
|
# 1st Separator
|
||||||
self.separator.setFrameShape(QFrame.HLine)
|
self.add_horizontal_separator(self.scan_control_layout)
|
||||||
self.separator.setFrameShadow(QFrame.Sunken)
|
|
||||||
self.scan_control_layout.addWidget(self.separator)
|
|
||||||
|
|
||||||
self.args_layout = QGridLayout()
|
# Buttons
|
||||||
|
self.button_layout = QHBoxLayout()
|
||||||
|
self.add_bundle_button = QPushButton("Add Bundle", self.scan_control_group)
|
||||||
|
self.add_bundle_button.clicked.connect(self.add_bundle)
|
||||||
|
self.remove_bundle_button = QPushButton("Remove Bundle", self.scan_control_group)
|
||||||
|
self.remove_bundle_button.clicked.connect(self.remove_bundle)
|
||||||
|
self.button_layout.addWidget(self.add_bundle_button)
|
||||||
|
self.button_layout.addWidget(self.remove_bundle_button)
|
||||||
|
self.scan_control_layout.addLayout(self.button_layout)
|
||||||
|
|
||||||
|
# 2nd Separator
|
||||||
|
self.add_horizontal_separator(self.scan_control_layout)
|
||||||
|
|
||||||
|
# Args layout
|
||||||
|
self.args_layout = QVBoxLayout()
|
||||||
self.scan_control_layout.addLayout(self.args_layout)
|
self.scan_control_layout.addLayout(self.args_layout)
|
||||||
|
|
||||||
self.populate_scans()
|
self.populate_scans()
|
||||||
self.comboBox_scan_selection.currentIndexChanged.connect(self.on_scan_selected)
|
self.comboBox_scan_selection.currentIndexChanged.connect(self.on_scan_selected)
|
||||||
|
|
||||||
|
def add_horizontal_separator(self, layout) -> None:
|
||||||
|
"""
|
||||||
|
Adds a horizontal separator to the given layout
|
||||||
|
Args:
|
||||||
|
layout: Layout to add the separator to
|
||||||
|
|
||||||
|
"""
|
||||||
|
separator = QFrame(self.scan_control_group)
|
||||||
|
separator.setFrameShape(QFrame.HLine)
|
||||||
|
separator.setFrameShadow(QFrame.Sunken)
|
||||||
|
layout.addWidget(separator)
|
||||||
|
|
||||||
def populate_scans(self):
|
def populate_scans(self):
|
||||||
msg = self.client.producer.get(MessageEndpoints.available_scans())
|
msg = self.client.producer.get(MessageEndpoints.available_scans())
|
||||||
self.available_scans = msgpack.loads(msg)
|
self.available_scans = msgpack.loads(msg)
|
||||||
@ -101,48 +142,148 @@ class ScanControl(QWidget):
|
|||||||
def on_scan_selected(self):
|
def on_scan_selected(self):
|
||||||
selected_scan_name = self.comboBox_scan_selection.currentText()
|
selected_scan_name = self.comboBox_scan_selection.currentText()
|
||||||
selected_scan_info = self.available_scans.get(selected_scan_name, {})
|
selected_scan_info = self.available_scans.get(selected_scan_name, {})
|
||||||
self.generate_input_fields(selected_scan_info)
|
|
||||||
|
# Clear the previous input fields
|
||||||
|
self.clear_layout(self.args_layout)
|
||||||
|
self.clear_layout(self.kwargs_layout)
|
||||||
|
|
||||||
|
# Generate kwargs input
|
||||||
|
self.generate_kwargs_input_fields(selected_scan_info)
|
||||||
|
|
||||||
|
# TODO until HERE!
|
||||||
|
# Args section
|
||||||
|
self.arg_input = selected_scan_info.get("arg_input", {}) # Get arg_input from selected scan
|
||||||
|
self.add_labels(self.arg_input.keys(), self.args_layout) # Add labels
|
||||||
|
self.add_widgets_row_to_layout(
|
||||||
|
self.args_layout, self.arg_input.items()
|
||||||
|
) # Add first row of widgets
|
||||||
|
|
||||||
|
# self.generate_input_fields(selected_scan_info) #TODO probably not needed
|
||||||
|
|
||||||
print(10 * "#" + f"{selected_scan_name}" + 10 * "#")
|
print(10 * "#" + f"{selected_scan_name}" + 10 * "#")
|
||||||
print(10 * "#" + "selected_scan_info" + 10 * "#")
|
print(10 * "#" + "selected_scan_info" + 10 * "#")
|
||||||
print(selected_scan_info)
|
print(selected_scan_info)
|
||||||
|
|
||||||
def clear_previous_fields(self, layout):
|
def add_labels(self, labels: list, layout) -> None:
|
||||||
for i in reversed(range(layout.count())):
|
"""
|
||||||
layout.itemAt(i).widget().deleteLater()
|
Adds labels to the given layout in QHBox layout
|
||||||
|
Args:
|
||||||
|
labels(list): List of labels to add
|
||||||
|
layout: Layout to add the labels to
|
||||||
|
|
||||||
def add_widgets_to_layout(self, layout, items, signature=None):
|
"""
|
||||||
|
label_layout = QHBoxLayout()
|
||||||
|
for col_idx, param in enumerate(labels):
|
||||||
|
label = QLabel(param.capitalize(), self.scan_control_group)
|
||||||
|
label_layout.addWidget(label)
|
||||||
|
|
||||||
|
layout.addLayout(label_layout)
|
||||||
|
|
||||||
|
def generate_kwargs_input_fields(self, scan_info: dict) -> None:
|
||||||
|
"""
|
||||||
|
Generates input fields for kwargs
|
||||||
|
Args:
|
||||||
|
scan_info(dict): Dictionary containing scan information
|
||||||
|
"""
|
||||||
|
# Clear the previous input fields
|
||||||
|
self.clear_layout(self.kwargs_layout)
|
||||||
|
|
||||||
|
# Get kwargs and signature
|
||||||
|
required_kwargs = scan_info.get("required_kwargs", [])
|
||||||
|
signature = scan_info.get("signature", [])
|
||||||
|
|
||||||
|
# Add labels
|
||||||
|
self.add_labels(required_kwargs, self.kwargs_layout)
|
||||||
|
|
||||||
|
# Add widgets
|
||||||
|
self.add_widgets_row_to_layout(self.kwargs_layout, required_kwargs, signature)
|
||||||
|
|
||||||
|
def add_widgets_row_to_layout(
|
||||||
|
self, layout: QLayout, items: list, signature: dict = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Adds widgets to the given layout as a row in QHBox layout
|
||||||
|
Args:
|
||||||
|
layout(QLayout): Layout to add the widgets to
|
||||||
|
items(list): List of items to add
|
||||||
|
signature(dict): Dictionary containing signature information for kwargs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
item_type, item_name = None, None
|
||||||
|
widget_row_layout = QHBoxLayout()
|
||||||
for row_idx, item in enumerate(items):
|
for row_idx, item in enumerate(items):
|
||||||
if signature: # handling kwargs
|
if signature:
|
||||||
kwarg_info = next((info for info in signature if info["name"] == item), None)
|
kwarg_info = next((info for info in signature if info["name"] == item), None)
|
||||||
if kwarg_info:
|
if kwarg_info:
|
||||||
item_type = kwarg_info.get("annotation", "_empty")
|
item_type = kwarg_info.get("annotation", "_empty")
|
||||||
item_name = item
|
item_name = item
|
||||||
else: # handling arg_input
|
else:
|
||||||
item_name, item_type = item
|
item_name, item_type = item
|
||||||
|
|
||||||
widget_class = self.WIDGET_HANDLER.get(item_type, None)
|
widget_class = self.WIDGET_HANDLER.get(
|
||||||
|
item_type, None
|
||||||
|
) # TODO fix crash when unsupported annotation
|
||||||
if widget_class is None:
|
if widget_class is None:
|
||||||
print(f"Unsupported annotation '{item_type}' for parameter '{item_name}'")
|
print(f"Unsupported annotation '{item_type}' for parameter '{item_name}'")
|
||||||
continue # Skip unsupported annotations
|
continue
|
||||||
|
# Generated widget by HANDLER
|
||||||
label = QLabel(item_name.capitalize(), self.scan_control_group)
|
|
||||||
widget = widget_class(self.scan_control_group)
|
widget = widget_class(self.scan_control_group)
|
||||||
layout.addWidget(label, row_idx, 0)
|
widget_row_layout.addWidget(widget)
|
||||||
layout.addWidget(widget, row_idx, 1)
|
print(
|
||||||
|
f"Added widget {widget} to layout {layout}"
|
||||||
|
) # TODO remove when app will be working correctly
|
||||||
|
|
||||||
def generate_input_fields(self, scan_info):
|
layout.addLayout(widget_row_layout)
|
||||||
# Clear the previous input fields
|
|
||||||
self.clear_previous_fields(self.kwargs_layout)
|
|
||||||
self.clear_previous_fields(self.args_layout)
|
|
||||||
|
|
||||||
arg_input = scan_info.get("arg_input", {})
|
def clear_layout(self, layout: QLayout) -> None: # TODO like this probably
|
||||||
required_kwargs = scan_info.get("required_kwargs", [])
|
"""
|
||||||
|
Clears completely the given layout, even if there are sub-layouts
|
||||||
|
Args:
|
||||||
|
layout: Layout to clear
|
||||||
|
"""
|
||||||
|
while layout.count():
|
||||||
|
item = layout.takeAt(0)
|
||||||
|
widget = item.widget()
|
||||||
|
if widget:
|
||||||
|
widget.setParent(None)
|
||||||
|
widget.deleteLater()
|
||||||
|
else:
|
||||||
|
sub_layout = item.layout()
|
||||||
|
if sub_layout:
|
||||||
|
self.clear_layout(sub_layout)
|
||||||
|
sub_layout.setParent(None) # disown layout
|
||||||
|
sub_layout.deleteLater() # schedule layout for deletion
|
||||||
|
|
||||||
self.add_widgets_to_layout(
|
def add_bundle(self) -> None:
|
||||||
self.kwargs_layout, required_kwargs, scan_info.get("signature", [])
|
"""Adds a bundle to the scan control layout"""
|
||||||
)
|
self.add_widgets_row_to_layout(
|
||||||
self.add_widgets_to_layout(self.args_layout, arg_input.items())
|
self.args_layout, self.arg_input.items()
|
||||||
|
) # Add first row of widgets
|
||||||
|
|
||||||
|
# def remove_bundle(self):
|
||||||
|
# # print layout children
|
||||||
|
# print("layout children:")
|
||||||
|
# for i in range(self.args_layout.count()):
|
||||||
|
# print(self.args_layout.itemAt(i).widget())
|
||||||
|
|
||||||
|
#
|
||||||
|
def remove_bundle(self) -> None:
|
||||||
|
"""Removes the last bundle from the scan control layout"""
|
||||||
|
last_bundle_index = self.args_layout.count() - 1 # Index of the last bundle
|
||||||
|
if last_bundle_index > 1: # Ensure that there is at least one bundle left
|
||||||
|
last_bundle_layout_item = self.args_layout.takeAt(last_bundle_index)
|
||||||
|
last_bundle_layout = last_bundle_layout_item.layout()
|
||||||
|
if last_bundle_layout:
|
||||||
|
self.clear_layout(last_bundle_layout) # Clear the last bundle layout
|
||||||
|
last_bundle_layout_item.setParent(None) # Disown layout item
|
||||||
|
last_bundle_layout_item.deleteLater() # Schedule layout item for deletion
|
||||||
|
|
||||||
|
self.window().resize(self.window().sizeHint()) # Resize window to fit contents
|
||||||
|
|
||||||
|
# remove last row of widgets
|
||||||
|
# self.args_layout.itemAt(self.args_layout.count() - 1).widget().deleteLater()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Reference in New Issue
Block a user