1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QTESTMOUSE_H
5#define QTESTMOUSE_H
6
7#if 0
8// inform syncqt
9#pragma qt_no_master_include
10#endif
11
12#include <QtTest/qttestglobal.h>
13#include <QtTest/qtestassert.h>
14#include <QtTest/qtestsystem.h>
15#include <QtTest/qtestspontaneevent.h>
16#include <QtCore/qpoint.h>
17#include <QtCore/qstring.h>
18#include <QtCore/qpointer.h>
19#include <QtGui/qevent.h>
20#include <QtGui/qwindow.h>
21
22#ifdef QT_WIDGETS_LIB
23#include <QtWidgets/qapplication.h>
24#include <QtWidgets/qwidget.h>
25#endif
26
27#include <QtCore/QDebug>
28
29QT_BEGIN_NAMESPACE
30
31Q_GUI_EXPORT void qt_handleMouseEvent(QWindow *window, const QPointF &local, const QPointF &global,
32 Qt::MouseButtons state, Qt::MouseButton button,
33 QEvent::Type type, Qt::KeyboardModifiers mods, int timestamp);
34
35namespace QTestPrivate
36{
37 extern Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons;
38}
39
40namespace QTest
41{
42 enum MouseAction { MousePress, MouseRelease, MouseClick, MouseDClick, MouseMove };
43
44 extern Q_TESTLIB_EXPORT int lastMouseTimestamp;
45
46 // This value is used to emulate timestamps to avoid creating double clicks by mistake.
47 // Use this constant instead of QStyleHints::mouseDoubleClickInterval property to avoid tests
48 // to depend on platform themes.
49 static const int mouseDoubleClickInterval = 500;
50
51 /*! \internal
52 This function creates a QPA mouse event of type specified by \a action
53 and calls QWindowSystemInterface::handleMouseEvent(), simulating the
54 windowing system and bypassing the platform plugin. \a delay is the
55 amount of time to be added to the simulated clock so that
56 QInputEvent::timestamp() will be greater than that of the previous
57 event. We expect all event-handling code to rely on the event
58 timestamps, not the system clock; therefore tests can be run faster
59 than real-time.
60
61 If \a delay is not given, a default minimum mouse delay is used, and
62 unintended double-click events are prevented by incrementing the
63 timestamp by 500ms after each mouse release. Therefore, to test
64 double-clicks, it's necessary to give a realistic \a delay value (for
65 example, 10ms).
66 */
67 static void mouseEvent(MouseAction action, QWindow *window, Qt::MouseButton button,
68 Qt::KeyboardModifiers stateKey, QPoint pos, int delay=-1)
69 {
70 QTEST_ASSERT(window);
71 extern int Q_TESTLIB_EXPORT defaultMouseDelay();
72
73 // pos is in window local coordinates
74 const QSize windowSize = window->geometry().size();
75 if (windowSize.width() <= pos.x() || windowSize.height() <= pos.y()) {
76 qWarning(msg: "Mouse event at %d, %d occurs outside target window (%dx%d).",
77 pos.x(), pos.y(), windowSize.width(), windowSize.height());
78 }
79
80 int actualDelay = (delay == -1 || delay < defaultMouseDelay()) ? defaultMouseDelay() : delay;
81 lastMouseTimestamp += qMax(a: 1, b: actualDelay);
82
83 if (pos.isNull())
84 pos = QPoint(window->width() / 2, window->height() / 2);
85
86 QTEST_ASSERT(!stateKey || stateKey & Qt::KeyboardModifierMask);
87
88 stateKey &= Qt::KeyboardModifierMask;
89
90 QPointF global = window->mapToGlobal(pos);
91 QPointer<QWindow> w(window);
92
93 using namespace QTestPrivate;
94 switch (action)
95 {
96 case MouseDClick:
97 qtestMouseButtons.setFlag(flag: button, on: true);
98 qt_handleMouseEvent(window: w, local: pos, global, state: qtestMouseButtons, button, type: QEvent::MouseButtonPress,
99 mods: stateKey, timestamp: lastMouseTimestamp);
100 qtestMouseButtons.setFlag(flag: button, on: false);
101 qt_handleMouseEvent(window: w, local: pos, global, state: qtestMouseButtons, button, type: QEvent::MouseButtonRelease,
102 mods: stateKey, timestamp: lastMouseTimestamp);
103 Q_FALLTHROUGH();
104 case MousePress:
105 case MouseClick:
106 qtestMouseButtons.setFlag(flag: button, on: true);
107 qt_handleMouseEvent(window: w, local: pos, global, state: qtestMouseButtons, button, type: QEvent::MouseButtonPress,
108 mods: stateKey, timestamp: lastMouseTimestamp);
109 if (action == MousePress)
110 break;
111 Q_FALLTHROUGH();
112 case MouseRelease:
113 qtestMouseButtons.setFlag(flag: button, on: false);
114 qt_handleMouseEvent(window: w, local: pos, global, state: qtestMouseButtons, button, type: QEvent::MouseButtonRelease,
115 mods: stateKey, timestamp: lastMouseTimestamp);
116 if (delay == -1)
117 lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
118 break;
119 case MouseMove:
120 qt_handleMouseEvent(window: w, local: pos, global, state: qtestMouseButtons, button: Qt::NoButton, type: QEvent::MouseMove,
121 mods: stateKey, timestamp: lastMouseTimestamp);
122 break;
123 default:
124 QTEST_ASSERT(false);
125 }
126 qApp->processEvents();
127 }
128
129 inline void mousePress(QWindow *window, Qt::MouseButton button,
130 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
131 QPoint pos = QPoint(), int delay=-1)
132 { mouseEvent(action: MousePress, window, button, stateKey, pos, delay); }
133 inline void mouseRelease(QWindow *window, Qt::MouseButton button,
134 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
135 QPoint pos = QPoint(), int delay=-1)
136 { mouseEvent(action: MouseRelease, window, button, stateKey, pos, delay); }
137 inline void mouseClick(QWindow *window, Qt::MouseButton button,
138 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
139 QPoint pos = QPoint(), int delay=-1)
140 { mouseEvent(action: MouseClick, window, button, stateKey, pos, delay); }
141 inline void mouseDClick(QWindow *window, Qt::MouseButton button,
142 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
143 QPoint pos = QPoint(), int delay=-1)
144 { mouseEvent(action: MouseDClick, window, button, stateKey, pos, delay); }
145 inline void mouseMove(QWindow *window, QPoint pos = QPoint(), int delay=-1)
146 { mouseEvent(action: MouseMove, window, button: Qt::NoButton, stateKey: Qt::KeyboardModifiers(), pos, delay); }
147
148#ifdef QT_WIDGETS_LIB
149 static void mouseEvent(MouseAction action, QWidget *widget, Qt::MouseButton button,
150 Qt::KeyboardModifiers stateKey, QPoint pos, int delay=-1)
151 {
152 QTEST_ASSERT(widget);
153
154 if (pos.isNull())
155 pos = widget->rect().center();
156
157#ifdef QTEST_QPA_MOUSE_HANDLING
158 QWindow *w = widget->window()->windowHandle();
159 QTEST_ASSERT(w);
160 mouseEvent(action, w, button, stateKey, w->mapFromGlobal(widget->mapToGlobal(pos)), delay);
161#else
162 extern int Q_TESTLIB_EXPORT defaultMouseDelay();
163
164 if (delay == -1 || delay < defaultMouseDelay())
165 delay = defaultMouseDelay();
166 lastMouseTimestamp += qMax(1, delay);
167
168 if (action == MouseClick) {
169 mouseEvent(MousePress, widget, button, stateKey, pos);
170 mouseEvent(MouseRelease, widget, button, stateKey, pos);
171 return;
172 }
173
174 QTEST_ASSERT(!stateKey || stateKey & Qt::KeyboardModifierMask);
175
176 stateKey &= Qt::KeyboardModifierMask;
177
178 QEvent::Type meType;
179 using namespace QTestPrivate;
180 switch (action)
181 {
182 case MousePress:
183 qtestMouseButtons.setFlag(button, true);
184 meType = QEvent::MouseButtonPress;
185 break;
186 case MouseRelease:
187 qtestMouseButtons.setFlag(button, false);
188 meType = QEvent::MouseButtonRelease;
189 break;
190 case MouseDClick:
191 qtestMouseButtons.setFlag(button, true);
192 meType = QEvent::MouseButtonDblClick;
193 break;
194 case MouseMove:
195 // ### Qt 7: compatibility with < Qt 6.3, we should not rely on QCursor::setPos
196 // for generating mouse move events, and code that depends on QCursor::pos should
197 // be tested using QCursor::setPos explicitly.
198 if (qtestMouseButtons == Qt::NoButton) {
199 QCursor::setPos(widget->mapToGlobal(pos));
200 qApp->processEvents();
201 return;
202 }
203 meType = QEvent::MouseMove;
204 break;
205 default:
206 QTEST_ASSERT(false);
207 }
208 QMouseEvent me(meType, pos, widget->mapToGlobal(pos), button, qtestMouseButtons, stateKey, QPointingDevice::primaryPointingDevice());
209 me.setTimestamp(lastMouseTimestamp);
210 if (action == MouseRelease) // avoid double clicks being generated
211 lastMouseTimestamp += mouseDoubleClickInterval;
212
213 QSpontaneKeyEvent::setSpontaneous(&me);
214 if (!qApp->notify(widget, &me)) {
215 static const char *const mouseActionNames[] =
216 { "MousePress", "MouseRelease", "MouseClick", "MouseDClick", "MouseMove" };
217 qWarning("Mouse event \"%s\" not accepted by receiving widget",
218 mouseActionNames[static_cast<int>(action)]);
219 }
220#endif
221 }
222
223 inline void mousePress(QWidget *widget, Qt::MouseButton button,
224 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
225 QPoint pos = QPoint(), int delay=-1)
226 { mouseEvent(MousePress, widget, button, stateKey, pos, delay); }
227 inline void mouseRelease(QWidget *widget, Qt::MouseButton button,
228 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
229 QPoint pos = QPoint(), int delay=-1)
230 { mouseEvent(MouseRelease, widget, button, stateKey, pos, delay); }
231 inline void mouseClick(QWidget *widget, Qt::MouseButton button,
232 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
233 QPoint pos = QPoint(), int delay=-1)
234 { mouseEvent(MouseClick, widget, button, stateKey, pos, delay); }
235 inline void mouseDClick(QWidget *widget, Qt::MouseButton button,
236 Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
237 QPoint pos = QPoint(), int delay=-1)
238 { mouseEvent(MouseDClick, widget, button, stateKey, pos, delay); }
239 inline void mouseMove(QWidget *widget, QPoint pos = QPoint(), int delay=-1)
240 { mouseEvent(MouseMove, widget, Qt::NoButton, Qt::KeyboardModifiers(), pos, delay); }
241#endif // QT_WIDGETS_LIB
242}
243
244QT_END_NAMESPACE
245
246#endif // QTESTMOUSE_H
247

source code of qtbase/src/testlib/qtestmouse.h