diff --git a/src/widgets/itemviews/qabstractitemview.cpp b/src/widgets/itemviews/qabstractitemview.cpp index d782eb71ab..3fb77b72a6 100644 --- a/src/widgets/itemviews/qabstractitemview.cpp +++ b/src/widgets/itemviews/qabstractitemview.cpp @@ -1798,8 +1798,7 @@ void QAbstractItemView::mousePressEvent(QMouseEvent *event) // this is the mouse press event that closed the last editor (via focus event) d->pressClosedEditor = d->pressClosedEditorWatcher.isActive() && d->lastEditedIndex == index; - if (!d->selectionModel - || (d->state == EditingState && d->hasEditor(index))) + if (!d->selectionModel || (d->state == EditingState && d->hasEditor(index))) return; d->pressedAlreadySelected = d->selectionModel->isSelected(index); @@ -1808,10 +1807,9 @@ void QAbstractItemView::mousePressEvent(QMouseEvent *event) QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); d->noSelectionOnMousePress = command == QItemSelectionModel::NoUpdate || !index.isValid(); QPoint offset = d->offset(); - d->pressedPosition = pos + offset; - if ((command & QItemSelectionModel::Current) == 0) { + d->pressedPosition = d->draggedPosition = pos + offset; + if (!(command & QItemSelectionModel::Current)) d->currentSelectionStartIndex = index; - } else if (!d->currentSelectionStartIndex.isValid()) d->currentSelectionStartIndex = currentIndex(); @@ -1863,6 +1861,8 @@ void QAbstractItemView::mouseMoveEvent(QMouseEvent *event) QPoint topLeft; QPoint bottomRight = event->position().toPoint(); + d->draggedPosition = bottomRight + d->offset(); + if (state() == ExpandingState || state() == CollapsingState) return; @@ -1922,10 +1922,10 @@ void QAbstractItemView::mouseMoveEvent(QMouseEvent *event) setSelection(selectionRect, command); // set at the end because it might scroll the view - if (index.isValid() - && (index != d->selectionModel->currentIndex()) - && d->isIndexEnabled(index)) + if (index.isValid() && (index != d->selectionModel->currentIndex()) && d->isIndexEnabled(index)) d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + else if (d->shouldAutoScroll(event->pos()) && !d->autoScrollTimer.isActive()) + startAutoScroll(); } } @@ -2047,6 +2047,7 @@ void QAbstractItemView::dragEnterEvent(QDragEnterEvent *event) void QAbstractItemView::dragMoveEvent(QDragMoveEvent *event) { Q_D(QAbstractItemView); + d->draggedPosition = event->position().toPoint() + d->offset(); if (dragDropMode() == InternalMove && (event->source() != this || !(event->possibleActions() & Qt::MoveAction))) return; @@ -3989,8 +3990,8 @@ void QAbstractItemView::doAutoScroll() int verticalValue = verticalScroll->value(); int horizontalValue = horizontalScroll->value(); - QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); - QRect area = QWidgetPrivate::get(d->viewport)->clipRect(); + const QPoint pos = d->draggedPosition - d->offset(); + const QRect area = QWidgetPrivate::get(d->viewport)->clipRect(); // do the scrolling if we are in the scroll margins if (pos.y() - area.top() < margin) @@ -4011,6 +4012,14 @@ void QAbstractItemView::doAutoScroll() d->dropIndicatorRect = QRect(); d->dropIndicatorPosition = QAbstractItemView::OnViewport; #endif + if (state() == QAbstractItemView::DragSelectingState) { + const QPoint globalPos = d->viewport->mapToGlobal(pos); + const QPoint windowPos = window()->mapFromGlobal(globalPos); + QMouseEvent mm(QEvent::MouseMove, pos, windowPos, globalPos, + Qt::NoButton, Qt::LeftButton, d->pressedModifiers, + Qt::MouseEventSynthesizedByQt); + QApplication::sendEvent(viewport(), &mm); + } d->viewport->update(); } } diff --git a/src/widgets/itemviews/qabstractitemview_p.h b/src/widgets/itemviews/qabstractitemview_p.h index 74850de992..14fae86c87 100644 --- a/src/widgets/itemviews/qabstractitemview_p.h +++ b/src/widgets/itemviews/qabstractitemview_p.h @@ -375,6 +375,7 @@ public: QPersistentModelIndex currentSelectionStartIndex; Qt::KeyboardModifiers pressedModifiers; QPoint pressedPosition; + QPoint draggedPosition; bool pressedAlreadySelected; bool releaseFromDoubleClick; diff --git a/tests/auto/widgets/itemviews/qabstractitemview/qtlogo.png b/tests/auto/widgets/itemviews/qabstractitemview/qtlogo.png new file mode 100644 index 0000000000..4f68e162de Binary files /dev/null and b/tests/auto/widgets/itemviews/qabstractitemview/qtlogo.png differ diff --git a/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp b/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp index d904f97d82..547d120bca 100644 --- a/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp +++ b/tests/auto/widgets/itemviews/qabstractitemview/tst_qabstractitemview.cpp @@ -166,6 +166,8 @@ private slots: void scrollerSmoothScroll(); void inputMethodOpensEditor_data(); void inputMethodOpensEditor(); + void selectionAutoScrolling_data(); + void selectionAutoScrolling(); private: static QAbstractItemView *viewFromString(const QByteArray &viewType, QWidget *parent = nullptr) @@ -3177,5 +3179,120 @@ void tst_QAbstractItemView::inputMethodOpensEditor() } } +void tst_QAbstractItemView::selectionAutoScrolling_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("direction"); // negative or positive + + QTest::addRow("scroll up") << Qt::Vertical << -1; + QTest::addRow("scroll left") << Qt::Horizontal << -1; + QTest::addRow("scroll down") << Qt::Vertical << +1; + QTest::addRow("scroll right") << Qt::Horizontal << +1; +} + +void tst_QAbstractItemView::selectionAutoScrolling() +{ + QFETCH(Qt::Orientation, orientation); + QFETCH(int, direction); + + QListView listview; + listview.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + listview.setResizeMode(QListView::Fixed); + listview.setAutoScroll(true); + listview.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listview.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listview.setSpacing(10); + listview.setGeometry(0, 0, 500, 500); + listview.setFrameShape(QFrame::Shape::NoFrame); + listview.setEditTriggers(QListView::NoEditTriggers); + + QStandardItemModel *listModel = new QStandardItemModel(&listview); + listview.setModel(listModel); + listview.setViewMode(QListView::IconMode); + listview.setSelectionMode(QListView::ExtendedSelection); + + QPixmap pm(50, 50); + pm.fill(Qt::red); + for (int i = 0; i < 80; i++) { + QStandardItem *item = new QStandardItem(pm, QString::number(i)); + listModel->appendRow(item); + } + + listview.show(); + QVERIFY(QTest::qWaitForWindowExposed(&listview)); + + listview.resize(200, 200); + // scroll to the middle + listview.verticalScrollBar()->setValue(listview.verticalScrollBar()->maximum() / 2); + listview.horizontalScrollBar()->setValue(listview.horizontalScrollBar()->maximum() / 2); + + // remove all visible items so that we don't select any items at the edges, as that + // would scroll the view already + for (int x = 0; x < listview.viewport()->width(); x += 5) { + for (int y = 0; y < listview.viewport()->height(); y += 5) { + const QModelIndex index = listview.indexAt(QPoint(x, y)); + if (index.isValid()) + delete listModel->itemFromIndex(index); + } + } + // remove all items around the edges of the model + QRect topLeftRect = listview.visualRect(listModel->index(0, 0)); + const QPoint topLeftCenter(topLeftRect.center()); + QPoint bottomRightCenter; + for (int x = 0; x < listview.horizontalScrollBar()->maximum() + listview.viewport()->width(); x += 5) { + const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(x, 0)); + if (index.isValid()) { + delete listModel->itemFromIndex(index); + bottomRightCenter.rx() = x; + } + } + for (int y = 0; y < listview.verticalScrollBar()->maximum() + listview.viewport()->height(); y += 5) { + const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(0, y)); + if (index.isValid()) { + delete listModel->itemFromIndex(index); + bottomRightCenter.ry() = y; + } + } + for (int x = 0; x < bottomRightCenter.x(); x += 5) { + const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(x, bottomRightCenter.y())); + if (index.isValid()) + delete listModel->itemFromIndex(index); + } + for (int y = 0; y < bottomRightCenter.y(); y += 5) { + const QModelIndex index = listview.indexAt(topLeftCenter + QPoint(bottomRightCenter.x(), y)); + if (index.isValid()) + delete listModel->itemFromIndex(index); + } + + + // Simulate multiple select behavior; start in the middle, drag to the edge + const QPoint pressPoint(listview.viewport()->width() / 2, listview.viewport()->height() / 2); + QPoint dragPoint = pressPoint; + if (orientation == Qt::Vertical) { + dragPoint.rx() += 50; + dragPoint.ry() = direction > 0 ? listview.viewport()->height() : 0; + } else { + dragPoint.rx() = direction > 0 ? listview.viewport()->width() : 0; + dragPoint.ry() += 50; + } + + QTest::mousePress(listview.viewport(), Qt::LeftButton, Qt::NoModifier, pressPoint); + QMouseEvent mmEvent(QEvent::MouseMove, dragPoint, Qt::NoButton, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(listview.viewport(), &mmEvent); // QTest::mouseMove is useless + + // check that we scrolled to the end + QScrollBar *scrollBar = orientation == Qt::Vertical + ? listview.verticalScrollBar() + : listview.horizontalScrollBar(); + + if (direction < 0) + QTRY_COMPARE(scrollBar->value(), 0); + else + QTRY_COMPARE(scrollBar->value(), scrollBar->maximum()); + QVERIFY(listview.selectionModel()->selectedIndexes().count() > 0); + + QTest::mouseRelease(listview.viewport(), Qt::LeftButton, Qt::NoModifier, dragPoint); +} + QTEST_MAIN(tst_QAbstractItemView) #include "tst_qabstractitemview.moc"