1 /****************************************************************************
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
6 ** This file is part of the QtBrowser project.
8 ** $QT_BEGIN_LICENSE:GPL$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 2 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.GPLv2 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU General Public License version 2 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 3.0 as published by the Free Software
28 ** Foundation and appearing in the file LICENSE.GPL included in the
29 ** packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 3.0 requirements will be
31 ** met: http://www.gnu.org/copyleft/gpl.html.
36 ****************************************************************************/
38 #include "touchmockingapplication.h"
40 #include <qpa/qwindowsysteminterface.h>
44 #include <QMouseEvent>
45 #include <QTouchEvent>
49 using namespace utils;
51 static inline QRectF touchRectForPosition(QPointF centerPoint)
53 QRectF touchRect(0, 0, 40, 40);
54 touchRect.moveCenter(centerPoint);
58 TouchMockingApplication::TouchMockingApplication(int& argc, char** argv)
59 : QGuiApplication(argc, argv)
60 , m_realTouchEventReceived(false)
61 , m_pendingFakeTouchEventCount(0)
62 , m_holdingControl(false)
66 bool TouchMockingApplication::notify(QObject* target, QEvent* event)
68 // We try to be smart, if we received real touch event, we are probably on a device
69 // with touch screen, and we should not have touch mocking.
71 if (!event->spontaneous() || m_realTouchEventReceived)
72 return QGuiApplication::notify(target, event);
74 if (isTouchEvent(event)) {
75 if (m_pendingFakeTouchEventCount)
76 --m_pendingFakeTouchEventCount;
78 m_realTouchEventReceived = true;
79 return QGuiApplication::notify(target, event);
82 BrowserWindow* window = qobject_cast<BrowserWindow*>(target);
84 return QGuiApplication::notify(target, event);
86 m_holdingControl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
88 if (event->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(event)->key() == Qt::Key_Control) {
89 foreach (int id, m_heldTouchPoints)
90 if (m_touchPoints.contains(id) && !QGuiApplication::mouseButtons().testFlag(Qt::MouseButton(id))) {
91 m_touchPoints[id].setState(Qt::TouchPointReleased);
92 m_heldTouchPoints.remove(id);
94 m_touchPoints[id].setState(Qt::TouchPointStationary);
96 sendTouchEvent(window, m_heldTouchPoints.isEmpty() ? QEvent::TouchEnd : QEvent::TouchUpdate, static_cast<QKeyEvent*>(event)->timestamp());
99 if (isMouseEvent(event)) {
100 const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
102 QTouchEvent::TouchPoint touchPoint;
103 touchPoint.setPressure(1);
105 QEvent::Type touchType = QEvent::None;
107 switch (mouseEvent->type()) {
108 case QEvent::MouseButtonPress:
109 touchPoint.setId(mouseEvent->button());
110 if (m_touchPoints.contains(touchPoint.id())) {
111 touchPoint.setState(Qt::TouchPointMoved);
112 touchType = QEvent::TouchUpdate;
114 touchPoint.setState(Qt::TouchPointPressed);
115 // Check if more buttons are held down than just the event triggering one.
116 if (mouseEvent->buttons() > mouseEvent->button())
117 touchType = QEvent::TouchUpdate;
119 touchType = QEvent::TouchBegin;
122 case QEvent::MouseMove:
123 if (!mouseEvent->buttons()) {
124 // We have to swallow the event instead of propagating it,
125 // since we avoid sending the mouse release events and if the
126 // Flickable is the mouse grabber it would receive the event
127 // and would move the content.
131 touchType = QEvent::TouchUpdate;
132 touchPoint.setId(mouseEvent->buttons());
133 touchPoint.setState(Qt::TouchPointMoved);
135 case QEvent::MouseButtonRelease:
136 // Check if any buttons are still held down after this event.
137 if (mouseEvent->buttons())
138 touchType = QEvent::TouchUpdate;
140 touchType = QEvent::TouchEnd;
141 touchPoint.setId(mouseEvent->button());
142 touchPoint.setState(Qt::TouchPointReleased);
144 case QEvent::MouseButtonDblClick:
145 // Eat double-clicks, their accompanying press event is all we need.
149 Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type");
152 // A move can have resulted in multiple buttons, so we need check them individually.
153 if (touchPoint.id() & Qt::LeftButton)
154 updateTouchPoint(mouseEvent, touchPoint, Qt::LeftButton);
155 if (touchPoint.id() & Qt::MidButton)
156 updateTouchPoint(mouseEvent, touchPoint, Qt::MidButton);
157 if (touchPoint.id() & Qt::RightButton)
158 updateTouchPoint(mouseEvent, touchPoint, Qt::RightButton);
160 if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
161 // We avoid sending the release event because the Flickable is
162 // listening to mouse events and would start a bounce-back
163 // animation if it received a mouse release.
168 // Update states for all other touch-points
169 for (QHash<int, QTouchEvent::TouchPoint>::iterator it = m_touchPoints.begin(), end = m_touchPoints.end(); it != end; ++it) {
170 if (!(it.value().id() & touchPoint.id()))
171 it.value().setState(Qt::TouchPointStationary);
174 Q_ASSERT(touchType != QEvent::None);
176 if (!sendTouchEvent(window, touchType, mouseEvent->timestamp()))
177 return QGuiApplication::notify(target, event);
183 return QGuiApplication::notify(target, event);
186 void TouchMockingApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QTouchEvent::TouchPoint touchPoint, Qt::MouseButton mouseButton)
188 // Ignore inserting additional touch points if Ctrl isn't held because it produces
189 // inconsistent touch events and results in assers in the gesture recognizers.
190 if (!m_holdingControl && m_touchPoints.size() && !m_touchPoints.contains(mouseButton))
193 if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
194 m_heldTouchPoints.insert(mouseButton);
198 // Gesture recognition uses the screen position for the initial threshold
199 // but since the canvas translates touch events we actually need to pass
200 // the screen position as the scene position to deliver the appropriate
201 // coordinates to the target.
202 touchPoint.setRect(touchRectForPosition(mouseEvent->localPos()));
203 touchPoint.setSceneRect(touchRectForPosition(mouseEvent->screenPos()));
205 if (touchPoint.state() == Qt::TouchPointPressed)
206 touchPoint.setStartScenePos(mouseEvent->screenPos());
208 const QTouchEvent::TouchPoint& oldTouchPoint = m_touchPoints[mouseButton];
209 touchPoint.setStartScenePos(oldTouchPoint.startScenePos());
210 touchPoint.setLastPos(oldTouchPoint.pos());
211 touchPoint.setLastScenePos(oldTouchPoint.scenePos());
214 // Update current touch-point.
215 touchPoint.setId(mouseButton);
216 m_touchPoints.insert(mouseButton, touchPoint);
219 bool TouchMockingApplication::sendTouchEvent(BrowserWindow* window, QEvent::Type type, ulong timestamp)
221 static QTouchDevice* device = 0;
223 device = new QTouchDevice;
224 device->setType(QTouchDevice::TouchScreen);
225 QWindowSystemInterface::registerTouchDevice(device);
228 m_pendingFakeTouchEventCount++;
230 const QList<QTouchEvent::TouchPoint>& currentTouchPoints = m_touchPoints.values();
231 Qt::TouchPointStates touchPointStates = 0;
232 foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints)
233 touchPointStates |= touchPoint.state();
235 QTouchEvent event(type, device, Qt::NoModifier, touchPointStates, currentTouchPoints);
236 event.setTimestamp(timestamp);
237 event.setAccepted(false);
239 QGuiApplication::notify(window, &event);
241 window->updateVisualMockTouchPoints(m_holdingControl ? currentTouchPoints : QList<QTouchEvent::TouchPoint>());
243 // Get rid of touch-points that are no longer valid
244 foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints) {
245 if (touchPoint.state() == Qt::TouchPointReleased)
246 m_touchPoints.remove(touchPoint.id());
249 return event.isAccepted();