QQuickTableView: support multi-selection

Implement support for doing multiple selections in
TableView by holding down the shift modifier while
dragging.

[ChangeLog][Quick][TableView] Added multi-selection support
if using a SelectionRectangle and holding down the shift-modifier.

Change-Id: Ife622aeea2ed60a5741df01f3aac2fb647108aa9
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
Richard Moe Gustavsen 2022-10-31 16:10:03 +01:00
parent e43638c8a8
commit 52cbcd947d
5 changed files with 56 additions and 14 deletions

View File

@ -25,7 +25,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickSelectable
public: public:
virtual QQuickItem *selectionPointerHandlerTarget() const = 0; virtual QQuickItem *selectionPointerHandlerTarget() const = 0;
virtual bool canStartSelection(const QPointF &pos) = 0; virtual bool startSelection(const QPointF &pos) = 0;
virtual void setSelectionStartPos(const QPointF &pos) = 0; virtual void setSelectionStartPos(const QPointF &pos) = 0;
virtual void setSelectionEndPos(const QPointF &pos) = 0; virtual void setSelectionEndPos(const QPointF &pos) = 0;
virtual void clearSelection() = 0; virtual void clearSelection() = 0;

View File

@ -1517,11 +1517,18 @@ QQuickItem *QQuickTableViewPrivate::selectionPointerHandlerTarget() const
return const_cast<QQuickTableView *>(q_func())->contentItem(); return const_cast<QQuickTableView *>(q_func())->contentItem();
} }
bool QQuickTableViewPrivate::canStartSelection(const QPointF &pos) bool QQuickTableViewPrivate::startSelection(const QPointF &pos)
{ {
Q_Q(QQuickTableView);
Q_UNUSED(pos); Q_UNUSED(pos);
// Only allow a selection if it doesn't conflict with resizing // Only allow a selection if it doesn't conflict with resizing
return resizeHandler->state() == QQuickTableViewResizeHandler::Listening; const bool canStartSelection = resizeHandler->state() == QQuickTableViewResizeHandler::Listening;
if (canStartSelection) {
selectionStartCell = QPoint(-1, -1);
selectionEndCell = QPoint(-1, -1);
q->closeEditor();
}
return canStartSelection;
} }
void QQuickTableViewPrivate::setSelectionStartPos(const QPointF &pos) void QQuickTableViewPrivate::setSelectionStartPos(const QPointF &pos)
@ -4633,12 +4640,12 @@ void QQuickTableViewPrivate::init()
positionYAnimation.stop(); positionYAnimation.stop();
if (!q->isInteractive()) if (!q->isInteractive())
handleTap(tapHandler->point().pressPosition()); handleTap(tapHandler->point());
}); });
QObject::connect(tapHandler, &QQuickTapHandler::singleTapped, [this, q, tapHandler] { QObject::connect(tapHandler, &QQuickTapHandler::singleTapped, [this, q, tapHandler] {
if (q->isInteractive()) if (q->isInteractive())
handleTap(tapHandler->point().pressPosition()); handleTap(tapHandler->point());
}); });
QObject::connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, q, tapHandler] { QObject::connect(tapHandler, &QQuickTapHandler::doubleTapped, [this, q, tapHandler] {
@ -4660,13 +4667,15 @@ void QQuickTableViewPrivate::init()
}); });
} }
void QQuickTableViewPrivate::handleTap(const QPointF &pos) void QQuickTableViewPrivate::handleTap(const QQuickHandlerPoint &point)
{ {
Q_Q(QQuickTableView); Q_Q(QQuickTableView);
if (keyNavigationEnabled) if (keyNavigationEnabled)
q->forceActiveFocus(Qt::MouseFocusReason); q->forceActiveFocus(Qt::MouseFocusReason);
if (point.modifiers() != Qt::NoModifier)
return;
if (resizableRows && hoverHandler->m_row != -1) if (resizableRows && hoverHandler->m_row != -1)
return; return;
if (resizableColumns && hoverHandler->m_column != -1) if (resizableColumns && hoverHandler->m_column != -1)
@ -4679,14 +4688,14 @@ void QQuickTableViewPrivate::handleTap(const QPointF &pos)
prevIndex = selectionModel->currentIndex(); prevIndex = selectionModel->currentIndex();
if (pointerNavigationEnabled) { if (pointerNavigationEnabled) {
clearSelection(); clearSelection();
setCurrentIndexFromTap(pos); setCurrentIndexFromTap(point.position());
} }
} }
if (editTriggers != QQuickTableView::NoEditTriggers) if (editTriggers != QQuickTableView::NoEditTriggers)
q->closeEditor(); q->closeEditor();
const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(pos)); const QModelIndex tappedIndex = q->modelIndex(q->cellAtPosition(point.position()));
if (canEdit(tappedIndex, false)) { if (canEdit(tappedIndex, false)) {
if (editTriggers & QQuickTableView::SingleTapped) if (editTriggers & QQuickTableView::SingleTapped)
q->edit(tappedIndex); q->edit(tappedIndex);

View File

@ -548,7 +548,7 @@ public:
int serializedModelIndex, int serializedModelIndex,
QObject *object, bool init); QObject *object, bool init);
void handleTap(const QPointF &pos); void handleTap(const QQuickHandlerPoint &point);
void setCurrentIndexFromTap(const QPointF &pos); void setCurrentIndexFromTap(const QPointF &pos);
void setCurrentIndex(const QPoint &cell); void setCurrentIndex(const QPoint &cell);
bool setCurrentIndexFromKeyEvent(QKeyEvent *e); bool setCurrentIndexFromKeyEvent(QKeyEvent *e);
@ -557,7 +557,7 @@ public:
// QQuickSelectable // QQuickSelectable
QQuickItem *selectionPointerHandlerTarget() const override; QQuickItem *selectionPointerHandlerTarget() const override;
bool canStartSelection(const QPointF &pos) override; bool startSelection(const QPointF &pos) override;
void setSelectionStartPos(const QPointF &pos) override; void setSelectionStartPos(const QPointF &pos) override;
void setSelectionEndPos(const QPointF &pos) override; void setSelectionEndPos(const QPointF &pos) override;
void clearSelection() override; void clearSelection() override;

