summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/widgets/kernel/qapplication.cpp114
-rw-r--r--tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp73
2 files changed, 110 insertions, 77 deletions
diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp
index 78eccc91d6..f5d857b623 100644
--- a/src/widgets/kernel/qapplication.cpp
+++ b/src/widgets/kernel/qapplication.cpp
@@ -2992,8 +2992,18 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
}
QWheelEvent* wheel = static_cast<QWheelEvent*>(e);
- const bool spontaneous = wheel->spontaneous();
+ if (!wheel->spontaneous()) {
+ /*
+ Synthesized events shouldn't propagate, e.g. QScrollArea passes events from the
+ viewport on to the scrollbars, which might ignore the event if there is no more
+ space to scroll. If we would propagate, the event would come back to the viewport.
+ */
+ res = d->notify_helper(w, wheel);
+ break;
+ }
+
const Qt::ScrollPhase phase = wheel->phase();
+ QPoint relpos = wheel->position().toPoint();
// Ideally, we should lock on a widget when it starts receiving wheel
// events. This avoids other widgets to start receiving those events
@@ -3014,68 +3024,54 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
//
// This means that we can have scrolling sequences (starting with ScrollBegin)
// or partial sequences (after a ScrollEnd and starting with ScrollUpdate).
- // If wheel_widget is null because it was deleted, we also take the same
- // code path as an initial sequence.
- if (phase == Qt::NoScrollPhase || phase == Qt::ScrollBegin || !QApplicationPrivate::wheel_widget) {
- // A system-generated ScrollBegin event starts a new user scrolling
- // sequence, so we reset wheel_widget in case no one accepts the event
- // or if we didn't get (or missed) a ScrollEnd previously.
- if (spontaneous && phase == Qt::ScrollBegin)
- QApplicationPrivate::wheel_widget = nullptr;
-
- const QPoint relpos = wheel->position().toPoint();
+ // 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());
+ }
+ /*
+ Start or finish a scrolling sequence by grabbing/releasing the wheel via
+ wheel_widget. The sequence might be partial (ie. not start with ScrollBegin),
+ e.g. if the previous wheel_widget was destroyed mid-sequence.
+ */
+ switch (phase) {
+ case Qt::ScrollEnd:
+ QApplicationPrivate::wheel_widget = nullptr;
+ break;
+ case Qt::ScrollBegin:
+ QApplicationPrivate::wheel_widget = w;
+ Q_FALLTHROUGH();
+ case Qt::ScrollUpdate:
+ case Qt::ScrollMomentum:
+ if (!QApplicationPrivate::wheel_widget)
+ QApplicationPrivate::wheel_widget = w;
+ Q_FALLTHROUGH();
+ case Qt::NoScrollPhase:
+ QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos);
+ break;
+ // no default: - we want warnings if we don't handle all phases explicitly
+ }
- if (spontaneous && (phase == Qt::NoScrollPhase || phase == Qt::ScrollUpdate))
- QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos);
+ QWheelEvent we(relpos, wheel->globalPosition(), wheel->pixelDelta(), wheel->angleDelta(), wheel->buttons(),
+ wheel->modifiers(), phase, wheel->inverted(), wheel->source());
- QWheelEvent we(relpos, wheel->globalPosition(), wheel->pixelDelta(), wheel->angleDelta(), wheel->buttons(),
- wheel->modifiers(), phase, wheel->inverted(), wheel->source());
- we.setTimestamp(wheel->timestamp());
- bool eventAccepted;
- do {
- we.spont = spontaneous && w == receiver;
- we.ignore();
- res = d->notify_helper(w, &we);
- eventAccepted = we.isAccepted();
- if (res && eventAccepted) {
- // A new scrolling sequence or partial sequence starts and w has accepted
- // the event. Therefore, we can set wheel_widget, but only if it's not
- // the end of a sequence.
- if (QApplicationPrivate::wheel_widget == nullptr && (phase == Qt::ScrollBegin || phase == Qt::ScrollUpdate))
- QApplicationPrivate::wheel_widget = w;
- break;
- }
- if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
- break;
+ we.setTimestamp(wheel->timestamp());
+ bool eventAccepted;
+ do {
+ we.spont = wheel->spontaneous() && w == receiver;
+ res = d->notify_helper(w, &we);
+ eventAccepted = we.isAccepted();
+ if (res && eventAccepted)
+ break;
+ if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
+ break;
- we.p += w->pos();
- w = w->parentWidget();
- } while (w);
- wheel->setAccepted(eventAccepted);
- } else if (!spontaneous) {
- // wheel_widget may forward the wheel event to a delegate widget,
- // either directly or indirectly (e.g. QAbstractScrollArea will
- // forward to its QScrollBars through viewportEvent()). In that
- // case, the event will not be spontaneous but synthesized, so
- // we can send it straight to the receiver.
- d->notify_helper(w, wheel);
- } else {
- // The phase is either ScrollUpdate, ScrollMomentum, or ScrollEnd, and wheel_widget
- // is set. Since it accepted the wheel event previously, we continue
- // sending those events until we get a ScrollEnd, which signifies
- // the end of the natural scrolling sequence.
- const QPoint &relpos = QApplicationPrivate::wheel_widget->mapFromGlobal(wheel->globalPosition().toPoint());
- QWheelEvent we(relpos, wheel->globalPosition(), wheel->pixelDelta(), wheel->angleDelta(), wheel->buttons(),
- wheel->modifiers(), wheel->phase(), wheel->inverted(), wheel->source());
- we.setTimestamp(wheel->timestamp());
- we.spont = true;
- we.ignore();
- d->notify_helper(QApplicationPrivate::wheel_widget, &we);
- wheel->setAccepted(we.isAccepted());
- if (phase == Qt::ScrollEnd)
- QApplicationPrivate::wheel_widget = nullptr;
- }
+ we.p += w->pos();
+ w = w->parentWidget();
+ } while (w);
+ wheel->setAccepted(eventAccepted);
}
break;
#endif
diff --git a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp
index e341e0b756..c9857b4379 100644
--- a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp
+++ b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp
@@ -2150,33 +2150,53 @@ void tst_QApplication::touchEventPropagation()
For scenario 2 "outer", the expectation is that the outer scrollarea handles all wheel
events.
*/
-using PhaseList = QList<Qt::ScrollPhase>;
+struct WheelEvent
+{
+ WheelEvent(Qt::ScrollPhase p = Qt::NoScrollPhase, Qt::Orientation o = Qt::Vertical)
+ : phase(p), orientation(o)
+ {}
+ Qt::ScrollPhase phase = Qt::NoScrollPhase;
+ Qt::Orientation orientation = Qt::Vertical;
+};
+using WheelEventList = QList<WheelEvent>;
void tst_QApplication::wheelEventPropagation_data()
{
- qRegisterMetaType<PhaseList>();
+ qRegisterMetaType<WheelEventList>();
QTest::addColumn<bool>("innerScrolls");
- QTest::addColumn<PhaseList>("phases");
+ QTest::addColumn<WheelEventList>("events");
QTest::addRow("inner, classic")
<< true
- << PhaseList{Qt::NoScrollPhase, Qt::NoScrollPhase, Qt::NoScrollPhase};
+ << WheelEventList{{}, {}, {}};
QTest::addRow("outer, classic")
<< false
- << PhaseList{Qt::NoScrollPhase, Qt::NoScrollPhase, Qt::NoScrollPhase};
+ << WheelEventList{{}, {}, {}};
QTest::addRow("inner, kinetic")
<< true
- << PhaseList{Qt::ScrollBegin, Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd};
+ << WheelEventList{Qt::ScrollBegin, Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd};
QTest::addRow("outer, kinetic")
<< false
- << PhaseList{Qt::ScrollBegin, Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd};
+ << WheelEventList{Qt::ScrollBegin, Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd};
+ QTest::addRow("inner, partial kinetic")
+ << true
+ << WheelEventList{Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd};
+ QTest::addRow("outer, partial kinetic")
+ << false
+ << WheelEventList{Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd};
+ QTest::addRow("inner, changing direction")
+ << true
+ << WheelEventList{Qt::ScrollUpdate, {Qt::ScrollUpdate, Qt::Horizontal}, Qt::ScrollMomentum, Qt::ScrollEnd};
+ QTest::addRow("outer, changing direction")
+ << false
+ << WheelEventList{Qt::ScrollUpdate, {Qt::ScrollUpdate, Qt::Horizontal}, Qt::ScrollMomentum, Qt::ScrollEnd};
}
void tst_QApplication::wheelEventPropagation()
{
QFETCH(bool, innerScrolls);
- QFETCH(PhaseList, phases);
+ QFETCH(WheelEventList, events);
const QSize baseSize(500, 500);
const QPointF center(baseSize.width() / 2, baseSize.height() / 2);
@@ -2199,7 +2219,7 @@ void tst_QApplication::wheelEventPropagation()
largeWidget.setFixedSize(baseSize * 8);
// classic wheel events will be grabbed by the widget under the mouse, so don't place a trap
- if (phases.at(0) == Qt::NoScrollPhase)
+ if (events.at(0).phase == Qt::NoScrollPhase)
trap.hide();
// kinetic wheel events should all go to the first widget; place a trap
else
@@ -2220,24 +2240,41 @@ void tst_QApplication::wheelEventPropagation()
auto innerVBar = innerArea.verticalScrollBar();
innerVBar->setObjectName("innerArea_vbar");
QCOMPARE(innerVBar->isVisible(), innerScrolls);
+ auto innerHBar = innerArea.horizontalScrollBar();
+ innerHBar->setObjectName("innerArea_hbar");
+ QCOMPARE(innerHBar->isVisible(), innerScrolls);
auto outerVBar = outerArea.verticalScrollBar();
outerVBar->setObjectName("outerArea_vbar");
QVERIFY(outerVBar->isVisible());
+ auto outerHBar = outerArea.horizontalScrollBar();
+ outerHBar->setObjectName("outerArea_hbar");
+ QVERIFY(outerHBar->isVisible());
const QPointF global(outerArea.mapToGlobal(center.toPoint()));
- QSignalSpy innerSpy(innerVBar, &QAbstractSlider::valueChanged);
- QSignalSpy outerSpy(outerVBar, &QAbstractSlider::valueChanged);
+ QSignalSpy innerVSpy(innerVBar, &QAbstractSlider::valueChanged);
+ QSignalSpy innerHSpy(innerHBar, &QAbstractSlider::valueChanged);
+ QSignalSpy outerVSpy(outerVBar, &QAbstractSlider::valueChanged);
+ QSignalSpy outerHSpy(outerHBar, &QAbstractSlider::valueChanged);
- int count = 0;
- for (const auto &phase : qAsConst(phases)) {
+ int vcount = 0;
+ int hcount = 0;
+
+ for (const auto &event : qAsConst(events)) {
+ const QPoint pixelDelta = event.orientation == Qt::Vertical ? QPoint(0, -scrollStep) : QPoint(-scrollStep, 0);
+ const QPoint angleDelta = event.orientation == Qt::Vertical ? QPoint(0, -120) : QPoint(-120, 0);
QWindowSystemInterface::handleWheelEvent(outerArea.windowHandle(), center, global,
- QPoint(0, -scrollStep), QPoint(0, -120), Qt::NoModifier,
- phase);
- ++count;
+ pixelDelta, angleDelta, Qt::NoModifier,
+ event.phase);
+ if (event.orientation == Qt::Vertical)
+ ++vcount;
+ else
+ ++hcount;
QCoreApplication::processEvents();
- QCOMPARE(innerSpy.count(), innerScrolls ? count : 0);
- QCOMPARE(outerSpy.count(), innerScrolls ? 0 : count);
+ QCOMPARE(innerVSpy.count(), innerScrolls ? vcount : 0);
+ QCOMPARE(innerHSpy.count(), innerScrolls ? hcount : 0);
+ QCOMPARE(outerVSpy.count(), innerScrolls ? 0 : vcount);
+ QCOMPARE(outerHSpy.count(), innerScrolls ? 0 : hcount);
}
}