diff --git a/bec_widgets/utils/forms_from_types/forms.py b/bec_widgets/utils/forms_from_types/forms.py index 04384d74..ece7e769 100644 --- a/bec_widgets/utils/forms_from_types/forms.py +++ b/bec_widgets/utils/forms_from_types/forms.py @@ -72,7 +72,7 @@ class TypedForm(BECWidget, QWidget): FormItemSpec(name=name, item_type=item_type, pretty_display=pretty_display) for name, item_type in items # type: ignore ] - self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self._layout = QVBoxLayout() self._layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._layout) @@ -80,11 +80,9 @@ class TypedForm(BECWidget, QWidget): self._enabled: bool = enabled self._form_grid_container = QWidget(parent=self) - self._form_grid_container.setSizePolicy( - QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding - ) + self._form_grid_container.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self._form_grid = QWidget(parent=self._form_grid_container) - self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self._layout.addWidget(self._form_grid_container) self._form_grid_container.setLayout(QVBoxLayout()) self._form_grid.setLayout(self._new_grid_layout()) @@ -113,7 +111,7 @@ class TypedForm(BECWidget, QWidget): grid.addWidget(label, row, 0) widget = self._widget_from_type(item, self._widget_types)(parent=self, spec=item) widget.valueChanged.connect(self.value_changed) - widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) grid.addWidget(widget, row, 1) def enumerate_form_widgets(self): @@ -139,7 +137,7 @@ class TypedForm(BECWidget, QWidget): old_layout.deleteLater() self._form_grid.deleteLater() self._form_grid = QWidget() - self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self._form_grid.setLayout(self._new_grid_layout()) self._form_grid_container.layout().addWidget(self._form_grid) diff --git a/bec_widgets/utils/forms_from_types/items.py b/bec_widgets/utils/forms_from_types/items.py index 9acbbfb8..389aaccb 100644 --- a/bec_widgets/utils/forms_from_types/items.py +++ b/bec_widgets/utils/forms_from_types/items.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined from qtpy import QtCore -from qtpy.QtCore import QSize, Signal # type: ignore +from qtpy.QtCore import QSize, Qt, Signal # type: ignore from qtpy.QtGui import QFontMetrics from qtpy.QtWidgets import ( QApplication, @@ -372,9 +372,11 @@ class ListFormItem(DynamicFormItem): else: self._types = _ItemAndWidgetType(str, QLineEdit, "") super().__init__(parent=parent, spec=spec) + self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._main_widget: QListWidget self._data = [] - self.setFixedHeight(QFontMetrics(self.font()).height() * 6) + self._min_lines = 2 if spec.pretty_display else 4 + self._repop(self._data) def sizeHint(self): default = super().sizeHint() @@ -383,6 +385,7 @@ class ListFormItem(DynamicFormItem): def _add_main_widget(self) -> None: self._main_widget = QListWidget() self._layout.addWidget(self._main_widget) + self._layout.setAlignment(Qt.AlignmentFlag.AlignTop) self._add_buttons() def _add_buttons(self): @@ -407,11 +410,13 @@ class ListFormItem(DynamicFormItem): self._main_widget.clear() for val in data: self._add_list_item(val) + self.scale_to_data() def _add_data_item(self, val=None): val = val or self._types.default self._data.append(val) self._add_list_item(val) + self._repop(self._data) def _add_list_item(self, val): item = QListWidgetItem(self._main_widget) @@ -453,15 +458,26 @@ class ListFormItem(DynamicFormItem): self._data = list(value) self._repop(self._data) + def _line_height(self): + return QFontMetrics(self._main_widget.font()).height() + + def set_max_height_in_lines(self, lines: int): + outer_inc = 1 if self._spec.pretty_display else 3 + self._main_widget.setFixedHeight(self._line_height() * max(lines, self._min_lines)) + self._button_holder.setFixedHeight(self._line_height() * (max(lines, self._min_lines) + 1)) + self.setFixedHeight(self._line_height() * (max(lines, self._min_lines) + outer_inc)) + + def scale_to_data(self, *_): + self.set_max_height_in_lines(self._main_widget.count() + 1) + class SetFormItem(ListFormItem): - def _add_main_widget(self) -> None: super()._add_main_widget() self._add_item_field = self._types.widget() self._buttons.addWidget(QLabel("Add new:")) self._buttons.addWidget(self._add_item_field) - self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding) + self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum) @SafeSlot() def _add_row(self): diff --git a/bec_widgets/widgets/editors/dict_backed_table.py b/bec_widgets/widgets/editors/dict_backed_table.py index 0ff4d941..3efe3887 100644 --- a/bec_widgets/widgets/editors/dict_backed_table.py +++ b/bec_widgets/widgets/editors/dict_backed_table.py @@ -113,11 +113,13 @@ class DictBackedTableModel(QAbstractTableModel): @SafeSlot() def add_row(self): self.insertRow(self.rowCount()) + self.dataChanged.emit(self.index(self.rowCount(), 0), self.index(self.rowCount(), 1), 0) @SafeSlot(list) def delete_rows(self, rows: list[int]): # delete from the end so indices stay correct for row in sorted(rows, reverse=True): + self.dataChanged.emit(self.index(row, 0), self.index(row, 1), 0) self.removeRows(row, 1, QModelIndex()) def set_default(self, value: dict | None): @@ -154,16 +156,20 @@ class DictBackedTable(QWidget): self._layout = QHBoxLayout() self.setLayout(self._layout) + self._layout.setContentsMargins(0, 0, 0, 0) + self._table_model = DictBackedTableModel(initial_data) self._table_view = QTreeView() self._table_view.setModel(self._table_model) - self.set_min_height_in_lines(max(5, len(initial_data))) - self.set_max_height_in_lines(len(initial_data)) + self._min_lines = 3 + self.set_height_in_lines(len(initial_data)) self._table_view.setSizePolicy( QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) ) self._table_view.setAlternatingRowColors(True) + self._table_view.setUniformRowHeights(True) + self._table_view.setWordWrap(False) self._table_view.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) self._table_view.header().setSectionResizeMode(5, QtWidgets.QHeaderView.Stretch) self.autoscale = autoscale_to_data @@ -218,19 +224,15 @@ class DictBackedTable(QWidget): keys (list[str]): list of keys which are forbidden.""" self._table_model.update_disallowed_keys(keys) - def set_min_height_in_lines(self, lines: int): - self._min_lines = lines - self._table_view.setMinimumHeight(QFontMetrics(self._table_view.font()).height() * lines) - - def set_max_height_in_lines(self, lines: int): + def set_height_in_lines(self, lines: int): self._table_view.setMaximumHeight( - QFontMetrics(self._table_view.font()).height() * max(lines, self._min_lines) + int(QFontMetrics(self._table_view.font()).height() * max(lines + 2, self._min_lines)) ) @SafeSlot() @SafeSlot(dict) def scale_to_data(self, *_): - self.set_max_height_in_lines(self._table_model.length()) + self.set_height_in_lines(self._table_model.length()) @SafeProperty(bool) def autoscale(self): # type: ignore diff --git a/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py b/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py index cf1537d2..d03b7949 100644 --- a/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py +++ b/bec_widgets/widgets/services/device_browser/device_item/device_config_dialog.py @@ -134,9 +134,7 @@ class DeviceConfigDialog(BECWidget, QDialog): self.client.device_manager is not None and self._device in self.client.device_manager.devices ): - dev = self.client.device_manager.devices.get(self._device) - dev.read_configuration(cached=False) - self._initial_config = dev._config + self._initial_config = self.client.device_manager.devices.get(self._device)._config def _fill_form(self): self._form.set_data(DeviceConfigModel.model_validate(self._initial_config)) @@ -146,8 +144,11 @@ class DeviceConfigDialog(BECWidget, QDialog): diff = { k: v for k, v in new_config.items() if self._initial_config.get(k) != new_config.get(k) } - # TODO: replace when https://github.com/bec-project/bec/issues/528 is resolved if diff.get("deviceConfig") is not None: + # TODO: special cased in some parts of device manager but not others, should + # be removed in config update as with below issue + diff["deviceConfig"].pop("device_access", None) + # TODO: replace when https://github.com/bec-project/bec/issues/528 is resolved diff["deviceConfig"] = { k: literal_eval(str(v)) for k, v in diff["deviceConfig"].items() } diff --git a/bec_widgets/widgets/services/device_browser/device_item/device_item.py b/bec_widgets/widgets/services/device_browser/device_item/device_item.py index 82691e9b..5718428c 100644 --- a/bec_widgets/widgets/services/device_browser/device_item/device_item.py +++ b/bec_widgets/widgets/services/device_browser/device_item/device_item.py @@ -78,7 +78,6 @@ class DeviceItem(ExpandableGroupFrame): @SafeSlot(popup_error=True) def _reload_config(self, *_): - self.dev[self.device].read_configuration(cached=False) self.set_display_config(self.dev[self.device]._config) def set_display_config(self, config_dict: dict): @@ -113,6 +112,7 @@ class DeviceItem(ExpandableGroupFrame): if __name__ == "__main__": # pragma: no cover import sys + from unittest.mock import MagicMock from qtpy.QtWidgets import QApplication @@ -125,22 +125,20 @@ if __name__ == "__main__": # pragma: no cover widget = QWidget() layout = QHBoxLayout() widget.setLayout(layout) - item = DeviceItem(widget, "Device") + mock_config = { + "name": "Test Device", + "enabled": True, + "deviceClass": "FakeDeviceClass", + "deviceConfig": {"kwarg1": "value1"}, + "readoutPriority": "baseline", + "description": "A device for testing out a widget", + "readOnly": True, + "softwareTrigger": False, + "deviceTags": {"tag1", "tag2", "tag3"}, + "userParameter": {"some_setting": "some_ value"}, + } + item = DeviceItem(widget, "Device", {"Device": MagicMock(enabled=True, _config=mock_config)}) layout.addWidget(DarkModeButton()) layout.addWidget(item) - item.set_display_config( - { - "name": "Test Device", - "enabled": True, - "deviceClass": "FakeDeviceClass", - "deviceConfig": {"kwarg1": "value1"}, - "readoutPriority": "baseline", - "description": "A device for testing out a widget", - "readOnly": True, - "softwareTrigger": False, - "deviceTags": {"tag1", "tag2", "tag3"}, - "userParameter": {"some_setting": "some_ value"}, - } - ) widget.show() sys.exit(app.exec_()) diff --git a/tests/unit_tests/test_device_browser.py b/tests/unit_tests/test_device_browser.py index 3992737e..62ad6e6c 100644 --- a/tests/unit_tests/test_device_browser.py +++ b/tests/unit_tests/test_device_browser.py @@ -89,7 +89,6 @@ def test_device_item_expansion(device_browser, qtbot): form = widget._contents.layout().itemAt(0).widget() qtbot.waitUntil(lambda: isinstance(form, DeviceConfigForm), timeout=500) assert widget.expanded - device_browser.dev["samx"].read_configuration.assert_called() assert (name_field := form.widget_dict.get("name")) is not None qtbot.waitUntil(lambda: name_field.getValue() == "samx", timeout=500) qtbot.mouseClick(widget._expansion_button, Qt.MouseButton.LeftButton)