Lomiri
Loading...
Searching...
No Matches
TouchDispatcher.cpp
1/*
2 * Copyright (C) 2014-2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "TouchDispatcher.h"
18
19#include <QGuiApplication>
20#include <QScopedPointer>
21#include <QStyleHints>
22#include <private/qquickitem_p.h>
23
24#define TOUCHDISPATCHER_DEBUG 0
25
26#if TOUCHDISPATCHER_DEBUG
27#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
28#include <DebugHelpers.h>
29#else // TOUCHDISPATCHER_DEBUG
30#define ugDebug(params) ((void)0)
31#endif // TOUCHDISPATCHER_DEBUG
32
33TouchDispatcher::TouchDispatcher()
34 : m_status(NoActiveTouch)
35 , m_touchMouseId(-1)
36 , m_touchMousePressTimestamp(0)
37{
38}
39
40void TouchDispatcher::setTargetItem(QQuickItem *target)
41{
42 if (target != m_targetItem) {
43 m_targetItem = target;
44 if (m_status != NoActiveTouch) {
45 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
46 setStatus(TargetRejectedTouches);
47 }
48 }
49}
50
51void TouchDispatcher::dispatch(QTouchDevice *device,
52 Qt::KeyboardModifiers modifiers,
53 const QList<QTouchEvent::TouchPoint> &touchPoints,
54 QWindow *window,
55 ulong timestamp)
56{
57 if (m_targetItem.isNull()) {
58 qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
59 return;
60 }
61
62 QEvent::Type eventType = resolveEventType(touchPoints);
63
64 if (eventType == QEvent::TouchBegin) {
65 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
66
67 } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
68
69 if (m_status == DeliveringTouchEvents) {
70 dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp);
71 } else if (m_status == DeliveringMouseEvents) {
72 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
73 } else {
74 Q_ASSERT(m_status == TargetRejectedTouches);
75 ugDebug("Not dispatching touch event to " << m_targetItem.data()
76 << "because it already rejected the touch stream.");
77 // Do nothing
78 }
79
80 if (eventType == QEvent::TouchEnd) {
81 setStatus(NoActiveTouch);
82 m_touchMouseId = -1;
83 }
84
85 } else {
86 // Should never happen
87 qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
88 Q_ASSERT(false);
89 return;
90 }
91}
92
93void TouchDispatcher::dispatchTouchBegin(
94 QTouchDevice *device,
95 Qt::KeyboardModifiers modifiers,
96 const QList<QTouchEvent::TouchPoint> &touchPoints,
97 QWindow *window,
98 ulong timestamp)
99{
100 Q_ASSERT(m_status == NoActiveTouch);
101 QQuickItem *targetItem = m_targetItem.data();
102
103 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
104 ugDebug("Cannot dispatch touch event to " << targetItem << " because it's disabled or invisible.");
105 return;
106 }
107
108 // Map touch points to targetItem coordinates
109 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
110 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
111
112 QScopedPointer<QTouchEvent> touchEvent(
113 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
114
115
116 ugDebug("dispatching " << qPrintable(touchEventToString(touchEvent.data()))
117 << " to " << targetItem);
118 QCoreApplication::sendEvent(targetItem, touchEvent.data());
119
120
121 if (touchEvent->isAccepted()) {
122 ugDebug("Item accepted the touch event.");
123 setStatus(DeliveringTouchEvents);
124 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
125 ugDebug("Item rejected the touch event. Trying a QMouseEvent");
126 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
127 QScopedPointer<QMouseEvent> mouseEvent(
128 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
129 modifiers, false /* transformNeeded */));
130 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
131
132 ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
133 << " to " << m_targetItem.data());
134 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
135 if (mouseEvent->isAccepted()) {
136 ugDebug("Item accepted the QMouseEvent.");
137 setStatus(DeliveringMouseEvents);
138 m_touchMouseId = targetTouchPoints.at(0).id();
139
140 if (checkIfDoubleClicked(timestamp)) {
141 QScopedPointer<QMouseEvent> doubleClickEvent(
142 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
143 modifiers, false /* transformNeeded */));
144 ugDebug("dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
145 << " to " << m_targetItem.data());
146 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
147 }
148
149 } else {
150 ugDebug("Item rejected the QMouseEvent.");
151 setStatus(TargetRejectedTouches);
152 }
153 } else {
154 ugDebug("Item rejected the touch event and does not accept mouse buttons.");
155 setStatus(TargetRejectedTouches);
156 }
157}
158
159void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
160 QTouchDevice *device,
161 Qt::KeyboardModifiers modifiers,
162 const QList<QTouchEvent::TouchPoint> &touchPoints,
163 QWindow *window,
164 ulong timestamp)
165{
166 QQuickItem *targetItem = m_targetItem.data();
167
168 // Map touch points to targetItem coordinates
169 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
170 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
171
172 QScopedPointer<QTouchEvent> eventForTargetItem(
173 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
174
175
176 ugDebug("dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
177 << " to " << targetItem);
178 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
179}
180
181void TouchDispatcher::dispatchAsMouse(
182 QTouchDevice * /*device*/,
183 Qt::KeyboardModifiers modifiers,
184 const QList<QTouchEvent::TouchPoint> &touchPoints,
185 ulong timestamp)
186{
187 // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
188
189 Q_ASSERT(!touchPoints.isEmpty());
190
191 const QTouchEvent::TouchPoint *touchMouse = nullptr;
192
193 if (m_touchMouseId != -1) {
194 for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
195 const auto &touchPoint = touchPoints.at(i);
196 if (touchPoint.id() == m_touchMouseId) {
197 touchMouse = &touchPoint;
198 }
199 }
200
201 Q_ASSERT(touchMouse);
202 if (!touchMouse) {
203 // should not happen, but deal with it just in case.
204 qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
205 m_touchMouseId);
206 m_touchMouseId = touchPoints.at(0).id();
207 touchMouse = &touchPoints.at(0);
208 }
209 } else {
210 // Try to find a new touch for mouse emulation
211 for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
212 const auto &touchPoint = touchPoints.at(i);
213 if (touchPoint.state() == Qt::TouchPointPressed) {
214 touchMouse = &touchPoint;
215 m_touchMouseId = touchMouse->id();
216 }
217 }
218 }
219
220 if (touchMouse) {
221 QEvent::Type eventType;
222 if (touchMouse->state() == Qt::TouchPointPressed) {
223 eventType = QEvent::MouseButtonPress;
224 } else if (touchMouse->state() == Qt::TouchPointReleased) {
225 eventType = QEvent::MouseButtonRelease;
226 m_touchMouseId = -1;
227 } else {
228 eventType = QEvent::MouseMove;
229 }
230
231 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
232 true /* transformNeeded */));
233
234 ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
235 << " to " << m_targetItem.data());
236 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
237 }
238}
239
240QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
241 QTouchDevice *device,
242 Qt::KeyboardModifiers modifiers,
243 const QList<QTouchEvent::TouchPoint> &touchPoints,
244 QWindow *window,
245 ulong timestamp)
246{
247 Qt::TouchPointStates eventStates = 0;
248 for (int i = 0; i < touchPoints.count(); i++)
249 eventStates |= touchPoints[i].state();
250 // if all points have the same state, set the event type accordingly
251 switch (eventStates) {
252 case Qt::TouchPointPressed:
253 eventType = QEvent::TouchBegin;
254 break;
255 case Qt::TouchPointReleased:
256 eventType = QEvent::TouchEnd;
257 break;
258 default:
259 eventType = QEvent::TouchUpdate;
260 break;
261 }
262
263 QTouchEvent *touchEvent = new QTouchEvent(eventType);
264 touchEvent->setWindow(window);
265 touchEvent->setTarget(m_targetItem.data());
266 touchEvent->setDevice(device);
267 touchEvent->setModifiers(modifiers);
268 touchEvent->setTouchPoints(touchPoints);
269 touchEvent->setTouchPointStates(eventStates);
270 touchEvent->setTimestamp(timestamp);
271 touchEvent->accept();
272 return touchEvent;
273}
274
275// NB: From QQuickWindow
276void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
277{
278 QMatrix4x4 transformMatrix(transform);
279 for (int i=0; i<touchPoints.count(); i++) {
280 QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
281 QPointF pos = touchPoint.scenePos ();
282 touchPoint.setPos(transform.map(pos));
283 touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
284 touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
285 touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
286 }
287}
288
289// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
290QMouseEvent *TouchDispatcher::touchToMouseEvent(
291 QEvent::Type type, const QTouchEvent::TouchPoint &p,
292 ulong timestamp, Qt::KeyboardModifiers modifiers,
293 bool transformNeeded)
294{
295 QQuickItem *item = m_targetItem.data();
296
297 // The touch point local position and velocity are not yet transformed.
298 QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
299 p.scenePos(), p.screenPos(), Qt::LeftButton,
300 (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
301 modifiers);
302 me->setAccepted(true);
303 me->setTimestamp(timestamp);
304 QVector2D transformedVelocity = p.velocity();
305 if (transformNeeded) {
306 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
307 QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
308 transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
309 }
310
311 // Add these later if needed:
312 //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
313 //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
314 return me;
315}
316
317/*
318 Copied from qquickwindow.cpp which has:
319 Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
320 Under GPL 3.0 license.
321*/
322bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
323{
324 bool doubleClicked;
325
326 if (m_touchMousePressTimestamp == 0) {
327 // just initialize the variable
328 m_touchMousePressTimestamp = newPressEventTimestamp;
329 doubleClicked = false;
330 } else {
331 ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
332 ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
333 mouseDoubleClickInterval());
334 doubleClicked = timeBetweenPresses < doubleClickInterval;
335 if (doubleClicked) {
336 m_touchMousePressTimestamp = 0;
337 } else {
338 m_touchMousePressTimestamp = newPressEventTimestamp;
339 }
340 }
341
342 return doubleClicked;
343}
344
345void TouchDispatcher::setStatus(Status status)
346{
347 if (status != m_status) {
348 #if TOUCHDISPATCHER_DEBUG
349 switch (status) {
350 case NoActiveTouch:
351 ugDebug("status = NoActiveTouch");
352 break;
353 case DeliveringTouchEvents:
354 ugDebug("status = DeliveringTouchEvents");
355 break;
356 case DeliveringMouseEvents:
357 ugDebug("status = DeliveringMouseEvents");
358 break;
359 case TargetRejectedTouches:
360 ugDebug("status = TargetRejectedTouches");
361 break;
362 default:
363 ugDebug("status = " << status);
364 break;
365 }
366 #endif
367 m_status = status;
368 }
369}
370
371void TouchDispatcher::reset()
372{
373 setStatus(NoActiveTouch);
374 m_touchMouseId = -1;
375 m_touchMousePressTimestamp =0;
376}
377
378QEvent::Type TouchDispatcher::resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints)
379{
380 QEvent::Type eventType;
381
382 Qt::TouchPointStates eventStates = 0;
383 for (int i = 0; i < touchPoints.count(); i++)
384 eventStates |= touchPoints[i].state();
385
386 switch (eventStates) {
387 case Qt::TouchPointPressed:
388 eventType = QEvent::TouchBegin;
389 break;
390 case Qt::TouchPointReleased:
391 eventType = QEvent::TouchEnd;
392 break;
393 default:
394 eventType = QEvent::TouchUpdate;
395 break;
396 }
397
398 return eventType;
399}