diff options
Diffstat (limited to 'tests/auto/widgets/kernel')
-rw-r--r-- | tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp index 245e107170..e341e0b756 100644 --- a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp +++ b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp @@ -50,6 +50,8 @@ #include <QtWidgets/QLineEdit> #include <QtWidgets/QLabel> #include <QtWidgets/QMainWindow> +#include <QtWidgets/QScrollArea> +#include <QtWidgets/QScrollBar> #include <QtWidgets/private/qapplication_p.h> #include <QtWidgets/QStyle> @@ -132,6 +134,8 @@ private slots: void setAttribute(); void touchEventPropagation(); + void wheelEventPropagation_data(); + void wheelEventPropagation(); void qtbug_12673(); void noQuitOnHide(); @@ -2118,6 +2122,125 @@ void tst_QApplication::touchEventPropagation() } } +/*! + Test that wheel events are propagated correctly. + + The event propagation of wheel events is complex: generally, they are propagated + up the parent tree like other input events, until a widget accepts the event. However, + wheel events are ignored by default (unlike mouse events, which are accepted by default, + and ignored in the default implementation of the event handler of QWidget). + + And Qt tries to make sure that wheel events that "belong together" are going to the same + widget. However, for low-precision events as generated by an old-fashioned + mouse wheel, each event is a distinct event, so Qt has no choice than to deliver the event + to the widget under the mouse. + High-precision events, as generated by track pads or other kinetic scrolling devices, come + in a continuous stream, with different phases. Qt tries to make sure that all events in the + same stream go to the widget that accepted the first event. + + Also, QAbstractScrollArea forwards wheel events from the viewport to the relevant scrollbar, + which adds more complexity to the handling. + + This tests two scenarios: + 1) a large widget inside a scrollarea that scrolls, inside a scrollarea that also scrolls + 2) a large widget inside a scrollarea that doesn't scroll, within a scrollarea that does + + For scenario 1 "inner", the expectation is that the inner scrollarea handles all wheel + events. + For scenario 2 "outer", the expectation is that the outer scrollarea handles all wheel + events. +*/ +using PhaseList = QList<Qt::ScrollPhase>; + +void tst_QApplication::wheelEventPropagation_data() +{ + qRegisterMetaType<PhaseList>(); + + QTest::addColumn<bool>("innerScrolls"); + QTest::addColumn<PhaseList>("phases"); + + QTest::addRow("inner, classic") + << true + << PhaseList{Qt::NoScrollPhase, Qt::NoScrollPhase, Qt::NoScrollPhase}; + QTest::addRow("outer, classic") + << false + << PhaseList{Qt::NoScrollPhase, Qt::NoScrollPhase, Qt::NoScrollPhase}; + QTest::addRow("inner, kinetic") + << true + << PhaseList{Qt::ScrollBegin, Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd}; + QTest::addRow("outer, kinetic") + << false + << PhaseList{Qt::ScrollBegin, Qt::ScrollUpdate, Qt::ScrollMomentum, Qt::ScrollEnd}; +} + +void tst_QApplication::wheelEventPropagation() +{ + QFETCH(bool, innerScrolls); + QFETCH(PhaseList, phases); + + const QSize baseSize(500, 500); + const QPointF center(baseSize.width() / 2, baseSize.height() / 2); + int scrollStep = 50; + + int argc = 1; + QApplication app(argc, &argv0); + + QScrollArea outerArea; + outerArea.setObjectName("outerArea"); + outerArea.viewport()->setObjectName("outerArea_viewport"); + QScrollArea innerArea; + innerArea.setObjectName("innerArea"); + innerArea.viewport()->setObjectName("innerArea_viewport"); + QWidget largeWidget; + largeWidget.setObjectName("largeWidget"); + QScrollBar trap(Qt::Vertical, &largeWidget); + trap.setObjectName("It's a trap!"); + + 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) + trap.hide(); + // kinetic wheel events should all go to the first widget; place a trap + else + trap.setGeometry(center.x() - 50, center.y() + scrollStep, 100, baseSize.height()); + + // if the inner area is large enough to host the widget, then it won't scroll + innerArea.setWidget(&largeWidget); + innerArea.setFixedSize(innerScrolls ? baseSize * 4 + : largeWidget.minimumSize() + QSize(100, 100)); + // the outer area always scrolls + outerArea.setFixedSize(baseSize); + outerArea.setWidget(&innerArea); + outerArea.show(); + + if (!QTest::qWaitForWindowExposed(&outerArea)) + QSKIP("Window failed to show, can't run test"); + + auto innerVBar = innerArea.verticalScrollBar(); + innerVBar->setObjectName("innerArea_vbar"); + QCOMPARE(innerVBar->isVisible(), innerScrolls); + auto outerVBar = outerArea.verticalScrollBar(); + outerVBar->setObjectName("outerArea_vbar"); + QVERIFY(outerVBar->isVisible()); + + const QPointF global(outerArea.mapToGlobal(center.toPoint())); + + QSignalSpy innerSpy(innerVBar, &QAbstractSlider::valueChanged); + QSignalSpy outerSpy(outerVBar, &QAbstractSlider::valueChanged); + + int count = 0; + for (const auto &phase : qAsConst(phases)) { + QWindowSystemInterface::handleWheelEvent(outerArea.windowHandle(), center, global, + QPoint(0, -scrollStep), QPoint(0, -120), Qt::NoModifier, + phase); + ++count; + QCoreApplication::processEvents(); + QCOMPARE(innerSpy.count(), innerScrolls ? count : 0); + QCOMPARE(outerSpy.count(), innerScrolls ? 0 : count); + } +} + void tst_QApplication::qtbug_12673() { #if QT_CONFIG(process) |