1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtTest/QSignalSpy>
31#include <QtGui/QStyleHints>
32#include <qpa/qwindowsysteminterface.h>
33#include <private/qquickwheelhandler_p.h>
34#include <QtQuick/private/qquickrectangle_p.h>
35#include <QtQuick/qquickview.h>
36#include <QtQml/qqmlcontext.h>
37#include "../../../shared/util.h"
38#include "../../shared/viewtestutil.h"
39
40Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
41
42class tst_QQuickWheelHandler: public QQmlDataTest
43{
44 Q_OBJECT
45public:
46 tst_QQuickWheelHandler() { }
47
48private slots:
49 void singleHandler_data();
50 void singleHandler();
51 void nestedHandler_data();
52 void nestedHandler();
53
54private:
55 void sendWheelEvent(QQuickView &window, QPoint pos, QPoint angleDelta,
56 QPoint pixelDelta = QPoint(), Qt::KeyboardModifiers modifiers = Qt::NoModifier,
57 Qt::ScrollPhase phase = Qt::NoScrollPhase, bool inverted = false);
58};
59
60void tst_QQuickWheelHandler::sendWheelEvent(QQuickView &window, QPoint pos, QPoint angleDelta,
61 QPoint pixelDelta, Qt::KeyboardModifiers modifiers, Qt::ScrollPhase phase, bool inverted)
62{
63 QWheelEvent wheelEvent(pos, window.mapToGlobal(pos), pixelDelta, angleDelta,
64 Qt::NoButton, modifiers, phase, inverted);
65 QGuiApplication::sendEvent(receiver: &window, event: &wheelEvent);
66 qApp->processEvents();
67}
68
69void tst_QQuickWheelHandler::singleHandler_data()
70{
71 // handler properties
72 QTest::addColumn<Qt::Orientation>(name: "orientation");
73 QTest::addColumn<bool>(name: "invertible");
74 QTest::addColumn<int>(name: "rotationScale");
75 QTest::addColumn<QString>(name: "property");
76 QTest::addColumn<qreal>(name: "targetScaleMultiplier");
77 QTest::addColumn<bool>(name: "targetTransformAroundCursor");
78 // event
79 QTest::addColumn<QPoint>(name: "eventPos");
80 QTest::addColumn<QPoint>(name: "eventAngleDelta");
81 QTest::addColumn<QPoint>(name: "eventPixelDelta");
82 QTest::addColumn<Qt::KeyboardModifiers>(name: "eventModifiers");
83 QTest::addColumn<bool>(name: "eventPhases");
84 QTest::addColumn<bool>(name: "eventInverted");
85 // result
86 QTest::addColumn<QPoint>(name: "expectedPosition");
87 QTest::addColumn<qreal>(name: "expectedScale");
88 QTest::addColumn<int>(name: "expectedRotation");
89
90 // move the item
91 QTest::newRow(dataTag: "vertical wheel angle delta to adjust x")
92 << Qt::Vertical << false << 1 << "x" << 1.5 << true
93 << QPoint(160, 120) << QPoint(-360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
94 << QPoint(15, 0) << 1.0 << 0;
95 QTest::newRow(dataTag: "horizontal wheel angle delta to adjust y")
96 << Qt::Horizontal << false << 1 << "y" << 1.5 << false
97 << QPoint(160, 120) << QPoint(-360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
98 << QPoint(0, -45) << 1.0 << 0;
99 QTest::newRow(dataTag: "vertical wheel angle delta to adjust y, amplified and inverted")
100 << Qt::Vertical << true << 4 << "y" << 1.5 << true
101 << QPoint(160, 120) << QPoint(60, 60) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << true
102 << QPoint(0, 30) << 1.0 << 0;
103 QTest::newRow(dataTag: "horizontal wheel angle delta to adjust x, amplified and reversed")
104 << Qt::Horizontal << false << -4 << "x" << 1.5 << false
105 << QPoint(160, 120) << QPoint(60, 60) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
106 << QPoint(-30, 0) << 1.0 << 0;
107 QTest::newRow(dataTag: "vertical wheel pixel delta to adjust x")
108 << Qt::Vertical << false << 1 << "x" << 1.5 << true
109 << QPoint(160, 120) << QPoint(-360, 120) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false
110 << QPoint(20, 0) << 1.0 << 0;
111 QTest::newRow(dataTag: "horizontal wheel pixel delta to adjust y")
112 << Qt::Horizontal << false << 1 << "y" << 1.5 << false
113 << QPoint(160, 120) << QPoint(-360, 120) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false
114 << QPoint(0, 20) << 1.0 << 0;
115 QTest::newRow(dataTag: "vertical wheel pixel delta to adjust y, amplified and inverted")
116 << Qt::Vertical << true << 4 << "y" << 1.5 << true
117 << QPoint(160, 120) << QPoint(60, 60) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << true
118 << QPoint(0, 80) << 1.0 << 0;
119 QTest::newRow(dataTag: "horizontal wheel pixel delta to adjust x, amplified and reversed")
120 << Qt::Horizontal << false << -4 << "x" << 1.5 << false
121 << QPoint(160, 120) << QPoint(60, 60) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false
122 << QPoint(-80, 0) << 1.0 << 0;
123
124 // scale the item
125 QTest::newRow(dataTag: "vertical wheel angle delta to adjust scale")
126 << Qt::Vertical << false << 1 << "scale" << 1.5 << true
127 << QPoint(50, 32) << QPoint(360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
128 << QPoint(55, 44) << 1.5 << 0;
129 QTest::newRow(dataTag: "horizontal wheel angle delta to adjust scale, amplified and reversed, don't adjust position")
130 << Qt::Horizontal << false << -2 << "scale" << 1.5 << false
131 << QPoint(50, 32) << QPoint(-240, 360) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
132 << QPoint(0, 0) << 5.0625 << 0;
133
134 // rotate the item
135 QTest::newRow(dataTag: "vertical wheel angle delta to adjust rotation")
136 << Qt::Vertical << false << 1 << "rotation" << 1.5 << true
137 << QPoint(50, 32) << QPoint(360, -120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
138 << QPoint(19, -31) << 1.0 << -15;
139 QTest::newRow(dataTag: "horizontal wheel angle delta to adjust rotation, amplified and reversed, don't adjust position")
140 << Qt::Horizontal << false << -2 << "rotation" << 1.5 << false
141 << QPoint(80, 80) << QPoint(240, 360) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false
142 << QPoint(0, 0) << 1.0 << -60;
143}
144
145void tst_QQuickWheelHandler::singleHandler()
146{
147 // handler properties
148 QFETCH(Qt::Orientation, orientation);
149 QFETCH(bool, invertible);
150 QFETCH(int, rotationScale);
151 QFETCH(QString, property);
152 QFETCH(qreal, targetScaleMultiplier);
153 QFETCH(bool, targetTransformAroundCursor);
154 // event
155 QFETCH(QPoint, eventPos);
156 QFETCH(QPoint, eventAngleDelta);
157 QFETCH(QPoint, eventPixelDelta);
158 QFETCH(Qt::KeyboardModifiers, eventModifiers);
159 QFETCH(bool, eventPhases);
160 QFETCH(bool, eventInverted);
161 // result
162 QFETCH(QPoint, expectedPosition);
163 QFETCH(qreal, expectedScale);
164 QFETCH(int, expectedRotation);
165
166 QQuickView window;
167 QByteArray errorMessage;
168 QVERIFY2(QQuickTest::initView(window, testFileUrl("rectWheel.qml"), true, &errorMessage), errorMessage.constData());
169 window.show();
170 QVERIFY(QTest::qWaitForWindowExposed(&window));
171
172 QQuickItem *rect = window.rootObject();
173 QVERIFY(rect != nullptr);
174 QQuickWheelHandler *handler = rect->findChild<QQuickWheelHandler*>();
175 QVERIFY(handler != nullptr);
176 handler->setOrientation(orientation);
177 handler->setInvertible(invertible);
178 handler->setRotationScale(rotationScale);
179 handler->setProperty(property);
180 handler->setTargetScaleMultiplier(targetScaleMultiplier);
181 handler->setTargetTransformAroundCursor(targetTransformAroundCursor);
182 QSignalSpy activeChangedSpy(handler, SIGNAL(activeChanged()));
183
184 if (eventPhases) {
185 sendWheelEvent(window, pos: eventPos, angleDelta: QPoint(), pixelDelta: QPoint(), modifiers: eventModifiers, phase: Qt::ScrollBegin, inverted: eventInverted);
186 sendWheelEvent(window, pos: eventPos, angleDelta: eventAngleDelta, pixelDelta: eventPixelDelta, modifiers: eventModifiers, phase: Qt::ScrollUpdate, inverted: eventInverted);
187 } else {
188 sendWheelEvent(window, pos: eventPos, angleDelta: eventAngleDelta, pixelDelta: eventPixelDelta, modifiers: eventModifiers, phase: Qt::NoScrollPhase, inverted: eventInverted);
189 }
190 QCOMPARE(rect->position().toPoint(), expectedPosition);
191 QCOMPARE(activeChangedSpy.count(), 1);
192 QCOMPARE(handler->active(), true);
193 QCOMPARE(rect->scale(), expectedScale);
194 QCOMPARE(rect->rotation(), expectedRotation);
195 if (!eventPhases) {
196 QTRY_COMPARE(handler->active(), false);
197 QCOMPARE(activeChangedSpy.count(), 2);
198 }
199
200 // restore by rotating backwards
201 if (eventPhases) {
202 sendWheelEvent(window, pos: eventPos, angleDelta: eventAngleDelta * -1, pixelDelta: eventPixelDelta * -1, modifiers: eventModifiers, phase: Qt::ScrollUpdate, inverted: eventInverted);
203 sendWheelEvent(window, pos: eventPos, angleDelta: QPoint(), pixelDelta: QPoint(), modifiers: eventModifiers, phase: Qt::ScrollEnd, inverted: eventInverted);
204 } else {
205 sendWheelEvent(window, pos: eventPos, angleDelta: eventAngleDelta * -1, pixelDelta: eventPixelDelta * -1, modifiers: eventModifiers, phase: Qt::NoScrollPhase, inverted: eventInverted);
206 }
207 QCOMPARE(activeChangedSpy.count(), eventPhases ? 2 : 3);
208 QCOMPARE(handler->active(), !eventPhases);
209 QCOMPARE(rect->position().toPoint(), QPoint(0, 0));
210 QCOMPARE(rect->scale(), 1);
211 QCOMPARE(rect->rotation(), 0);
212}
213
214void tst_QQuickWheelHandler::nestedHandler_data()
215{
216 // handler properties
217 QTest::addColumn<Qt::Orientation>(name: "orientation");
218 QTest::addColumn<bool>(name: "invertible");
219 QTest::addColumn<int>(name: "rotationScale");
220 QTest::addColumn<QString>(name: "property");
221 QTest::addColumn<qreal>(name: "targetScaleMultiplier");
222 QTest::addColumn<bool>(name: "targetTransformAroundCursor");
223 // event
224 QTest::addColumn<QPoint>(name: "eventPos");
225 QTest::addColumn<QPoint>(name: "eventAngleDelta");
226 QTest::addColumn<QPoint>(name: "eventPixelDelta");
227 QTest::addColumn<Qt::KeyboardModifiers>(name: "eventModifiers");
228 QTest::addColumn<bool>(name: "eventPhases");
229 QTest::addColumn<bool>(name: "eventInverted");
230 QTest::addColumn<int>(name: "eventCount");
231 // result: inner handler
232 QTest::addColumn<QPoint>(name: "innerPosition");
233 QTest::addColumn<qreal>(name: "innerScale");
234 QTest::addColumn<int>(name: "innerRotation");
235 // result: outer handler
236 QTest::addColumn<QPoint>(name: "outerPosition");
237 QTest::addColumn<qreal>(name: "outerScale");
238 QTest::addColumn<int>(name: "outerRotation");
239
240 // move the item
241 QTest::newRow(dataTag: "vertical wheel angle delta to adjust x")
242 << Qt::Vertical << false << 1 << "x" << 1.5 << true
243 << QPoint(160, 120) << QPoint(120, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false << 10
244 << QPoint(175,60) << 1.0 << 0
245 << QPoint(75, 0) << 1.0 << 0;
246 QTest::newRow(dataTag: "horizontal wheel pixel delta to adjust y")
247 << Qt::Horizontal << false << 1 << "y" << 1.5 << false
248 << QPoint(160, 120) << QPoint(120, 120) << QPoint(50, 50) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false << 4
249 << QPoint(100, 160) << 1.0 << 0
250 << QPoint(0, 100) << 1.0 << 0;
251}
252
253void tst_QQuickWheelHandler::nestedHandler()
254{
255 // handler properties
256 QFETCH(Qt::Orientation, orientation);
257 QFETCH(bool, invertible);
258 QFETCH(int, rotationScale);
259 QFETCH(QString, property);
260 QFETCH(qreal, targetScaleMultiplier);
261 QFETCH(bool, targetTransformAroundCursor);
262 // event
263 QFETCH(QPoint, eventPos);
264 QFETCH(QPoint, eventAngleDelta);
265 QFETCH(QPoint, eventPixelDelta);
266 QFETCH(Qt::KeyboardModifiers, eventModifiers);
267 QFETCH(bool, eventPhases);
268 QFETCH(bool, eventInverted);
269 QFETCH(int, eventCount);
270 // result: inner handler
271 QFETCH(QPoint, innerPosition);
272 QFETCH(qreal, innerScale);
273 QFETCH(int, innerRotation);
274 // result: outer handler
275 QFETCH(QPoint, outerPosition);
276 QFETCH(qreal, outerScale);
277 QFETCH(int, outerRotation);
278
279 QQuickView window;
280 QByteArray errorMessage;
281 QVERIFY2(QQuickTest::initView(window, testFileUrl("nested.qml"), true, &errorMessage), errorMessage.constData());
282 window.show();
283 QVERIFY(QTest::qWaitForWindowExposed(&window));
284
285 QQuickItem *outerRect = window.rootObject();
286 QVERIFY(outerRect != nullptr);
287 QQuickWheelHandler *outerHandler = outerRect->findChild<QQuickWheelHandler*>(aName: "outerWheelHandler");
288 QVERIFY(outerHandler != nullptr);
289 QQuickWheelHandler *innerHandler = outerRect->findChild<QQuickWheelHandler*>(aName: "innerWheelHandler");
290 QVERIFY(innerHandler != nullptr);
291 QQuickItem *innerRect = innerHandler->parentItem();
292 QVERIFY(innerRect != nullptr);
293 innerHandler->setOrientation(orientation);
294 innerHandler->setInvertible(invertible);
295 innerHandler->setRotationScale(rotationScale);
296 innerHandler->setProperty(property);
297 innerHandler->setTargetScaleMultiplier(targetScaleMultiplier);
298 innerHandler->setTargetTransformAroundCursor(targetTransformAroundCursor);
299 outerHandler->setOrientation(orientation);
300 outerHandler->setInvertible(invertible);
301 outerHandler->setRotationScale(rotationScale);
302 outerHandler->setProperty(property);
303 outerHandler->setTargetScaleMultiplier(targetScaleMultiplier);
304 outerHandler->setTargetTransformAroundCursor(targetTransformAroundCursor);
305 QSignalSpy innerActiveChangedSpy(innerHandler, SIGNAL(activeChanged()));
306 QSignalSpy outerActiveChangedSpy(outerHandler, SIGNAL(activeChanged()));
307
308 if (eventPhases)
309 sendWheelEvent(window, pos: eventPos, angleDelta: QPoint(), pixelDelta: QPoint(), modifiers: eventModifiers, phase: Qt::ScrollBegin, inverted: eventInverted);
310 for (int i = 0; i < eventCount; ++i)
311 sendWheelEvent(window, pos: eventPos, angleDelta: eventAngleDelta, pixelDelta: eventPixelDelta, modifiers: eventModifiers,
312 phase: (eventPhases ? Qt::ScrollUpdate : Qt::NoScrollPhase), inverted: eventInverted);
313 QCOMPARE(innerRect->position().toPoint(), innerPosition);
314
315 /*
316 If outer is activated, maybe inner should be deactivated? But the event
317 doesn't get delivered to inner anymore, so it doesn't find out that
318 it's no longer getting events. It will get deactivated after the
319 timeout, just as if the user stopped scrolling.
320
321 This situation is similar to QTBUG-50199, but it's questionable whether
322 that was really so important. So far in Qt Quick, if you move the mouse
323 while wheel momentum continues, or if the item moves out from under the
324 mouse, a different item starts getting the events immediately. In
325 non-Qt applications on most OSes, that's quite normal.
326 */
327 // QCOMPARE(innerActiveChangedSpy.count(), 2);
328 // QCOMPARE(innerHandler->active(), false);
329 QCOMPARE(innerRect->scale(), innerScale);
330 QCOMPARE(innerRect->rotation(), innerRotation);
331 QCOMPARE(outerRect->position().toPoint(), outerPosition);
332 QCOMPARE(outerActiveChangedSpy.count(), 1);
333 QCOMPARE(outerHandler->active(), true);
334 QCOMPARE(outerRect->scale(), outerScale);
335 QCOMPARE(outerRect->rotation(), outerRotation);
336 if (!eventPhases) {
337 QTRY_COMPARE(outerHandler->active(), false);
338 QCOMPARE(outerActiveChangedSpy.count(), 2);
339 }
340}
341
342QTEST_MAIN(tst_QQuickWheelHandler)
343
344#include "tst_qquickwheelhandler.moc"
345

source code of qtdeclarative/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp