]> rtime.felk.cvut.cz Git - coffee/qtwebbrowser.git/blob - src/app/touchmockingapplication.cpp
Use qml extension plugin
[coffee/qtwebbrowser.git] / src / app / touchmockingapplication.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtBrowser project.
7 **
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.
16 **
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.
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 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.
32 **
33 **
34 ** $QT_END_LICENSE$
35 **
36 ****************************************************************************/
37
38 #include "touchmockingapplication.h"
39
40 #include <qpa/qwindowsysteminterface.h>
41
42 #include <QRegExp>
43 #include <QEvent>
44 #include <QMouseEvent>
45 #include <QTouchEvent>
46
47 #include "engine.h"
48
49 using namespace utils;
50
51 static inline QRectF touchRectForPosition(QPointF centerPoint)
52 {
53     QRectF touchRect(0, 0, 40, 40);
54     touchRect.moveCenter(centerPoint);
55     return touchRect;
56 }
57
58 TouchMockingApplication::TouchMockingApplication(int& argc, char** argv)
59     : QGuiApplication(argc, argv)
60     , m_realTouchEventReceived(false)
61     , m_pendingFakeTouchEventCount(0)
62     , m_holdingControl(false)
63 {
64 }
65
66 bool TouchMockingApplication::notify(QObject* target, QEvent* event)
67 {
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.
70
71     if (!event->spontaneous() || m_realTouchEventReceived)
72         return QGuiApplication::notify(target, event);
73
74     if (isTouchEvent(event)) {
75         if (m_pendingFakeTouchEventCount)
76             --m_pendingFakeTouchEventCount;
77         else
78             m_realTouchEventReceived = true;
79         return QGuiApplication::notify(target, event);
80     }
81
82     BrowserWindow* window = qobject_cast<BrowserWindow*>(target);
83     if (!window)
84         return QGuiApplication::notify(target, event);
85
86     m_holdingControl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
87
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);
93             } else
94                 m_touchPoints[id].setState(Qt::TouchPointStationary);
95
96         sendTouchEvent(window, m_heldTouchPoints.isEmpty() ? QEvent::TouchEnd : QEvent::TouchUpdate, static_cast<QKeyEvent*>(event)->timestamp());
97     }
98
99     if (isMouseEvent(event)) {
100         const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
101
102         QTouchEvent::TouchPoint touchPoint;
103         touchPoint.setPressure(1);
104
105         QEvent::Type touchType = QEvent::None;
106
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;
113             } else {
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;
118                 else
119                     touchType = QEvent::TouchBegin;
120             }
121             break;
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.
128                 event->accept();
129                 return true;
130             }
131             touchType = QEvent::TouchUpdate;
132             touchPoint.setId(mouseEvent->buttons());
133             touchPoint.setState(Qt::TouchPointMoved);
134             break;
135         case QEvent::MouseButtonRelease:
136             // Check if any buttons are still held down after this event.
137             if (mouseEvent->buttons())
138                 touchType = QEvent::TouchUpdate;
139             else
140                 touchType = QEvent::TouchEnd;
141             touchPoint.setId(mouseEvent->button());
142             touchPoint.setState(Qt::TouchPointReleased);
143             break;
144         case QEvent::MouseButtonDblClick:
145             // Eat double-clicks, their accompanying press event is all we need.
146             event->accept();
147             return true;
148         default:
149             Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type");
150         }
151
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);
159
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.
164             event->accept();
165             return true;
166         }
167
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);
172         }
173
174         Q_ASSERT(touchType != QEvent::None);
175
176         if (!sendTouchEvent(window, touchType, mouseEvent->timestamp()))
177             return QGuiApplication::notify(target, event);
178
179         event->accept();
180         return true;
181     }
182
183     return QGuiApplication::notify(target, event);
184 }
185
186 void TouchMockingApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QTouchEvent::TouchPoint touchPoint, Qt::MouseButton mouseButton)
187 {
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))
191         return;
192
193     if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
194         m_heldTouchPoints.insert(mouseButton);
195         return;
196     }
197
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()));
204
205     if (touchPoint.state() == Qt::TouchPointPressed)
206         touchPoint.setStartScenePos(mouseEvent->screenPos());
207     else {
208         const QTouchEvent::TouchPoint& oldTouchPoint = m_touchPoints[mouseButton];
209         touchPoint.setStartScenePos(oldTouchPoint.startScenePos());
210         touchPoint.setLastPos(oldTouchPoint.pos());
211         touchPoint.setLastScenePos(oldTouchPoint.scenePos());
212     }
213
214     // Update current touch-point.
215     touchPoint.setId(mouseButton);
216     m_touchPoints.insert(mouseButton, touchPoint);
217 }
218
219 bool TouchMockingApplication::sendTouchEvent(BrowserWindow* window, QEvent::Type type, ulong timestamp)
220 {
221     static QTouchDevice* device = 0;
222     if (!device) {
223         device = new QTouchDevice;
224         device->setType(QTouchDevice::TouchScreen);
225         QWindowSystemInterface::registerTouchDevice(device);
226     }
227
228     m_pendingFakeTouchEventCount++;
229
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();
234
235     QTouchEvent event(type, device, Qt::NoModifier, touchPointStates, currentTouchPoints);
236     event.setTimestamp(timestamp);
237     event.setAccepted(false);
238
239     QGuiApplication::notify(window, &event);
240
241     window->updateVisualMockTouchPoints(m_holdingControl ? currentTouchPoints : QList<QTouchEvent::TouchPoint>());
242
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());
247     }
248
249     return event.isAccepted();
250 }
251