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