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 QtTest module 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 #ifndef QTESTMOUSE_H
41 #define QTESTMOUSE_H
42 
43 #if 0
44 // inform syncqt
45 #pragma qt_no_master_include
46 #endif
47 
48 #include <QtTest/qttestglobal.h>
49 #include <QtTest/qtestassert.h>
50 #include <QtTest/qtestsystem.h>
51 #include <QtTest/qtestspontaneevent.h>
52 #include <QtCore/qpoint.h>
53 #include <QtCore/qstring.h>
54 #include <QtCore/qpointer.h>
55 #include <QtGui/qevent.h>
56 #include <QtGui/qwindow.h>
57 
58 #ifdef QT_WIDGETS_LIB
59 #include <QtWidgets/qapplication.h>
60 #include <QtWidgets/qwidget.h>
61 #endif
62 
63 #include <QtCore/QDebug>
64 
65 QT_BEGIN_NAMESPACE
66 
67 Q_GUI_EXPORT void qt_handleMouseEvent(QWindow *window, const QPointF &local, const QPointF &global,
68                                       Qt::MouseButtons state, Qt::MouseButton button,
69                                       QEvent::Type type, Qt::KeyboardModifiers mods, int timestamp);
70 
71 namespace QTestPrivate
72 {
73     extern Q_TESTLIB_EXPORT Qt::MouseButtons qtestMouseButtons;
74 }
75 
76 namespace QTest
77 {
78     enum MouseAction { MousePress, MouseRelease, MouseClick, MouseDClick, MouseMove };
79 
80     extern Q_TESTLIB_EXPORT Qt::MouseButton lastMouseButton; // ### unsued
81     extern Q_TESTLIB_EXPORT int lastMouseTimestamp;
82 
83     // This value is used to emulate timestamps to avoid creating double clicks by mistake.
84     // Use this constant instead of QStyleHints::mouseDoubleClickInterval property to avoid tests
85     // to depend on platform themes.
86     static const int mouseDoubleClickInterval = 500;
87 
88 /*! \internal
89 
90     This function mocks all mouse events by bypassing the windowing system. The
91     result is that the mouse events do not come from the system via Qt platform
92     plugins, but are created on the spot and immediately available for processing
93     by Qt.
94 */
95     static void mouseEvent(MouseAction action, QWindow *window, Qt::MouseButton button,
96                            Qt::KeyboardModifiers stateKey, QPoint pos, int delay=-1)
97     {
98         QTEST_ASSERT(window);
99         extern int Q_TESTLIB_EXPORT defaultMouseDelay();
100 
101         // pos is in window local coordinates
102         const QSize windowSize = window->geometry().size();
103         if (windowSize.width() <= pos.x() || windowSize.height() <= pos.y()) {
104             QTest::qWarn(qPrintable(QString::fromLatin1("Mouse event at %1, %2 occurs outside of target window (%3x%4).")
105                 .arg(pos.x()).arg(pos.y()).arg(windowSize.width()).arg(windowSize.height())));
106         }
107 
108         if (delay == -1 || delay < defaultMouseDelay())
109             delay = defaultMouseDelay();
110         if (delay > 0) {
111             QTest::qWait(delay);
112             lastMouseTimestamp += delay;
113         }
114 
115         if (pos.isNull())
116             pos = QPoint(window->width() / 2, window->height() / 2);
117 
118         QTEST_ASSERT(uint(stateKey) == 0 || stateKey & Qt::KeyboardModifierMask);
119 
120         stateKey &= static_cast<unsigned int>(Qt::KeyboardModifierMask);
121 
122         QPointF global = window->mapToGlobal(pos);
123         QPointer<QWindow> w(window);
124 
125         using namespace QTestPrivate;
126         switch (action)
127         {
128         case MouseDClick:
129             qtestMouseButtons.setFlag(button, true);
130             qt_handleMouseEvent(w, pos, global, qtestMouseButtons, button, QEvent::MouseButtonPress,
131                                 stateKey, ++lastMouseTimestamp);
132             qtestMouseButtons.setFlag(button, false);
133             qt_handleMouseEvent(w, pos, global, qtestMouseButtons, button, QEvent::MouseButtonRelease,
134                                 stateKey, ++lastMouseTimestamp);
135             Q_FALLTHROUGH();
136         case MousePress:
137         case MouseClick:
138             qtestMouseButtons.setFlag(button, true);
139             qt_handleMouseEvent(w, pos, global, qtestMouseButtons, button, QEvent::MouseButtonPress,
140                                 stateKey, ++lastMouseTimestamp);
141             lastMouseButton = button; // ### unsued
142             if (action == MousePress)
143                 break;
144             Q_FALLTHROUGH();
145         case MouseRelease:
146             qtestMouseButtons.setFlag(button, false);
147             qt_handleMouseEvent(w, pos, global, qtestMouseButtons, button, QEvent::MouseButtonRelease,
148                                 stateKey, ++lastMouseTimestamp);
149             lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
150             lastMouseButton = Qt::NoButton; // ### unsued
151             break;
152         case MouseMove:
153             qt_handleMouseEvent(w, pos, global, qtestMouseButtons, Qt::NoButton, QEvent::MouseMove,
154                                 stateKey, ++lastMouseTimestamp);
155             break;
156         default:
157             QTEST_ASSERT(false);
158         }
159         qApp->processEvents();
160     }
161 
162     inline void mousePress(QWindow *window, Qt::MouseButton button,
163                            Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
164                            QPoint pos = QPoint(), int delay=-1)
165     { mouseEvent(MousePress, window, button, stateKey, pos, delay); }
166     inline void mouseRelease(QWindow *window, Qt::MouseButton button,
167                              Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
168                              QPoint pos = QPoint(), int delay=-1)
169     { mouseEvent(MouseRelease, window, button, stateKey, pos, delay); }
170     inline void mouseClick(QWindow *window, Qt::MouseButton button,
171                            Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
172                            QPoint pos = QPoint(), int delay=-1)
173     { mouseEvent(MouseClick, window, button, stateKey, pos, delay); }
174     inline void mouseDClick(QWindow *window, Qt::MouseButton button,
175                             Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
176                             QPoint pos = QPoint(), int delay=-1)
177     { mouseEvent(MouseDClick, window, button, stateKey, pos, delay); }
178     inline void mouseMove(QWindow *window, QPoint pos = QPoint(), int delay=-1)
179     { mouseEvent(MouseMove, window, Qt::NoButton, Qt::KeyboardModifiers(), pos, delay); }
180 
181 #ifdef QT_WIDGETS_LIB
182     static void mouseEvent(MouseAction action, QWidget *widget, Qt::MouseButton button,
183                            Qt::KeyboardModifiers stateKey, QPoint pos, int delay=-1)
184     {
185         QTEST_ASSERT(widget);
186 
187         if (pos.isNull())
188             pos = widget->rect().center();
189 
190 #ifdef QTEST_QPA_MOUSE_HANDLING
191         QWindow *w = widget->window()->windowHandle();
192         QTEST_ASSERT(w);
193         mouseEvent(action, w, button, stateKey, w->mapFromGlobal(widget->mapToGlobal(pos)), delay);
194 #else
195         extern int Q_TESTLIB_EXPORT defaultMouseDelay();
196 
197         if (delay == -1 || delay < defaultMouseDelay())
198             delay = defaultMouseDelay();
199         if (delay > 0) {
200             QTest::qWait(delay);
201             lastMouseTimestamp += delay;
202         }
203 
204         if (action == MouseClick) {
205             mouseEvent(MousePress, widget, button, stateKey, pos);
206             mouseEvent(MouseRelease, widget, button, stateKey, pos);
207             return;
208         }
209 
210         QTEST_ASSERT(stateKey == 0 || stateKey & Qt::KeyboardModifierMask);
211 
212         stateKey &= static_cast<unsigned int>(Qt::KeyboardModifierMask);
213 
214         QMouseEvent me(QEvent::User, QPoint(), Qt::LeftButton, button, stateKey);
215         switch (action)
216         {
217             case MousePress:
218                 me = QMouseEvent(QEvent::MouseButtonPress, pos, widget->mapToGlobal(pos), button, button, stateKey);
219                 me.setTimestamp(++lastMouseTimestamp);
220                 break;
221             case MouseRelease:
222                 me = QMouseEvent(QEvent::MouseButtonRelease, pos, widget->mapToGlobal(pos), button, Qt::MouseButton(), stateKey);
223                 me.setTimestamp(++lastMouseTimestamp);
224                 lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
225                 break;
226             case MouseDClick:
227                 me = QMouseEvent(QEvent::MouseButtonDblClick, pos, widget->mapToGlobal(pos), button, button, stateKey);
228                 me.setTimestamp(++lastMouseTimestamp);
229                 break;
230             case MouseMove:
231                 QCursor::setPos(widget->mapToGlobal(pos));
232 #ifdef Q_OS_MAC
233                 QTest::qWait(20);
234 #else
235                 qApp->processEvents();
236 #endif
237                 return;
238             default:
239                 QTEST_ASSERT(false);
240         }
241         QSpontaneKeyEvent::setSpontaneous(&me);
242         if (!qApp->notify(widget, &me)) {
243             static const char *const mouseActionNames[] =
244                 { "MousePress", "MouseRelease", "MouseClick", "MouseDClick", "MouseMove" };
245             QString warning = QString::fromLatin1("Mouse event \"%1\" not accepted by receiving widget");
246             QTest::qWarn(warning.arg(QString::fromLatin1(mouseActionNames[static_cast<int>(action)])).toLatin1().data());
247         }
248 #endif
249     }
250 
251     inline void mousePress(QWidget *widget, Qt::MouseButton button,
252                            Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
253                            QPoint pos = QPoint(), int delay=-1)
254     { mouseEvent(MousePress, widget, button, stateKey, pos, delay); }
255     inline void mouseRelease(QWidget *widget, Qt::MouseButton button,
256                              Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
257                              QPoint pos = QPoint(), int delay=-1)
258     { mouseEvent(MouseRelease, widget, button, stateKey, pos, delay); }
259     inline void mouseClick(QWidget *widget, Qt::MouseButton button,
260                            Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
261                            QPoint pos = QPoint(), int delay=-1)
262     { mouseEvent(MouseClick, widget, button, stateKey, pos, delay); }
263     inline void mouseDClick(QWidget *widget, Qt::MouseButton button,
264                             Qt::KeyboardModifiers stateKey = Qt::KeyboardModifiers(),
265                             QPoint pos = QPoint(), int delay=-1)
266     { mouseEvent(MouseDClick, widget, button, stateKey, pos, delay); }
267     inline void mouseMove(QWidget *widget, QPoint pos = QPoint(), int delay=-1)
268     { mouseEvent(MouseMove, widget, Qt::NoButton, Qt::KeyboardModifiers(), pos, delay); }
269 #endif // QT_WIDGETS_LIB
270 }
271 
272 QT_END_NAMESPACE
273 
274 #endif // QTESTMOUSE_H
275