mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-05-13 18:15:42 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcc246cf92 | |||
| 550753078b |
@@ -28,6 +28,7 @@ class SimpleFileLikeFromLogOutputFunc:
|
|||||||
def __init__(self, log_func):
|
def __init__(self, log_func):
|
||||||
self._log_func = log_func
|
self._log_func = log_func
|
||||||
self._buffer = []
|
self._buffer = []
|
||||||
|
self.encoding = "utf8"
|
||||||
|
|
||||||
def write(self, buffer):
|
def write(self, buffer):
|
||||||
self._buffer.append(buffer)
|
self._buffer.append(buffer)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" Module for DapComboBox widget class to select a DAP model from a combobox. """
|
"""Module for DapComboBox widget class to select a DAP model from a combobox."""
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from qtpy.QtCore import Property, Signal, Slot
|
from qtpy.QtCore import Property, Signal, Slot
|
||||||
@@ -16,7 +16,7 @@ class DapComboBox(BECWidget, QWidget):
|
|||||||
Args:
|
Args:
|
||||||
parent: Parent widget.
|
parent: Parent widget.
|
||||||
client: BEC client object.
|
client: BEC client object.
|
||||||
gui_id: GUI ID.
|
gui_id: GUI ID.--
|
||||||
default: Default device name.
|
default: Default device name.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -154,7 +154,9 @@ class DapComboBox(BECWidget, QWidget):
|
|||||||
def populate_fit_model_combobox(self):
|
def populate_fit_model_combobox(self):
|
||||||
"""Populate the fit_model_combobox with the devices."""
|
"""Populate the fit_model_combobox with the devices."""
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
self.available_models = [model for model in self.client.dap._available_dap_plugins.keys()]
|
self.available_models = [
|
||||||
|
model for model in self.client.dap._available_dap_plugins.keys()
|
||||||
|
]
|
||||||
self.fit_model_combobox.clear()
|
self.fit_model_combobox.clear()
|
||||||
self.fit_model_combobox.addItems(self.available_models)
|
self.fit_model_combobox.addItems(self.available_models)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import inspect
|
||||||
|
|
||||||
|
from bec_lib.signature_serializer import deserialize_dtype
|
||||||
|
from bec_server.data_processing.dap_framework_refactoring.dap_blocks import (
|
||||||
|
BlockWithLotsOfArgs,
|
||||||
|
DAPBlock,
|
||||||
|
GradientBlock,
|
||||||
|
SmoothBlock,
|
||||||
|
)
|
||||||
|
from pydantic.fields import FieldInfo
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QCheckBox,
|
||||||
|
QDoubleSpinBox,
|
||||||
|
QLabel,
|
||||||
|
QLayout,
|
||||||
|
QRadioButton,
|
||||||
|
QScrollArea,
|
||||||
|
)
|
||||||
|
from qtpy.QtCore import QMimeData, Qt, Signal
|
||||||
|
from qtpy.QtGui import QDrag, QPixmap
|
||||||
|
from qtpy.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
|
QHBoxLayout,
|
||||||
|
QMainWindow,
|
||||||
|
QVBoxLayout,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
from bec_widgets.utils.bec_widget import BECWidget
|
||||||
|
from bec_widgets.utils.expandable_frame import ExpandableGroupFrame
|
||||||
|
from bec_widgets.widgets.editors.scan_metadata._metadata_widgets import widget_from_type
|
||||||
|
from bec_widgets.widgets.editors.scan_metadata.scan_metadata import ScanMetadata
|
||||||
|
from tests.unit_tests.test_scan_metadata import metadata_widget
|
||||||
|
|
||||||
|
|
||||||
|
class DragItem(ExpandableGroupFrame):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.setContentsMargins(25, 5, 25, 5)
|
||||||
|
self._layout = QVBoxLayout()
|
||||||
|
self.set_layout(self._layout)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, e):
|
||||||
|
if e.buttons() == Qt.MouseButton.LeftButton:
|
||||||
|
drag = QDrag(self)
|
||||||
|
mime = QMimeData()
|
||||||
|
drag.setMimeData(mime)
|
||||||
|
|
||||||
|
pixmap = QPixmap(self.size())
|
||||||
|
self.render(pixmap)
|
||||||
|
drag.setPixmap(pixmap)
|
||||||
|
|
||||||
|
drag.exec(Qt.DropAction.MoveAction)
|
||||||
|
|
||||||
|
|
||||||
|
class DragWidget(QWidget):
|
||||||
|
"""
|
||||||
|
Generic list sorting handler.
|
||||||
|
"""
|
||||||
|
|
||||||
|
orderChanged = Signal(list)
|
||||||
|
|
||||||
|
def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
|
||||||
|
# Store the orientation for drag checks later.
|
||||||
|
self.orientation = orientation
|
||||||
|
|
||||||
|
if self.orientation == Qt.Orientation.Vertical:
|
||||||
|
self.blayout = QVBoxLayout()
|
||||||
|
else:
|
||||||
|
self.blayout = QHBoxLayout()
|
||||||
|
|
||||||
|
self.setLayout(self.blayout)
|
||||||
|
|
||||||
|
def dragEnterEvent(self, e):
|
||||||
|
e.accept()
|
||||||
|
|
||||||
|
def dropEvent(self, e):
|
||||||
|
pos = e.position()
|
||||||
|
widget = e.source()
|
||||||
|
self.blayout.removeWidget(widget)
|
||||||
|
|
||||||
|
for n in range(self.blayout.count()):
|
||||||
|
# Get the widget at each index in turn.
|
||||||
|
w = self.blayout.itemAt(n).widget()
|
||||||
|
if self.orientation == Qt.Orientation.Vertical:
|
||||||
|
# Drag drop vertically.
|
||||||
|
drop_here = pos.y() < w.y() + w.size().height() // 2
|
||||||
|
else:
|
||||||
|
# Drag drop horizontally.
|
||||||
|
drop_here = pos.x() < w.x() + w.size().width() // 2
|
||||||
|
|
||||||
|
if drop_here:
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
# We aren't on the left hand/upper side of any widget,
|
||||||
|
# so we're at the end. Increment 1 to insert after.
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
self.blayout.insertWidget(n, widget)
|
||||||
|
self.orderChanged.emit(self.get_item_data())
|
||||||
|
|
||||||
|
e.accept()
|
||||||
|
|
||||||
|
def add_item(self, item):
|
||||||
|
self.blayout.addWidget(item)
|
||||||
|
|
||||||
|
def get_item_data(self):
|
||||||
|
data = []
|
||||||
|
for n in range(self.blayout.count()):
|
||||||
|
# Get the widget at each index in turn.
|
||||||
|
w: "DAPBlockWidget" = self.blayout.itemAt(n).widget()
|
||||||
|
data.append(w._title.text())
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class DAPBlockWidget(BECWidget, DragItem):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent=None,
|
||||||
|
content: type[DAPBlock] = None,
|
||||||
|
client=None,
|
||||||
|
gui_id: str | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
parent=parent,
|
||||||
|
client=client,
|
||||||
|
gui_id=gui_id,
|
||||||
|
title=content.__name__,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
self._content = content
|
||||||
|
self.add_form(self._content)
|
||||||
|
|
||||||
|
def add_form(self, block_type: type[DAPBlock]):
|
||||||
|
run_signature = inspect.signature(block_type.run)
|
||||||
|
self._title.setText(block_type.__name__)
|
||||||
|
layout = self._contents.layout()
|
||||||
|
if layout is None:
|
||||||
|
return
|
||||||
|
self._add_widgets_for_signature(layout, run_signature)
|
||||||
|
|
||||||
|
def _add_widgets_for_signature(self, layout: QLayout, signature: inspect.Signature):
|
||||||
|
for arg_name, arg_spec in signature.parameters.items():
|
||||||
|
annotation: str | type = arg_spec.annotation
|
||||||
|
if isinstance(annotation, str):
|
||||||
|
annotation = deserialize_dtype(annotation) or annotation
|
||||||
|
w = QWidget()
|
||||||
|
w.setLayout(QHBoxLayout())
|
||||||
|
w.layout().addWidget(QLabel(arg_name))
|
||||||
|
w.layout().addWidget(
|
||||||
|
widget_from_type(annotation)(
|
||||||
|
FieldInfo(
|
||||||
|
annotation=annotation
|
||||||
|
) # FIXME this class should not be initialised directly...
|
||||||
|
)
|
||||||
|
)
|
||||||
|
w.layout().addWidget(QLabel(str(annotation)))
|
||||||
|
|
||||||
|
layout.addWidget(w)
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
|
||||||
|
for block_type in [SmoothBlock, GradientBlock, BlockWithLotsOfArgs] * 2:
|
||||||
|
item = DAPBlockWidget(content=block_type)
|
||||||
|
self.drag.add_item(item)
|
||||||
|
|
||||||
|
# Print out the changed order.
|
||||||
|
self.drag.orderChanged.connect(print)
|
||||||
|
|
||||||
|
container = QWidget()
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addStretch(1)
|
||||||
|
layout.addWidget(self.drag)
|
||||||
|
layout.addStretch(1)
|
||||||
|
container.setLayout(layout)
|
||||||
|
|
||||||
|
self.setCentralWidget(container)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication([])
|
||||||
|
w = MainWindow()
|
||||||
|
w.show()
|
||||||
|
|
||||||
|
app.exec()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""Panel to compose DAP task runs
|
||||||
|
- new ones are added as tabs
|
||||||
|
- can be enabled and disabled, continuous and oneoff
|
||||||
|
- fill in extra kwargs using thing from MD widget
|
||||||
|
- output to topic for the name, which looks like a data topic
|
||||||
|
"""
|
||||||
@@ -83,7 +83,6 @@ class ClearableBoolEntry(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class MetadataWidget(QWidget):
|
class MetadataWidget(QWidget):
|
||||||
|
|
||||||
valueChanged = Signal()
|
valueChanged = Signal()
|
||||||
|
|
||||||
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
||||||
@@ -250,7 +249,9 @@ def widget_from_type(annotation: type | None) -> Callable[[FieldInfo], MetadataW
|
|||||||
if annotation in [bool, bool | None]:
|
if annotation in [bool, bool | None]:
|
||||||
return BoolMetadataField
|
return BoolMetadataField
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Type {annotation} is not (yet) supported in metadata form creation.")
|
logger.warning(
|
||||||
|
f"Type {annotation} is not (yet) supported in metadata form creation."
|
||||||
|
)
|
||||||
return StrMetadataField
|
return StrMetadataField
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
class ScanMetadata(BECWidget, QWidget):
|
class PydanticModelForm(BECWidget, QWidget):
|
||||||
"""Dynamically generates a form for inclusion of metadata for a scan. Uses the
|
"""Dynamically generates a form for inclusion of metadata for a scan. Uses the
|
||||||
metadata schema registry supplied in the plugin repo to find pydantic models
|
metadata schema registry supplied in the plugin repo to find pydantic models
|
||||||
associated with the scan type. Sets limits for numerical values if specified."""
|
associated with the scan type. Sets limits for numerical values if specified."""
|
||||||
@@ -75,7 +75,9 @@ class ScanMetadata(BECWidget, QWidget):
|
|||||||
self._new_grid_layout()
|
self._new_grid_layout()
|
||||||
self._grid_container.addLayout(self._md_grid_layout)
|
self._grid_container.addLayout(self._md_grid_layout)
|
||||||
|
|
||||||
self._additional_md_box = ExpandableGroupFrame("Additional metadata", expanded=False)
|
self._additional_md_box = ExpandableGroupFrame(
|
||||||
|
"Additional metadata", expanded=False
|
||||||
|
)
|
||||||
self._layout.addWidget(self._additional_md_box)
|
self._layout.addWidget(self._additional_md_box)
|
||||||
self._additional_md_box_layout = QHBoxLayout()
|
self._additional_md_box_layout = QHBoxLayout()
|
||||||
self._additional_md_box.set_layout(self._additional_md_box_layout)
|
self._additional_md_box.set_layout(self._additional_md_box_layout)
|
||||||
@@ -129,7 +131,9 @@ class ScanMetadata(BECWidget, QWidget):
|
|||||||
self._populate()
|
self._populate()
|
||||||
|
|
||||||
def _populate(self):
|
def _populate(self):
|
||||||
self._additional_metadata.update_disallowed_keys(list(self._md_schema.model_fields.keys()))
|
self._additional_metadata.update_disallowed_keys(
|
||||||
|
list(self._md_schema.model_fields.keys())
|
||||||
|
)
|
||||||
for i, (field_name, info) in enumerate(self._md_schema.model_fields.items()):
|
for i, (field_name, info) in enumerate(self._md_schema.model_fields.items()):
|
||||||
self._add_griditem(field_name, info, i)
|
self._add_griditem(field_name, info, i)
|
||||||
|
|
||||||
@@ -146,7 +150,11 @@ class ScanMetadata(BECWidget, QWidget):
|
|||||||
def _dict_from_grid(self) -> dict[str, str | int | float | Decimal | bool]:
|
def _dict_from_grid(self) -> dict[str, str | int | float | Decimal | bool]:
|
||||||
grid = self._md_grid_layout
|
grid = self._md_grid_layout
|
||||||
return {
|
return {
|
||||||
grid.itemAtPosition(i, 0).widget().property("_model_field_name"): grid.itemAtPosition(i, 1).widget().getValue() # type: ignore # we only add 'MetadataWidget's here
|
grid.itemAtPosition(i, 0)
|
||||||
|
.widget()
|
||||||
|
.property("_model_field_name"): grid.itemAtPosition(i, 1)
|
||||||
|
.widget()
|
||||||
|
.getValue() # type: ignore # we only add 'MetadataWidget's here
|
||||||
for i in range(grid.rowCount())
|
for i in range(grid.rowCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +190,9 @@ class ScanMetadata(BECWidget, QWidget):
|
|||||||
self._additional_md_box.setVisible(not hide)
|
self._additional_md_box.setVisible(not hide)
|
||||||
|
|
||||||
|
|
||||||
|
class ScanMetadata(PydanticModelForm): ...
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": # pragma: no cover
|
if __name__ == "__main__": # pragma: no cover
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -190,15 +201,21 @@ if __name__ == "__main__": # pragma: no cover
|
|||||||
from bec_widgets.utils.colors import set_theme
|
from bec_widgets.utils.colors import set_theme
|
||||||
|
|
||||||
class ExampleSchema1(BasicScanMetadata):
|
class ExampleSchema1(BasicScanMetadata):
|
||||||
abc: int = Field(gt=0, lt=2000, description="Heating temperature abc", title="A B C")
|
abc: int = Field(
|
||||||
foo: str = Field(max_length=12, description="Sample database code", default="DEF123")
|
gt=0, lt=2000, description="Heating temperature abc", title="A B C"
|
||||||
|
)
|
||||||
|
foo: str = Field(
|
||||||
|
max_length=12, description="Sample database code", default="DEF123"
|
||||||
|
)
|
||||||
xyz: Decimal = Field(decimal_places=4)
|
xyz: Decimal = Field(decimal_places=4)
|
||||||
baz: bool
|
baz: bool
|
||||||
|
|
||||||
class ExampleSchema2(BasicScanMetadata):
|
class ExampleSchema2(BasicScanMetadata):
|
||||||
checkbox_up_top: bool
|
checkbox_up_top: bool
|
||||||
checkbox_again: bool = Field(
|
checkbox_again: bool = Field(
|
||||||
title="Checkbox Again", description="this one defaults to True", default=True
|
title="Checkbox Again",
|
||||||
|
description="this one defaults to True",
|
||||||
|
default=True,
|
||||||
)
|
)
|
||||||
different_items: int | None = Field(
|
different_items: int | None = Field(
|
||||||
None, description="This is just one different item...", gt=-100, lt=0
|
None, description="This is just one different item...", gt=-100, lt=0
|
||||||
@@ -211,9 +228,12 @@ if __name__ == "__main__": # pragma: no cover
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"bec_lib.metadata_schema._get_metadata_schema_registry",
|
"bec_lib.metadata_schema._get_metadata_schema_registry",
|
||||||
lambda: {"scan1": ExampleSchema1, "scan2": ExampleSchema2, "scan3": ExampleSchema3},
|
lambda: {
|
||||||
|
"scan1": ExampleSchema1,
|
||||||
|
"scan2": ExampleSchema2,
|
||||||
|
"scan3": ExampleSchema3,
|
||||||
|
},
|
||||||
):
|
):
|
||||||
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
w = QWidget()
|
w = QWidget()
|
||||||
selection = QComboBox()
|
selection = QComboBox()
|
||||||
|
|||||||
Reference in New Issue
Block a user