QTableView: optimize selection when spans are present

If a span is selected, we used to assume that rows and columns might be
moved, and made a selection with a range for each cell in the span. This
resulted in very large selection models.

We already had optimized the case that we didn't have any moved rows or
columns, skipping the mapping for the respective (or, usually, both)
directions and just making a single range. Apply that same optimization
for the case where a span exists and intersects with the selection area.

Avoid code duplication by only updating the top/left/bottom/right values
depending on the configuration of the table, and then create the
selection based on those.

Adapt the test case; we now get a single range, even when a span is
present, and the range includes all cells included in the span. Add a
debug streaming operator in the test case, as there is none implemented
in QTableWidgetSelectionRange, to ease debugging. That operator can
become a hidden friend of QTableWidgetSelectionRange in a follow-up
commit.

Pick-to: 6.7 6.5
Fixes: QTBUG-119076
Change-Id: If699463944ca2abaed8f93a2cd3ea30f33b79145
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
(cherry picked from commit 57d209c4fdea4766f24479a1f20c2975d34a1a0f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2024-06-17 21:32:28 +02:00 committed by Qt Cherry-pick Bot
parent 208ad426b4
commit 38f08eec14
2 changed files with 56 additions and 38 deletions

View File

@ -2009,28 +2009,32 @@ void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionF
if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br))
return;
bool verticalMoved = verticalHeader()->sectionsMoved();
bool horizontalMoved = horizontalHeader()->sectionsMoved();
const bool verticalMoved = verticalHeader()->sectionsMoved();
const bool horizontalMoved = horizontalHeader()->sectionsMoved();
QItemSelection selection;
int top = tl.row();
int bottom = br.row();
int left = tl.column();
int right = br.column();
if (d->hasSpans()) {
bool expanded;
// when the current selection does not intersect with any spans of merged cells,
// the range of selected cells must be the same as if there were no merged cells
bool intersectsSpan = false;
int top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
int left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
int bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
int right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
do {
expanded = false;
for (QSpanCollection::Span *it : d->spans.spans) {
const QSpanCollection::Span &span = *it;
int t = d->visualRow(span.top());
int l = d->visualColumn(span.left());
int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
const int t = d->visualRow(span.top());
const int l = d->visualColumn(span.left());
const int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
const int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
if ((t > bottom) || (l > right) || (top > b) || (left > r))
continue; // no intersect
intersectsSpan = true;
@ -2054,26 +2058,34 @@ void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionF
break;
}
} while (expanded);
if (intersectsSpan) {
selection.reserve((right - left + 1) * (bottom - top + 1));
for (int horizontal = left; horizontal <= right; ++horizontal) {
int column = d->logicalColumn(horizontal);
for (int vertical = top; vertical <= bottom; ++vertical) {
int row = d->logicalRow(vertical);
QModelIndex index = d->model->index(row, column, d->root);
selection.append(QItemSelectionRange(index));
}
}
} else {
QItemSelectionRange range(tl, br);
if (!range.isEmpty())
selection.append(range);
if (!intersectsSpan) {
top = tl.row();
bottom = br.row();
left = tl.column();
right = br.column();
} else if (!verticalMoved && !horizontalMoved) {
// top/left/bottom/right are visual, update indexes
tl = d->model->index(top, left, d->root);
br = d->model->index(bottom, right, d->root);
}
} else if (verticalMoved && horizontalMoved) {
int top = d->visualRow(tl.row());
int left = d->visualColumn(tl.column());
int bottom = d->visualRow(br.row());
int right = d->visualColumn(br.column());
top = d->visualRow(tl.row());
bottom = d->visualRow(br.row());
left = d->visualColumn(tl.column());
right = d->visualColumn(br.column());
} else if (horizontalMoved) {
top = tl.row();
bottom = br.row();
left = d->visualColumn(tl.column());
right = d->visualColumn(br.column());
} else if (verticalMoved) {
top = d->visualRow(tl.row());
bottom = d->visualRow(br.row());
left = tl.column();
right = br.column();
}
if (horizontalMoved && verticalMoved) {
selection.reserve((right - left + 1) * (bottom - top + 1));
for (int horizontal = left; horizontal <= right; ++horizontal) {
int column = d->logicalColumn(horizontal);
@ -2084,23 +2096,19 @@ void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionF
}
}
} else if (horizontalMoved) {
int left = d->visualColumn(tl.column());
int right = d->visualColumn(br.column());
selection.reserve(right - left + 1);
for (int visual = left; visual <= right; ++visual) {
int column = d->logicalColumn(visual);
QModelIndex topLeft = d->model->index(tl.row(), column, d->root);
QModelIndex bottomRight = d->model->index(br.row(), column, d->root);
QModelIndex topLeft = d->model->index(top, column, d->root);
QModelIndex bottomRight = d->model->index(bottom, column, d->root);
selection.append(QItemSelectionRange(topLeft, bottomRight));
}
} else if (verticalMoved) {
int top = d->visualRow(tl.row());
int bottom = d->visualRow(br.row());
selection.reserve(bottom - top + 1);
for (int visual = top; visual <= bottom; ++visual) {
int row = d->logicalRow(visual);
QModelIndex topLeft = d->model->index(row, tl.column(), d->root);
QModelIndex bottomRight = d->model->index(row, br.column(), d->root);
QModelIndex topLeft = d->model->index(row, left, d->root);
QModelIndex bottomRight = d->model->index(row, right, d->root);
selection.append(QItemSelectionRange(topLeft, bottomRight));
}
} else { // nothing moved

View File

@ -9,6 +9,16 @@
#include <QTableWidget>
#include <QTest>
QT_BEGIN_NAMESPACE
QDebug operator<<(QDebug dbg, const QTableWidgetSelectionRange &range)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "Range(" << range.topRow() << "," << range.leftColumn() << "->"
<< range.bottomRow() << "," << range.rightColumn() << ")";
return dbg;
}
QT_END_NAMESPACE
class QObjectTableItem : public QObject, public QTableWidgetItem
{
Q_OBJECT
@ -578,7 +588,7 @@ void tst_QTableWidget::selectedSpannedCells_data()
QTest::newRow("merge 2 cells in column, select those and one more")
<< QRect(1, 2, 1, 2) << QPoint(1, 1) << QPoint(1, 3)
<< 3 << QTableWidgetSelectionRange(1, 1, 1, 1);
<< 1 << QTableWidgetSelectionRange(1, 1, 3, 1);
QTest::newRow("merge 2 cells in column, select rows above")
<< QRect(1, 2, 1, 2) << QPoint(0, 0) << QPoint(3, 1)
@ -590,7 +600,7 @@ void tst_QTableWidget::selectedSpannedCells_data()
QTest::newRow("merge 3 cells in row, select those and one more")
<< QRect(0, 1, 3, 1) << QPoint(0, 1) << QPoint(3, 1)
<< 4 << QTableWidgetSelectionRange(1, 0, 1, 0);
<< 1 << QTableWidgetSelectionRange(1, 0, 1, 3);
QTest::newRow("merge 3 cells in row, select adjacent to right")
<< QRect(0, 1, 3, 1) << QPoint(3, 0) << QPoint(3, 2)