diff --git a/bec_widgets/widgets/editors/dict_backed_table.py b/bec_widgets/widgets/editors/dict_backed_table.py index 9ab0e301..248b0b5d 100644 --- a/bec_widgets/widgets/editors/dict_backed_table.py +++ b/bec_widgets/widgets/editors/dict_backed_table.py @@ -63,7 +63,10 @@ class DictBackedTableModel(QAbstractTableModel): def setData(self, index, value, role): if role == Qt.ItemDataRole.EditRole: - if value in self._disallowed_keys or value in self._other_keys(index.row()): + # only keys have to be unique and allowed, values are free + if index.column() == 0 and ( + value in self._disallowed_keys or value in self._other_keys(index.row()) + ): return False self._data[index.row()][index.column()] = str(value) self.dataChanged.emit(index, index) @@ -181,9 +184,8 @@ class DictBackedTable(QWidget): self._table_view.setWordWrap(False) self._table_view.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) self._table_view.header().setSectionResizeMode(5, QtWidgets.QHeaderView.Stretch) + self._autoscale = False self.autoscale = autoscale_to_data - if self.autoscale: - self.data_changed.connect(self.scale_to_data) self._layout.addWidget(self._table_view) self._button_holder = QWidget() @@ -258,12 +260,14 @@ class DictBackedTable(QWidget): @autoscale.setter def autoscale(self, autoscale: bool): - self._autoscale = autoscale - if self._autoscale: - self.scale_to_data() + # connect or disconnect only on a change, so the slot is never connected twice + if autoscale and not self._autoscale: self.data_changed.connect(self.scale_to_data) - else: + elif self._autoscale and not autoscale: self.data_changed.disconnect(self.scale_to_data) + self._autoscale = autoscale + if autoscale: + self.scale_to_data() if __name__ == "__main__": # pragma: no cover diff --git a/tests/unit_tests/test_dict_backed_table_model.py b/tests/unit_tests/test_dict_backed_table_model.py index 4b0cf3e4..4087ab5f 100644 --- a/tests/unit_tests/test_dict_backed_table_model.py +++ b/tests/unit_tests/test_dict_backed_table_model.py @@ -11,7 +11,7 @@ process); its failures are collected from the Qt log. # pylint: disable=protected-access import pytest -from qtpy.QtCore import QItemSelectionModel, QSortFilterProxyModel, Qt +from qtpy.QtCore import SIGNAL, QItemSelectionModel, QSortFilterProxyModel, Qt from qtpy.QtTest import QAbstractItemModelTester from bec_widgets.tests.utils import create_widget @@ -185,3 +185,35 @@ def test_scan_metadata_form_drops_deleted_extra_row(qtbot): assert received, "deleting a row must re-validate the form" assert "k1" not in received[-1] assert received[-1]["k2"] == "v2" + + +def test_values_are_not_checked_against_keys(table): + model = table._table_model + table.update_disallowed_keys(["forbidden"]) + + # a value may repeat another row's key or a disallowed key + assert model.setData(model.index(0, 1), "key2", Qt.ItemDataRole.EditRole) + assert model.setData(model.index(1, 1), "forbidden", Qt.ItemDataRole.EditRole) + # keys still have to be unique and allowed + assert not model.setData(model.index(0, 0), "key2", Qt.ItemDataRole.EditRole) + assert not model.setData(model.index(0, 0), "forbidden", Qt.ItemDataRole.EditRole) + assert table.dump_dict() == {"key1": "key2", "key2": "forbidden", "key3": "value3"} + + +def _scale_receivers(widget) -> int: + return widget.receivers(SIGNAL("data_changed(QVariantMap)")) + + +@pytest.mark.parametrize("autoscale", [True, False]) +def test_autoscale_connects_scale_to_data_once(qtbot, autoscale, recwarn): + table = create_widget(qtbot, DictBackedTable, autoscale_to_data=autoscale) + assert _scale_receivers(table) == int(autoscale) + + table.autoscale = True + table.autoscale = True + assert _scale_receivers(table) == 1 + + table.autoscale = False + table.autoscale = False + assert _scale_receivers(table) == 0 + assert not [w for w in recwarn if "Failed to disconnect" in str(w.message)]