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