Unity 8
 All Classes Functions
TouchGate.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 "TouchGate.h"
18 
19 #include <QCoreApplication>
20 #include <QDebug>
21 
22 #include <TouchOwnershipEvent.h>
23 #include <TouchRegistry.h>
24 
25 #pragma GCC diagnostic push
26 #pragma GCC diagnostic ignored "-pedantic"
27 #include <private/qquickitem_p.h>
28 #pragma GCC diagnostic pop
29 
30 #if TOUCHGATE_DEBUG
31 #include <DebugHelpers.h>
32 #endif
33 
34 
35 bool TouchGate::event(QEvent *e)
36 {
37  if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
38  touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e));
39  return true;
40  } else {
41  return QQuickItem::event(e);
42  }
43 }
44 
45 void TouchGate::touchEvent(QTouchEvent *event)
46 {
47  #if TOUCHGATE_DEBUG
48  qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
49  #endif
50  event->accept();
51 
52  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
53  bool goodToGo = true;
54  for (int i = 0; i < touchPoints.count(); ++i) {
55  const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
56 
57  if (touchPoint.state() == Qt::TouchPointPressed) {
58  Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
59  m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
60  m_touchInfoMap[touchPoint.id()].ended = false;
61  TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
62  }
63 
64  goodToGo &= m_touchInfoMap.contains(touchPoint.id())
65  && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
66 
67  if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
68  m_touchInfoMap[touchPoint.id()].ended = true;
69  }
70 
71  }
72 
73  if (goodToGo) {
74  if (m_storedEvents.isEmpty()) {
75  // let it pass through
76  dispatchTouchEventToTarget(event);
77  } else {
78  // Retain the event to ensure TouchGate dispatches them in order.
79  // Otherwise the current event would come before the stored ones, which are older.
80  #if TOUCHGATE_DEBUG
81  qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
82  qPrintable(oldestPendingTouchIdsString()));
83  #endif
84  storeTouchEvent(event);
85  }
86  } else {
87  // Retain events that have unowned touches
88  storeTouchEvent(event);
89  }
90 }
91 
92 void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
93 {
94  // TODO: Optimization: batch those actions as TouchOwnershipEvents
95  // might come one right after the other.
96 
97  Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
98 
99  TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
100 
101  if (event->gained()) {
102  #if TOUCHGATE_DEBUG
103  qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
104  #endif
105  touchInfo.ownership = OwnershipGranted;
106  } else {
107  #if TOUCHGATE_DEBUG
108  qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
109  #endif
110  m_touchInfoMap.remove(event->touchId());
111  removeTouchFromStoredEvents(event->touchId());
112  }
113 
114  dispatchFullyOwnedEvents();
115 }
116 
117 bool TouchGate::isTouchPointOwned(int touchId) const
118 {
119  return m_touchInfoMap[touchId].ownership == OwnershipGranted;
120 }
121 
122 void TouchGate::storeTouchEvent(const QTouchEvent *event)
123 {
124  #if TOUCHGATE_DEBUG
125  qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
126  #endif
127 
128  TouchEvent clonedEvent(event);
129  m_storedEvents.append(std::move(clonedEvent));
130 }
131 
132 void TouchGate::removeTouchFromStoredEvents(int touchId)
133 {
134  int i = 0;
135  while (i < m_storedEvents.count()) {
136  TouchEvent &event = m_storedEvents[i];
137  bool removed = event.removeTouch(touchId);
138 
139  if (removed && event.touchPoints.isEmpty()) {
140  m_storedEvents.removeAt(i);
141  } else {
142  ++i;
143  }
144  }
145 }
146 
147 void TouchGate::dispatchFullyOwnedEvents()
148 {
149  while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
150  TouchEvent event = m_storedEvents.takeFirst();
151  dispatchTouchEventToTarget(event);
152  }
153 }
154 
155 #if TOUCHGATE_DEBUG
156 QString TouchGate::oldestPendingTouchIdsString()
157 {
158  Q_ASSERT(!m_storedEvents.isEmpty());
159 
160  QString str;
161 
162  const auto &touchPoints = m_storedEvents.first().touchPoints;
163  for (int i = 0; i < touchPoints.count(); ++i) {
164  if (!isTouchPointOwned(touchPoints[i].id())) {
165  if (!str.isEmpty()) {
166  str.append(", ");
167  }
168  str.append(QString::number(touchPoints[i].id()));
169  }
170  }
171 
172  return str;
173 }
174 #endif
175 
176 bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const
177 {
178  for (int i = 0; i < event.touchPoints.count(); ++i) {
179  if (!isTouchPointOwned(event.touchPoints[i].id())) {
180  return false;
181  }
182  }
183 
184  return true;
185 }
186 
187 void TouchGate::setTargetItem(QQuickItem *item)
188 {
189  // TODO: changing the target item while dispatch of touch events is taking place will
190  // create a mess
191 
192  if (item == m_targetItem.data())
193  return;
194 
195  m_targetItem = item;
196  Q_EMIT targetItemChanged(item);
197 }
198 
199 void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
200 {
201  dispatchTouchEventToTarget(event.eventType,
202  event.device,
203  event.modifiers,
204  event.touchPoints,
205  event.target,
206  event.window,
207  event.timestamp);
208 }
209 
210 void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
211 {
212  dispatchTouchEventToTarget(event->type(),
213  event->device(),
214  event->modifiers(),
215  event->touchPoints(),
216  event->target(),
217  event->window(),
218  event->timestamp());
219 }
220 
221 void TouchGate::dispatchTouchEventToTarget(QEvent::Type eventType,
222  QTouchDevice *device,
223  Qt::KeyboardModifiers modifiers,
224  const QList<QTouchEvent::TouchPoint> &touchPoints,
225  QObject *target,
226  QWindow *window,
227  ulong timestamp)
228 {
229  removeTouchInfoForEndedTouches(touchPoints);
230 
231  if (m_targetItem.isNull()) {
232  qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
233  return;
234  }
235 
236  QQuickItem *targetItem = m_targetItem.data();
237 
238  if (!targetItem->isEnabled() || !targetItem->isVisible()) {
239  #if TOUCHGATE_DEBUG
240  qDebug() << "[TouchGate] Cannot dispatch touch event to" << targetItem
241  << "because it's disabled or invisible.";
242  #endif
243  return;
244  }
245 
246  // Map touch points to targetItem coordinates
247  QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
248  transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
249  QTouchEvent *eventForTargetItem = createQTouchEvent(eventType, device, modifiers, targetTouchPoints,
250  target, window, timestamp);
251 
252  #if TOUCHGATE_DEBUG
253  qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem))
254  << "to" << targetItem;
255  #endif
256 
257  QCoreApplication::sendEvent(targetItem, eventForTargetItem);
258 
259  delete eventForTargetItem;
260 }
261 
262 // NB: From QQuickWindow
263 void TouchGate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
264 {
265  QMatrix4x4 transformMatrix(transform);
266  for (int i=0; i<touchPoints.count(); i++) {
267  QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
268  touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
269  touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
270  touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
271  touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
272  }
273 }
274 
275 QTouchEvent *TouchGate::createQTouchEvent(QEvent::Type eventType,
276  QTouchDevice *device,
277  Qt::KeyboardModifiers modifiers,
278  const QList<QTouchEvent::TouchPoint> &touchPoints,
279  QObject *target,
280  QWindow *window,
281  ulong timestamp)
282 {
283  Qt::TouchPointStates eventStates = 0;
284  for (int i = 0; i < touchPoints.count(); i++)
285  eventStates |= touchPoints[i].state();
286  // if all points have the same state, set the event type accordingly
287  switch (eventStates) {
288  case Qt::TouchPointPressed:
289  eventType = QEvent::TouchBegin;
290  break;
291  case Qt::TouchPointReleased:
292  eventType = QEvent::TouchEnd;
293  break;
294  default:
295  eventType = QEvent::TouchUpdate;
296  break;
297  }
298 
299  QTouchEvent *touchEvent = new QTouchEvent(eventType);
300  touchEvent->setWindow(window);
301  touchEvent->setTarget(target);
302  touchEvent->setDevice(device);
303  touchEvent->setModifiers(modifiers);
304  touchEvent->setTouchPoints(touchPoints);
305  touchEvent->setTouchPointStates(eventStates);
306  touchEvent->setTimestamp(timestamp);
307  touchEvent->accept();
308  return touchEvent;
309 }
310 
311 void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
312 {
313  for (int i = 0; i < touchPoints.size(); ++i) {\
314  const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
315 
316  if (touchPoint.state() == Qt::TouchPointReleased) {
317  Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
318  Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
319  Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
320  m_touchInfoMap.remove(touchPoint.id());
321  }
322  }
323 }
324 
325 TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
326  : eventType(event->type())
327  , device(event->device())
328  , modifiers(event->modifiers())
329  , touchPoints(event->touchPoints())
330  , target(event->target())
331  , window(event->window())
332  , timestamp(event->timestamp())
333 {
334 }
335 
336 bool TouchGate::TouchEvent::removeTouch(int touchId)
337 {
338  bool removed = false;
339  for (int i = 0; i < touchPoints.count() && !removed; ++i) {
340  if (touchPoints[i].id() == touchId) {
341  touchPoints.removeAt(i);
342  removed = true;
343  }
344  }
345 
346  return removed;
347 }