0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

fix: adjust height of list widget

This commit is contained in:
2025-06-10 14:59:34 +02:00
committed by David Perl
parent 5e4c129af6
commit 11131ef14c
6 changed files with 55 additions and 41 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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()
}

View File

@ -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,11 +125,7 @@ if __name__ == "__main__": # pragma: no cover
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
item = DeviceItem(widget, "Device")
layout.addWidget(DarkModeButton())
layout.addWidget(item)
item.set_display_config(
{
mock_config = {
"name": "Test Device",
"enabled": True,
"deviceClass": "FakeDeviceClass",
@ -141,6 +137,8 @@ if __name__ == "__main__": # pragma: no cover
"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)
widget.show()
sys.exit(app.exec_())

View File

@ -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)