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