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