View File

@ -182,7 +182,9 @@ QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate()
QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, [this]() { QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, [this]() {
const QPointF pos = m_tapHandler->point().pressPosition(); const QPointF pos = m_tapHandler->point().pressPosition();
if (!m_selectable->canStartSelection(pos)) const auto modifiers = m_tapHandler->point().modifiers();
if (!m_selectable->startSelection(pos))
return; return;
if (handleUnderPos(pos) != nullptr) { if (handleUnderPos(pos) != nullptr) {
// Don't allow press'n'hold to start a new // Don't allow press'n'hold to start a new
@ -199,6 +201,7 @@ QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate()
} }
} }
if (!modifiers.testFlag(Qt::ShiftModifier))
m_selectable->clearSelection(); m_selectable->clearSelection();
m_selectable->setSelectionStartPos(pos); m_selectable->setSelectionStartPos(pos);
m_selectable->setSelectionEndPos(pos); m_selectable->setSelectionEndPos(pos);
@ -209,10 +212,12 @@ QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate()
QObject::connect(m_dragHandler, &QQuickDragHandler::activeChanged, [this]() { QObject::connect(m_dragHandler, &QQuickDragHandler::activeChanged, [this]() {
const QPointF startPos = m_dragHandler->centroid().pressPosition(); const QPointF startPos = m_dragHandler->centroid().pressPosition();
const QPointF dragPos = m_dragHandler->centroid().position(); const QPointF dragPos = m_dragHandler->centroid().position();
const auto modifiers = m_dragHandler->centroid().modifiers();
if (m_dragHandler->active()) { if (m_dragHandler->active()) {
if (!m_selectable->canStartSelection(startPos)) if (!m_selectable->startSelection(startPos))
return; return;
if (!modifiers.testFlag(Qt::ShiftModifier))
m_selectable->clearSelection(); m_selectable->clearSelection();
m_selectable->setSelectionStartPos(startPos); m_selectable->setSelectionStartPos(startPos);
m_selectable->setSelectionEndPos(dragPos); m_selectable->setSelectionEndPos(dragPos);

View File

@ -198,8 +198,36 @@ TestCase {
mousePress(tableView, 1, 1, Qt.LeftButton) mousePress(tableView, 1, 1, Qt.LeftButton)
mousePress(tableView, 1, 1, Qt.LeftButton, Qt.NoModifier, 1000) mousePress(tableView, 1, 1, Qt.LeftButton, Qt.NoModifier, 1000)
verify(!tableView.selectionModel.hasSelection) verify(!tableView.selectionModel.hasSelection)
} }
// TODO: enable this test when mouseDrag sends modifiers for all mouse events
// (including mouseMove)
// function test_multi_selection() {
// let tableView = createTemporaryObject(tableviewComp, testCase)
// verify(tableView)
// let selectionRectangle = tableView.selectionRectangle
// verify(selectionRectangle)
// verify(!tableView.selectionModel.hasSelection)
// selectionRectangle.selectionMode = SelectionRectangle.Drag
// mouseDrag(tableView, 1, 1, (cellWidth * 2) - 2, 1, Qt.LeftButton)
// verify(tableView.selectionModel.hasSelection)
// compare(tableView.selectionModel.selectedIndexes.length, 2)
// verify(tableView.selectionModel.isSelected(tableView.model.index(0, 0)))
// verify(tableView.selectionModel.isSelected(tableView.model.index(0, 1)))
// // Hold down shift, and drag again to do a multi-selection
// mouseDrag(tableView, 1, cellHeight + 5, (cellWidth * 2) - 2, 1, Qt.LeftButton, Qt.ShiftModifier)
// verify(tableView.selectionModel.hasSelection)
// compare(tableView.selectionModel.selectedIndexes.length, 4)
// verify(tableView.selectionModel.isSelected(tableView.model.index(0, 0)))
// verify(tableView.selectionModel.isSelected(tableView.model.index(0, 1)))
// verify(tableView.selectionModel.isSelected(tableView.model.index(1, 0)))
// verify(tableView.selectionModel.isSelected(tableView.model.index(1, 1)))
// }
function test_pressAndHold_data() { function test_pressAndHold_data() {
return [ return [
{ tag: "resize enabled", resizeEnabled: true }, { tag: "resize enabled", resizeEnabled: true },