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
45Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
46
47static bool isPlatformWayland()
48{
49 return !QGuiApplication::platformName().compare(other: QLatin1String("wayland"), cs: Qt::CaseInsensitive);
50}
51
52class tst_HoverHandler : public QQmlDataTest
53{
54 Q_OBJECT
55public:
56 tst_HoverHandler()
57 {}
58
59private slots:
60 void hoverHandlerAndUnderlyingHoverHandler();
61 void mouseAreaAndUnderlyingHoverHandler();
62 void hoverHandlerAndUnderlyingMouseArea();
63 void movingItemWithHoverHandler();
64 void margin();
65 void window();
66
67private:
68 void createView(QScopedPointer<QQuickView> &window, const char *fileName);
69};
70
71void 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
84void tst_HoverHandler::hoverHandlerAndUnderlyingHoverHandler()
85{
86 QScopedPointer<QQuickView> windowPtr;
87 createView(window&: windowPtr, fileName: "lesHoverables.qml");
88 QQuickView * window = windowPtr.data();
89 QQuickItem * topSidebar = window->rootObject()->findChild<QQuickItem *>(aName: "topSidebar");
90 QVERIFY(topSidebar);
91 QQuickItem * button = topSidebar->findChild<QQuickItem *>(aName: "buttonWithHH");
92 QVERIFY(button);
93 QQuickHoverHandler *topSidebarHH = 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 outOfSidebar(topSidebar->mapToScene(point: QPointF(topSidebar->width() + 2, topSidebar->height() / 2)).toPoint());
101 QSignalSpy sidebarHoveredSpy(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
150void tst_HoverHandler::mouseAreaAndUnderlyingHoverHandler()
151{
152 QScopedPointer<QQuickView> windowPtr;
153 createView(window&: windowPtr, fileName: "lesHoverables.qml");
154 QQuickView * window = windowPtr.data();
155 QQuickItem * topSidebar = window->rootObject()->findChild<QQuickItem *>(aName: "topSidebar");
156 QVERIFY(topSidebar);
157 QQuickMouseArea * buttonMA = topSidebar->findChild<QQuickMouseArea *>(aName: "buttonMA");
158 QVERIFY(buttonMA);
159 QQuickHoverHandler *topSidebarHH = 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 outOfSidebar(topSidebar->mapToScene(point: QPointF(topSidebar->width() + 2, topSidebar->height() / 2)).toPoint());
165 QSignalSpy sidebarHoveredSpy(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
214void tst_HoverHandler::hoverHandlerAndUnderlyingMouseArea()
215{
216 QScopedPointer<QQuickView> windowPtr;
217 createView(window&: windowPtr, fileName: "lesHoverables.qml");
218 QQuickView * window = windowPtr.data();
219 QQuickItem * bottomSidebar = window->rootObject()->findChild<QQuickItem *>(aName: "bottomSidebar");
220 QVERIFY(bottomSidebar);
221 QQuickMouseArea *bottomSidebarMA = 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 outOfSidebar(bottomSidebar->mapToScene(point: QPointF(bottomSidebar->width() + 2, bottomSidebar->height() / 2)).toPoint());
231 QSignalSpy sidebarHoveredSpy(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
280void 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
317void 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
367void 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
389QTEST_MAIN(tst_HoverHandler)
390
391#include "tst_qquickhoverhandler.moc"
392

source code of qtdeclarative/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp