1/****************************************************************************
2**
3** Copyright (C) 2018 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>
30#include <QtGui/QWindow>
31#include <QtGui/QCursor>
32#include <QtGui/private/qguiapplication_p.h>
33
34QT_BEGIN_NAMESPACE
35namespace QTestPrivate {
36extern Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons; // from qtestcase.cpp
37}
38QT_END_NAMESPACE
39
40class tst_Mouse : public QObject
41{
42 Q_OBJECT
43
44private slots:
45 void timestampBetweenTestFunction_data();
46 void timestampBetweenTestFunction();
47 void stateHandlingPart1_data();
48 void stateHandlingPart1();
49 void stateHandlingPart2();
50 void deterministicEvents_data();
51 void deterministicEvents();
52};
53
54class MouseWindow : public QWindow
55{
56public:
57 Qt::MouseButtons stateInMouseMove = Qt::NoButton;
58 int moveCount = 0;
59 int pressCount = 0;
60 int doubleClickCount = 0;
61 ulong lastTimeStamp = 0;
62
63protected:
64 void mousePressEvent(QMouseEvent *e)
65 {
66 pressCount++;
67 processEvent(e);
68 }
69
70 void mouseMoveEvent(QMouseEvent *e)
71 {
72 moveCount++;
73 stateInMouseMove = e->buttons();
74 processEvent(e);
75 }
76
77 void mouseReleaseEvent(QMouseEvent *e)
78 {
79 processEvent(e);
80 }
81
82 void mouseDoubleClickEvent(QMouseEvent *e)
83 {
84 doubleClickCount++;
85 processEvent(e);
86 }
87
88 void processEvent(QMouseEvent *e)
89 {
90 lastTimeStamp = e->timestamp();
91 }
92
93};
94
95static ulong lastTimeStampInPreviousTestFunction = 0;
96
97void tst_Mouse::timestampBetweenTestFunction_data()
98{
99 QTest::addColumn<bool>(name: "hoverLast");
100 QTest::addColumn<bool>(name: "pressAndRelease");
101 QTest::newRow(dataTag: "press, release") << true << false;
102 QTest::newRow(dataTag: "press, release, hover") << true << true;
103 QTest::newRow(dataTag: "hover") << false << true;
104 QTest::newRow(dataTag: "hover #2") << false << true;
105 QTest::newRow(dataTag: "press, release #2") << true << false;
106 QTest::newRow(dataTag: "press, release, hover #2") << true << true;
107}
108
109void tst_Mouse::timestampBetweenTestFunction()
110{
111 QFETCH(bool, hoverLast);
112 QFETCH(bool, pressAndRelease);
113
114 MouseWindow w;
115 w.show();
116 w.setGeometry(posx: 100, posy: 100, w: 200, h: 200);
117 QVERIFY(QTest::qWaitForWindowActive(&w));
118
119 QPoint point(10, 10);
120 QCOMPARE(w.pressCount, 0);
121 if (pressAndRelease) {
122 QTest::mousePress(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
123 QVERIFY(w.lastTimeStamp - lastTimeStampInPreviousTestFunction > 500); // Should be at least 500 ms timestamp between each test case
124 QCOMPARE(w.pressCount, 1);
125 QTest::mouseRelease(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
126 }
127 QCOMPARE(w.doubleClickCount, 0);
128 if (hoverLast) {
129 static int xMove = 0;
130 xMove += 5; // Just make sure we generate different hover coordinates
131 point.rx() += xMove;
132 QTest::mouseMove(window: &w, pos: point); // a hover move. This doesn't generate a timestamp delay of 500 ms
133 }
134 lastTimeStampInPreviousTestFunction = w.lastTimeStamp;
135}
136
137void tst_Mouse::stateHandlingPart1_data()
138{
139 QTest::addColumn<bool>(name: "dummy");
140 QTest::newRow(dataTag: "dummy-1") << true;
141 QTest::newRow(dataTag: "dummy-2") << true;
142}
143
144void tst_Mouse::stateHandlingPart1()
145{
146 QFETCH(bool, dummy);
147 Q_UNUSED(dummy);
148
149 QWindow w;
150 w.setFlags(w.flags() | Qt::FramelessWindowHint); // ### FIXME: QTBUG-63542
151 w.show();
152 w.setGeometry(posx: 100, posy: 100, w: 200, h: 200);
153 QVERIFY(QTest::qWaitForWindowActive(&w));
154
155 QPoint point(10, 10);
156 QPoint step(1, 1);
157
158 // verify that we have a clean state after the previous data set
159 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::NoButton);
160
161 QTest::mousePress(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
162 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::LeftButton);
163 QTest::mousePress(window: &w, button: Qt::RightButton, stateKey: { }, pos: point);
164 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::LeftButton | Qt::RightButton);
165 QTest::mouseMove(window: &w, pos: point += step);
166 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::LeftButton | Qt::RightButton);
167 QTest::mouseRelease(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
168 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::RightButton);
169 QTest::mouseMove(window: &w, pos: point += step);
170 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::RightButton);
171 // test invalid input - left button was already released
172 QTest::mouseRelease(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point += point);
173 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::RightButton);
174 // test invalid input - right button is already pressed
175 QTest::mousePress(window: &w, button: Qt::RightButton, stateKey: { }, pos: point);
176 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::RightButton);
177 // now continue with valid input
178 QTest::mouseRelease(window: &w, button: Qt::RightButton, stateKey: { }, pos: point += point);
179 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::NoButton);
180 QTest::mouseMove(window: &w, pos: point += step);
181 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::NoButton);
182
183 // exit this test function with some button in a pressed state
184 QTest::mousePress(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
185 QTest::mousePress(window: &w, button: Qt::RightButton, stateKey: { }, pos: point);
186 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::LeftButton | Qt::RightButton);
187}
188
189void tst_Mouse::stateHandlingPart2()
190{
191 MouseWindow w;
192 w.setFlags(w.flags() | Qt::FramelessWindowHint); // ### FIXME: QTBUG-63542
193 w.show();
194 w.setGeometry(posx: 100, posy: 100, w: 200, h: 200);
195 QVERIFY(QTest::qWaitForWindowActive(&w));
196
197 // verify that we have a clean state after stateHandlingPart1()
198 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::NoButton);
199
200#if !QT_CONFIG(cursor)
201 QSKIP("This part of the test requires the QCursor API");
202#else
203 // The windowing system's view on a current button state might be different
204 // from the qtestlib's mouse button state. This test verifies that the mouse
205 // events generated by the system are adjusted to reflect qtestlib's view
206 // on the current button state.
207 // SKIP: not convinced yet that there is a valid use case for this.
208
209 QSKIP("Not implemented beyond this point!");
210
211 QPoint point(40, 40);
212 QTest::mousePress(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
213 QTest::mousePress(window: &w, button: Qt::RightButton, stateKey: { }, pos: point);
214 QCOMPARE(QTestPrivate::qtestMouseButtons, Qt::LeftButton | Qt::RightButton);
215 w.moveCount = 0;
216 // The windowing system will send mouse events with no buttons set
217 QPoint moveToPoint = w.mapToGlobal(pos: point + QPoint(1, 1));
218 if (QCursor::pos() == moveToPoint)
219 moveToPoint += QPoint(1, 1);
220 QCursor::setPos(moveToPoint);
221 QTRY_COMPARE(w.moveCount, 1);
222 // Verify that qtestlib adjusted the button state
223 QCOMPARE(w.stateInMouseMove, Qt::LeftButton | Qt::RightButton);
224#endif
225}
226
227void tst_Mouse::deterministicEvents_data()
228{
229 QTest::addColumn<bool>(name: "firstRun");
230 QTest::newRow(dataTag: "first-run-true") << true;
231 QTest::newRow(dataTag: "first-run-false") << false;
232}
233
234void tst_Mouse::deterministicEvents()
235{
236 /* QGuiApplication uses QGuiApplicationPrivate::lastCursorPosition to
237 determine if it needs to generate an additional mouse move event for
238 mouse press/release. Verify that this property is reset to it's default
239 value, ensuring deterministic event generation behavior. Not resetting
240 this value might affect event generation for subsequent tests runs (in
241 unlikely case where a subsquent test does a mouse press in a pos that is
242 equal to QGuiApplicationPrivate::lastCursorPosition, not causing mouse
243 move to be generated.
244 NOTE: running this test alone as in "./mouse deterministicEvents:first-run-false"
245 won't test what this test is designed to test. */
246
247 QSKIP("Not implemented!");
248
249 /* It is undecided how and at what scope we want to handle reseting
250 lastCursorPosition, or perhaps Qt should not be generating mouse move
251 events as documented in QGuiApplicationPrivate::processMouseEvent(),
252 then the problem would go away - ### Qt6 ? */
253
254 QVERIFY(qIsInf(QGuiApplicationPrivate::lastCursorPosition.x()));
255 QVERIFY(qIsInf(QGuiApplicationPrivate::lastCursorPosition.y()));
256
257 QFETCH(bool, firstRun);
258
259 MouseWindow w;
260 w.setFlags(w.flags() | Qt::FramelessWindowHint); // ### FIXME: QTBUG-63542
261 w.show();
262 w.setGeometry(posx: 100, posy: 100, w: 200, h: 200);
263 QVERIFY(QTest::qWaitForWindowActive(&w));
264
265 QCOMPARE(w.pressCount, 0);
266 QCOMPARE(w.moveCount, 0);
267 static QPoint m_cachedLastCursorPosition;
268 if (firstRun) {
269 QTest::mousePress(window: &w, button: Qt::LeftButton, stateKey: { }, pos: QPoint(40, 40));
270 m_cachedLastCursorPosition = QGuiApplicationPrivate::lastCursorPosition.toPoint();
271 } else {
272 QPoint point = w.mapFromGlobal(pos: m_cachedLastCursorPosition);
273 QTest::mousePress(window: &w, button: Qt::LeftButton, stateKey: { }, pos: point);
274 }
275 QCOMPARE(w.pressCount, 1);
276 QCOMPARE(w.moveCount, 1);
277}
278
279QTEST_MAIN(tst_Mouse)
280#include "tst_mouse.moc"
281

source code of qtbase/tests/auto/testlib/selftests/mouse/tst_mouse.cpp