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#include "quicktestevent_p.h"
5#include <QtTest/qtestkeyboard.h>
6#include <QtQml/qqml.h>
7#include <QtQuick/qquickitem.h>
8#include <QtQuick/qquickwindow.h>
9#include <qpa/qwindowsysteminterface.h>
10
11QT_BEGIN_NAMESPACE
12
13namespace QTest {
14 extern int Q_TESTLIB_EXPORT defaultMouseDelay();
15}
16
17QuickTestEvent::QuickTestEvent(QObject *parent)
18 : QObject(parent)
19{
20}
21
22QuickTestEvent::~QuickTestEvent()
23{
24}
25
26int QuickTestEvent::defaultMouseDelay() const
27{
28 return QTest::defaultMouseDelay();
29}
30
31bool QuickTestEvent::keyPress(int key, int modifiers, int delay)
32{
33 QWindow *window = activeWindow();
34 if (!window)
35 return false;
36 QTest::keyPress(window, key: Qt::Key(key), modifier: Qt::KeyboardModifiers(modifiers), delay);
37 return true;
38}
39
40bool QuickTestEvent::keyRelease(int key, int modifiers, int delay)
41{
42 QWindow *window = activeWindow();
43 if (!window)
44 return false;
45 QTest::keyRelease(window, key: Qt::Key(key), modifier: Qt::KeyboardModifiers(modifiers), delay);
46 return true;
47}
48
49bool QuickTestEvent::keyClick(int key, int modifiers, int delay)
50{
51 QWindow *window = activeWindow();
52 if (!window)
53 return false;
54 QTest::keyClick(window, key: Qt::Key(key), modifier: Qt::KeyboardModifiers(modifiers), delay);
55 return true;
56}
57
58bool QuickTestEvent::keyPressChar(const QString &character, int modifiers, int delay)
59{
60 QTEST_ASSERT(character.size() == 1);
61 QWindow *window = activeWindow();
62 if (!window)
63 return false;
64 QTest::keyPress(window, key: character[0].toLatin1(), modifier: Qt::KeyboardModifiers(modifiers), delay);
65 return true;
66}
67
68bool QuickTestEvent::keyReleaseChar(const QString &character, int modifiers, int delay)
69{
70 QTEST_ASSERT(character.size() == 1);
71 QWindow *window = activeWindow();
72 if (!window)
73 return false;
74 QTest::keyRelease(window, key: character[0].toLatin1(), modifier: Qt::KeyboardModifiers(modifiers), delay);
75 return true;
76}
77
78bool QuickTestEvent::keyClickChar(const QString &character, int modifiers, int delay)
79{
80 QTEST_ASSERT(character.size() == 1);
81 QWindow *window = activeWindow();
82 if (!window)
83 return false;
84 QTest::keyClick(window, key: character[0].toLatin1(), modifier: Qt::KeyboardModifiers(modifiers), delay);
85 return true;
86}
87
88#if QT_CONFIG(shortcut)
89// valueToKeySequence() is copied from qquickshortcut.cpp
90static QKeySequence valueToKeySequence(const QVariant &value)
91{
92 if (value.userType() == QMetaType::Int)
93 return QKeySequence(static_cast<QKeySequence::StandardKey>(value.toInt()));
94 return QKeySequence::fromString(str: value.toString());
95}
96#endif
97
98bool QuickTestEvent::keySequence(const QVariant &keySequence)
99{
100 QWindow *window = activeWindow();
101 if (!window)
102 return false;
103#if QT_CONFIG(shortcut)
104 QTest::keySequence(window, keySequence: valueToKeySequence(value: keySequence));
105#else
106 Q_UNUSED(keySequence);
107#endif
108 return true;
109}
110
111namespace QtQuickTest
112{
113 enum MouseAction { MousePress, MouseRelease, MouseClick, MouseDoubleClick, MouseMove, MouseDoubleClickSequence };
114
115 int lastMouseTimestamp = 0;
116
117 // TODO should be Qt::MouseButtons buttons in case multiple buttons are pressed
118 static void mouseEvent(MouseAction action, QWindow *window,
119 QObject *item, Qt::MouseButton button,
120 Qt::KeyboardModifiers stateKey, const QPointF &_pos, int delay=-1)
121 {
122 QTEST_ASSERT(window);
123 QTEST_ASSERT(item);
124
125 if (delay == -1 || delay < QTest::defaultMouseDelay())
126 delay = QTest::defaultMouseDelay();
127 if (delay > 0) {
128 QTest::qWait(ms: delay);
129 lastMouseTimestamp += delay;
130 }
131
132 if (action == MouseClick) {
133 mouseEvent(action: MousePress, window, item, button, stateKey, _pos);
134 mouseEvent(action: MouseRelease, window, item, button, stateKey, _pos);
135 return;
136 }
137
138 if (action == MouseDoubleClickSequence) {
139 mouseEvent(action: MousePress, window, item, button, stateKey, _pos);
140 mouseEvent(action: MouseRelease, window, item, button, stateKey, _pos);
141 mouseEvent(action: MousePress, window, item, button, stateKey, _pos);
142 mouseEvent(action: MouseDoubleClick, window, item, button, stateKey, _pos);
143 mouseEvent(action: MouseRelease, window, item, button, stateKey, _pos);
144 return;
145 }
146
147 QPoint pos = _pos.toPoint();
148 QQuickItem *sgitem = qobject_cast<QQuickItem *>(o: item);
149 if (sgitem)
150 pos = sgitem->mapToScene(point: _pos).toPoint();
151 QTEST_ASSERT(button == Qt::NoButton || button & Qt::MouseButtonMask);
152 QTEST_ASSERT(stateKey == 0 || stateKey & Qt::KeyboardModifierMask);
153
154 stateKey &= static_cast<unsigned int>(Qt::KeyboardModifierMask);
155
156 QEvent::Type meType;
157 Qt::MouseButton meButton;
158 Qt::MouseButtons meButtons;
159 switch (action)
160 {
161 case MousePress:
162 meType = QEvent::MouseButtonPress;
163 meButton = button;
164 meButtons = button;
165 break;
166 case MouseRelease:
167 meType = QEvent::MouseButtonRelease;
168 meButton = button;
169 meButtons = Qt::MouseButton();
170 break;
171 case MouseDoubleClick:
172 meType = QEvent::MouseButtonDblClick;
173 meButton = button;
174 meButtons = button;
175 break;
176 case MouseMove:
177 meType = QEvent::MouseMove;
178 meButton = Qt::NoButton;
179 meButtons = button;
180 break;
181 default:
182 QTEST_ASSERT(false);
183 }
184 QMouseEvent me(meType, pos, window->mapToGlobal(pos), meButton, meButtons, stateKey);
185 me.setTimestamp(++lastMouseTimestamp);
186 if (action == MouseRelease) // avoid double clicks being generated
187 lastMouseTimestamp += 500;
188
189 QSpontaneKeyEvent::setSpontaneous(&me);
190 if (!qApp->notify(window, &me)) {
191 static const char *mouseActionNames[] =
192 { "MousePress", "MouseRelease", "MouseClick", "MouseDoubleClick", "MouseMove",
193 "MouseDoubleClickSequence" };
194 qWarning(msg: "Mouse event \"%s\" not accepted by receiving window",
195 mouseActionNames[static_cast<int>(action)]);
196 }
197 }
198
199#if QT_CONFIG(wheelevent)
200 static void mouseWheel(QWindow* window, QObject* item, Qt::MouseButtons buttons,
201 Qt::KeyboardModifiers stateKey,
202 QPointF _pos, int xDelta, int yDelta, int delay = -1)
203 {
204 QTEST_ASSERT(window);
205 QTEST_ASSERT(item);
206 if (delay == -1 || delay < QTest::defaultMouseDelay())
207 delay = QTest::defaultMouseDelay();
208 if (delay > 0) {
209 QTest::qWait(ms: delay);
210 lastMouseTimestamp += delay;
211 }
212
213 QPoint pos;
214 QQuickItem *sgitem = qobject_cast<QQuickItem *>(o: item);
215 if (sgitem)
216 pos = sgitem->mapToScene(point: _pos).toPoint();
217
218 QTEST_ASSERT(buttons == Qt::NoButton || buttons & Qt::MouseButtonMask);
219 QTEST_ASSERT(stateKey == 0 || stateKey & Qt::KeyboardModifierMask);
220
221 stateKey &= static_cast<unsigned int>(Qt::KeyboardModifierMask);
222 QWheelEvent we(pos, window->mapToGlobal(pos), QPoint(0, 0), QPoint(xDelta, yDelta), buttons,
223 stateKey, Qt::NoScrollPhase, false);
224 we.setTimestamp(++lastMouseTimestamp);
225
226 QSpontaneKeyEvent::setSpontaneous(&we); // hmmmm
227 if (!qApp->notify(window, &we))
228 qWarning(msg: "Wheel event not accepted by receiving window");
229 }
230#endif
231};
232
233bool QuickTestEvent::mousePress
234 (QObject *item, qreal x, qreal y, int button,
235 int modifiers, int delay)
236{
237 QWindow *view = eventWindow(item);
238 if (!view)
239 return false;
240 m_pressedButtons.setFlag(flag: Qt::MouseButton(button), on: true);
241 QtQuickTest::mouseEvent(action: QtQuickTest::MousePress, window: view, item,
242 button: Qt::MouseButton(button),
243 stateKey: Qt::KeyboardModifiers(modifiers),
244 pos: QPointF(x, y), delay);
245 return true;
246}
247
248#if QT_CONFIG(wheelevent)
249bool QuickTestEvent::mouseWheel(
250 QObject *item, qreal x, qreal y, int buttons,
251 int modifiers, int xDelta, int yDelta, int delay)
252{
253 QWindow *view = eventWindow(item);
254 if (!view)
255 return false;
256 QtQuickTest::mouseWheel(window: view, item, buttons: Qt::MouseButtons(buttons),
257 stateKey: Qt::KeyboardModifiers(modifiers),
258 pos: QPointF(x, y), xDelta, yDelta, delay);
259 return true;
260}
261#endif
262
263bool QuickTestEvent::mouseRelease
264 (QObject *item, qreal x, qreal y, int button,
265 int modifiers, int delay)
266{
267 QWindow *view = eventWindow(item);
268 if (!view)
269 return false;
270 m_pressedButtons.setFlag(flag: Qt::MouseButton(button), on: false);
271 QtQuickTest::mouseEvent(action: QtQuickTest::MouseRelease, window: view, item,
272 button: Qt::MouseButton(button),
273 stateKey: Qt::KeyboardModifiers(modifiers),
274 pos: QPointF(x, y), delay);
275 return true;
276}
277
278bool QuickTestEvent::mouseClick
279 (QObject *item, qreal x, qreal y, int button,
280 int modifiers, int delay)
281{
282 QWindow *view = eventWindow(item);
283 if (!view)
284 return false;
285 QtQuickTest::mouseEvent(action: QtQuickTest::MouseClick, window: view, item,
286 button: Qt::MouseButton(button),
287 stateKey: Qt::KeyboardModifiers(modifiers),
288 pos: QPointF(x, y), delay);
289 return true;
290}
291
292bool QuickTestEvent::mouseDoubleClick
293 (QObject *item, qreal x, qreal y, int button,
294 int modifiers, int delay)
295{
296 QWindow *view = eventWindow(item);
297 if (!view)
298 return false;
299 QtQuickTest::mouseEvent(action: QtQuickTest::MouseDoubleClick, window: view, item,
300 button: Qt::MouseButton(button),
301 stateKey: Qt::KeyboardModifiers(modifiers),
302 pos: QPointF(x, y), delay);
303 return true;
304}
305
306bool QuickTestEvent::mouseDoubleClickSequence
307 (QObject *item, qreal x, qreal y, int button,
308 int modifiers, int delay)
309{
310 QWindow *view = eventWindow(item);
311 if (!view)
312 return false;
313 QtQuickTest::mouseEvent(action: QtQuickTest::MouseDoubleClickSequence, window: view, item,
314 button: Qt::MouseButton(button),
315 stateKey: Qt::KeyboardModifiers(modifiers),
316 pos: QPointF(x, y), delay);
317 return true;
318}
319
320bool QuickTestEvent::mouseMove
321 (QObject *item, qreal x, qreal y, int delay, int buttons)
322{
323 QWindow *view = eventWindow(item);
324 if (!view)
325 return false;
326 const Qt::MouseButtons effectiveButtons = buttons ? Qt::MouseButtons(buttons) : m_pressedButtons;
327 QtQuickTest::mouseEvent(action: QtQuickTest::MouseMove, window: view, item,
328 button: Qt::MouseButton(int(effectiveButtons)), stateKey: Qt::NoModifier,
329 pos: QPointF(x, y), delay);
330 return true;
331}
332
333QWindow *QuickTestEvent::eventWindow(QObject *item)
334{
335 QWindow * window = qobject_cast<QWindow *>(o: item);
336 if (window)
337 return window;
338
339 QQuickItem *quickItem = qobject_cast<QQuickItem *>(o: item);
340 if (quickItem)
341 return quickItem->window();
342
343 QQuickItem *testParentitem = qobject_cast<QQuickItem *>(o: parent());
344 if (testParentitem)
345 return testParentitem->window();
346 return nullptr;
347}
348
349QWindow *QuickTestEvent::activeWindow()
350{
351 if (QWindow *window = QGuiApplication::focusWindow())
352 return window;
353 return eventWindow();
354}
355
356QQuickTouchEventSequence::QQuickTouchEventSequence(QuickTestEvent *testEvent, QObject *item)
357 : QObject(testEvent)
358 , m_sequence(QTest::touchEvent(window: testEvent->eventWindow(item), device: testEvent->touchDevice()))
359 , m_testEvent(testEvent)
360{
361}
362
363QObject *QQuickTouchEventSequence::press(int touchId, QObject *item, qreal x, qreal y)
364{
365 QWindow *view = m_testEvent->eventWindow(item);
366 if (view) {
367 QPointF pos(x, y);
368 QQuickItem *quickItem = qobject_cast<QQuickItem *>(o: item);
369 if (quickItem) {
370 pos = quickItem->mapToScene(point: pos);
371 }
372 m_sequence.press(touchId, pt: pos.toPoint(), window: view);
373 }
374 return this;
375}
376
377QObject *QQuickTouchEventSequence::move(int touchId, QObject *item, qreal x, qreal y)
378{
379 QWindow *view = m_testEvent->eventWindow(item);
380 if (view) {
381 QPointF pos(x, y);
382 QQuickItem *quickItem = qobject_cast<QQuickItem *>(o: item);
383 if (quickItem) {
384 pos = quickItem->mapToScene(point: pos);
385 }
386 m_sequence.move(touchId, pt: pos.toPoint(), window: view);
387 }
388 return this;
389}
390
391QObject *QQuickTouchEventSequence::release(int touchId, QObject *item, qreal x, qreal y)
392{
393 QWindow *view = m_testEvent->eventWindow(item);
394 if (view) {
395 QPointF pos(x, y);
396 QQuickItem *quickItem = qobject_cast<QQuickItem *>(o: item);
397 if (quickItem) {
398 pos = quickItem->mapToScene(point: pos);
399 }
400 m_sequence.release(touchId, pt: pos.toPoint(), window: view);
401 }
402 return this;
403}
404
405QObject *QQuickTouchEventSequence::stationary(int touchId)
406{
407 m_sequence.stationary(touchId);
408 return this;
409}
410
411QObject *QQuickTouchEventSequence::commit()
412{
413 m_sequence.commit();
414 return this;
415}
416
417/*!
418 Return a simulated touchscreen, creating one if necessary
419
420 \internal
421*/
422
423QPointingDevice *QuickTestEvent::touchDevice()
424{
425 static QPointingDevice *device(nullptr);
426
427 if (!device) {
428 device = new QPointingDevice(QLatin1String("test touchscreen"), 42,
429 QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger,
430 QInputDevice::Capability::Position, 10, 0);
431 QWindowSystemInterface::registerInputDevice(device);
432 }
433 return device;
434}
435
436/*!
437 Creates a new QQuickTouchEventSequence.
438
439 If valid, \a item determines the QWindow that touch events are sent to.
440 Test code should use touchEvent() from the QML TestCase type.
441
442 \internal
443*/
444QQuickTouchEventSequence *QuickTestEvent::touchEvent(QObject *item)
445{
446 return new QQuickTouchEventSequence(this, item);
447}
448
449QT_END_NAMESPACE
450
451#include "moc_quicktestevent_p.cpp"
452

source code of qtdeclarative/src/qmltest/quicktestevent.cpp