on ba4ffba help sign in
unity8/libs/UbuntuGestures/TouchRegistry.cpp
498 lines 17 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 : 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 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
100 // Maps an item to the touches in this event he should be informed about.
101 // E.g.: a QTouchEvent might have three touches but a given item might be interested in only
102 // one of them. So he will get a UnownedTouchEvent from this QTouchEvent containing only that
103 // touch point.
104 QMap<QQuickItem*, QList<int>> touchIdsForItems;
106 // Build touchIdsForItems
107 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
108 if (touchInfo->isOwned() && touchInfo->watchers.isEmpty())
109 return true;
111 for (int j = 0; j < updatedTouchPoints.count(); ++j) {
112 if (updatedTouchPoints[j].id() == touchInfo->id) {
113 if (!touchInfo->isOwned()) {
114 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
115 CandidateInfo &candidate = touchInfo->candidates[i];
116 Q_ASSERT(!candidate.item.isNull());
117 if (candidate.state != CandidateInfo::InterimOwner) {
118 touchIdsForItems[candidate.item.data()].append(touchInfo->id);
119 }
120 }
121 }
123 const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers;
124 for (int i = 0; i < watchers.count(); ++i) {
125 if (!watchers[i].isNull()) {
126 touchIdsForItems[watchers[i].data()].append(touchInfo->id);
127 }
128 }
130 return true;
131 }
132 }
134 return true;
135 });
137 // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
138 // from the event handler callback.
139 m_inDispatchLoop = true;
140 auto it = touchIdsForItems.constBegin();
141 while (it != touchIdsForItems.constEnd()) {
142 QQuickItem *item = it.key();
143 const QList<int> &touchIds = it.value();
144 dispatchPointsToItem(event, touchIds, item);
145 ++it;
146 };
147 m_inDispatchLoop = false;
150void TouchRegistry::freeEndedTouchInfos()
152 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
153 if (touchInfo->ended()) {
154 m_touchInfoPool.freeSlot(touchInfo);
155 }
156 return true;
157 });
160/*
161 Extracts the touches with the given touchIds from event and send them in a
162 UnownedTouchEvent to the given item
163 */
164void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
165 QQuickItem *item)
167 Qt::TouchPointStates touchPointStates = 0;
168 QList<QTouchEvent::TouchPoint> touchPoints;
170 const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints();
172 QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform();
173 QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform);
175 for (int i = 0; i < allTouchPoints.count(); ++i) {
176 const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i];
177 if (touchIds.contains(originalTouchPoint.id())) {
178 QTouchEvent::TouchPoint touchPoint = originalTouchPoint;
180 translateTouchPointFromScreenToWindowCoords(touchPoint);
182 // Set the point's local coordinates to that of the item
183 touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect()));
184 touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos()));
185 touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos()));
186 touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D());
188 touchPoints.append(touchPoint);
189 touchPointStates |= touchPoint.state();
190 }
191 }
193 QTouchEvent *eventForItem = new QTouchEvent(event->type(),
194 event->device(),
195 event->modifiers(),
196 touchPointStates,
197 touchPoints);
198 eventForItem->setWindow(event->window());
199 eventForItem->setTimestamp(event->timestamp());
200 eventForItem->setTarget(event->target());
202 UnownedTouchEvent unownedTouchEvent(eventForItem);
204 #if TOUCHREGISTRY_DEBUG
205 UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem))
206 << "to" << item;
207 #endif
209 QCoreApplication::sendEvent(item, &unownedTouchEvent);
212void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint)
214 touchPoint.setScreenRect(touchPoint.sceneRect());
215 touchPoint.setStartScreenPos(touchPoint.startScenePos());
216 touchPoint.setLastScreenPos(touchPoint.lastScenePos());
218 touchPoint.setSceneRect(touchPoint.rect());
219 touchPoint.setStartScenePos(touchPoint.startPos());
220 touchPoint.setLastScenePos(touchPoint.lastPos());
223bool TouchRegistry::eventFilter(QObject *watched, QEvent *event)
225 Q_UNUSED(watched);
227 switch (event->type()) {
228 case QEvent::TouchBegin:
229 case QEvent::TouchUpdate:
230 case QEvent::TouchEnd:
231 case QEvent::TouchCancel:
232 update(static_cast<QTouchEvent*>(event));
233 break;
234 default:
235 // do nothing
236 break;
237 }
239 // Do not filter out the event. i.e., let it be handled further as
240 // we're just monitoring events
241 return false;
244void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
246 #if TOUCHREGISTRY_DEBUG
247 UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
248 #endif
250 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
251 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
253 if (touchInfo->isOwned()) {
254 qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned");
255 return;
256 }
258 // TODO: Check if candidate already exists
260 CandidateInfo candidateInfo;
261 candidateInfo.state = CandidateInfo::Undecided;
262 candidateInfo.item = candidate;
263 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
264 *m_timerFactory,
265 this);
266 connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted,
267 this, &TouchRegistry::rejectCandidateOwnerForTouch);
269 touchInfo->candidates.append(candidateInfo);
272void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
274 #if TOUCHREGISTRY_DEBUG
275 UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
276 #endif
278 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
279 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
281 // TODO: Check if watcher already exists
283 touchInfo->watchers.append(watcher);
286void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
288 #if TOUCHREGISTRY_DEBUG
289 UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
290 #endif
292 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
293 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
295 int indexRemoved = -1;
297 // TODO: check if the candidate is in fact the owner of the touch
299 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
300 CandidateInfo &candidateInfo = touchInfo->candidates[i];
301 if (candidateInfo.item == candidate) {
302 Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
303 if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
304 qCritical("TouchRegistry: touch owner is being removed.");
305 }
306 delete candidateInfo.inactivityTimer;
307 candidateInfo.inactivityTimer = nullptr;
308 touchInfo->candidates.removeAt(i);
309 indexRemoved = i;
310 }
311 }
313 if (indexRemoved == 0) {
314 // the top candidate has been removed. if the new top candidate
315 // wants the touch let him know he's now the owner.
316 if (touchInfo->isOwned()) {
317 touchInfo->notifyCandidatesOfOwnershipResolution();
318 }
319 }
321 if (!m_inDispatchLoop && touchInfo->ended()) {
322 m_touchInfoPool.freeSlot(touchInfo);
323 }
326void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
328 #if TOUCHREGISTRY_DEBUG
329 UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
330 #endif
332 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
333 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
335 Q_ASSERT(!touchInfo->isOwned());
337 int candidateIndex = -1;
338 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
339 CandidateInfo &candidateInfo = touchInfo->candidates[i];
340 if (candidateInfo.item == candidate) {
341 candidateInfo.state = CandidateInfo::Requested;
342 delete candidateInfo.inactivityTimer;
343 candidateInfo.inactivityTimer = nullptr;
344 candidateIndex = i;
345 break;
346 }
347 }
349 // add it as a candidate if not present yet
350 if (candidateIndex < 0) {
351 CandidateInfo candidateInfo;
352 candidateInfo.state = CandidateInfo::InterimOwner;
353 candidateInfo.item = candidate;
354 candidateInfo.inactivityTimer = nullptr;
355 touchInfo->candidates.append(candidateInfo);
356 // it's the last one
357 candidateIndex = touchInfo->candidates.count() - 1;
358 }
360 // If it's the top candidate it means it's now the owner. Let
361 // it know about it.
362 if (candidateIndex == 0) {
363 touchInfo->notifyCandidatesOfOwnershipResolution();
364 }
367Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
369 Pool<TouchInfo>::Iterator touchInfo;
371 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
372 if (someTouchInfo->id == id) {
373 touchInfo = someTouchInfo;
374 return false;
375 } else {
376 return true;
377 }
378 });
380 return touchInfo;
384void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
386 // NB: It's technically possible that candidate is a dangling pointer at this point.
387 // Although that would most likely be due to a bug in our code.
388 // In any case, only dereference it after it's confirmed that it indeed exists.
390 #if TOUCHREGISTRY_DEBUG
391 UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
392 #endif
394 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
395 if (!touchInfo) {
396 #if TOUCHREGISTRY_DEBUG
397 UG_DEBUG << "Failed to find TouchInfo for id" << id;
398 #endif
399 return;
400 }
402 int rejectedCandidateIndex = -1;
404 // Check if the given candidate is valid and still undecided
405 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
406 CandidateInfo &candidateInfo = touchInfo->candidates[i];
407 if (candidateInfo.item == candidate) {
408 Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
409 if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
410 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
411 << ") as it already owns touch" << id;
412 return;
413 } else {
414 // we found the guy and it's all fine.
415 rejectedCandidateIndex = i;
416 }
417 }
418 }
420 // If we reached this point it's because the given candidate exists and is indeed undecided.
422 Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
424 {
425 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
426 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
427 }
429 touchInfo->candidates.removeAt(rejectedCandidateIndex);
431 if (rejectedCandidateIndex == 0) {
432 // the top candidate has been removed. if the new top candidate
433 // wants the touch let him know he's now the owner.
434 if (touchInfo->isOwned()) {
435 touchInfo->notifyCandidatesOfOwnershipResolution();
436 }
437 }
440////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
442TouchRegistry::TouchInfo::TouchInfo(int id)
444 init(id);
447void TouchRegistry::TouchInfo::reset()
449 id = -1;
451 for (int i = 0; i < candidates.count(); ++i) {
452 CandidateInfo &candidate = candidates[i];
453 delete candidate.inactivityTimer;
454 candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
455 }
458void TouchRegistry::TouchInfo::init(int id)
460 this->id = id;
461 physicallyEnded = false;
462 candidates.clear();
463 watchers.clear();
466bool TouchRegistry::TouchInfo::isOwned() const
468 return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
471bool TouchRegistry::TouchInfo::ended() const
473 Q_ASSERT(isValid());
474 return physicallyEnded && (isOwned() || candidates.isEmpty());
477void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
479 Q_ASSERT(isOwned());
481 #if TOUCHREGISTRY_DEBUG
482 UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
483 << " gained) to candidate" << candidates[0].item;
484 #endif
486 TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
487 QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
490 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
491 for (int i = 1; i < candidates.count(); ++i) {
492 #if TOUCHREGISTRY_DEBUG
493 UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
494 << candidates[i].item;
495 #endif
496 QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
497 }