Skip to content

Commit

Permalink
QAbstractItemView::dataChanged(): optimize call to QWidget::update()
Browse files Browse the repository at this point in the history
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 <samuel.gaist@idiap.ch>
  • Loading branch information
chehrlic committed Mar 28, 2020
1 parent b8be5b4 commit 8de62d3
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 4 deletions.
28 changes: 26 additions & 2 deletions src/widgets/itemviews/qabstractitemview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/widgets/itemviews/qabstractitemview_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
46 changes: 46 additions & 0 deletions src/widgets/itemviews/qtableview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, int>;
const auto calcMinMax = [q, rect](QHeaderView *hdr, int startIdx, int endIdx, minMaxPair bounds) -> minMaxPair
{
minMaxPair ret(std::numeric_limits<int>::max(), std::numeric_limits<int>::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).
Expand Down
1 change: 1 addition & 0 deletions src/widgets/itemviews/qtableview_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions src/widgets/itemviews/qtreeview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/widgets/itemviews/qtreeview_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ add_qt_test(tst_qabstractitemview
Qt::GuiPrivate
Qt::TestPrivate
Qt::Widgets
Qt::WidgetsPrivate
)
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <QTest>
#include <QVBoxLayout>
#include <QtTest/private/qtesthelpers_p.h>
#include <private/qabstractitemview_p.h>

Q_DECLARE_METATYPE(Qt::ItemFlags);

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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<QStandardItem *> sil({new QStandardItem(QLatin1String("Row %1 Item").arg(i)),
new QStandardItem(QLatin1String("2nd column"))});
model->appendRow(sil);
}
return model;
};
QTest::addColumn<QStandardItemModel *>("model");
QTest::addColumn<QVector<QModelIndex>>("changedIndexes");
QTest::addColumn<bool>("isEmpty");
{
auto model = createModel(5);
QTest::newRow("multiple columns") << model
<< QVector<QModelIndex>({model->index(0, 0),
model->index(0, 1)})
<< false;
}
{
auto model = createModel(5);
QTest::newRow("multiple rows") << model
<< QVector<QModelIndex>({model->index(0, 0),
model->index(1, 0),
model->index(2, 0)})
<< false;
}
{
auto model = createModel(5);
QTest::newRow("hidden rows") << model
<< QVector<QModelIndex>({model->index(3, 0),
model->index(4, 0)})
<< true;
}
{
auto model = createModel(500);
QTest::newRow("rows outside viewport") << model
<< QVector<QModelIndex>({model->index(498, 0),
model->index(499, 0)})
<< true;
}
}

void tst_QAbstractItemView::checkIntersectedRect()
{
QFETCH(QStandardItemModel *, model);
QFETCH(const QVector<QModelIndex>, changedIndexes);
QFETCH(bool, isEmpty);

class TableView : public QTableView
{
public:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override
{
QTableView::dataChanged(topLeft, bottomRight, roles);
// we want to check the base class implementation here!
QAbstractItemViewPrivate *av = static_cast<QAbstractItemViewPrivate*>(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))
Expand Down
Loading

0 comments on commit 8de62d3

Please sign in to comment.