summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/widgets')
-rw-r--r--tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp123
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)