From 550ab11f6676b10cc9089a2261c678fed9067a55 Mon Sep 17 00:00:00 2001 From: Antti Soininen Date: Wed, 4 Sep 2024 16:20:29 +0300 Subject: [PATCH] Really remove rows in compound models In CompoundModelBase.handle_items_removed() we took a shortcut and emitted layoutAboutToBeChanged and layoutChanged signals. This doesn't update e.g. a view's current index which causes some warning messages (at least on Linux). We now properly remove the rows using beginRemoveRows() and endRemoveRows(). --- .../mvcmodels/compound_table_model.py | 3 +-- .../mvcmodels/compound_models.py | 22 +++++++++++++++---- .../mvcmodels/test_compound_models.py | 4 +++- tests/spine_db_editor/widgets/helpers.py | 3 +++ tests/test_SpineToolboxProject.py | 2 +- tests/widgets/test_custom_combobox.py | 2 +- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/spinetoolbox/mvcmodels/compound_table_model.py b/spinetoolbox/mvcmodels/compound_table_model.py index cd00d794a..0c002ed01 100644 --- a/spinetoolbox/mvcmodels/compound_table_model.py +++ b/spinetoolbox/mvcmodels/compound_table_model.py @@ -20,8 +20,7 @@ class CompoundTableModel(MinimalTableModel): """A model that concatenates several sub table models vertically.""" def __init__(self, parent=None, header=None): - """Initializes model. - + """ Args: parent (QObject, optional): the parent object header (list of str, optional): header labels diff --git a/spinetoolbox/spine_db_editor/mvcmodels/compound_models.py b/spinetoolbox/spine_db_editor/mvcmodels/compound_models.py index 735944a1a..ba1b6ee63 100644 --- a/spinetoolbox/spine_db_editor/mvcmodels/compound_models.py +++ b/spinetoolbox/spine_db_editor/mvcmodels/compound_models.py @@ -192,7 +192,7 @@ def _auto_filter_accepts_model(self, model): for db_map, entity_class_id in values: if model.db_map == db_map and (entity_class_id is None or model.entity_class_id == entity_class_id): break - else: # nobreak + else: return False return True @@ -391,7 +391,6 @@ def handle_items_removed(self, db_map_data): Args: db_map_data (dict): list of removed dict-items keyed by DatabaseMapping """ - self.layoutAboutToBeChanged.emit() for db_map, items in db_map_data.items(): if db_map not in self.db_maps: continue @@ -411,15 +410,30 @@ def handle_items_removed(self, db_map_data): removed_ids.remove(id_) if not removed_ids: break + class_filter_accepted = self._class_filter_accepts_model(model) for row, count in sorted(rows_to_row_count_tuples(removed_rows), reverse=True): del model._main_data[row : row + count] + if not class_filter_accepted: + continue + compound_row = self._inv_row_map[(model, row)] + self.beginRemoveRows(QModelIndex(), compound_row, compound_row + count - 1) + tail_row_map = self._row_map[compound_row + count :] + self._row_map = self._row_map[:compound_row] + for mapped_row in tail_row_map: + if model is mapped_row[0]: + self._row_map.append((model, mapped_row[1] - count)) + else: + self._row_map.append(mapped_row) + for r in range(row, row + count): + del self._inv_row_map[(model, r)] + for i, r in enumerate(self._row_map[compound_row:]): + self._inv_row_map[r] = i + compound_row + self.endRemoveRows() if model.rowCount() == 0: emptied_single_model_indexes.append(model_index) for model_index in reversed(emptied_single_model_indexes): model = self.sub_models.pop(model_index) model.deleteLater() - self._do_refresh() - self.layoutChanged.emit() def db_item(self, index): sub_index = self.map_to_sub(index) diff --git a/tests/spine_db_editor/mvcmodels/test_compound_models.py b/tests/spine_db_editor/mvcmodels/test_compound_models.py index 9e06845c5..b9d75d2db 100644 --- a/tests/spine_db_editor/mvcmodels/test_compound_models.py +++ b/tests/spine_db_editor/mvcmodels/test_compound_models.py @@ -73,8 +73,10 @@ def test_model_updates_when_entity_class_is_removed(self): model = CompoundParameterDefinitionModel(self._db_editor, self._db_mngr, self._db_map) model.init_model() fetch_model(model) - model.set_filter_class_ids({self._db_map: {entity_class_2["id"]}}) self.assertEqual(model.rowCount(), 4) + model.set_filter_class_ids({self._db_map: {entity_class_2["id"]}}) + model.refresh() + self.assertEqual(model.rowCount(), 3) self._db_mngr.remove_items({self._db_map: {"entity_class": [entity_class_2["id"]]}}) self.assertEqual(model.rowCount(), 1) diff --git a/tests/spine_db_editor/widgets/helpers.py b/tests/spine_db_editor/widgets/helpers.py index 5f8455df0..a75e10f30 100644 --- a/tests/spine_db_editor/widgets/helpers.py +++ b/tests/spine_db_editor/widgets/helpers.py @@ -42,6 +42,7 @@ def create_and_store_editor(instance, parent, option, target_index): view.edit(index) if self._cell_editor is None: # Native editor widget is being used, fall back to setting value directly in model. + view.closeEditor() view.model().setData(index, value) return if isinstance(self._cell_editor, SearchBarEditor): @@ -71,6 +72,8 @@ def create_and_store_editor(instance, parent, option, target_index): view.edit(index) def reset(self): + if self._cell_editor is not None: + self._cell_editor.deleteLater() self._cell_editor = None diff --git a/tests/test_SpineToolboxProject.py b/tests/test_SpineToolboxProject.py index 31a89a693..d4e638155 100644 --- a/tests/test_SpineToolboxProject.py +++ b/tests/test_SpineToolboxProject.py @@ -20,7 +20,7 @@ import networkx as nx from PySide6.QtCore import QVariantAnimation from PySide6.QtGui import QColor -from PySide6.QtWidgets import QApplication, QMessageBox, QGraphicsRectItem +from PySide6.QtWidgets import QApplication, QGraphicsRectItem, QMessageBox from spine_engine.project_item.executable_item_base import ExecutableItemBase from spine_engine.project_item.project_item_specification import ProjectItemSpecification from spine_engine.spine_engine import ItemExecutionFinishState diff --git a/tests/widgets/test_custom_combobox.py b/tests/widgets/test_custom_combobox.py index 5beb32b6b..b59e3e4e6 100644 --- a/tests/widgets/test_custom_combobox.py +++ b/tests/widgets/test_custom_combobox.py @@ -13,7 +13,7 @@ """Unit tests for the classes in ``custom_combobox`` module. OpenProjectDialogComboBox is tested in test_open_project_dialog module.""" import unittest -from PySide6.QtGui import QPaintEvent, QImage, QColor +from PySide6.QtGui import QColor, QImage, QPaintEvent from PySide6.QtWidgets import QWidget from spinetoolbox.widgets.custom_combobox import CustomQComboBox, ElidedCombobox from tests.mock_helpers import TestCaseWithQApplication