Extend selections in QGraphicsView when selection extension key down

If the user has some objects selected and holds down the "extend selection"
key (Control on Windows, Command on Mac OS X), clicking and dragging a
rubber band selection deselects the current selection.  This is
counter-intuitive and confusing for users.

This commit fixes the behavior so users can extend selections using the
rubber band when the proper key is held down.

Task-number: QTBUG-6523
Change-Id: Ieda4aaa50adb351c0405f5cb8aae23332eec58a9
Reviewed-by: Andreas Aardal Hanssen <andreas@hanssen.name>
This commit is contained in:
Andy Maloney 2015-01-04 13:41:25 -05:00
parent f0f5beb0e5
commit 75f2a0b4ef
8 changed files with 132 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -1468,6 +1468,8 @@ void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mou
QGraphicsView *view = mouseEvent->widget() ? qobject_cast<QGraphicsView *>(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.

View File

@ -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<QGraphicsItem *> &items);
void destroyItemGroup(QGraphicsItemGroup *group);

View File

@ -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());

View File

@ -134,6 +134,7 @@ public:
void updateRubberBand(const QMouseEvent *event);
bool rubberBanding;
Qt::ItemSelectionMode rubberBandSelectionMode;
Qt::ItemSelectionOperation rubberBandSelectionOperation;
#endif
int handScrollMotions;

View File

@ -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());

View File

@ -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<QGraphicsItem *>() << 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<QGraphicsItem *>() << 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;