on c42e54e help sign in
unity8/libs/UbuntuGestures/TouchRegistry.cpp
502 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 : 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 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 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);
276void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
278 #if TOUCHREGISTRY_DEBUG
279 UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
280 #endif
282 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
283 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
285 // TODO: Check if watcher already exists
287 touchInfo->watchers.append(watcher);
290void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
292 #if TOUCHREGISTRY_DEBUG
293 UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
294 #endif
296 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
297 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
299 int indexRemoved = -1;
301 // TODO: check if the candidate is in fact the owner of the touch
303 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
304 CandidateInfo &candidateInfo = touchInfo->candidates[i];
305 if (candidateInfo.item == candidate) {
306 Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
307 if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
308 qCritical("TouchRegistry: touch owner is being removed.");
309 }
310 delete candidateInfo.inactivityTimer;
311 candidateInfo.inactivityTimer = nullptr;
312 touchInfo->candidates.removeAt(i);
313 indexRemoved = i;
314 }
315 }
317 if (indexRemoved == 0) {
318 // the top candidate has been removed. if the new top candidate
319 // wants the touch let him know he's now the owner.
320 if (touchInfo->isOwned()) {
321 touchInfo->notifyCandidatesOfOwnershipResolution();
322 }
323 }
325 if (!m_inDispatchLoop && touchInfo->ended()) {
326 m_touchInfoPool.freeSlot(touchInfo);
327 }
330void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
332 #if TOUCHREGISTRY_DEBUG
333 UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
334 #endif
336 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
337 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
339 Q_ASSERT(!touchInfo->isOwned());
341 int candidateIndex = -1;
342 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
343 CandidateInfo &candidateInfo = touchInfo->candidates[i];
344 if (candidateInfo.item == candidate) {
345 candidateInfo.state = CandidateInfo::Requested;
346 delete candidateInfo.inactivityTimer;
347 candidateInfo.inactivityTimer = nullptr;
348 candidateIndex = i;
349 break;
350 }
351 }
353 // add it as a candidate if not present yet
354 if (candidateIndex < 0) {
355 CandidateInfo candidateInfo;
356 candidateInfo.state = CandidateInfo::InterimOwner;
357 candidateInfo.item = candidate;
358 candidateInfo.inactivityTimer = nullptr;
359 touchInfo->candidates.append(candidateInfo);
360 // it's the last one
361 candidateIndex = touchInfo->candidates.count() - 1;
362 }
364 // If it's the top candidate it means it's now the owner. Let
365 // it know about it.
366 if (candidateIndex == 0) {
367 touchInfo->notifyCandidatesOfOwnershipResolution();
368 }
371Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
373 Pool<TouchInfo>::Iterator touchInfo;
375 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
376 if (someTouchInfo->id == id) {
377 touchInfo = someTouchInfo;
378 return false;
379 } else {
380 return true;
381 }
382 });
384 return touchInfo;
388void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
390 // NB: It's technically possible that candidate is a dangling pointer at this point.
391 // Although that would most likely be due to a bug in our code.
392 // In any case, only dereference it after it's confirmed that it indeed exists.
394 #if TOUCHREGISTRY_DEBUG
395 UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
396 #endif
398 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
399 if (!touchInfo) {
400 #if TOUCHREGISTRY_DEBUG
401 UG_DEBUG << "Failed to find TouchInfo for id" << id;
402 #endif
403 return;
404 }
406 int rejectedCandidateIndex = -1;
408 // Check if the given candidate is valid and still undecided
409 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
410 CandidateInfo &candidateInfo = touchInfo->candidates[i];
411 if (candidateInfo.item == candidate) {
412 Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
413 if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
414 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
415 << ") as it already owns touch" << id;
416 return;
417 } else {
418 // we found the guy and it's all fine.
419 rejectedCandidateIndex = i;
420 }
421 }
422 }
424 // If we reached this point it's because the given candidate exists and is indeed undecided.
426 Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
428 {
429 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
430 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
431 }
433 touchInfo->candidates.removeAt(rejectedCandidateIndex);
435 if (rejectedCandidateIndex == 0) {
436 // the top candidate has been removed. if the new top candidate
437 // wants the touch let him know he's now the owner.
438 if (touchInfo->isOwned()) {
439 touchInfo->notifyCandidatesOfOwnershipResolution();
440 }
441 }
444////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
446TouchRegistry::TouchInfo::TouchInfo(int id)
448 init(id);
451void TouchRegistry::TouchInfo::reset()
453 id = -1;
455 for (int i = 0; i < candidates.count(); ++i) {
456 CandidateInfo &candidate = candidates[i];
457 delete candidate.inactivityTimer;
458 candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
459 }
462void TouchRegistry::TouchInfo::init(int id)
464 this->id = id;
465 physicallyEnded = false;
466 candidates.clear();
467 watchers.clear();
470bool TouchRegistry::TouchInfo::isOwned() const
472 return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
475bool TouchRegistry::TouchInfo::ended() const
477 Q_ASSERT(isValid());
478 return physicallyEnded && (isOwned() || candidates.isEmpty());
481void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
483 Q_ASSERT(isOwned());
485 #if TOUCHREGISTRY_DEBUG
486 UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
487 << " gained) to candidate" << candidates[0].item;
488 #endif
490 TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
491 QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
494 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
495 for (int i = 1; i < candidates.count(); ++i) {
496 #if TOUCHREGISTRY_DEBUG
497 UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
498 << candidates[i].item;
499 #endif
500 QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
501 }