From a9e464119a8008d9b1464bf81fad2537954ba739 Mon Sep 17 00:00:00 2001 From: David Faure Date: Mon, 12 Aug 2024 18:54:10 +0200 Subject: [PATCH] QTableView: implement moving rows by drag-n-drop This is only available when calling setDragDropOverwriteMode(false) so this makes no difference in most applications (see implementation of QAbstractItemView::position). QTableWidget::dropEvent() was assuming we were dropping onto items without checking that this was the case, this is fixed here, and QTableView::dropEvent() is added to handle moving rows, with very similar code to QListView::dropEvent(). Task-number: QTBUG-13873 Task-number: QTBUG-101475 Change-Id: I96e75da2c0abf6f568edba461735905dee5cb84f Reviewed-by: Richard Moe Gustavsen (cherry picked from commit 8e63cf282cb248ddd7fbc908766091659b3a0b22) Reviewed-by: Qt Cherry-pick Bot --- src/widgets/itemviews/qtableview.cpp | 62 ++++++++++++++++++++++++++ src/widgets/itemviews/qtableview.h | 4 ++ src/widgets/itemviews/qtablewidget.cpp | 6 +-- 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp index 6ac85650ba..977cb4e301 100644 --- a/src/widgets/itemviews/qtableview.cpp +++ b/src/widgets/itemviews/qtableview.cpp @@ -3115,6 +3115,68 @@ void QTableView::timerEvent(QTimerEvent *event) QAbstractItemView::timerEvent(event); } +#if QT_CONFIG(draganddrop) +/*! \reimp */ +void QTableView::dropEvent(QDropEvent *event) +{ + Q_D(QTableView); + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + // check whether a subclass has already accepted the event, ie. moved the data + if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex) && !topIndex.isValid() && col != -1) { + // Drop between items (reordering) - can only happen with setDragDropOverwriteMode(false) + const QModelIndexList indexes = selectedIndexes(); + QList persIndexes; + persIndexes.reserve(indexes.size()); + + bool topIndexDropped = false; + for (const auto &index : indexes) { + // Reorder entire rows + QPersistentModelIndex firstColIndex = index.siblingAtColumn(0); + if (!persIndexes.contains(firstColIndex)) + persIndexes.append(firstColIndex); + if (index.row() == topIndex.row()) { + topIndexDropped = true; + break; + } + } + if (!topIndexDropped) { + std::sort(persIndexes.begin(), persIndexes.end()); // The dropped items will remain in the same visual order. + + QPersistentModelIndex dropRow = model()->index(row, col, topIndex); + + int r = row == -1 ? model()->rowCount() : (dropRow.row() >= 0 ? dropRow.row() : row); + bool dataMoved = false; + for (int i = 0; i < persIndexes.size(); ++i) { + const QPersistentModelIndex &pIndex = persIndexes.at(i); + // only generate a move when not same row or behind itself + if (r != pIndex.row() && r != pIndex.row() + 1) { + // try to move (preserves selection) + dataMoved |= model()->moveRow(QModelIndex(), pIndex.row(), QModelIndex(), r); + if (!dataMoved) // can't move - abort and let QAbstractItemView handle this + break; + } else { + // move onto itself is blocked, don't delete anything + dataMoved = true; + } + r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order. + } + if (dataMoved) + event->accept(); + } + } + } + + if (!event->isAccepted()) { + // moveRows not implemented, fall back to default + QAbstractItemView::dropEvent(event); + } +} +#endif + /*! This slot is called to change the index of the given \a row in the table view. The old index is specified by \a oldIndex, and the new diff --git a/src/widgets/itemviews/qtableview.h b/src/widgets/itemviews/qtableview.h index eff0ea3502..dc5af4f428 100644 --- a/src/widgets/itemviews/qtableview.h +++ b/src/widgets/itemviews/qtableview.h @@ -114,6 +114,10 @@ protected: void timerEvent(QTimerEvent *event) override; +#if QT_CONFIG(draganddrop) + void dropEvent(QDropEvent *event) override; +#endif + int horizontalOffset() const override; int verticalOffset() const override; QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; diff --git a/src/widgets/itemviews/qtablewidget.cpp b/src/widgets/itemviews/qtablewidget.cpp index 2d78564281..3d69fae041 100644 --- a/src/widgets/itemviews/qtablewidget.cpp +++ b/src/widgets/itemviews/qtablewidget.cpp @@ -2722,7 +2722,8 @@ void QTableWidget::dropEvent(QDropEvent *event) { int col = -1; int row = -1; // check whether a subclass has already accepted the event, ie. moved the data - if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex)) { + if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex) && row == -1 && col == -1) { + // Drop onto item const QModelIndexList indexes = selectedIndexes(); int top = INT_MAX; int left = INT_MAX; @@ -2730,7 +2731,6 @@ void QTableWidget::dropEvent(QDropEvent *event) { top = qMin(index.row(), top); left = qMin(index.column(), left); } - QList taken; const int indexesCount = indexes.size(); taken.reserve(indexesCount); @@ -2745,7 +2745,7 @@ void QTableWidget::dropEvent(QDropEvent *event) { event->accept(); } - // either we or a subclass accepted the move event, so assume that the data was + // either we or a subclass accepted the drop event, so assume that the data was // moved and that QAbstractItemView shouldn't remove the source when QDrag::exec returns if (event->isAccepted()) d->dropEventMoved = true;