QGraphicsScene: respect that items can override selection changes

QGraphicsItems may override itemChange to prevent certain attribute
changes. Overriding ItemSelectedChange this way is explicitly documented
to be allowed.

However QGraphicsScene::clearSelection did not test whether items were
in fact deselected after the call to setSelection, and always cleared
the stored set of selected items.

Fix this by checking the actual selected state of the item as we iterate
over them, and store those items that are still selected in a set that
becomes the new selectedItems set (which will be empty if no item
overrides, which is the default).

Add a test that also checks that clearing the selection emits the
selectionChanged signal correctly (and does not if all selected items
block being deselected).

Fixes: QTBUG-85474
Pick-to: 6.4 6.3 6.2
Change-Id: I665afc132876e02e6e1061b7be37f4f6e4be418f
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Volker Hilsheimer 2022-06-18 23:41:57 +02:00
parent bb674adb25
commit 2e12479e06
2 changed files with 56 additions and 3 deletions

View File

@ -2251,11 +2251,16 @@ void QGraphicsScene::clearSelection()
++d->selectionChanging;
// iterate over a copy, as clearing selection might invalidate selectedItems
const auto selectedItems = d->selectedItems;
bool changed = !selectedItems.isEmpty();
QSet<QGraphicsItem *> stillSelectedSet;
for (QGraphicsItem *item : selectedItems)
for (QGraphicsItem *item : selectedItems) {
item->setSelected(false);
d->selectedItems.clear();
// items might override itemChange to prevent deselection
if (item->isSelected())
stillSelectedSet << item;
}
const bool changed = stillSelectedSet != selectedItems;
d->selectedItems = stillSelectedSet;
// Re-enable emitting selectionChanged() for individual items.
--d->selectionChanging;

View File

@ -253,6 +253,7 @@ private slots:
void focusItemChangedSignal();
void minimumRenderSize();
void focusOnTouch();
void clearSelection();
// task specific tests below me
void task139710_bspTreeCrash();
@ -4834,6 +4835,53 @@ void tst_QGraphicsScene::focusOnTouch()
QVERIFY(rect->hasFocus());
}
void tst_QGraphicsScene::clearSelection()
{
class AlwaysSelectedItem : public QGraphicsRectItem
{
public:
using QGraphicsRectItem::QGraphicsRectItem;
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant& value)
{
if (change == ItemSelectedChange)
return true;
return QGraphicsRectItem::itemChange(change, value);
}
};
QGraphicsScene scene;
QSignalSpy spy(&scene, &QGraphicsScene::selectionChanged);
QGraphicsRectItem *regularRect = new QGraphicsRectItem;
regularRect->setFlag(QGraphicsItem::ItemIsSelectable);
regularRect->setRect(0, 0, 50, 50);
regularRect->setSelected(true);
AlwaysSelectedItem *selectedRect = new AlwaysSelectedItem;
selectedRect->setFlag(QGraphicsItem::ItemIsSelectable);
selectedRect->setRect(50, 50, 50, 50);
selectedRect->setSelected(true);
scene.addItem(regularRect);
scene.addItem(selectedRect);
QCOMPARE(spy.count(), 2);
QCOMPARE(scene.selectedItems().count(), 2);
scene.clearSelection();
QVERIFY(!regularRect->isSelected());
QVERIFY(selectedRect->isSelected());
QCOMPARE(scene.selectedItems().count(), 1);
QCOMPARE(spy.count(), 3);
delete regularRect;
QCOMPARE(spy.count(), 3);
scene.clearSelection();
QCOMPARE(spy.count(), 3);
delete selectedRect;
QCOMPARE(spy.count(), 4);
}
void tst_QGraphicsScene::taskQTBUG_15977_renderWithDeviceCoordinateCache()
{
QGraphicsScene scene;