From 8de62d34321cd827e60b0a7b6e7434070de301ae Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Mon, 23 Dec 2019 15:44:48 +0100 Subject: [PATCH] QAbstractItemView::dataChanged(): optimize call to QWidget::update() When topLeft and bottomRight are different in QAIV::dataChanged(), the current implementation simply calls QWidget::update() without checking if the affected cells are visible. This results in a big performance hit when cells are updated frequently. Now try to compute the exact update rect by iterating through the modified indexes. Fixes: QTBUG-58580 Change-Id: I97de567d494e40ed8cdb1ea1f5b3cf3a2f60455e Reviewed-by: Samuel Gaist --- src/widgets/itemviews/qabstractitemview.cpp | 28 +++- src/widgets/itemviews/qabstractitemview_p.h | 1 + src/widgets/itemviews/qtableview.cpp | 46 +++++++ src/widgets/itemviews/qtableview_p.h | 1 + src/widgets/itemviews/qtreeview.cpp | 18 +++ src/widgets/itemviews/qtreeview_p.h | 1 + .../qabstractitemview/CMakeLists.txt | 1 + .../qabstractitemview/qabstractitemview.pro | 2 +- .../tst_qabstractitemview.cpp | 102 +++++++++++++- .../itemviews/qtableview/tst_qtableview.cpp | 126 ++++++++++++++++++ .../itemviews/qtreeview/tst_qtreeview.cpp | 114 ++++++++++++++++ 11 files changed, 436 insertions(+), 4 deletions(-) diff --git a/src/widgets/itemviews/qabstractitemview.cpp b/src/widgets/itemviews/qabstractitemview.cpp index ebef36d0336..b8c30321ffc 100644 --- a/src/widgets/itemviews/qabstractitemview.cpp +++ b/src/widgets/itemviews/qabstractitemview.cpp @@ -3335,8 +3335,19 @@ void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelInde } } else { d->updateEditorData(topLeft, bottomRight); - if (isVisible() && !d->delayedPendingLayout) - d->viewport->update(); + if (isVisible() && !d->delayedPendingLayout) { + if (!topLeft.isValid() || + topLeft.parent() != bottomRight.parent() || + topLeft.row() > bottomRight.row() || + topLeft.column() > bottomRight.column()) { + // invalid parameter - call update() to redraw all + d->viewport->update(); + } else { + const QRect updateRect = d->intersectedRect(d->viewport->rect(), topLeft, bottomRight); + if (!updateRect.isEmpty()) + d->viewport->update(updateRect); + } + } } #ifndef QT_NO_ACCESSIBILITY @@ -3620,6 +3631,19 @@ void QAbstractItemViewPrivate::_q_columnsMoved(const QModelIndex &, int, int, co _q_layoutChanged(); } +QRect QAbstractItemViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const +{ + Q_Q(const QAbstractItemView); + + const auto parentIdx = topLeft.parent(); + QRect updateRect; + for (int r = topLeft.row(); r <= bottomRight.row(); ++r) { + for (int c = topLeft.column(); c <= bottomRight.column(); ++c) + updateRect |= q->visualRect(model->index(r, c, parentIdx)); + } + return rect.intersected(updateRect); +} + /*! This slot is called when the selection is changed. The previous selection (which may be empty), is specified by \a deselected, and the diff --git a/src/widgets/itemviews/qabstractitemview_p.h b/src/widgets/itemviews/qabstractitemview_p.h index fe1c00248fd..4b29b68b66b 100644 --- a/src/widgets/itemviews/qabstractitemview_p.h +++ b/src/widgets/itemviews/qabstractitemview_p.h @@ -108,6 +108,7 @@ class Q_AUTOTEST_EXPORT QAbstractItemViewPrivate : public QAbstractScrollAreaPri virtual void _q_layoutChanged(); virtual void _q_rowsMoved(const QModelIndex &source, int sourceStart, int sourceEnd, const QModelIndex &destination, int destinationStart); virtual void _q_columnsMoved(const QModelIndex &source, int sourceStart, int sourceEnd, const QModelIndex &destination, int destinationStart); + virtual QRect intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const; void _q_headerDataChanged() { doDelayedItemsLayout(); } void _q_scrollerStateChanged(); diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp index 81fceba8dc5..c3ccfac5be2 100644 --- a/src/widgets/itemviews/qtableview.cpp +++ b/src/widgets/itemviews/qtableview.cpp @@ -669,6 +669,52 @@ void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const *range = QItemSelectionRange(topLeft, bottomRight); } +QRect QTableViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const +{ + Q_Q(const QTableView); + + using minMaxPair = std::pair; + const auto calcMinMax = [q, rect](QHeaderView *hdr, int startIdx, int endIdx, minMaxPair bounds) -> minMaxPair + { + minMaxPair ret(std::numeric_limits::max(), std::numeric_limits::min()); + if (hdr->sectionsMoved()) { + for (int i = startIdx; i <= endIdx; ++i) { + const int start = hdr->sectionViewportPosition(i); + const int end = start + hdr->sectionSize(i); + ret.first = std::min(start, ret.first); + ret.second = std::max(end, ret.second); + if (ret.first <= bounds.first && ret.second >= bounds.second) + break; + } + } else { + if (q->isRightToLeft() && q->horizontalHeader() == hdr) + std::swap(startIdx, endIdx); + ret.first = hdr->sectionViewportPosition(startIdx); + ret.second = hdr->sectionViewportPosition(endIdx) + + hdr->sectionSize(endIdx); + } + return ret; + }; + + const auto yVals = calcMinMax(verticalHeader, topLeft.row(), bottomRight.row(), + minMaxPair(rect.top(), rect.bottom())); + if (yVals.first == yVals.second) // all affected rows are hidden + return QRect(); + + // short circuit: check if no row is inside rect + const QRect colRect(QPoint(rect.left(), yVals.first), + QPoint(rect.right(), yVals.second)); + const QRect intersected = rect.intersected(colRect); + if (intersected.isNull()) + return QRect(); + + const auto xVals = calcMinMax(horizontalHeader, topLeft.column(), bottomRight.column(), + minMaxPair(rect.left(), rect.right())); + const QRect updateRect(QPoint(xVals.first, yVals.first), + QPoint(xVals.second, yVals.second)); + return rect.intersected(updateRect); +} + /*! \internal Sets the span for the cell at (\a row, \a column). diff --git a/src/widgets/itemviews/qtableview_p.h b/src/widgets/itemviews/qtableview_p.h index 8f174351d2a..e17773c4bd7 100644 --- a/src/widgets/itemviews/qtableview_p.h +++ b/src/widgets/itemviews/qtableview_p.h @@ -149,6 +149,7 @@ class QTableViewPrivate : public QAbstractItemViewPrivate } void init(); void trimHiddenSelections(QItemSelectionRange *range) const; + QRect intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const override; inline bool isHidden(int row, int col) const { return verticalHeader->isSectionHidden(row) diff --git a/src/widgets/itemviews/qtreeview.cpp b/src/widgets/itemviews/qtreeview.cpp index 9aba17be707..a3bebb8f3cd 100644 --- a/src/widgets/itemviews/qtreeview.cpp +++ b/src/widgets/itemviews/qtreeview.cpp @@ -1396,6 +1396,24 @@ void QTreeViewPrivate::_q_modelDestroyed() QAbstractItemViewPrivate::_q_modelDestroyed(); } +QRect QTreeViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const +{ + Q_Q(const QTreeView); + + const auto parentIdx = topLeft.parent(); + executePostedLayout(); + QRect updateRect; + for (int r = topLeft.row(); r <= bottomRight.row(); ++r) { + if (isRowHidden(model->index(r, 0, parentIdx))) + continue; + for (int c = topLeft.column(); c <= bottomRight.column(); ++c) { + const QModelIndex idx(model->index(r, c, parentIdx)); + updateRect |= q->visualRect(idx); + } + } + return rect.intersected(updateRect); +} + /*! \reimp diff --git a/src/widgets/itemviews/qtreeview_p.h b/src/widgets/itemviews/qtreeview_p.h index 836d8f0c2df..1206850b717 100644 --- a/src/widgets/itemviews/qtreeview_p.h +++ b/src/widgets/itemviews/qtreeview_p.h @@ -135,6 +135,7 @@ class Q_WIDGETS_EXPORT QTreeViewPrivate : public QAbstractItemViewPrivate void _q_modelAboutToBeReset(); void _q_sortIndicatorChanged(int column, Qt::SortOrder order); void _q_modelDestroyed() override; + QRect intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const override; void layout(int item, bool recusiveExpanding = false, bool afterIsUninitialized = false); diff --git a/tests/auto/widgets/itemviews/qabstractitemview/CMakeLists.txt b/tests/auto/widgets/itemviews/qabstractitemview/CMakeLists.txt index 520a0b4b981..2fc8c14eb6f 100644 --- a/tests/auto/widgets/itemviews/qabstractitemview/CMakeLists.txt +++ b/tests/auto/widgets/itemviews/qabstractitemview/CMakeLists.txt @@ -12,4 +12,5 @@ add_qt_test(tst_qabstractitemview Qt::GuiPrivate Qt::TestPrivate Qt::Widgets + Qt::WidgetsPrivate ) diff --git a/tests/auto/widgets/itemviews/qabstractitemview/qabstractitemview.pro b/tests/auto/widgets/itemviews/qabstractitemview/qabstractitemview.pro index 809a996324d..b9091a134e2 100644 --- a/tests/auto/widgets/itemviews/qabstractitemview/qabstractitemview.pro +++ b/tests/auto/widgets/itemviews/qabstractitemview/qabstractitemview.pro @@ -1,4 +1,4 @@ CONFIG += testcase TARGET = tst_qabstractitemview -QT += widgets testlib testlib-private gui-private +QT += widgets testlib testlib-private gui-private widgets-private SOURCES += tst_qabstractitemview.cpp diff --git a/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp b/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp index 0834d989e04..44b728a0422 100644 --- a/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp +++ b/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp @@ -51,6 +51,7 @@ #include #include #include +#include Q_DECLARE_METATYPE(Qt::ItemFlags); @@ -115,6 +116,9 @@ private slots: void setCurrentIndex_data(); void setCurrentIndex(); + void checkIntersectedRect_data(); + void checkIntersectedRect(); + void task221955_selectedEditor(); void task250754_fontChange(); void task200665_itemEntered(); @@ -468,7 +472,8 @@ void tst_QAbstractItemView::basic_tests(QAbstractItemView *view) view->setCurrentIndex(QModelIndex()); // protected methods - view->dataChanged(QModelIndex(), QModelIndex()); + // will assert because an invalid index is passed + //view->dataChanged(QModelIndex(), QModelIndex()); view->rowsInserted(QModelIndex(), -1, -1); view->rowsAboutToBeRemoved(QModelIndex(), -1, -1); view->selectionChanged(QItemSelection(), QItemSelection()); @@ -1098,6 +1103,101 @@ void tst_QAbstractItemView::setCurrentIndex() QVERIFY(view->currentIndex() == model->index(result ? 1 : 0, 0)); } +void tst_QAbstractItemView::checkIntersectedRect_data() +{ + auto createModel = [](int rowCount) -> QStandardItemModel* + { + QStandardItemModel *model = new QStandardItemModel; + for (int i = 0; i < rowCount; ++i) { + const QList sil({new QStandardItem(QLatin1String("Row %1 Item").arg(i)), + new QStandardItem(QLatin1String("2nd column"))}); + model->appendRow(sil); + } + return model; + }; + QTest::addColumn("model"); + QTest::addColumn>("changedIndexes"); + QTest::addColumn("isEmpty"); + { + auto model = createModel(5); + QTest::newRow("multiple columns") << model + << QVector({model->index(0, 0), + model->index(0, 1)}) + << false; + } + { + auto model = createModel(5); + QTest::newRow("multiple rows") << model + << QVector({model->index(0, 0), + model->index(1, 0), + model->index(2, 0)}) + << false; + } + { + auto model = createModel(5); + QTest::newRow("hidden rows") << model + << QVector({model->index(3, 0), + model->index(4, 0)}) + << true; + } + { + auto model = createModel(500); + QTest::newRow("rows outside viewport") << model + << QVector({model->index(498, 0), + model->index(499, 0)}) + << true; + } +} + +void tst_QAbstractItemView::checkIntersectedRect() +{ + QFETCH(QStandardItemModel *, model); + QFETCH(const QVector, changedIndexes); + QFETCH(bool, isEmpty); + + class TableView : public QTableView + { + public: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override + { + QTableView::dataChanged(topLeft, bottomRight, roles); + // we want to check the base class implementation here! + QAbstractItemViewPrivate *av = static_cast(qt_widget_private(this)); + m_intersectecRect = av->intersectedRect(av->viewport->rect(), topLeft, bottomRight); + } + mutable QRect m_intersectecRect; + }; + + TableView view; + model->setParent(&view); + view.setModel(model); + view.resize(400, 400); + view.show(); + view.setRowHidden(3, true); + view.setRowHidden(4, true); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + view.m_intersectecRect = QRect(); + emit view.model()->dataChanged(changedIndexes.first(), changedIndexes.last()); + if (isEmpty) { + QVERIFY(view.m_intersectecRect.isEmpty()); + } else { + const auto parent = changedIndexes.first().parent(); + const int rCount = view.model()->rowCount(parent); + const int cCount = view.model()->columnCount(parent); + for (int r = 0; r < rCount; ++r) { + for (int c = 0; c < cCount; ++c) { + const QModelIndex &idx = view.model()->index(r, c, parent); + const auto rect = view.visualRect(idx); + if (changedIndexes.contains(idx)) + QVERIFY(view.m_intersectecRect.contains(rect)); + else + QVERIFY(!view.m_intersectecRect.contains(rect)); + } + } + } +} + void tst_QAbstractItemView::task221955_selectedEditor() { if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) diff --git a/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp b/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp index 544069243f6..ccc6997f263 100644 --- a/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp +++ b/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp @@ -243,6 +243,14 @@ class QtTestTableView : public QTableView verticalHeader()->setMinimumSectionSize(0); } + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override + { + QTableView::dataChanged(topLeft, bottomRight, roles); + QTableViewPrivate *av = static_cast(qt_widget_private(this)); + m_intersectecRect = av->intersectedRect(av->viewport->rect(), topLeft, bottomRight); + } + mutable QRect m_intersectecRect; + using QTableView::moveCursor; using QTableView::isIndexHidden; using QTableView::setSelection; @@ -402,6 +410,9 @@ private slots: void selectionSignal(); void setCurrentIndex(); + void checkIntersectedRect_data(); + void checkIntersectedRect(); + // task-specific tests: void task173773_updateVerticalHeader(); void task227953_setRootIndex(); @@ -3842,6 +3853,121 @@ void tst_QTableView::setCurrentIndex() QCOMPARE(model.submit_count, 4); } +void tst_QTableView::checkIntersectedRect_data() +{ + QTest::addColumn("model"); + QTest::addColumn>("changedIndexes"); + QTest::addColumn("isEmpty"); + QTest::addColumn("swapFirstAndLastIndexRow"); // for QHeaderView::sectionsMoved() + QTest::addColumn("swapFirstAndLastIndexColumn"); // for QHeaderView::sectionsMoved() + QTest::addColumn("layoutDirection"); + QTest::addColumn("hiddenRow"); + QTest::addColumn("hiddenCol"); + const auto testName = [](const QByteArray &prefix, Qt::LayoutDirection dir, bool r, bool c) + { + const char *strDir = dir == Qt::LeftToRight ? ", LeftToRight" : ", RightToLeft"; + const char *strRow = r ? ", rowsSwapped" : ""; + const char *strCol = c ? ", colsSwapped" : ""; + return prefix + strDir + strRow + strCol; + }; + for (int i = 0; i < 2; ++i) { + const Qt::LayoutDirection dir(i == 0 ? Qt::LeftToRight : Qt::RightToLeft); + for (int j = 0; j < 4; ++j) { + const bool swapRow = ((j & 1) == 1); + const bool swapColumn = ((j & 2) == 2); + { + QtTestTableModel *model = new QtTestTableModel(10, 3); + QTest::newRow(testName("multiple columns", dir, swapRow, swapColumn).data()) + << model + << QVector({model->index(0, 0), + model->index(0, 1)}) + << false << swapRow << swapColumn << dir << -1 << -1; + } + { + QtTestTableModel *model = new QtTestTableModel(10, 3); + QTest::newRow(testName("multiple rows", dir, swapRow, swapColumn).data()) + << model + << QVector({model->index(0, 0), + model->index(1, 0), + model->index(2, 0)}) + << false << swapRow << swapColumn << dir << -1 << -1; + } + { + QtTestTableModel *model = new QtTestTableModel(10, 3); + QTest::newRow(testName("hidden row", dir, swapRow, swapColumn).data()) + << model + << QVector({model->index(3, 0), + model->index(3, 1)}) + << true << swapRow << swapColumn << dir << 3 << -1; + } + { + QtTestTableModel *model = new QtTestTableModel(50, 2); + QTest::newRow(testName("row outside viewport", dir, swapRow, swapColumn).data()) + << model + << QVector({model->index(49, 0), + model->index(49, 1)}) + << true << swapRow << swapColumn << dir << -1 << -1; + } + } + } +} + +void tst_QTableView::checkIntersectedRect() +{ + QFETCH(QtTestTableModel *, model); + QFETCH(const QVector, changedIndexes); + QFETCH(bool, isEmpty); + QFETCH(bool, swapFirstAndLastIndexRow); + QFETCH(bool, swapFirstAndLastIndexColumn); + QFETCH(Qt::LayoutDirection, layoutDirection); + QFETCH(int, hiddenRow); + QFETCH(int, hiddenCol); + + QtTestTableView view; + model->setParent(&view); + view.setLayoutDirection(layoutDirection); + view.setModel(model); + view.resize(400, 400); + view.show(); + if (hiddenRow >= 0) + view.hideRow(hiddenRow); + if (hiddenCol >= 0) + view.hideRow(hiddenCol); + if (swapFirstAndLastIndexRow) + view.verticalHeader()->swapSections(changedIndexes.first().row(), changedIndexes.last().row()); + if (swapFirstAndLastIndexColumn) + view.horizontalHeader()->swapSections(changedIndexes.first().column(), changedIndexes.last().column()); + + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + const auto toString = [](const QModelIndex &idx) + { + return QStringLiteral("idx: %1/%2").arg(idx.row()).arg(idx.column()); + }; + + view.m_intersectecRect = QRect(); + emit view.model()->dataChanged(changedIndexes.first(), changedIndexes.last()); + if (isEmpty) { + QVERIFY(view.m_intersectecRect.isEmpty()); + } else if (!changedIndexes.first().isValid()) { + QCOMPARE(view.m_intersectecRect, view.viewport()->rect()); + } else { + const auto parent = changedIndexes.first().parent(); + const int rCount = view.model()->rowCount(parent); + const int cCount = view.model()->columnCount(parent); + for (int r = 0; r < rCount; ++r) { + for (int c = 0; c < cCount; ++c) { + const QModelIndex &idx = view.model()->index(r, c, parent); + const auto rect = view.visualRect(idx); + if (changedIndexes.contains(idx)) + QVERIFY2(view.m_intersectecRect.contains(rect), qPrintable(toString(idx))); + else + QVERIFY2(!view.m_intersectecRect.contains(rect), qPrintable(toString(idx))); + } + } + } +} + class task173773_EventFilter : public QObject { int paintEventCount_ = 0; diff --git a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp index 7259fa1ab0a..95501136cc7 100644 --- a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp +++ b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp @@ -89,6 +89,13 @@ class TreeView : public QTreeView QTreeView::paintEvent(event); wasPainted = true; } + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override + { + QTreeView::dataChanged(topLeft, bottomRight, roles); + QTreeViewPrivate *av = static_cast(qt_widget_private(this)); + m_intersectecRect = av->intersectedRect(av->viewport->rect(), topLeft, bottomRight); + } + mutable QRect m_intersectecRect; bool wasPainted = false; public slots: void handleSelectionChanged() @@ -208,6 +215,8 @@ private slots: void statusTip_data(); void statusTip(); void fetchMoreOnScroll(); + void checkIntersectedRect_data(); + void checkIntersectedRect(); // task-specific tests: void task174627_moveLeftToRoot(); @@ -4832,6 +4841,111 @@ void tst_QTreeView::fetchMoreOnScroll() QCOMPARE(im.item(19)->rowCount(), 20); } +void tst_QTreeView::checkIntersectedRect_data() +{ + auto createModel = [](int rowCount) + { + QStandardItemModel *model = new QStandardItemModel; + for (int i = 0; i < rowCount; ++i) { + const QList sil({new QStandardItem(QLatin1String("Row %1 Item").arg(i)), + new QStandardItem(QLatin1String("2nd column"))}); + model->appendRow(sil); + } + for (int i = 2; i < 4; ++i) { + const QList sil({new QStandardItem(QLatin1String("Row %1 Item").arg(i)), + new QStandardItem(QLatin1String("2nd column"))}); + model->item(i)->appendRow(sil); + } + return model; + }; + QTest::addColumn("model"); + QTest::addColumn>("changedIndexes"); + QTest::addColumn("isEmpty"); + { + auto model = createModel(5); + QTest::newRow("multiple columns") << model + << QVector({model->index(0, 0), + model->index(0, 1)}) + << false; + } + { + auto model = createModel(5); + QTest::newRow("multiple rows") << model + << QVector({model->index(0, 0), + model->index(1, 0), + model->index(2, 0)}) + << false; + } + { + auto model = createModel(5); + const QModelIndex idxRow2(model->indexFromItem(model->item(2))); + QTest::newRow("child row") << model + << QVector({model->index(0, 0, idxRow2), + model->index(0, 1, idxRow2)}) + << false; + } + { + auto model = createModel(5); + QTest::newRow("hidden row") << model + << QVector({model->index(3, 0), + model->index(3, 1)}) + << true; + } + { + auto model = createModel(5); + const QModelIndex idxRow3(model->indexFromItem(model->item(3))); + QTest::newRow("hidden child row") << model + << QVector({model->index(0, 0, idxRow3), + model->index(0, 1, idxRow3)}) + << true; + } + { + auto model = createModel(50); + QTest::newRow("row outside viewport") << model + << QVector({model->index(49, 0), + model->index(49, 1)}) + << true; + } +} + +void tst_QTreeView::checkIntersectedRect() +{ + QFETCH(QStandardItemModel *, model); + QFETCH(const QVector, changedIndexes); + QFETCH(bool, isEmpty); + + TreeView view; + model->setParent(&view); + view.setModel(model); + view.resize(400, 400); + view.show(); + view.expandAll(); + view.setRowHidden(3, QModelIndex(), true); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + view.m_intersectecRect = QRect(); + emit view.model()->dataChanged(changedIndexes.first(), changedIndexes.last()); + if (isEmpty) { + QVERIFY(view.m_intersectecRect.isEmpty()); + } else if (!changedIndexes.first().isValid()) { + QCOMPARE(view.m_intersectecRect, view.viewport()->rect()); + } else { + const auto parent = changedIndexes.first().parent(); + const int rCount = view.model()->rowCount(parent); + const int cCount = view.model()->columnCount(parent); + for (int r = 0; r < rCount; ++r) { + for (int c = 0; c < cCount; ++c) { + const QModelIndex &idx = view.model()->index(r, c, parent); + const auto rect = view.visualRect(idx); + if (changedIndexes.contains(idx)) + QVERIFY(view.m_intersectecRect.contains(rect)); + else + QVERIFY(!view.m_intersectecRect.contains(rect)); + } + } + } +} + static void fillModeltaskQTBUG_8376(QAbstractItemModel &model) { model.insertRow(0);