on ee042fd help sign in
unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp
367 lines 14 kB history raw
1/*
2 * Copyright (C) 2014 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#include <DebugHelpers.h>
32#endif
34TouchDispatcher::TouchDispatcher()
35 : m_status(NoActiveTouch)
36 , m_touchMouseId(-1)
37 , m_touchMousePressTimestamp(0)
41void TouchDispatcher::setTargetItem(QQuickItem *target)
43 if (target != m_targetItem) {
44 m_targetItem = target;
45 if (m_status != NoActiveTouch) {
46 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
47 m_status = TargetRejectedTouches;
48 }
49 }
52void TouchDispatcher::dispatch(QEvent::Type eventType,
53 QTouchDevice *device,
54 Qt::KeyboardModifiers modifiers,
55 const QList<QTouchEvent::TouchPoint> &touchPoints,
56 QWindow *window,
57 ulong timestamp)
59 if (m_targetItem.isNull()) {
60 qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
61 return;
62 }
64 if (eventType == QEvent::TouchBegin) {
65 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
67 } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
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 #if TOUCHDISPATCHER_DEBUG
76 qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
77 << "because it already rejected the touch stream.";
78 #endif
79 // Do nothing
80 }
82 if (eventType == QEvent::TouchEnd) {
83 m_status = NoActiveTouch;
84 m_touchMouseId = -1;
85 }
87 } else {
88 // Should never happen
89 qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
90 Q_ASSERT(false);
91 return;
92 }
95void TouchDispatcher::dispatchTouchBegin(
96 QTouchDevice *device,
97 Qt::KeyboardModifiers modifiers,
98 const QList<QTouchEvent::TouchPoint> &touchPoints,
99 QWindow *window,
100 ulong timestamp)
102 Q_ASSERT(m_status == NoActiveTouch);
103 QQuickItem *targetItem = m_targetItem.data();
105 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
106 #if TOUCHDISPATCHER_DEBUG
107 qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
108 << "because it's disabled or invisible.";
109 #endif
110 return;
111 }
113 // Map touch points to targetItem coordinates
114 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
115 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
117 QScopedPointer<QTouchEvent> touchEvent(
118 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
121 #if TOUCHDISPATCHER_DEBUG
122 qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
123 << "to" << targetItem;
124 #endif
125 QCoreApplication::sendEvent(targetItem, touchEvent.data());
128 if (touchEvent->isAccepted()) {
129 #if TOUCHDISPATCHER_DEBUG
130 qDebug() << "[TouchDispatcher] Item accepted the touch event.";
131 #endif
132 m_status = DeliveringTouchEvents;
133 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
134 #if TOUCHDISPATCHER_DEBUG
135 qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
136 #endif
137 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
138 QScopedPointer<QMouseEvent> mouseEvent(
139 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
140 modifiers, false /* transformNeeded */));
141 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
143 #if TOUCHDISPATCHER_DEBUG
144 qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
145 << "to" << m_targetItem.data();
146 #endif
147 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
148 if (mouseEvent->isAccepted()) {
149 #if TOUCHDISPATCHER_DEBUG
150 qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
151 #endif
152 m_status = DeliveringMouseEvents;
153 m_touchMouseId = targetTouchPoints.at(0).id();
155 if (checkIfDoubleClicked(timestamp)) {
156 QScopedPointer<QMouseEvent> doubleClickEvent(
157 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
158 modifiers, false /* transformNeeded */));
159 #if TOUCHDISPATCHER_DEBUG
160 qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
161 << "to" << m_targetItem.data();
162 #endif
163 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
164 }
166 } else {
167 #if TOUCHDISPATCHER_DEBUG
168 qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
169 #endif
170 m_status = TargetRejectedTouches;
171 }
172 } else {
173 #if TOUCHDISPATCHER_DEBUG
174 qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
175 #endif
176 m_status = TargetRejectedTouches;
177 }
180void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
181 QTouchDevice *device,
182 Qt::KeyboardModifiers modifiers,
183 const QList<QTouchEvent::TouchPoint> &touchPoints,
184 QWindow *window,
185 ulong timestamp)
187 QQuickItem *targetItem = m_targetItem.data();
189 // Map touch points to targetItem coordinates
190 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
191 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
193 QScopedPointer<QTouchEvent> eventForTargetItem(
194 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
197 #if TOUCHDISPATCHER_DEBUG
198 qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
199 << "to" << targetItem;
200 #endif
201 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
204void TouchDispatcher::dispatchAsMouse(
205 QTouchDevice * /*device*/,
206 Qt::KeyboardModifiers modifiers,
207 const QList<QTouchEvent::TouchPoint> &touchPoints,
208 ulong timestamp)
210 // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
212 Q_ASSERT(!touchPoints.isEmpty());
214 const QTouchEvent::TouchPoint *touchMouse = nullptr;
216 if (m_touchMouseId != -1) {
217 for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
218 const auto &touchPoint = touchPoints.at(i);
219 if (touchPoint.id() == m_touchMouseId) {
220 touchMouse = &touchPoint;
221 }
222 }
224 Q_ASSERT(touchMouse);
225 if (!touchMouse) {
226 // should not happen, but deal with it just in case.
227 qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
228 m_touchMouseId);
229 m_touchMouseId = touchPoints.at(0).id();
230 touchMouse = &touchPoints.at(0);
231 }
232 } else {
233 // Try to find a new touch for mouse emulation
234 for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
235 const auto &touchPoint = touchPoints.at(i);
236 if (touchPoint.state() == Qt::TouchPointPressed) {
237 touchMouse = &touchPoint;
238 m_touchMouseId = touchMouse->id();
239 }
240 }
241 }
243 if (touchMouse) {
244 QEvent::Type eventType;
245 if (touchMouse->state() == Qt::TouchPointPressed) {
246 eventType = QEvent::MouseButtonPress;
247 } if (touchMouse->state() == Qt::TouchPointReleased) {
248 eventType = QEvent::MouseButtonRelease;
249 m_touchMouseId = -1;
250 } else {
251 eventType = QEvent::MouseMove;
252 }
254 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
255 true /* transformNeeded */));
257 #if TOUCHDISPATCHER_DEBUG
258 qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
259 << "to" << m_targetItem.data();
260 #endif
261 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
262 }
265QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
266 QTouchDevice *device,
267 Qt::KeyboardModifiers modifiers,
268 const QList<QTouchEvent::TouchPoint> &touchPoints,
269 QWindow *window,
270 ulong timestamp)
272 Qt::TouchPointStates eventStates = 0;
273 for (int i = 0; i < touchPoints.count(); i++)
274 eventStates |= touchPoints[i].state();
275 // if all points have the same state, set the event type accordingly
276 switch (eventStates) {
277 case Qt::TouchPointPressed:
278 eventType = QEvent::TouchBegin;
279 break;
280 case Qt::TouchPointReleased:
281 eventType = QEvent::TouchEnd;
282 break;
283 default:
284 eventType = QEvent::TouchUpdate;
285 break;
286 }
288 QTouchEvent *touchEvent = new QTouchEvent(eventType);
289 touchEvent->setWindow(window);
290 touchEvent->setTarget(m_targetItem.data());
291 touchEvent->setDevice(device);
292 touchEvent->setModifiers(modifiers);
293 touchEvent->setTouchPoints(touchPoints);
294 touchEvent->setTouchPointStates(eventStates);
295 touchEvent->setTimestamp(timestamp);
296 touchEvent->accept();
297 return touchEvent;
300// NB: From QQuickWindow
301void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
303 QMatrix4x4 transformMatrix(transform);
304 for (int i=0; i<touchPoints.count(); i++) {
305 QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
306 touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
307 touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
308 touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
309 touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
310 }
313// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
314QMouseEvent *TouchDispatcher::touchToMouseEvent(
315 QEvent::Type type, const QTouchEvent::TouchPoint &p,
316 ulong timestamp, Qt::KeyboardModifiers modifiers,
317 bool transformNeeded)
319 QQuickItem *item = m_targetItem.data();
321 // The touch point local position and velocity are not yet transformed.
322 QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
323 p.scenePos(), p.screenPos(), Qt::LeftButton,
324 (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
325 modifiers);
326 me->setAccepted(true);
327 me->setTimestamp(timestamp);
328 QVector2D transformedVelocity = p.velocity();
329 if (transformNeeded) {
330 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
331 QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
332 transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
333 }
335 // Add these later if needed:
336 //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
337 //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
338 return me;
341/*
342 Copied from qquickwindow.cpp which has:
343 Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
344 Under GPL 3.0 license.
345*/
346bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
348 bool doubleClicked;
350 if (m_touchMousePressTimestamp == 0) {
351 // just initialize the variable
352 m_touchMousePressTimestamp = newPressEventTimestamp;
353 doubleClicked = false;
354 } else {
355 ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
356 ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
357 mouseDoubleClickInterval());
358 doubleClicked = timeBetweenPresses < doubleClickInterval;
359 if (doubleClicked) {
360 m_touchMousePressTimestamp = 0;
361 } else {
362 m_touchMousePressTimestamp = newPressEventTimestamp;
363 }
364 }
366 return doubleClicked;