on bbce293 help sign in
unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp
402 lines 15 kB history raw
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 */
17#include "TouchDispatcher.h"
19#include <QGuiApplication>
20#include <QScopedPointer>
21#include <QStyleHints>
23#pragma GCC diagnostic push
24#pragma GCC diagnostic ignored "-pedantic"
25#include <private/qquickitem_p.h>
26#pragma GCC diagnostic pop
28#define TOUCHDISPATCHER_DEBUG 0
30#if TOUCHDISPATCHER_DEBUG
31#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
32#include <DebugHelpers.h>
33#else // TOUCHDISPATCHER_DEBUG
34#define ugDebug(params) ((void)0)
35#endif // TOUCHDISPATCHER_DEBUG
37TouchDispatcher::TouchDispatcher()
38 : m_status(NoActiveTouch)
39 , m_touchMouseId(-1)
40 , m_touchMousePressTimestamp(0)
44void TouchDispatcher::setTargetItem(QQuickItem *target)
46 if (target != m_targetItem) {
47 m_targetItem = target;
48 if (m_status != NoActiveTouch) {
49 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
50 setStatus(TargetRejectedTouches);
51 }
52 }
55void TouchDispatcher::dispatch(QTouchDevice *device,
56 Qt::KeyboardModifiers modifiers,
57 const QList<QTouchEvent::TouchPoint> &touchPoints,
58 QWindow *window,
59 ulong timestamp)
61 if (m_targetItem.isNull()) {
62 qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
63 return;
64 }
66 QEvent::Type eventType = resolveEventType(touchPoints);
68 if (eventType == QEvent::TouchBegin) {
69 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
71 } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
73 if (m_status == DeliveringTouchEvents) {
74 dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp);
75 } else if (m_status == DeliveringMouseEvents) {
76 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
77 } else {
78 Q_ASSERT(m_status == TargetRejectedTouches);
79 ugDebug("Not dispatching touch event to " << m_targetItem.data()
80 << "because it already rejected the touch stream.");
81 // Do nothing
82 }
84 if (eventType == QEvent::TouchEnd) {
85 setStatus(NoActiveTouch);
86 m_touchMouseId = -1;
87 }
89 } else {
90 // Should never happen
91 qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
92 Q_ASSERT(false);
93 return;
94 }
97void TouchDispatcher::dispatchTouchBegin(
98 QTouchDevice *device,
99 Qt::KeyboardModifiers modifiers,
100 const QList<QTouchEvent::TouchPoint> &touchPoints,
101 QWindow *window,
102 ulong timestamp)
104 Q_ASSERT(m_status == NoActiveTouch);
105 QQuickItem *targetItem = m_targetItem.data();
107 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
108 ugDebug("Cannot dispatch touch event to " << targetItem << " because it's disabled or invisible.");
109 return;
110 }
112 // Map touch points to targetItem coordinates
113 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
114 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
116 QScopedPointer<QTouchEvent> touchEvent(
117 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
120 ugDebug("dispatching " << qPrintable(touchEventToString(touchEvent.data()))
121 << " to " << targetItem);
122 QCoreApplication::sendEvent(targetItem, touchEvent.data());
125 if (touchEvent->isAccepted()) {
126 ugDebug("Item accepted the touch event.");
127 setStatus(DeliveringTouchEvents);
128 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
129 ugDebug("Item rejected the touch event. Trying a QMouseEvent");
130 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
131 QScopedPointer<QMouseEvent> mouseEvent(
132 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
133 modifiers, false /* transformNeeded */));
134 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
136 ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
137 << " to " << m_targetItem.data());
138 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
139 if (mouseEvent->isAccepted()) {
140 ugDebug("Item accepted the QMouseEvent.");
141 setStatus(DeliveringMouseEvents);
142 m_touchMouseId = targetTouchPoints.at(0).id();
144 if (checkIfDoubleClicked(timestamp)) {
145 QScopedPointer<QMouseEvent> doubleClickEvent(
146 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
147 modifiers, false /* transformNeeded */));
148 ugDebug("dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
149 << " to " << m_targetItem.data());
150 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
151 }
153 } else {
154 ugDebug("Item rejected the QMouseEvent.");
155 setStatus(TargetRejectedTouches);
156 }
157 } else {
158 ugDebug("Item rejected the touch event and does not accept mouse buttons.");
159 setStatus(TargetRejectedTouches);
160 }
163void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
164 QTouchDevice *device,
165 Qt::KeyboardModifiers modifiers,
166 const QList<QTouchEvent::TouchPoint> &touchPoints,
167 QWindow *window,
168 ulong timestamp)
170 QQuickItem *targetItem = m_targetItem.data();
172 // Map touch points to targetItem coordinates
173 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
174 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
176 QScopedPointer<QTouchEvent> eventForTargetItem(
177 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
180 ugDebug("dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
181 << " to " << targetItem);
182 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
185void TouchDispatcher::dispatchAsMouse(
186 QTouchDevice * /*device*/,
187 Qt::KeyboardModifiers modifiers,
188 const QList<QTouchEvent::TouchPoint> &touchPoints,
189 ulong timestamp)
191 // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
193 Q_ASSERT(!touchPoints.isEmpty());
195 const QTouchEvent::TouchPoint *touchMouse = nullptr;
197 if (m_touchMouseId != -1) {
198 for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
199 const auto &touchPoint = touchPoints.at(i);
200 if (touchPoint.id() == m_touchMouseId) {
201 touchMouse = &touchPoint;
202 }
203 }
205 Q_ASSERT(touchMouse);
206 if (!touchMouse) {
207 // should not happen, but deal with it just in case.
208 qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
209 m_touchMouseId);
210 m_touchMouseId = touchPoints.at(0).id();
211 touchMouse = &touchPoints.at(0);
212 }
213 } else {
214 // Try to find a new touch for mouse emulation
215 for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
216 const auto &touchPoint = touchPoints.at(i);
217 if (touchPoint.state() == Qt::TouchPointPressed) {
218 touchMouse = &touchPoint;
219 m_touchMouseId = touchMouse->id();
220 }
221 }
222 }
224 if (touchMouse) {
225 QEvent::Type eventType;
226 if (touchMouse->state() == Qt::TouchPointPressed) {
227 eventType = QEvent::MouseButtonPress;
228 } if (touchMouse->state() == Qt::TouchPointReleased) {
229 eventType = QEvent::MouseButtonRelease;
230 m_touchMouseId = -1;
231 } else {
232 eventType = QEvent::MouseMove;
233 }
235 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
236 true /* transformNeeded */));
238 ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
239 << " to " << m_targetItem.data());
240 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
241 }
244QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
245 QTouchDevice *device,
246 Qt::KeyboardModifiers modifiers,
247 const QList<QTouchEvent::TouchPoint> &touchPoints,
248 QWindow *window,
249 ulong timestamp)
251 Qt::TouchPointStates eventStates = 0;
252 for (int i = 0; i < touchPoints.count(); i++)
253 eventStates |= touchPoints[i].state();
254 // if all points have the same state, set the event type accordingly
255 switch (eventStates) {
256 case Qt::TouchPointPressed:
257 eventType = QEvent::TouchBegin;
258 break;
259 case Qt::TouchPointReleased:
260 eventType = QEvent::TouchEnd;
261 break;
262 default:
263 eventType = QEvent::TouchUpdate;
264 break;
265 }
267 QTouchEvent *touchEvent = new QTouchEvent(eventType);
268 touchEvent->setWindow(window);
269 touchEvent->setTarget(m_targetItem.data());
270 touchEvent->setDevice(device);
271 touchEvent->setModifiers(modifiers);
272 touchEvent->setTouchPoints(touchPoints);
273 touchEvent->setTouchPointStates(eventStates);
274 touchEvent->setTimestamp(timestamp);
275 touchEvent->accept();
276 return touchEvent;
279// NB: From QQuickWindow
280void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
282 QMatrix4x4 transformMatrix(transform);
283 for (int i=0; i<touchPoints.count(); i++) {
284 QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
285 touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
286 touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
287 touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
288 touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
289 }
292// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
293QMouseEvent *TouchDispatcher::touchToMouseEvent(
294 QEvent::Type type, const QTouchEvent::TouchPoint &p,
295 ulong timestamp, Qt::KeyboardModifiers modifiers,
296 bool transformNeeded)
298 QQuickItem *item = m_targetItem.data();
300 // The touch point local position and velocity are not yet transformed.
301 QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
302 p.scenePos(), p.screenPos(), Qt::LeftButton,
303 (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
304 modifiers);
305 me->setAccepted(true);
306 me->setTimestamp(timestamp);
307 QVector2D transformedVelocity = p.velocity();
308 if (transformNeeded) {
309 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
310 QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
311 transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
312 }
314 // Add these later if needed:
315 //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
316 //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
317 return me;
320/*
321 Copied from qquickwindow.cpp which has:
322 Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
323 Under GPL 3.0 license.
324*/
325bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
327 bool doubleClicked;
329 if (m_touchMousePressTimestamp == 0) {
330 // just initialize the variable
331 m_touchMousePressTimestamp = newPressEventTimestamp;
332 doubleClicked = false;
333 } else {
334 ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
335 ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
336 mouseDoubleClickInterval());
337 doubleClicked = timeBetweenPresses < doubleClickInterval;
338 if (doubleClicked) {
339 m_touchMousePressTimestamp = 0;
340 } else {
341 m_touchMousePressTimestamp = newPressEventTimestamp;
342 }
343 }
345 return doubleClicked;
348void TouchDispatcher::setStatus(Status status)
350 if (status != m_status) {
351 #if TOUCHDISPATCHER_DEBUG
352 switch (status) {
353 case NoActiveTouch:
354 ugDebug("status = NoActiveTouch");
355 break;
356 case DeliveringTouchEvents:
357 ugDebug("status = DeliveringTouchEvents");
358 break;
359 case DeliveringMouseEvents:
360 ugDebug("status = DeliveringMouseEvents");
361 break;
362 case TargetRejectedTouches:
363 ugDebug("status = TargetRejectedTouches");
364 break;
365 default:
366 ugDebug("status = " << status);
367 break;
368 }
369 #endif
370 m_status = status;
371 }
374void TouchDispatcher::reset()
376 setStatus(NoActiveTouch);
377 m_touchMouseId = -1;
378 m_touchMousePressTimestamp =0;
381QEvent::Type TouchDispatcher::resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints)
383 QEvent::Type eventType;
385 Qt::TouchPointStates eventStates = 0;
386 for (int i = 0; i < touchPoints.count(); i++)
387 eventStates |= touchPoints[i].state();
389 switch (eventStates) {
390 case Qt::TouchPointPressed:
391 eventType = QEvent::TouchBegin;
392 break;
393 case Qt::TouchPointReleased:
394 eventType = QEvent::TouchEnd;
395 break;
396 default:
397 eventType = QEvent::TouchUpdate;
398 break;
399 }
401 return eventType;