Unity 8
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 #if TOUCHGATE_DEBUG
26 #include <DebugHelpers.h>
27 #endif
28 
29 bool TouchGate::event(QEvent *e)
30 {
31  if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
32  touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e));
33  return true;
34  } else {
35  return QQuickItem::event(e);
36  }
37 }
38 
39 void TouchGate::touchEvent(QTouchEvent *event)
40 {
41  #if TOUCHGATE_DEBUG
42  qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
43  #endif
44  event->accept();
45 
46  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
47  bool goodToGo = true;
48  for (int i = 0; i < touchPoints.count(); ++i) {
49  const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
50 
51  if (touchPoint.state() == Qt::TouchPointPressed) {
52  Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
53  m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
54  m_touchInfoMap[touchPoint.id()].ended = false;
55  TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
56  }
57 
58  goodToGo &= m_touchInfoMap.contains(touchPoint.id())
59  && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
60 
61  if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
62  m_touchInfoMap[touchPoint.id()].ended = true;
63  }
64 
65  }
66 
67  if (goodToGo) {
68  if (m_storedEvents.isEmpty()) {
69  // let it pass through
70  dispatchTouchEventToTarget(event);
71  } else {
72  // Retain the event to ensure TouchGate dispatches them in order.
73  // Otherwise the current event would come before the stored ones, which are older.
74  #if TOUCHGATE_DEBUG
75  qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
76  qPrintable(oldestPendingTouchIdsString()));
77  #endif
78  storeTouchEvent(event);
79  }
80  } else {
81  // Retain events that have unowned touches
82  storeTouchEvent(event);
83  }
84 }
85 
86 void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
87 {
88  // TODO: Optimization: batch those actions as TouchOwnershipEvents
89  // might come one right after the other.
90 
91  Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
92 
93  TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
94 
95  if (event->gained()) {
96  #if TOUCHGATE_DEBUG
97  qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
98  #endif
99  touchInfo.ownership = OwnershipGranted;
100  } else {
101  #if TOUCHGATE_DEBUG
102  qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
103  #endif
104  m_touchInfoMap.remove(event->touchId());
105  removeTouchFromStoredEvents(event->touchId());
106  }
107 
108  dispatchFullyOwnedEvents();
109 }
110 
111 bool TouchGate::isTouchPointOwned(int touchId) const
112 {
113  return m_touchInfoMap[touchId].ownership == OwnershipGranted;
114 }
115 
116 void TouchGate::storeTouchEvent(const QTouchEvent *event)
117 {
118  #if TOUCHGATE_DEBUG
119  qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
120  #endif
121 
122  TouchEvent clonedEvent(event);
123  m_storedEvents.append(std::move(clonedEvent));
124 }
125 
126 void TouchGate::removeTouchFromStoredEvents(int touchId)
127 {
128  int i = 0;
129  while (i < m_storedEvents.count()) {
130  TouchEvent &event = m_storedEvents[i];
131  bool removed = event.removeTouch(touchId);
132 
133  if (removed && event.touchPoints.isEmpty()) {
134  m_storedEvents.removeAt(i);
135  } else {
136  ++i;
137  }
138  }
139 }
140 
141 void TouchGate::dispatchFullyOwnedEvents()
142 {
143  while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
144  TouchEvent event = m_storedEvents.takeFirst();
145  dispatchTouchEventToTarget(event);
146  }
147 }
148 
149 #if TOUCHGATE_DEBUG
150 QString TouchGate::oldestPendingTouchIdsString()
151 {
152  Q_ASSERT(!m_storedEvents.isEmpty());
153 
154  QString str;
155 
156  const auto &touchPoints = m_storedEvents.first().touchPoints;
157  for (int i = 0; i < touchPoints.count(); ++i) {
158  if (!isTouchPointOwned(touchPoints[i].id())) {
159  if (!str.isEmpty()) {
160  str.append(", ");
161  }
162  str.append(QString::number(touchPoints[i].id()));
163  }
164  }
165 
166  return str;
167 }
168 #endif
169 
170 bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const
171 {
172  for (int i = 0; i < event.touchPoints.count(); ++i) {
173  if (!isTouchPointOwned(event.touchPoints[i].id())) {
174  return false;
175  }
176  }
177 
178  return true;
179 }
180 
181 void TouchGate::setTargetItem(QQuickItem *item)
182 {
183  // TODO: changing the target item while dispatch of touch events is taking place will
184  // create a mess
185 
186  if (item == m_dispatcher.targetItem())
187  return;
188 
189  m_dispatcher.setTargetItem(item);
190  Q_EMIT targetItemChanged(item);
191 }
192 
193 void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
194 {
195  removeTouchInfoForEndedTouches(event.touchPoints);
196  m_dispatcher.dispatch(event.eventType,
197  event.device,
198  event.modifiers,
199  event.touchPoints,
200  event.window,
201  event.timestamp);
202 }
203 
204 void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
205 {
206  removeTouchInfoForEndedTouches(event->touchPoints());
207  m_dispatcher.dispatch(event->type(),
208  event->device(),
209  event->modifiers(),
210  event->touchPoints(),
211  event->window(),
212  event->timestamp());
213 }
214 
215 void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
216 {
217  for (int i = 0; i < touchPoints.size(); ++i) {\
218  const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
219 
220  if (touchPoint.state() == Qt::TouchPointReleased) {
221  Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
222  Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
223  Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
224  m_touchInfoMap.remove(touchPoint.id());
225  }
226  }
227 }
228 
229 TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
230  : eventType(event->type())
231  , device(event->device())
232  , modifiers(event->modifiers())
233  , touchPoints(event->touchPoints())
234  , target(qobject_cast<QQuickItem*>(event->target()))
235  , window(event->window())
236  , timestamp(event->timestamp())
237 {
238 }
239 
240 bool TouchGate::TouchEvent::removeTouch(int touchId)
241 {
242  bool removed = false;
243  for (int i = 0; i < touchPoints.count() && !removed; ++i) {
244  if (touchPoints[i].id() == touchId) {
245  touchPoints.removeAt(i);
246  removed = true;
247  }
248  }
249 
250  return removed;
251 }