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

source code of qtdeclarative/src/imports/testlib/quicktestevent.cpp