From 2008a5d5f831caf32ef668c37dd75e138ee57b6a Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Mon, 25 Mar 2024 10:55:22 +0100 Subject: [PATCH 1/2] ListViewFilter: Retain selection when filtering --- orangewidget/utils/listview.py | 34 ++++++++++++++++++++++- orangewidget/utils/tests/test_listview.py | 15 ++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/orangewidget/utils/listview.py b/orangewidget/utils/listview.py index dda8e0836..76f10b6d8 100644 --- a/orangewidget/utils/listview.py +++ b/orangewidget/utils/listview.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager from typing import Iterable, Optional import warnings @@ -10,9 +11,19 @@ QSortFilterProxyModel, QItemSelection, QSize, + QItemSelectionModel, ) +@contextmanager +def disconnected(signal, slot, connection_type=Qt.AutoConnection): + signal.disconnect(slot) + try: + yield + finally: + signal.connect(slot, connection_type) + + class ListViewFilter(QListView): """ A QListView with implicit and transparent row filtering. @@ -27,6 +38,7 @@ def __init__( **kwargs ): super().__init__(*args, **kwargs) + self.__selection = QItemSelection() self.__search = QLineEdit(self, placeholderText="Filter...") self.__search.textEdited.connect(self.__on_text_edited) self.__preferred_size = preferred_size @@ -40,9 +52,29 @@ def __init__( assert isinstance(proxy, QSortFilterProxyModel) super().setModel(proxy) self.set_source_model(model) + self.selectionModel().selectionChanged.connect(self.__on_sel_changed) + + def __on_sel_changed( + self, + selected: QItemSelection, + deselected: QItemSelection + ): + selected = self.model().mapSelectionToSource(selected) + deselected = self.model().mapSelectionToSource(deselected) + self.__selection.merge(selected, QItemSelectionModel.Select) + self.__selection.merge(deselected, QItemSelectionModel.Deselect) + self.__select() def __on_text_edited(self, string: str): - self.model().setFilterFixedString(string) + with disconnected(self.selectionModel().selectionChanged, + self.__on_sel_changed): + self.model().setFilterFixedString(string) + self.__select() + + def __select(self): + selection = self.model().mapSelectionFromSource(self.__selection) + self.selectionModel().select(selection, + QItemSelectionModel.ClearAndSelect) def setModel(self, _): raise TypeError("The model cannot be changed. " diff --git a/orangewidget/utils/tests/test_listview.py b/orangewidget/utils/tests/test_listview.py index b43ef8340..aeb91730b 100644 --- a/orangewidget/utils/tests/test_listview.py +++ b/orangewidget/utils/tests/test_listview.py @@ -134,6 +134,21 @@ def test_set_proxy(self): def test_set_proxy_raises(self): self.assertRaises(Exception, ListViewFilter, proxy=PyListModel()) + def test_selection(self): + view = ListViewFilter() + view.model().setSourceModel(PyListModel(["one", "two", "three"])) + sel_model = view.selectionModel() + selected_row = 0 + index = view.model().index(selected_row, 0) + sel_model.select(index, sel_model.ClearAndSelect) + self.assertEqual(sel_model.selectedRows(0)[0].row(), selected_row) + + view._ListViewFilter__search.textEdited.emit("two") + self.assertEqual(len(sel_model.selectedRows(0)), 0) + + view._ListViewFilter__search.textEdited.emit("") + self.assertEqual(sel_model.selectedRows(0)[0].row(), selected_row) + if __name__ == "__main__": unittest.main() From 25afe0689cd3acad299e3beecc0145fadd842a4b Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 14 May 2024 10:00:04 +0200 Subject: [PATCH 2/2] ListViewFilter: Use signal blocking --- orangewidget/utils/listview.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/orangewidget/utils/listview.py b/orangewidget/utils/listview.py index 76f10b6d8..3d9f43306 100644 --- a/orangewidget/utils/listview.py +++ b/orangewidget/utils/listview.py @@ -1,4 +1,3 @@ -from contextlib import contextmanager from typing import Iterable, Optional import warnings @@ -14,14 +13,7 @@ QItemSelectionModel, ) - -@contextmanager -def disconnected(signal, slot, connection_type=Qt.AutoConnection): - signal.disconnect(slot) - try: - yield - finally: - signal.connect(slot, connection_type) +from orangewidget.utils.itemmodels import signal_blocking class ListViewFilter(QListView): @@ -66,8 +58,7 @@ def __on_sel_changed( self.__select() def __on_text_edited(self, string: str): - with disconnected(self.selectionModel().selectionChanged, - self.__on_sel_changed): + with signal_blocking(self.selectionModel()): self.model().setFilterFixedString(string) self.__select()