0
0
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:
wyzula-jan
2023-11-07 18:40:22 +01:00
parent f5ff15fb9a
commit 8bc88ca195

View File

@ -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__":