on 276c910 help sign in
unity8/libs/UbuntuGestures/TouchRegistry.cpp
500 lines 17 kB history raw
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 */
17#include "TouchRegistry.h"
19#include <QCoreApplication>
20#include <QDebug>
22#pragma GCC diagnostic push
23#pragma GCC diagnostic ignored "-pedantic"
24#include <private/qquickitem_p.h>
25#pragma GCC diagnostic pop
27#include "CandidateInactivityTimer.h"
28#include "Timer.h"
29#include "TouchOwnershipEvent.h"
30#include "UnownedTouchEvent.h"
32#define TOUCHREGISTRY_DEBUG 0
34#if TOUCHREGISTRY_DEBUG
35 #include "DebugHelpers.h"
36 #define UG_DEBUG qDebug() << "[TouchRegistry]"
37#endif // TOUCHREGISTRY_DEBUG
39using namespace UbuntuGestures;
41TouchRegistry *TouchRegistry::m_instance = nullptr;
43TouchRegistry::TouchRegistry(QObject *parent)
44 : TouchRegistry(parent, new TimerFactory)
48TouchRegistry::TouchRegistry(QObject *parent, AbstractTimerFactory *timerFactory)
49 : QObject(parent)
50 , m_inDispatchLoop(false)
51 , m_timerFactory(timerFactory)
53 if (m_instance == nullptr) {
54 m_instance = this;
55 } else {
56 qFatal("Cannot have more than one instance of TouchRegistry. It must be a singleton.");
57 }
60TouchRegistry::~TouchRegistry()
62 Q_ASSERT(m_instance != nullptr);
63 m_instance = nullptr;
64 delete m_timerFactory;
67void TouchRegistry::update(const QTouchEvent *event)
69 #if TOUCHREGISTRY_DEBUG
70 UG_DEBUG << "got" << qPrintable(touchEventToString(event));
71 #endif
73 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
74 for (int i = 0; i < touchPoints.count(); ++i) {
75 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
76 if (touchPoint.state() == Qt::TouchPointPressed) {
77 TouchInfo &touchInfo = m_touchInfoPool.getEmptySlot();
78 touchInfo.init(touchPoint.id());
79 } else if (touchPoint.state() == Qt::TouchPointReleased) {
80 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchPoint.id());
82 touchInfo->physicallyEnded = true;
83 }
84 }
86 deliverTouchUpdatesToUndecidedCandidatesAndWatchers(event);
88 freeEndedTouchInfos();
91void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event)
93 // TODO: Look into how we could optimize this whole thing.
94 // Although it's not really a problem as we should have at most two candidates
95 // for each point and there should not be many active points at any given moment.
96 // But having three nested for-loops does scare.
98 // TODO: Don't send it to the object that is already receiving the regular event
99 // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view)
100 // Problem is, we cannnot easily get this information.
102 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
104 // Maps an item to the touches in this event he should be informed about.
105 // E.g.: a QTouchEvent might have three touches but a given item might be interested in only
106 // one of them. So he will get a UnownedTouchEvent from this QTouchEvent containing only that
107 // touch point.
108 QMap<QQuickItem*, QList<int>> touchIdsForItems;
110 // Build touchIdsForItems
111 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
112 if (touchInfo->isOwned() && touchInfo->watchers.isEmpty())
113 return true;
115 for (int j = 0; j < updatedTouchPoints.count(); ++j) {
116 if (updatedTouchPoints[j].id() == touchInfo->id) {
117 if (!touchInfo->isOwned()) {
118 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
119 CandidateInfo &candidate = touchInfo->candidates[i];
120 Q_ASSERT(!candidate.item.isNull());
121 touchIdsForItems[candidate.item.data()].append(touchInfo->id);
122 }
123 }
125 const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers;
126 for (int i = 0; i < watchers.count(); ++i) {
127 if (!watchers[i].isNull()) {
128 touchIdsForItems[watchers[i].data()].append(touchInfo->id);
129 }
130 }
132 return true;
133 }
134 }
136 return true;
137 });
139 // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
140 // from the event handler callback.
141 m_inDispatchLoop = true;
142 auto it = touchIdsForItems.constBegin();
143 while (it != touchIdsForItems.constEnd()) {
144 QQuickItem *item = it.key();
145 const QList<int> &touchIds = it.value();
146 dispatchPointsToItem(event, touchIds, item);
147 ++it;
148 };
149 m_inDispatchLoop = false;
152void TouchRegistry::freeEndedTouchInfos()
154 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
155 if (touchInfo->ended()) {
156 m_touchInfoPool.freeSlot(touchInfo);
157 }
158 return true;
159 });
162/*
163 Extracts the touches with the given touchIds from event and send them in a
164 UnownedTouchEvent to the given item
165 */
166void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
167 QQuickItem *item)
169 Qt::TouchPointStates touchPointStates = 0;
170 QList<QTouchEvent::TouchPoint> touchPoints;
172 const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints();
174 QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform();
175 QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform);
177 for (int i = 0; i < allTouchPoints.count(); ++i) {
178 const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i];
179 if (touchIds.contains(originalTouchPoint.id())) {
180 QTouchEvent::TouchPoint touchPoint = originalTouchPoint;
182 translateTouchPointFromScreenToWindowCoords(touchPoint);
184 // Set the point's local coordinates to that of the item
185 touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect()));
186 touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos()));
187 touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos()));
188 touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D());
190 touchPoints.append(touchPoint);
191 touchPointStates |= touchPoint.state();
192 }
193 }
195 QTouchEvent *eventForItem = new QTouchEvent(event->type(),
196 event->device(),
197 event->modifiers(),
198 touchPointStates,
199 touchPoints);
200 eventForItem->setWindow(event->window());
201 eventForItem->setTimestamp(event->timestamp());
202 eventForItem->setTarget(event->target());
204 UnownedTouchEvent unownedTouchEvent(eventForItem);
206 #if TOUCHREGISTRY_DEBUG
207 UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem))
208 << "to" << item;
209 #endif
211 QCoreApplication::sendEvent(item, &unownedTouchEvent);
214void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint)
216 touchPoint.setScreenRect(touchPoint.sceneRect());
217 touchPoint.setStartScreenPos(touchPoint.startScenePos());
218 touchPoint.setLastScreenPos(touchPoint.lastScenePos());
220 touchPoint.setSceneRect(touchPoint.rect());
221 touchPoint.setStartScenePos(touchPoint.startPos());
222 touchPoint.setLastScenePos(touchPoint.lastPos());
225bool TouchRegistry::eventFilter(QObject *watched, QEvent *event)
227 Q_UNUSED(watched);
229 switch (event->type()) {
230 case QEvent::TouchBegin:
231 case QEvent::TouchUpdate:
232 case QEvent::TouchEnd:
233 case QEvent::TouchCancel:
234 update(static_cast<QTouchEvent*>(event));
235 break;
236 default:
237 // do nothing
238 break;
239 }
241 // Do not filter out the event. i.e., let it be handled further as
242 // we're just monitoring events
243 return false;
246void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
248 #if TOUCHREGISTRY_DEBUG
249 UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
250 #endif
252 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
253 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
255 if (touchInfo->isOwned()) {
256 qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned");
257 return;
258 }
260 // TODO: Check if candidate already exists
262 CandidateInfo candidateInfo;
263 candidateInfo.undecided = true;
264 candidateInfo.item = candidate;
265 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
266 *m_timerFactory,
267 this);
268 connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted,
269 this, &TouchRegistry::rejectCandidateOwnerForTouch);
271 touchInfo->candidates.append(candidateInfo);
274void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
276 #if TOUCHREGISTRY_DEBUG
277 UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
278 #endif
280 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
281 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
283 // TODO: Check if watcher already exists
285 touchInfo->watchers.append(watcher);
288void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
290 #if TOUCHREGISTRY_DEBUG
291 UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
292 #endif
294 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
295 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
297 int indexRemoved = -1;
299 // TODO: check if the candidate is in fact the owner of the touch
301 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
302 CandidateInfo &candidateInfo = touchInfo->candidates[i];
303 if (candidateInfo.item == candidate) {
304 Q_ASSERT(i > 0 || candidateInfo.undecided);
305 if (i == 0 && !candidateInfo.undecided) {
306 qCritical("TouchRegistry: touch owner is being removed.");
307 }
308 delete candidateInfo.inactivityTimer;
309 candidateInfo.inactivityTimer = nullptr;
310 touchInfo->candidates.removeAt(i);
311 indexRemoved = i;
312 }
313 }
315 if (indexRemoved == 0) {
316 // the top candidate has been removed. if the new top candidate
317 // wants the touch let him know he's now the owner.
318 if (touchInfo->isOwned()) {
319 touchInfo->notifyCandidatesOfOwnershipResolution();
320 }
321 }
323 if (!m_inDispatchLoop && touchInfo->ended()) {
324 m_touchInfoPool.freeSlot(touchInfo);
325 }
328void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
330 #if TOUCHREGISTRY_DEBUG
331 UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
332 #endif
334 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
335 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
337 Q_ASSERT(!touchInfo->isOwned());
339 int candidateIndex = -1;
340 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
341 CandidateInfo &candidateInfo = touchInfo->candidates[i];
342 if (candidateInfo.item == candidate) {
343 candidateInfo.undecided = false;
344 delete candidateInfo.inactivityTimer;
345 candidateInfo.inactivityTimer = nullptr;
346 candidateIndex = i;
347 break;
348 }
349 }
351 // add it as a candidate if not present yet
352 if (candidateIndex < 0) {
353 CandidateInfo candidateInfo;
354 candidateInfo.undecided = false;
355 candidateInfo.item = candidate;
356 candidateInfo.inactivityTimer = nullptr;
357 touchInfo->candidates.append(candidateInfo);
358 // it's the last one
359 candidateIndex = touchInfo->candidates.count() - 1;
360 }
362 // If it's the top candidate it means it's now the owner. Let
363 // it know about it.
364 if (candidateIndex == 0) {
365 touchInfo->notifyCandidatesOfOwnershipResolution();
366 }
369Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
371 Pool<TouchInfo>::Iterator touchInfo;
373 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
374 if (someTouchInfo->id == id) {
375 touchInfo = someTouchInfo;
376 return false;
377 } else {
378 return true;
379 }
380 });
382 return touchInfo;
386void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
388 // NB: It's technically possible that candidate is a dangling pointer at this point.
389 // Although that would most likely be due to a bug in our code.
390 // In any case, only dereference it after it's confirmed that it indeed exists.
392 #if TOUCHREGISTRY_DEBUG
393 UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
394 #endif
396 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
397 if (!touchInfo) {
398 #if TOUCHREGISTRY_DEBUG
399 UG_DEBUG << "Failed to find TouchInfo for id" << id;
400 #endif
401 return;
402 }
404 int rejectedCandidateIndex = -1;
406 // Check if the given candidate is valid and still undecided
407 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
408 CandidateInfo &candidateInfo = touchInfo->candidates[i];
409 if (candidateInfo.item == candidate) {
410 Q_ASSERT(i > 0 || candidateInfo.undecided);
411 if (i == 0 && !candidateInfo.undecided) {
412 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
413 << ") as it already owns touch" << id;
414 return;
415 } else {
416 // we found the guy and it's all fine.
417 rejectedCandidateIndex = i;
418 }
419 }
420 }
422 // If we reached this point it's because the given candidate exists and is indeed undecided.
424 Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
426 {
427 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
428 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
429 }
431 touchInfo->candidates.removeAt(rejectedCandidateIndex);
433 if (rejectedCandidateIndex == 0) {
434 // the top candidate has been removed. if the new top candidate
435 // wants the touch let him know he's now the owner.
436 if (touchInfo->isOwned()) {
437 touchInfo->notifyCandidatesOfOwnershipResolution();
438 }
439 }
442////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
444TouchRegistry::TouchInfo::TouchInfo(int id)
446 init(id);
449void TouchRegistry::TouchInfo::reset()
451 id = -1;
453 for (int i = 0; i < candidates.count(); ++i) {
454 CandidateInfo &candidate = candidates[i];
455 delete candidate.inactivityTimer;
456 candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
457 }
460void TouchRegistry::TouchInfo::init(int id)
462 this->id = id;
463 physicallyEnded = false;
464 candidates.clear();
465 watchers.clear();
468bool TouchRegistry::TouchInfo::isOwned() const
470 return !candidates.isEmpty() && !candidates.first().undecided;
473bool TouchRegistry::TouchInfo::ended() const
475 Q_ASSERT(isValid());
476 return physicallyEnded && (isOwned() || candidates.isEmpty());
479void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
481 Q_ASSERT(isOwned());
483 #if TOUCHREGISTRY_DEBUG
484 UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
485 << " gained) to candidate" << candidates[0].item;
486 #endif
488 TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
489 QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
492 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
493 for (int i = 1; i < candidates.count(); ++i) {
494 #if TOUCHREGISTRY_DEBUG
495 UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
496 << candidates[i].item;
497 #endif
498 QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
499 }