diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index e239a11cda..fccb0d5421 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -73,6 +73,7 @@ Qt { Q_ENUMS(TextInteractionFlag) Q_FLAGS(TextInteractionFlags) Q_ENUMS(ItemSelectionMode) + Q_ENUMS(ItemSelectionOperation) Q_FLAGS(ItemFlags) Q_ENUMS(CheckState) Q_ENUMS(SortOrder CaseSensitivity) @@ -1298,6 +1299,11 @@ public: IntersectsItemBoundingRect = 0x3 }; + enum ItemSelectionOperation { + ReplaceSelection, + AddToSelection + }; + enum TransformationMode { FastTransformation, SmoothTransformation diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index 04055a3308..4b582642f3 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -2233,6 +2233,21 @@ QGraphicsItem::collidesWithPath() */ +/*! + \enum Qt::ItemSelectionOperation + + This enum is used in QGraphicsScene to specify what to do with currently selected + items when setting a selection area. + + \value ReplaceSelection The currently selected items are replaced by items + in the selection area. + + \value AddToSelection The items in the selection area are added to the currently + selected items. + + \sa QGraphicsScene::setSelectionArea() +*/ + /*! \enum Qt::FillRule diff --git a/src/widgets/graphicsview/qgraphicsscene.cpp b/src/widgets/graphicsview/qgraphicsscene.cpp index 8765b8114d..aa42445b2a 100644 --- a/src/widgets/graphicsview/qgraphicsscene.cpp +++ b/src/widgets/graphicsview/qgraphicsscene.cpp @@ -1468,6 +1468,8 @@ void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mou QGraphicsView *view = mouseEvent->widget() ? qobject_cast(mouseEvent->widget()->parentWidget()) : 0; bool dontClearSelection = view && view->dragMode() == QGraphicsView::ScrollHandDrag; + bool extendSelection = (mouseEvent->modifiers() & Qt::ControlModifier) != 0; + dontClearSelection |= extendSelection; if (!dontClearSelection) { // Clear the selection if the originating view isn't in scroll // hand drag mode. The view will clear the selection if no drag @@ -2262,6 +2264,28 @@ void QGraphicsScene::setSelectionArea(const QPainterPath &path, const QTransform */ void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode, const QTransform &deviceTransform) +{ + setSelectionArea(path, Qt::ReplaceSelection, mode, deviceTransform); +} + +/*! + \overload + \since 5.5 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \a selectionOperation determines what to do with the currently selected items. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, + Qt::ItemSelectionOperation selectionOperation, + Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) { Q_D(QGraphicsScene); @@ -2287,10 +2311,16 @@ void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectio } } - // Unselect all items outside path. - foreach (QGraphicsItem *item, unselectItems) { - item->setSelected(false); - changed = true; + switch (selectionOperation) { + case Qt::ReplaceSelection: + // Deselect all items outside path. + foreach (QGraphicsItem *item, unselectItems) { + item->setSelected(false); + changed = true; + } + break; + default: + break; } // Reenable emitting selectionChanged() for individual items. diff --git a/src/widgets/graphicsview/qgraphicsscene.h b/src/widgets/graphicsview/qgraphicsscene.h index 6cd5da00c8..03df18fbce 100644 --- a/src/widgets/graphicsview/qgraphicsscene.h +++ b/src/widgets/graphicsview/qgraphicsscene.h @@ -177,6 +177,8 @@ public: QPainterPath selectionArea() const; void setSelectionArea(const QPainterPath &path, const QTransform &deviceTransform); void setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape, const QTransform &deviceTransform = QTransform()); + void setSelectionArea(const QPainterPath &path, Qt::ItemSelectionOperation selectionOperation, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape, const QTransform &deviceTransform = QTransform()); + // ### Qt6 merge the last 2 functions and add a default: Qt::ItemSelectionOperation selectionOperation = Qt::ReplaceSelection QGraphicsItemGroup *createItemGroup(const QList &items); void destroyItemGroup(QGraphicsItemGroup *group); diff --git a/src/widgets/graphicsview/qgraphicsview.cpp b/src/widgets/graphicsview/qgraphicsview.cpp index 5484ecb96e..b92655013e 100644 --- a/src/widgets/graphicsview/qgraphicsview.cpp +++ b/src/widgets/graphicsview/qgraphicsview.cpp @@ -350,6 +350,7 @@ QGraphicsViewPrivate::QGraphicsViewPrivate() #ifndef QT_NO_RUBBERBAND rubberBanding(false), rubberBandSelectionMode(Qt::IntersectsItemShape), + rubberBandSelectionOperation(Qt::ReplaceSelection), #endif handScrollMotions(0), cacheMode(0), #ifndef QT_NO_CURSOR @@ -735,6 +736,7 @@ void QGraphicsViewPrivate::updateRubberBand(const QMouseEvent *event) // if we didn't get the release events). if (!event->buttons()) { rubberBanding = false; + rubberBandSelectionOperation = Qt::ReplaceSelection; if (!rubberBandRect.isNull()) { rubberBandRect = QRect(); emit q->rubberBandChanged(rubberBandRect, QPointF(), QPointF()); @@ -768,7 +770,7 @@ void QGraphicsViewPrivate::updateRubberBand(const QMouseEvent *event) selectionArea.addPolygon(q->mapToScene(rubberBandRect)); selectionArea.closeSubpath(); if (scene) - scene->setSelectionArea(selectionArea, rubberBandSelectionMode, q->viewportTransform()); + scene->setSelectionArea(selectionArea, rubberBandSelectionOperation, rubberBandSelectionMode, q->viewportTransform()); } #endif @@ -3289,8 +3291,14 @@ void QGraphicsView::mousePressEvent(QMouseEvent *event) d->rubberBanding = true; d->rubberBandRect = QRect(); if (d->scene) { - // Initiating a rubber band always clears the selection. - d->scene->clearSelection(); + bool extendSelection = (event->modifiers() & Qt::ControlModifier) != 0; + + if (extendSelection) { + d->rubberBandSelectionOperation = Qt::AddToSelection; + } else { + d->rubberBandSelectionOperation = Qt::ReplaceSelection; + d->scene->clearSelection(); + } } } } else @@ -3347,6 +3355,7 @@ void QGraphicsView::mouseReleaseEvent(QMouseEvent *event) d->updateAll(); } d->rubberBanding = false; + d->rubberBandSelectionOperation = Qt::ReplaceSelection; if (!d->rubberBandRect.isNull()) { d->rubberBandRect = QRect(); emit rubberBandChanged(d->rubberBandRect, QPointF(), QPointF()); diff --git a/src/widgets/graphicsview/qgraphicsview_p.h b/src/widgets/graphicsview/qgraphicsview_p.h index ca87b932e2..546f34ba81 100644 --- a/src/widgets/graphicsview/qgraphicsview_p.h +++ b/src/widgets/graphicsview/qgraphicsview_p.h @@ -134,6 +134,7 @@ public: void updateRubberBand(const QMouseEvent *event); bool rubberBanding; Qt::ItemSelectionMode rubberBandSelectionMode; + Qt::ItemSelectionOperation rubberBandSelectionOperation; #endif int handScrollMotions; diff --git a/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp b/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp index 51efc47094..44d06c6244 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsitem/tst_qgraphicsitem.cpp @@ -1799,6 +1799,12 @@ void tst_QGraphicsItem::selected_multi() // Ctrl-click on scene QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(0, 0)); QTest::qWait(20); + QVERIFY(item1->isSelected()); + QVERIFY(!item2->isSelected()); + + // Click on scene + QTest::mouseClick(view.viewport(), Qt::LeftButton, 0, view.mapFromScene(0, 0)); + QTest::qWait(20); QVERIFY(!item1->isSelected()); QVERIFY(!item2->isSelected()); diff --git a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp index 04852721db..c051f38ffb 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp @@ -164,6 +164,7 @@ private slots: void dragMode_scrollHand(); void dragMode_rubberBand(); void rubberBandSelectionMode(); + void rubberBandExtendSelection(); void rotated_rubberBand(); void backgroundBrush(); void foregroundBrush(); @@ -941,6 +942,61 @@ void tst_QGraphicsView::rubberBandSelectionMode() QCOMPARE(scene.selectedItems(), QList() << rect); } +void tst_QGraphicsView::rubberBandExtendSelection() +{ + QWidget toplevel; + setFrameless(&toplevel); + + QGraphicsScene scene(0, 0, 1000, 1000); + + QGraphicsView view(&scene, &toplevel); + view.setDragMode(QGraphicsView::RubberBandDrag); + toplevel.show(); + + // Disable mouse tracking to prevent the window system from sending mouse + // move events to the viewport while we are synthesizing events. If + // QGraphicsView gets a mouse move event with no buttons down, it'll + // terminate the rubber band. + view.viewport()->setMouseTracking(false); + + QGraphicsItem *item1 = scene.addRect(10, 10, 100, 100); + QGraphicsItem *item2 = scene.addRect(10, 120, 100, 100); + QGraphicsItem *item3 = scene.addRect(10, 230, 100, 100); + + item1->setFlag(QGraphicsItem::ItemIsSelectable); + item2->setFlag(QGraphicsItem::ItemIsSelectable); + item3->setFlag(QGraphicsItem::ItemIsSelectable); + + // select first item + item1->setSelected(true); + QCOMPARE(scene.selectedItems(), QList() << item1); + + // first rubberband without modifier key + sendMousePress(view.viewport(), view.mapFromScene(20, 115), Qt::LeftButton); + sendMouseMove(view.viewport(), view.mapFromScene(20, 300), Qt::LeftButton, Qt::LeftButton); + QVERIFY(!item1->isSelected()); + QVERIFY(item2->isSelected()); + QVERIFY(item3->isSelected()); + sendMouseRelease(view.viewport(), QPoint(), Qt::LeftButton); + + scene.clearSelection(); + + // select first item + item1->setSelected(true); + QVERIFY(item1->isSelected()); + + // now rubberband with modifier key + { + QPoint clickPoint = view.mapFromScene(20, 115); + QMouseEvent event(QEvent::MouseButtonPress, clickPoint, view.viewport()->mapToGlobal(clickPoint), Qt::LeftButton, 0, Qt::ControlModifier); + QApplication::sendEvent(view.viewport(), &event); + } + sendMouseMove(view.viewport(), view.mapFromScene(20, 300), Qt::LeftButton, Qt::LeftButton); + QVERIFY(item1->isSelected()); + QVERIFY(item2->isSelected()); + QVERIFY(item3->isSelected()); +} + void tst_QGraphicsView::rotated_rubberBand() { QWidget toplevel;