summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/widgets/graphicsview/qgraphicsproxywidget.cpp2
-rw-r--r--src/widgets/kernel/qapplication.cpp12
-rw-r--r--tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp147
3 files changed, 157 insertions, 4 deletions
diff --git a/src/widgets/graphicsview/qgraphicsproxywidget.cpp b/src/widgets/graphicsview/qgraphicsproxywidget.cpp
index fcd675aa76..aaddaa894f 100644
--- a/src/widgets/graphicsview/qgraphicsproxywidget.cpp
+++ b/src/widgets/graphicsview/qgraphicsproxywidget.cpp
@@ -1302,7 +1302,7 @@ void QGraphicsProxyWidget::wheelEvent(QGraphicsSceneWheelEvent *event)
// were not preserved in the QGraphicsSceneWheelEvent unfortunately
QWheelEvent wheelEvent(pos, event->screenPos(), QPoint(), angleDelta,
event->buttons(), event->modifiers(), Qt::NoScrollPhase,
- false, Qt::MouseEventNotSynthesized,
+ false, Qt::MouseEventSynthesizedByQt,
QPointingDevice::primaryPointingDevice());
QPointer<QWidget> focusWidget = d->widget->focusWidget();
extern bool qt_sendSpontaneousEvent(QObject *, QEvent *);
diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp
index 059617eb83..0daa0cfcb6 100644
--- a/src/widgets/kernel/qapplication.cpp
+++ b/src/widgets/kernel/qapplication.cpp
@@ -2949,9 +2949,15 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
// a widget has already grabbed the wheel for a sequence
if (QApplicationPrivate::wheel_widget) {
- Q_ASSERT(phase != Qt::NoScrollPhase);
- w = QApplicationPrivate::wheel_widget;
- relpos = w->mapFromGlobal(wheel->globalPosition().toPoint());
+ // Qt explicitly synthesizes a spontaneous event for the receiver, done
+ // by QGraphicsProxyWidget - so trust it
+ if (wheel->source() == Qt::MouseEventSynthesizedByQt) {
+ QApplicationPrivate::wheel_widget = w;
+ } else {
+ Q_ASSERT(phase != Qt::NoScrollPhase);
+ w = QApplicationPrivate::wheel_widget;
+ relpos = w->mapFromGlobal(wheel->globalPosition().toPoint());
+ }
}
/*
Start or finish a scrolling sequence by grabbing/releasing the wheel via
diff --git a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp
index e666ac24c9..bbcef8849e 100644
--- a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp
+++ b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp
@@ -198,6 +198,7 @@ private slots:
void sendEvent();
#if QT_CONFIG(wheelevent)
void wheelEvent();
+ void wheelEventPropagation();
#endif
#ifndef QT_NO_CURSOR
void cursor();
@@ -2242,6 +2243,152 @@ void tst_QGraphicsView::wheelEvent()
QCOMPARE(spy.count(), 2);
QVERIFY(widget->hasFocus());
}
+
+/*!
+ QGraphicsProxyWidget receives wheel events from QGraphicsScene, and then
+ generates a new event that is sent spontaneously in order to enable event
+ propagation. This requires extra handling of the wheel grabbing we do for
+ high-precision wheel event streams.
+
+ Test that this doesn't trigger infinite recursion, while still resulting in
+ event propagation within the embedded widget hierarchy, and back to the
+ QGraphicsView if the event is not accepted.
+
+ See tst_QApplication::wheelEventPropagation for a similar test.
+*/
+void tst_QGraphicsView::wheelEventPropagation()
+{
+ QGraphicsScene scene(0, 0, 600, 600);
+
+ QWidget *label = new QLabel("Direct");
+ label->setFixedSize(300, 30);
+ QGraphicsProxyWidget *labelProxy = scene.addWidget(label);
+ labelProxy->setPos(0, 50);
+ labelProxy->show();
+
+ class NestedWidget : public QWidget
+ {
+ public:
+ NestedWidget(const QString &text)
+ {
+ setObjectName("Nested Label");
+ QLabel *nested = new QLabel(text);
+ QHBoxLayout *hbox = new QHBoxLayout;
+ hbox->addWidget(nested);
+ setLayout(hbox);
+ }
+
+ int wheelEventCount = 0;
+ protected:
+ void wheelEvent(QWheelEvent *) override
+ {
+ ++wheelEventCount;
+ }
+ };
+ NestedWidget *nestedWidget = new NestedWidget("Nested");
+ nestedWidget->setFixedSize(300, 60);
+ QGraphicsProxyWidget *nestedProxy = scene.addWidget(nestedWidget);
+ nestedProxy->setPos(0, 120);
+ nestedProxy->show();
+
+ QGraphicsView view(&scene);
+ view.setFixedHeight(200);
+ view.show();
+
+ QVERIFY(QTest::qWaitForWindowActive(&view));
+ QVERIFY(view.verticalScrollBar()->isVisible());
+
+ view.verticalScrollBar()->setValue(0);
+ QSignalSpy scrollSpy(view.verticalScrollBar(), &QScrollBar::valueChanged);
+
+ const QPoint wheelPosition(50, 25);
+ auto wheelUp = [&view, wheelPosition](Qt::ScrollPhase phase) {
+ const QPoint global = view.mapToGlobal(wheelPosition);
+ const QPoint pixelDelta(0, -25);
+ const QPoint angleDelta(0, -120);
+ QWindowSystemInterface::handleWheelEvent(view.windowHandle(), wheelPosition, global,
+ pixelDelta, angleDelta, Qt::NoModifier,
+ phase);
+ QCoreApplication::processEvents();
+ };
+
+ int scrollCount = 0;
+ // test non-kinetic events; they are not grabbed, and should scroll the view unless
+ // accepted by the embedded widget
+ QCOMPARE(view.itemAt(wheelPosition), nullptr);
+ wheelUp(Qt::NoScrollPhase);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+
+ // wheeling on the label, which ignores the event, should scroll the view
+ QCOMPARE(view.itemAt(wheelPosition), labelProxy);
+ wheelUp(Qt::NoScrollPhase);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+ QCOMPARE(view.itemAt(wheelPosition), labelProxy);
+ wheelUp(Qt::NoScrollPhase);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+
+ // left the widget
+ QCOMPARE(view.itemAt(wheelPosition), nullptr);
+ wheelUp(Qt::NoScrollPhase);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+
+ // reached the nested widget, which accepts the wheel event, so no more scrolling
+ QCOMPARE(view.itemAt(wheelPosition), nestedProxy);
+ // remember this position for later
+ const int scrollBarValueOnNestedProxy = view.verticalScrollBar()->value();
+ wheelUp(Qt::NoScrollPhase);
+ QCOMPARE(scrollSpy.count(), scrollCount);
+ QCOMPARE(nestedWidget->wheelEventCount, 1);
+
+ // reset, try with kinetic events
+ view.verticalScrollBar()->setValue(0);
+ ++scrollCount;
+
+ // starting a scroll outside any widget and scrolling through the widgets should work,
+ // no matter if the widget accepts wheel events - the view has the grab
+ QCOMPARE(view.itemAt(wheelPosition), nullptr);
+ wheelUp(Qt::ScrollBegin);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+ for (int i = 0; i < 5; ++i) {
+ wheelUp(Qt::ScrollUpdate);
+ if (i >= 1)
+ QEXPECT_FAIL("", "Fixed for Qt 6.2 - QTBUG-65552", Continue);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+ }
+ wheelUp(Qt::ScrollEnd);
+ QEXPECT_FAIL("", "Fixed for Qt 6.2 - QTBUG-65552", Continue);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+
+ // reset
+ view.verticalScrollBar()->setValue(0);
+ scrollCount = scrollSpy.count();
+
+ // starting a scroll on a widget that doesn't accept wheel events
+ // should also scroll the view, which still gets the grab
+ wheelUp(Qt::NoScrollPhase);
+ scrollCount = scrollSpy.count();
+
+ QCOMPARE(view.itemAt(wheelPosition), labelProxy);
+ wheelUp(Qt::ScrollBegin);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+ for (int i = 0; i < 5; ++i) {
+ wheelUp(Qt::ScrollUpdate);
+ QEXPECT_FAIL("", "Fixed for Qt 6.2 - QTBUG-65552", Continue);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+ }
+ wheelUp(Qt::ScrollEnd);
+ QEXPECT_FAIL("", "Fixed for Qt 6.2 - QTBUG-65552", Continue);
+ QCOMPARE(scrollSpy.count(), ++scrollCount);
+
+ // starting a scroll on a widget that does accept wheel events
+ // should not scroll the view
+ view.verticalScrollBar()->setValue(scrollBarValueOnNestedProxy);
+ scrollCount = scrollSpy.count();
+
+ QCOMPARE(view.itemAt(wheelPosition), nestedProxy);
+ wheelUp(Qt::ScrollBegin);
+ QCOMPARE(scrollSpy.count(), scrollCount);
+}
#endif // QT_CONFIG(wheelevent)
#ifndef QT_NO_CURSOR