on 3d5fc02 help sign in
unity8/libs/UbuntuGestures/TouchRegistry.cpp
553 lines 19 kB history raw
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 */
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 : QObject(parent)
45 , m_inDispatchLoop(false)
46 , m_timerFactory(new TimerFactory)
50TouchRegistry::~TouchRegistry()
52 Q_ASSERT(m_instance != nullptr);
53 m_instance = nullptr;
54 delete m_timerFactory;
57TouchRegistry *TouchRegistry::instance()
59 if (m_instance == nullptr) {
60 m_instance = new TouchRegistry;
61 }
62 return m_instance;
65void TouchRegistry::setTimerFactory(AbstractTimerFactory *timerFactory)
67 delete m_timerFactory;
68 m_timerFactory = timerFactory;
71void TouchRegistry::update(const QTouchEvent *event)
73 #if TOUCHREGISTRY_DEBUG
74 UG_DEBUG << "got" << qPrintable(touchEventToString(event));
75 #endif
77 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
78 for (int i = 0; i < touchPoints.count(); ++i) {
79 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
80 if (touchPoint.state() == Qt::TouchPointPressed) {
81 TouchInfo &touchInfo = m_touchInfoPool.getEmptySlot();
82 touchInfo.init(touchPoint.id());
83 } else if (touchPoint.state() == Qt::TouchPointReleased) {
84 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchPoint.id());
86 touchInfo->physicallyEnded = true;
87 }
88 }
90 deliverTouchUpdatesToUndecidedCandidatesAndWatchers(event);
92 freeEndedTouchInfos();
95void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event)
97 // TODO: Look into how we could optimize this whole thing.
98 // Although it's not really a problem as we should have at most two candidates
99 // for each point and there should not be many active points at any given moment.
100 // But having three nested for-loops does scare.
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 QHash<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 if (candidate.state != CandidateInfo::InterimOwner) {
122 touchIdsForItems[candidate.item.data()].append(touchInfo->id);
123 }
124 }
125 }
127 const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers;
128 for (int i = 0; i < watchers.count(); ++i) {
129 if (!watchers[i].isNull()) {
130 touchIdsForItems[watchers[i].data()].append(touchInfo->id);
131 }
132 }
134 return true;
135 }
136 }
138 return true;
139 });
141 // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
142 // from the event handler callback.
143 m_inDispatchLoop = true;
144 auto it = touchIdsForItems.constBegin();
145 while (it != touchIdsForItems.constEnd()) {
146 QQuickItem *item = it.key();
147 const QList<int> &touchIds = it.value();
148 dispatchPointsToItem(event, touchIds, item);
149 ++it;
150 };
151 m_inDispatchLoop = false;
154void TouchRegistry::freeEndedTouchInfos()
156 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
157 if (touchInfo->ended()) {
158 m_touchInfoPool.freeSlot(touchInfo);
159 }
160 return true;
161 });
164/*
165 Extracts the touches with the given touchIds from event and send them in a
166 UnownedTouchEvent to the given item
167 */
168void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
169 QQuickItem *item)
171 Qt::TouchPointStates touchPointStates = 0;
172 QList<QTouchEvent::TouchPoint> touchPoints;
174 const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints();
176 QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform();
177 QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform);
179 for (int i = 0; i < allTouchPoints.count(); ++i) {
180 const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i];
181 if (touchIds.contains(originalTouchPoint.id())) {
182 QTouchEvent::TouchPoint touchPoint = originalTouchPoint;
184 translateTouchPointFromScreenToWindowCoords(touchPoint);
186 // Set the point's local coordinates to that of the item
187 touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect()));
188 touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos()));
189 touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos()));
190 touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D());
192 touchPoints.append(touchPoint);
193 touchPointStates |= touchPoint.state();
194 }
195 }
197 QTouchEvent *eventForItem = new QTouchEvent(event->type(),
198 event->device(),
199 event->modifiers(),
200 touchPointStates,
201 touchPoints);
202 eventForItem->setWindow(event->window());
203 eventForItem->setTimestamp(event->timestamp());
204 eventForItem->setTarget(event->target());
206 UnownedTouchEvent unownedTouchEvent(eventForItem);
208 #if TOUCHREGISTRY_DEBUG
209 UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem))
210 << "to" << item;
211 #endif
213 QCoreApplication::sendEvent(item, &unownedTouchEvent);
216void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint)
218 touchPoint.setScreenRect(touchPoint.sceneRect());
219 touchPoint.setStartScreenPos(touchPoint.startScenePos());
220 touchPoint.setLastScreenPos(touchPoint.lastScenePos());
222 touchPoint.setSceneRect(touchPoint.rect());
223 touchPoint.setStartScenePos(touchPoint.startPos());
224 touchPoint.setLastScenePos(touchPoint.lastPos());
227bool TouchRegistry::eventFilter(QObject *watched, QEvent *event)
229 Q_UNUSED(watched);
231 switch (event->type()) {
232 case QEvent::TouchBegin:
233 case QEvent::TouchUpdate:
234 case QEvent::TouchEnd:
235 case QEvent::TouchCancel:
236 update(static_cast<QTouchEvent*>(event));
237 break;
238 default:
239 // do nothing
240 break;
241 }
243 // Do not filter out the event. i.e., let it be handled further as
244 // we're just monitoring events
245 return false;
248void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
250 #if TOUCHREGISTRY_DEBUG
251 UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
252 #endif
254 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
255 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
257 if (touchInfo->isOwned()) {
258 qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned");
259 return;
260 }
262 // TODO: Check if candidate already exists
264 CandidateInfo candidateInfo;
265 candidateInfo.state = CandidateInfo::Undecided;
266 candidateInfo.item = candidate;
267 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
268 m_timerFactory->createTimer(),
269 this);
270 connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted,
271 this, &TouchRegistry::rejectCandidateOwnerForTouch);
273 touchInfo->candidates.append(candidateInfo);
275 connect(candidate, &QObject::destroyed, this, [=](){ pruneNullCandidatesForTouch(id); });
278void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
280 #if TOUCHREGISTRY_DEBUG
281 UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
282 #endif
284 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
285 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
287 // TODO: Check if watcher already exists
289 touchInfo->watchers.append(watcher);
292void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
294 #if TOUCHREGISTRY_DEBUG
295 UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
296 #endif
298 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
299 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
302 // TODO: check if the candidate is in fact the owner of the touch
304 bool removed = false;
305 for (int i = 0; i < touchInfo->candidates.count() && !removed; ++i) {
306 if (touchInfo->candidates[i].item == candidate) {
307 removeCandidateOwnerForTouchByIndex(touchInfo, i);
308 removed = true;
309 }
310 }
313void TouchRegistry::pruneNullCandidatesForTouch(int touchId)
315 #if TOUCHREGISTRY_DEBUG
316 UG_DEBUG << "pruneNullCandidatesForTouch touchId" << touchId;
317 #endif
319 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
320 if (!touchInfo) {
321 // doesn't matter as touch is already gone.
322 return;
323 }
325 int i = 0;
326 while (i < touchInfo->candidates.count()) {
327 if (touchInfo->candidates[i].item.isNull()) {
328 removeCandidateOwnerForTouchByIndex(touchInfo, i);
329 } else {
330 ++i;
331 }
332 }
335void TouchRegistry::removeCandidateOwnerForTouchByIndex(Pool<TouchRegistry::TouchInfo>::Iterator &touchInfo,
336 int candidateIndex)
338 // TODO: check if the candidate is in fact the owner of the touch
340 Q_ASSERT(candidateIndex < touchInfo->candidates.count());
342 if (candidateIndex == 0 && touchInfo->candidates[candidateIndex].state != CandidateInfo::Undecided) {
343 qCritical("TouchRegistry: touch owner is being removed.");
344 }
345 removeCandidateHelper(touchInfo, candidateIndex);
347 if (candidateIndex == 0) {
348 // the top candidate has been removed. if the new top candidate
349 // wants the touch let him know he's now the owner.
350 if (touchInfo->isOwned()) {
351 touchInfo->notifyCandidatesOfOwnershipResolution();
352 }
353 }
355 if (!m_inDispatchLoop && touchInfo->ended()) {
356 m_touchInfoPool.freeSlot(touchInfo);
357 }
360void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
362 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
363 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
365 Q_ASSERT(!touchInfo->isOwned());
367 int candidateIndex = -1;
368 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
369 CandidateInfo &candidateInfo = touchInfo->candidates[i];
370 if (candidateInfo.item == candidate) {
371 candidateInfo.state = CandidateInfo::Requested;
372 delete candidateInfo.inactivityTimer;
373 candidateInfo.inactivityTimer = nullptr;
374 candidateIndex = i;
375 break;
376 }
377 }
378 #if TOUCHREGISTRY_DEBUG
379 UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate << "index: " << candidateIndex;
380 #endif
382 // add it as a candidate if not present yet
383 if (candidateIndex < 0) {
384 CandidateInfo candidateInfo;
385 candidateInfo.state = CandidateInfo::InterimOwner;
386 candidateInfo.item = candidate;
387 candidateInfo.inactivityTimer = nullptr;
388 touchInfo->candidates.append(candidateInfo);
389 // it's the last one
390 candidateIndex = touchInfo->candidates.count() - 1;
391 connect(candidate, &QObject::destroyed, this, [=](){ pruneNullCandidatesForTouch(id); });
392 }
394 // If it's the top candidate it means it's now the owner. Let
395 // it know about it.
396 if (candidateIndex == 0) {
397 touchInfo->notifyCandidatesOfOwnershipResolution();
398 }
401Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
403 Pool<TouchInfo>::Iterator touchInfo;
405 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
406 if (someTouchInfo->id == id) {
407 touchInfo = someTouchInfo;
408 return false;
409 } else {
410 return true;
411 }
412 });
414 return touchInfo;
418void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
420 // NB: It's technically possible that candidate is a dangling pointer at this point.
421 // Although that would most likely be due to a bug in our code.
422 // In any case, only dereference it after it's confirmed that it indeed exists.
424 #if TOUCHREGISTRY_DEBUG
425 UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
426 #endif
428 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
429 if (!touchInfo) {
430 #if TOUCHREGISTRY_DEBUG
431 UG_DEBUG << "Failed to find TouchInfo for id" << id;
432 #endif
433 return;
434 }
436 int rejectedCandidateIndex = -1;
438 // Check if the given candidate is valid and still undecided
439 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
440 CandidateInfo &candidateInfo = touchInfo->candidates[i];
441 if (candidateInfo.item == candidate) {
442 Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
443 if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
444 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
445 << ") as it already owns touch" << id;
446 return;
447 } else {
448 // we found the guy and it's all fine.
449 rejectedCandidateIndex = i;
450 }
451 }
452 }
454 // If we reached this point it's because the given candidate exists and is indeed undecided.
456 Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
458 {
459 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
460 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
461 }
463 removeCandidateHelper(touchInfo, rejectedCandidateIndex);
465 if (rejectedCandidateIndex == 0) {
466 // the top candidate has been removed. if the new top candidate
467 // wants the touch let him know he's now the owner.
468 if (touchInfo->isOwned()) {
469 touchInfo->notifyCandidatesOfOwnershipResolution();
470 }
471 }
474void TouchRegistry::removeCandidateHelper(Pool<TouchInfo>::Iterator &touchInfo, int candidateIndex)
476 {
477 CandidateInfo &candidateInfo = touchInfo->candidates[candidateIndex];
479 delete candidateInfo.inactivityTimer;
480 candidateInfo.inactivityTimer = nullptr;
482 if (candidateInfo.item) {
483 disconnect(candidateInfo.item.data(), nullptr, this, nullptr);
484 }
485 }
486 touchInfo->candidates.removeAt(candidateIndex);
489////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
491TouchRegistry::TouchInfo::TouchInfo(int id)
493 init(id);
496void TouchRegistry::TouchInfo::reset()
498 id = -1;
500 for (int i = 0; i < candidates.count(); ++i) {
501 CandidateInfo &candidate = candidates[i];
502 delete candidate.inactivityTimer;
503 candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
504 }
507void TouchRegistry::TouchInfo::init(int id)
509 this->id = id;
510 physicallyEnded = false;
511 candidates.clear();
512 watchers.clear();
515bool TouchRegistry::TouchInfo::isOwned() const
517 return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
520bool TouchRegistry::TouchInfo::ended() const
522 Q_ASSERT(isValid());
523 return physicallyEnded && (isOwned() || candidates.isEmpty());
526void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
528 Q_ASSERT(isOwned());
530 #if TOUCHREGISTRY_DEBUG
531 UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
532 << " gained) to candidate" << candidates[0].item;
533 #endif
535 // need to take a copy of the item list in case
536 // we call back in to remove candidate during the lost ownership event.
537 QList<QPointer<QQuickItem>> items;
538 Q_FOREACH(const CandidateInfo& info, candidates) {
539 items << info.item;
540 }
542 TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
543 QCoreApplication::sendEvent(items[0], &gainedOwnershipEvent);
545 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
546 for (int i = 1; i < items.count(); ++i) {
547 #if TOUCHREGISTRY_DEBUG
548 UG_DEBUG << "sending TouchOwnershipEvent(id =" << id << " lost) to candidate"
549 << items[i];
550 #endif
551 QCoreApplication::sendEvent(items[i], &lostOwnershipEvent);
552 }