Unity 8
 All Classes Functions
TouchDispatcher.cpp
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  */
16 
17 #include "TouchDispatcher.h"
18 
19 #include <QGuiApplication>
20 #include <QScopedPointer>
21 #include <QStyleHints>
22 
23 #pragma GCC diagnostic push
24 #pragma GCC diagnostic ignored "-pedantic"
25 #include <private/qquickitem_p.h>
26 #pragma GCC diagnostic pop
27 
28 #define TOUCHDISPATCHER_DEBUG 0
29 
30 #if TOUCHDISPATCHER_DEBUG
31 #include <DebugHelpers.h>
32 #endif
33 
34 TouchDispatcher::TouchDispatcher()
35  : m_status(NoActiveTouch)
36  , m_touchMouseId(-1)
37  , m_touchMousePressTimestamp(0)
38 {
39 }
40 
41 void TouchDispatcher::setTargetItem(QQuickItem *target)
42 {
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  }
50 }
51 
52 void TouchDispatcher::dispatch(QEvent::Type eventType,
53  QTouchDevice *device,
54  Qt::KeyboardModifiers modifiers,
55  const QList<QTouchEvent::TouchPoint> &touchPoints,
56  QWindow *window,
57  ulong timestamp)
58 {
59  if (m_targetItem.isNull()) {
60  qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
61  return;
62  }
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  #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  }
81 
82  if (eventType == QEvent::TouchEnd) {
83  m_status = NoActiveTouch;
84  m_touchMouseId = -1;
85  }
86 
87  } else {
88  // Should never happen
89  qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
90  Q_ASSERT(false);
91  return;
92  }
93 }
94 
95 void TouchDispatcher::dispatchTouchBegin(
96  QTouchDevice *device,
97  Qt::KeyboardModifiers modifiers,
98  const QList<QTouchEvent::TouchPoint> &touchPoints,
99  QWindow *window,
100  ulong timestamp)
101 {
102  Q_ASSERT(m_status == NoActiveTouch);
103  QQuickItem *targetItem = m_targetItem.data();
104 
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  }
112 
113  // Map touch points to targetItem coordinates
114  QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
115  transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
116 
117  QScopedPointer<QTouchEvent> touchEvent(
118  createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
119 
120 
121  #if TOUCHDISPATCHER_DEBUG
122  qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
123  << "to" << targetItem;
124  #endif
125  QCoreApplication::sendEvent(targetItem, touchEvent.data());
126 
127 
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);
142 
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();
154 
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  }
165 
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  }
178 }
179 
180 void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
181  QTouchDevice *device,
182  Qt::KeyboardModifiers modifiers,
183  const QList<QTouchEvent::TouchPoint> &touchPoints,
184  QWindow *window,
185  ulong timestamp)
186 {
187  QQuickItem *targetItem = m_targetItem.data();
188 
189  // Map touch points to targetItem coordinates
190  QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
191  transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
192 
193  QScopedPointer<QTouchEvent> eventForTargetItem(
194  createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
195 
196 
197  #if TOUCHDISPATCHER_DEBUG
198  qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
199  << "to" << targetItem;
200  #endif
201  QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
202 }
203 
204 void TouchDispatcher::dispatchAsMouse(
205  QTouchDevice * /*device*/,
206  Qt::KeyboardModifiers modifiers,
207  const QList<QTouchEvent::TouchPoint> &touchPoints,
208  ulong timestamp)
209 {
210  // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
211 
212  Q_ASSERT(!touchPoints.isEmpty());
213 
214  const QTouchEvent::TouchPoint *touchMouse = nullptr;
215 
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  }
223 
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  }
242 
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  }
253 
254  QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
255  true /* transformNeeded */));
256 
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  }
263 }
264 
265 QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
266  QTouchDevice *device,
267  Qt::KeyboardModifiers modifiers,
268  const QList<QTouchEvent::TouchPoint> &touchPoints,
269  QWindow *window,
270  ulong timestamp)
271 {
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  }
287 
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;
298 }
299 
300 // NB: From QQuickWindow
301 void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
302 {
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  }
311 }
312 
313 // Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
314 QMouseEvent *TouchDispatcher::touchToMouseEvent(
315  QEvent::Type type, const QTouchEvent::TouchPoint &p,
316  ulong timestamp, Qt::KeyboardModifiers modifiers,
317  bool transformNeeded)
318 {
319  QQuickItem *item = m_targetItem.data();
320 
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  }
334 
335  // Add these later if needed:
336  //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
337  //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
338  return me;
339 }
340 
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 */
346 bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
347 {
348  bool doubleClicked;
349 
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  }
365 
366  return doubleClicked;
367 }