mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
refactor: scan_control.py kwargs are in grid layout, args in table widget
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import msgpack
|
import msgpack
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QWidget,
|
QWidget,
|
||||||
@ -15,9 +16,14 @@ from PyQt5.QtWidgets import (
|
|||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLayout,
|
QLayout,
|
||||||
QGridLayout,
|
QGridLayout,
|
||||||
|
QTableWidget,
|
||||||
|
QTableWidgetItem,
|
||||||
)
|
)
|
||||||
|
|
||||||
from bec_lib.core import MessageEndpoints
|
from bec_lib.core import MessageEndpoints
|
||||||
|
from bec_widgets.qt_utils.widget_hierarchy import print_widget_hierarchy
|
||||||
|
|
||||||
|
# from bec_widgets.qt_utils.layout_tools import remove_empty_cells
|
||||||
|
|
||||||
|
|
||||||
class ScanArgType:
|
class ScanArgType:
|
||||||
@ -56,7 +62,9 @@ class ScanControl(QWidget):
|
|||||||
self.scan_selection_group = QGroupBox("Scan Selection", self)
|
self.scan_selection_group = QGroupBox("Scan Selection", self)
|
||||||
self.scan_selection_layout = QVBoxLayout(self.scan_selection_group)
|
self.scan_selection_layout = QVBoxLayout(self.scan_selection_group)
|
||||||
self.comboBox_scan_selection = QComboBox(self.scan_selection_group)
|
self.comboBox_scan_selection = QComboBox(self.scan_selection_group)
|
||||||
|
self.button_run_scan = QPushButton("Run Scan", 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(self.button_run_scan)
|
||||||
self.verticalLayout.addWidget(self.scan_selection_group)
|
self.verticalLayout.addWidget(self.scan_selection_group)
|
||||||
|
|
||||||
# Scan control group box
|
# Scan control group box
|
||||||
@ -64,7 +72,7 @@ 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)
|
||||||
|
|
||||||
# Kwargs layout
|
# Kwargs layout - just placeholder
|
||||||
self.kwargs_layout = QGridLayout()
|
self.kwargs_layout = QGridLayout()
|
||||||
self.scan_control_layout.addLayout(self.kwargs_layout)
|
self.scan_control_layout.addLayout(self.kwargs_layout)
|
||||||
|
|
||||||
@ -84,14 +92,16 @@ class ScanControl(QWidget):
|
|||||||
# 2nd Separator
|
# 2nd Separator
|
||||||
self.add_horizontal_separator(self.scan_control_layout)
|
self.add_horizontal_separator(self.scan_control_layout)
|
||||||
|
|
||||||
# Args layout
|
# Initialize the QTableWidget for args
|
||||||
self.args_layout = QGridLayout()
|
self.args_table = QTableWidget()
|
||||||
self.scan_control_layout.addLayout(self.args_layout)
|
self.scan_control_layout.addWidget(self.args_table)
|
||||||
|
|
||||||
|
# Connect signals
|
||||||
|
self.comboBox_scan_selection.currentIndexChanged.connect(self.on_scan_selected)
|
||||||
|
self.button_run_scan.clicked.connect(self.run_scan)
|
||||||
|
|
||||||
# Initialize scan selection
|
# Initialize scan selection
|
||||||
self.populate_scans()
|
self.populate_scans()
|
||||||
self.comboBox_scan_selection.currentIndexChanged.connect(self.on_scan_selected)
|
|
||||||
self.on_scan_selected()
|
|
||||||
|
|
||||||
def add_horizontal_separator(self, layout) -> None:
|
def add_horizontal_separator(self, layout) -> None:
|
||||||
"""
|
"""
|
||||||
@ -119,21 +129,18 @@ class ScanControl(QWidget):
|
|||||||
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, {})
|
||||||
|
|
||||||
# Clear the previous input fields
|
# Create a new kwarg layout to replace the old one - this is necessary because otherwise row count is not reseted
|
||||||
self.clear_layout(self.args_layout)
|
self.clear_and_delete_layout(self.kwargs_layout)
|
||||||
self.clear_layout(self.kwargs_layout)
|
self.kwargs_layout = self.create_new_grid_layout() # Create new grid layout
|
||||||
|
self.scan_control_layout.insertLayout(0, self.kwargs_layout)
|
||||||
|
|
||||||
# Generate kwargs input
|
# Generate kwargs input
|
||||||
self.generate_kwargs_input_fields(selected_scan_info)
|
self.generate_kwargs_input_fields(selected_scan_info)
|
||||||
|
|
||||||
# Args section
|
# Args section
|
||||||
self.arg_input = selected_scan_info.get("arg_input", {}) # Get arg_input from selected scan
|
self.generate_args_input_fields(selected_scan_info)
|
||||||
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
|
|
||||||
|
|
||||||
def add_labels(self, labels: list, grid_layout: QGridLayout) -> None:
|
def add_labels_to_layout(self, labels: list, grid_layout: QGridLayout) -> None:
|
||||||
"""
|
"""
|
||||||
Adds labels to the given grid layout as a separate row.
|
Adds labels to the given grid layout as a separate row.
|
||||||
Args:
|
Args:
|
||||||
@ -146,15 +153,26 @@ class ScanControl(QWidget):
|
|||||||
# Add the label to the grid layout at the calculated row and current column
|
# Add the label to the grid layout at the calculated row and current column
|
||||||
grid_layout.addWidget(label, row_index, column_index)
|
grid_layout.addWidget(label, row_index, column_index)
|
||||||
|
|
||||||
|
def add_labels_to_table(self, labels: list, table: QTableWidget) -> None:
|
||||||
|
table.setColumnCount(len(labels))
|
||||||
|
table.setHorizontalHeaderLabels(labels)
|
||||||
|
|
||||||
|
def generate_args_input_fields(self, scan_info: dict) -> None:
|
||||||
|
# Get arg_input from selected scan
|
||||||
|
self.arg_input = scan_info.get("arg_input", {})
|
||||||
|
|
||||||
|
# Generate labels for table
|
||||||
|
self.add_labels_to_table(list(self.arg_input.keys()), self.args_table)
|
||||||
|
|
||||||
|
# Generate first row for input
|
||||||
|
self.add_bundle()
|
||||||
|
|
||||||
def generate_kwargs_input_fields(self, scan_info: dict) -> None:
|
def generate_kwargs_input_fields(self, scan_info: dict) -> None:
|
||||||
"""
|
"""
|
||||||
Generates input fields for kwargs
|
Generates input fields for kwargs
|
||||||
Args:
|
Args:
|
||||||
scan_info(dict): Dictionary containing scan information
|
scan_info(dict): Dictionary containing scan information
|
||||||
"""
|
"""
|
||||||
# Clear the previous input fields
|
|
||||||
self.clear_layout(self.kwargs_layout)
|
|
||||||
|
|
||||||
# Get signature
|
# Get signature
|
||||||
signature = scan_info.get("signature", [])
|
signature = scan_info.get("signature", [])
|
||||||
|
|
||||||
@ -162,23 +180,16 @@ class ScanControl(QWidget):
|
|||||||
kwargs = [param["name"] for param in signature if param["kind"] == "KEYWORD_ONLY"]
|
kwargs = [param["name"] for param in signature if param["kind"] == "KEYWORD_ONLY"]
|
||||||
|
|
||||||
# Add labels
|
# Add labels
|
||||||
self.add_labels(kwargs, self.kwargs_layout)
|
self.add_labels_to_layout(kwargs, self.kwargs_layout)
|
||||||
|
|
||||||
# Add widgets
|
# Add widgets
|
||||||
self.add_widgets_row_to_layout(self.kwargs_layout, kwargs, signature)
|
widgets = self.generate_widgets_from_signature(kwargs, signature)
|
||||||
|
self.add_widgets_row_to_layout(self.kwargs_layout, widgets)
|
||||||
|
|
||||||
def add_widgets_row_to_layout(
|
def generate_widgets_from_signature(self, items: list, signature: dict = None) -> list:
|
||||||
self, grid_layout: QGridLayout, items: list, signature: dict = None
|
widgets = [] # Initialize an empty list to hold the widgets
|
||||||
) -> None:
|
|
||||||
"""
|
for item in items:
|
||||||
Adds widgets to the given grid layout as a row.
|
|
||||||
Args:
|
|
||||||
grid_layout (QGridLayout): The grid layout to which widgets will be added.
|
|
||||||
items (list): List of items to add, where each item is a tuple (item_name, item_type).
|
|
||||||
signature (dict): Dictionary containing signature information for kwargs.
|
|
||||||
"""
|
|
||||||
row_index = grid_layout.rowCount() # Get the next available row
|
|
||||||
for column_index, item in enumerate(items):
|
|
||||||
if signature:
|
if signature:
|
||||||
# If a signature is provided, extract type and name from it
|
# If a signature is provided, extract type and name from it
|
||||||
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)
|
||||||
@ -191,74 +202,146 @@ class ScanControl(QWidget):
|
|||||||
|
|
||||||
widget_class = self.WIDGET_HANDLER.get(item_type, None)
|
widget_class = self.WIDGET_HANDLER.get(item_type, None)
|
||||||
if widget_class is None:
|
if widget_class is None:
|
||||||
print(
|
print(f"Unsupported annotation '{item_type}' for parameter '{item_name}'")
|
||||||
f"Unsupported annotation '{item_type}' for parameter '{item_name}'"
|
|
||||||
) # TODO add type hinting!!!
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Instantiate the widget and set some properties if necessary
|
# Instantiate the widget and set some properties if necessary
|
||||||
widget = widget_class(self.scan_control_group)
|
widget = widget_class()
|
||||||
if isinstance(widget, QLineEdit):
|
|
||||||
widget.setMinimumWidth(100) # Set a minimum width for QLineEdit if needed
|
|
||||||
|
|
||||||
# Add the widget to the grid layout at the calculated row and current column
|
# Add the widget to the list
|
||||||
|
widgets.append(widget)
|
||||||
|
|
||||||
|
return widgets
|
||||||
|
|
||||||
|
def add_widgets_row_to_layout(
|
||||||
|
self, grid_layout: QGridLayout, widgets: list, row_index: int = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Adds a row of widgets to the given grid layout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
grid_layout (QGridLayout): The grid layout to which widgets will be added.
|
||||||
|
items (list): List of parameter names to create widgets for.
|
||||||
|
row_index (int): The row index where the widgets should be added.
|
||||||
|
"""
|
||||||
|
# If row_index is not specified, add to the next available row
|
||||||
|
if row_index is None:
|
||||||
|
row_index = grid_layout.rowCount()
|
||||||
|
|
||||||
|
for column_index, widget in enumerate(widgets):
|
||||||
|
# Add the widget to the grid layout at the specified row and column
|
||||||
grid_layout.addWidget(widget, row_index, column_index)
|
grid_layout.addWidget(widget, row_index, column_index)
|
||||||
|
|
||||||
def clear_layout(self, layout: QLayout) -> None: # TODO like this probably
|
def add_widgets_row_to_table(
|
||||||
|
self, table_widget: QTableWidget, widgets: list, row_index: int = None
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Clears completely the given layout, even if there are sub-layouts
|
Adds a row of widgets to the given QTableWidget.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
layout: Layout to clear
|
table_widget (QTableWidget): The table widget to which widgets will be added.
|
||||||
|
widgets (list): List of widgets to add to the table.
|
||||||
|
row_index (int): The row index where the widgets should be added. If None, add to the end.
|
||||||
"""
|
"""
|
||||||
while layout.count():
|
# If row_index is not specified, add to the end of the table
|
||||||
item = layout.takeAt(0)
|
if row_index is None or row_index > table_widget.rowCount():
|
||||||
widget = item.widget()
|
row_index = table_widget.rowCount()
|
||||||
if widget:
|
table_widget.insertRow(row_index)
|
||||||
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
|
|
||||||
|
|
||||||
def remove_last_row_from_grid_layout(self, grid_layout: QGridLayout) -> None:
|
for column_index, widget in enumerate(widgets):
|
||||||
|
# If the widget is a subclass of QWidget, use setCellWidget
|
||||||
|
if issubclass(type(widget), QWidget):
|
||||||
|
table_widget.setCellWidget(row_index, column_index, widget)
|
||||||
|
else:
|
||||||
|
# Otherwise, assume it's a string or some other value that should be displayed as text
|
||||||
|
item = QTableWidgetItem(str(widget))
|
||||||
|
table_widget.setItem(row_index, column_index, item)
|
||||||
|
|
||||||
|
# Optionally, adjust the row height based on the content #TODO decide if needed
|
||||||
|
table_widget.setRowHeight(
|
||||||
|
row_index,
|
||||||
|
max(widget.sizeHint().height() for widget in widgets if isinstance(widget, QWidget)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove_last_row_from_table(self, table_widget: QTableWidget) -> None:
|
||||||
"""
|
"""
|
||||||
Removes the last row from the given grid layout.
|
Removes the last row from the given QTableWidget until only one row is left.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
grid_layout(QGridLayout): Layout to remove the last row from
|
table_widget (QTableWidget): The table widget from which the last row will be removed.
|
||||||
"""
|
"""
|
||||||
row_index = grid_layout.rowCount() - 1
|
row_count = table_widget.rowCount()
|
||||||
# Find the actual last occupied row
|
if row_count > 1: # Check to ensure at least one row remains after removal
|
||||||
while row_index > 0:
|
table_widget.removeRow(row_count - 1)
|
||||||
items_in_row = [
|
|
||||||
grid_layout.itemAtPosition(row_index, col)
|
|
||||||
for col in range(grid_layout.columnCount())
|
|
||||||
]
|
|
||||||
if not any(items_in_row): # If the row is empty, decrement the row index
|
|
||||||
row_index -= 1
|
|
||||||
else:
|
|
||||||
break # Found the last occupied row
|
|
||||||
|
|
||||||
# Proceed if we have more than one occupied row
|
def create_new_grid_layout(self):
|
||||||
if row_index > 2:
|
new_layout = QGridLayout()
|
||||||
for column_index in range(grid_layout.columnCount()):
|
# TODO maybe setup other layouts properties here?
|
||||||
item = grid_layout.itemAtPosition(row_index, column_index)
|
return new_layout
|
||||||
if item is not None:
|
|
||||||
widget = item.widget()
|
|
||||||
if widget:
|
|
||||||
grid_layout.removeWidget(widget)
|
|
||||||
widget.deleteLater()
|
|
||||||
|
|
||||||
# Adjust the window size
|
def clear_and_delete_layout(self, layout: QLayout):
|
||||||
self.window().resize(self.window().sizeHint())
|
"""
|
||||||
|
Clears and deletes the given layout and all its child widgets.
|
||||||
|
Args:
|
||||||
|
layout(QLayout): Layout to clear and delete
|
||||||
|
"""
|
||||||
|
if layout is not None:
|
||||||
|
while layout.count():
|
||||||
|
item = layout.takeAt(0)
|
||||||
|
widget = item.widget()
|
||||||
|
if widget:
|
||||||
|
widget.deleteLater()
|
||||||
|
else:
|
||||||
|
sub_layout = item.layout()
|
||||||
|
if sub_layout:
|
||||||
|
self.clear_and_delete_layout(sub_layout)
|
||||||
|
layout.deleteLater()
|
||||||
|
|
||||||
def add_bundle(self) -> None:
|
def add_bundle(self) -> None:
|
||||||
self.add_widgets_row_to_layout(self.args_layout, self.arg_input.items())
|
"""Adds a new bundle to the scan control layout"""
|
||||||
|
# Get widgets used for particular scan and save them to be able to use for adding bundles
|
||||||
|
args_widgets = self.generate_widgets_from_signature(
|
||||||
|
self.arg_input.items()
|
||||||
|
) # TODO decide if make sense to put widget list into method parameters
|
||||||
|
|
||||||
|
# Add first widgets row to the table
|
||||||
|
self.add_widgets_row_to_table(self.args_table, args_widgets)
|
||||||
|
|
||||||
def remove_bundle(self) -> None:
|
def remove_bundle(self) -> None:
|
||||||
self.remove_last_row_from_grid_layout(self.args_layout)
|
"""Removes the last bundle from the scan control layout"""
|
||||||
|
self.remove_last_row_from_table(self.args_table)
|
||||||
|
|
||||||
|
def extract_kwargs_from_row(self, grid_layout: QGridLayout, row: int) -> dict:
|
||||||
|
"""
|
||||||
|
Extracts keyword arguments and their values from a specific row of a QGridLayout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
grid_layout (QGridLayout): The grid layout from which to extract kwargs.
|
||||||
|
row (int): The row index from which to extract the kwargs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary with kwargs names as keys and their corresponding values.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
# kwargs = {}
|
||||||
|
# for col in range(0, grid_layout.columnCount(), 2): # Assuming even columns are labels
|
||||||
|
# label_item = grid_layout.itemAtPosition(row, col)
|
||||||
|
# widget_item = grid_layout.itemAtPosition(row, col + 1)
|
||||||
|
# if label_item is not None and widget_item is not None:
|
||||||
|
# label_widget = label_item.widget()
|
||||||
|
# value_widget = widget_item.widget()
|
||||||
|
# if label_widget and value_widget:
|
||||||
|
# kwarg_name = label_widget.text().rstrip(":").lower()
|
||||||
|
# kwarg_value = self.get_widget_value(value_widget)
|
||||||
|
# kwargs[kwarg_name] = kwarg_value
|
||||||
|
# return kwargs}
|
||||||
|
|
||||||
|
def run_scan(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_widget_value(self, widget):
|
||||||
|
if isinstance(widget, QLabel):
|
||||||
|
return widget.text()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
Reference in New Issue
Block a user