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/QtTest> |
30 | |
31 | #include <QtQuick/qquickview.h> |
32 | #include <QtQuick/qquickitem.h> |
33 | #include <QtQuick/private/qquickhoverhandler_p.h> |
34 | #include <QtQuick/private/qquickmousearea_p.h> |
35 | #include <qpa/qwindowsysteminterface.h> |
36 | |
37 | #include <private/qquickwindow_p.h> |
38 | |
39 | #include <QtQml/qqmlengine.h> |
40 | #include <QtQml/qqmlproperty.h> |
41 | |
42 | #include "../../../shared/util.h" |
43 | #include "../../shared/viewtestutil.h" |
44 | |
45 | Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests" ) |
46 | |
47 | static bool isPlatformWayland() |
48 | { |
49 | return !QGuiApplication::platformName().compare(other: QLatin1String("wayland" ), cs: Qt::CaseInsensitive); |
50 | } |
51 | |
52 | class tst_HoverHandler : public QQmlDataTest |
53 | { |
54 | Q_OBJECT |
55 | public: |
56 | tst_HoverHandler() |
57 | {} |
58 | |
59 | private slots: |
60 | void hoverHandlerAndUnderlyingHoverHandler(); |
61 | void mouseAreaAndUnderlyingHoverHandler(); |
62 | void hoverHandlerAndUnderlyingMouseArea(); |
63 | void movingItemWithHoverHandler(); |
64 | void margin(); |
65 | void window(); |
66 | |
67 | private: |
68 | void createView(QScopedPointer<QQuickView> &window, const char *fileName); |
69 | }; |
70 | |
71 | void tst_HoverHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName) |
72 | { |
73 | window.reset(other: new QQuickView); |
74 | window->setSource(testFileUrl(fileName)); |
75 | QTRY_COMPARE(window->status(), QQuickView::Ready); |
76 | QQuickViewTestUtil::centerOnScreen(window: window.data()); |
77 | QQuickViewTestUtil::moveMouseAway(window: window.data()); |
78 | |
79 | window->show(); |
80 | QVERIFY(QTest::qWaitForWindowActive(window.data())); |
81 | QVERIFY(window->rootObject() != nullptr); |
82 | } |
83 | |
84 | void tst_HoverHandler::hoverHandlerAndUnderlyingHoverHandler() |
85 | { |
86 | QScopedPointer<QQuickView> windowPtr; |
87 | createView(window&: windowPtr, fileName: "lesHoverables.qml" ); |
88 | QQuickView * window = windowPtr.data(); |
89 | QQuickItem * = window->rootObject()->findChild<QQuickItem *>(aName: "topSidebar" ); |
90 | QVERIFY(topSidebar); |
91 | QQuickItem * button = topSidebar->findChild<QQuickItem *>(aName: "buttonWithHH" ); |
92 | QVERIFY(button); |
93 | QQuickHoverHandler * = topSidebar->findChild<QQuickHoverHandler *>(aName: "topSidebarHH" ); |
94 | QVERIFY(topSidebarHH); |
95 | QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>(aName: "buttonHH" ); |
96 | QVERIFY(buttonHH); |
97 | |
98 | QPoint buttonCenter(button->mapToScene(point: QPointF(button->width() / 2, button->height() / 2)).toPoint()); |
99 | QPoint rightOfButton(button->mapToScene(point: QPointF(button->width() + 2, button->height() / 2)).toPoint()); |
100 | QPoint (topSidebar->mapToScene(point: QPointF(topSidebar->width() + 2, topSidebar->height() / 2)).toPoint()); |
101 | QSignalSpy (topSidebarHH, SIGNAL(hoveredChanged())); |
102 | QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged())); |
103 | |
104 | QTest::mouseMove(window, pos: outOfSidebar); |
105 | QCOMPARE(topSidebarHH->isHovered(), false); |
106 | QCOMPARE(sidebarHoveredSpy.count(), 0); |
107 | QCOMPARE(buttonHH->isHovered(), false); |
108 | QCOMPARE(buttonHoveredSpy.count(), 0); |
109 | #if QT_CONFIG(cursor) |
110 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
111 | #endif |
112 | |
113 | QTest::mouseMove(window, pos: rightOfButton); |
114 | QCOMPARE(topSidebarHH->isHovered(), true); |
115 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
116 | QCOMPARE(buttonHH->isHovered(), false); |
117 | QCOMPARE(buttonHoveredSpy.count(), 0); |
118 | #if QT_CONFIG(cursor) |
119 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
120 | #endif |
121 | |
122 | QTest::mouseMove(window, pos: buttonCenter); |
123 | QCOMPARE(topSidebarHH->isHovered(), true); |
124 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
125 | QCOMPARE(buttonHH->isHovered(), true); |
126 | QCOMPARE(buttonHoveredSpy.count(), 1); |
127 | #if QT_CONFIG(cursor) |
128 | QCOMPARE(window->cursor().shape(), Qt::PointingHandCursor); |
129 | #endif |
130 | |
131 | QTest::mouseMove(window, pos: rightOfButton); |
132 | QCOMPARE(topSidebarHH->isHovered(), true); |
133 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
134 | QCOMPARE(buttonHH->isHovered(), false); |
135 | QCOMPARE(buttonHoveredSpy.count(), 2); |
136 | #if QT_CONFIG(cursor) |
137 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
138 | #endif |
139 | |
140 | QTest::mouseMove(window, pos: outOfSidebar); |
141 | QCOMPARE(topSidebarHH->isHovered(), false); |
142 | QCOMPARE(sidebarHoveredSpy.count(), 2); |
143 | QCOMPARE(buttonHH->isHovered(), false); |
144 | QCOMPARE(buttonHoveredSpy.count(), 2); |
145 | #if QT_CONFIG(cursor) |
146 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
147 | #endif |
148 | } |
149 | |
150 | void tst_HoverHandler::mouseAreaAndUnderlyingHoverHandler() |
151 | { |
152 | QScopedPointer<QQuickView> windowPtr; |
153 | createView(window&: windowPtr, fileName: "lesHoverables.qml" ); |
154 | QQuickView * window = windowPtr.data(); |
155 | QQuickItem * = window->rootObject()->findChild<QQuickItem *>(aName: "topSidebar" ); |
156 | QVERIFY(topSidebar); |
157 | QQuickMouseArea * buttonMA = topSidebar->findChild<QQuickMouseArea *>(aName: "buttonMA" ); |
158 | QVERIFY(buttonMA); |
159 | QQuickHoverHandler * = topSidebar->findChild<QQuickHoverHandler *>(aName: "topSidebarHH" ); |
160 | QVERIFY(topSidebarHH); |
161 | |
162 | QPoint buttonCenter(buttonMA->mapToScene(point: QPointF(buttonMA->width() / 2, buttonMA->height() / 2)).toPoint()); |
163 | QPoint rightOfButton(buttonMA->mapToScene(point: QPointF(buttonMA->width() + 2, buttonMA->height() / 2)).toPoint()); |
164 | QPoint (topSidebar->mapToScene(point: QPointF(topSidebar->width() + 2, topSidebar->height() / 2)).toPoint()); |
165 | QSignalSpy (topSidebarHH, SIGNAL(hoveredChanged())); |
166 | QSignalSpy buttonHoveredSpy(buttonMA, SIGNAL(hoveredChanged())); |
167 | |
168 | QTest::mouseMove(window, pos: outOfSidebar); |
169 | QCOMPARE(topSidebarHH->isHovered(), false); |
170 | QCOMPARE(sidebarHoveredSpy.count(), 0); |
171 | QCOMPARE(buttonMA->hovered(), false); |
172 | QCOMPARE(buttonHoveredSpy.count(), 0); |
173 | #if QT_CONFIG(cursor) |
174 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
175 | #endif |
176 | |
177 | QTest::mouseMove(window, pos: rightOfButton); |
178 | QCOMPARE(topSidebarHH->isHovered(), true); |
179 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
180 | QCOMPARE(buttonMA->hovered(), false); |
181 | QCOMPARE(buttonHoveredSpy.count(), 0); |
182 | #if QT_CONFIG(cursor) |
183 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
184 | #endif |
185 | |
186 | QTest::mouseMove(window, pos: buttonCenter); |
187 | QCOMPARE(topSidebarHH->isHovered(), true); |
188 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
189 | QCOMPARE(buttonMA->hovered(), true); |
190 | QCOMPARE(buttonHoveredSpy.count(), 1); |
191 | #if QT_CONFIG(cursor) |
192 | QCOMPARE(window->cursor().shape(), Qt::UpArrowCursor); |
193 | #endif |
194 | |
195 | QTest::mouseMove(window, pos: rightOfButton); |
196 | QCOMPARE(topSidebarHH->isHovered(), true); |
197 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
198 | QCOMPARE(buttonMA->hovered(), false); |
199 | QCOMPARE(buttonHoveredSpy.count(), 2); |
200 | #if QT_CONFIG(cursor) |
201 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
202 | #endif |
203 | |
204 | QTest::mouseMove(window, pos: outOfSidebar); |
205 | QCOMPARE(topSidebarHH->isHovered(), false); |
206 | QCOMPARE(sidebarHoveredSpy.count(), 2); |
207 | QCOMPARE(buttonMA->hovered(), false); |
208 | QCOMPARE(buttonHoveredSpy.count(), 2); |
209 | #if QT_CONFIG(cursor) |
210 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
211 | #endif |
212 | } |
213 | |
214 | void tst_HoverHandler::hoverHandlerAndUnderlyingMouseArea() |
215 | { |
216 | QScopedPointer<QQuickView> windowPtr; |
217 | createView(window&: windowPtr, fileName: "lesHoverables.qml" ); |
218 | QQuickView * window = windowPtr.data(); |
219 | QQuickItem * = window->rootObject()->findChild<QQuickItem *>(aName: "bottomSidebar" ); |
220 | QVERIFY(bottomSidebar); |
221 | QQuickMouseArea * = bottomSidebar->findChild<QQuickMouseArea *>(aName: "bottomSidebarMA" ); |
222 | QVERIFY(bottomSidebarMA); |
223 | QQuickItem * button = bottomSidebar->findChild<QQuickItem *>(aName: "buttonWithHH" ); |
224 | QVERIFY(button); |
225 | QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>(aName: "buttonHH" ); |
226 | QVERIFY(buttonHH); |
227 | |
228 | QPoint buttonCenter(button->mapToScene(point: QPointF(button->width() / 2, button->height() / 2)).toPoint()); |
229 | QPoint rightOfButton(button->mapToScene(point: QPointF(button->width() + 2, button->height() / 2)).toPoint()); |
230 | QPoint (bottomSidebar->mapToScene(point: QPointF(bottomSidebar->width() + 2, bottomSidebar->height() / 2)).toPoint()); |
231 | QSignalSpy (bottomSidebarMA, SIGNAL(hoveredChanged())); |
232 | QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged())); |
233 | |
234 | QTest::mouseMove(window, pos: outOfSidebar); |
235 | QCOMPARE(bottomSidebarMA->hovered(), false); |
236 | QCOMPARE(sidebarHoveredSpy.count(), 0); |
237 | QCOMPARE(buttonHH->isHovered(), false); |
238 | QCOMPARE(buttonHoveredSpy.count(), 0); |
239 | #if QT_CONFIG(cursor) |
240 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
241 | #endif |
242 | |
243 | QTest::mouseMove(window, pos: rightOfButton); |
244 | QCOMPARE(bottomSidebarMA->hovered(), true); |
245 | QCOMPARE(sidebarHoveredSpy.count(), 1); |
246 | QCOMPARE(buttonHH->isHovered(), false); |
247 | QCOMPARE(buttonHoveredSpy.count(), 0); |
248 | #if QT_CONFIG(cursor) |
249 | QCOMPARE(window->cursor().shape(), Qt::ClosedHandCursor); |
250 | #endif |
251 | |
252 | QTest::mouseMove(window, pos: buttonCenter); |
253 | QCOMPARE(bottomSidebarMA->hovered(), false); |
254 | QCOMPARE(sidebarHoveredSpy.count(), 2); |
255 | QCOMPARE(buttonHH->isHovered(), true); |
256 | QCOMPARE(buttonHoveredSpy.count(), 1); |
257 | #if QT_CONFIG(cursor) |
258 | QCOMPARE(window->cursor().shape(), Qt::PointingHandCursor); |
259 | #endif |
260 | |
261 | QTest::mouseMove(window, pos: rightOfButton); |
262 | QCOMPARE(bottomSidebarMA->hovered(), true); |
263 | QCOMPARE(sidebarHoveredSpy.count(), 3); |
264 | QCOMPARE(buttonHH->isHovered(), false); |
265 | QCOMPARE(buttonHoveredSpy.count(), 2); |
266 | #if QT_CONFIG(cursor) |
267 | QCOMPARE(window->cursor().shape(), Qt::ClosedHandCursor); |
268 | #endif |
269 | |
270 | QTest::mouseMove(window, pos: outOfSidebar); |
271 | QCOMPARE(bottomSidebarMA->hovered(), false); |
272 | QCOMPARE(sidebarHoveredSpy.count(), 4); |
273 | QCOMPARE(buttonHH->isHovered(), false); |
274 | QCOMPARE(buttonHoveredSpy.count(), 2); |
275 | #if QT_CONFIG(cursor) |
276 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
277 | #endif |
278 | } |
279 | |
280 | void tst_HoverHandler::movingItemWithHoverHandler() |
281 | { |
282 | if (isPlatformWayland()) |
283 | QSKIP("Wayland: QCursor::setPos() doesn't work." ); |
284 | |
285 | QScopedPointer<QQuickView> windowPtr; |
286 | createView(window&: windowPtr, fileName: "lesHoverables.qml" ); |
287 | QQuickView * window = windowPtr.data(); |
288 | QQuickItem * paddle = window->rootObject()->findChild<QQuickItem *>(aName: "paddle" ); |
289 | QVERIFY(paddle); |
290 | QQuickHoverHandler *paddleHH = paddle->findChild<QQuickHoverHandler *>(aName: "paddleHH" ); |
291 | QVERIFY(paddleHH); |
292 | |
293 | // Find the global coordinate of the paddle |
294 | const QPoint p(paddle->mapToScene(point: paddle->clipRect().center()).toPoint()); |
295 | const QPoint paddlePos = window->mapToGlobal(pos: p); |
296 | |
297 | // Now hide the window, put the cursor where the paddle was and show it again |
298 | window->hide(); |
299 | QTRY_COMPARE(window->isVisible(), false); |
300 | QCursor::setPos(paddlePos); |
301 | window->show(); |
302 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
303 | |
304 | QTRY_COMPARE(paddleHH->isHovered(), true); |
305 | // TODO check the cursor shape after fixing QTBUG-53987 |
306 | |
307 | paddle->setX(100); |
308 | QTRY_COMPARE(paddleHH->isHovered(), false); |
309 | |
310 | paddle->setX(p.x()); |
311 | QTRY_COMPARE(paddleHH->isHovered(), true); |
312 | |
313 | paddle->setX(540); |
314 | QTRY_COMPARE(paddleHH->isHovered(), false); |
315 | } |
316 | |
317 | void tst_HoverHandler::margin() // QTBUG-85303 |
318 | { |
319 | QScopedPointer<QQuickView> windowPtr; |
320 | createView(window&: windowPtr, fileName: "hoverMargin.qml" ); |
321 | QQuickView * window = windowPtr.data(); |
322 | QQuickItem * item = window->rootObject()->findChild<QQuickItem *>(); |
323 | QVERIFY(item); |
324 | QQuickHoverHandler *hh = item->findChild<QQuickHoverHandler *>(); |
325 | QVERIFY(hh); |
326 | |
327 | QPoint itemCenter(item->mapToScene(point: QPointF(item->width() / 2, item->height() / 2)).toPoint()); |
328 | QPoint leftMargin = itemCenter - QPoint(35, 35); |
329 | QSignalSpy hoveredSpy(hh, SIGNAL(hoveredChanged())); |
330 | |
331 | QTest::mouseMove(window, pos: {10, 10}); |
332 | QCOMPARE(hh->isHovered(), false); |
333 | QCOMPARE(hoveredSpy.count(), 0); |
334 | #if QT_CONFIG(cursor) |
335 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
336 | #endif |
337 | |
338 | QTest::mouseMove(window, pos: leftMargin); |
339 | QCOMPARE(hh->isHovered(), true); |
340 | QCOMPARE(hoveredSpy.count(), 1); |
341 | #if QT_CONFIG(cursor) |
342 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
343 | #endif |
344 | |
345 | QTest::mouseMove(window, pos: itemCenter); |
346 | QCOMPARE(hh->isHovered(), true); |
347 | QCOMPARE(hoveredSpy.count(), 1); |
348 | #if QT_CONFIG(cursor) |
349 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
350 | #endif |
351 | |
352 | QTest::mouseMove(window, pos: leftMargin); |
353 | QCOMPARE(hh->isHovered(), true); |
354 | // QCOMPARE(hoveredSpy.count(), 1); |
355 | #if QT_CONFIG(cursor) |
356 | QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
357 | #endif |
358 | |
359 | QTest::mouseMove(window, pos: {10, 10}); |
360 | QCOMPARE(hh->isHovered(), false); |
361 | // QCOMPARE(hoveredSpy.count(), 2); |
362 | #if QT_CONFIG(cursor) |
363 | QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); |
364 | #endif |
365 | } |
366 | |
367 | void tst_HoverHandler::window() // QTBUG-98717 |
368 | { |
369 | QQmlEngine engine; |
370 | QQmlComponent component(&engine); |
371 | component.loadUrl(url: testFileUrl(fileName: "windowCursorShape.qml" )); |
372 | QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create())); |
373 | QVERIFY(!window.isNull()); |
374 | window->show(); |
375 | QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
376 | #if QT_CONFIG(cursor) |
377 | if (isPlatformWayland()) |
378 | QSKIP("Wayland: QCursor::setPos() doesn't work." ); |
379 | #ifdef Q_OS_MACOS |
380 | QSKIP("macOS: QCursor::setPos() doesn't work (QTBUG-76312)." ); |
381 | #endif |
382 | auto cursorPos = window->mapToGlobal(pos: QPoint(100, 100)); |
383 | qCDebug(lcPointerTests) << "in window @" << window->position() << "setting cursor pos" << cursorPos; |
384 | QCursor::setPos(cursorPos); |
385 | QTRY_COMPARE(window->cursor().shape(), Qt::OpenHandCursor); |
386 | #endif |
387 | } |
388 | |
389 | QTEST_MAIN(tst_HoverHandler) |
390 | |
391 | #include "tst_qquickhoverhandler.moc" |
392 | |