2 * Copyright (C) 2014-2015 Canonical, Ltd.
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.
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.
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/>.
17#include "TouchRegistry.h"
19#include <QCoreApplication>
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"
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)
50 , m_inDispatchLoop(false)
51 , m_timerFactory(timerFactory)
53 if (m_instance == nullptr) {
56 qFatal("Cannot have more than one instance of TouchRegistry. It must be a singleton.");
60TouchRegistry::~TouchRegistry()
62 Q_ASSERT(m_instance != nullptr);
64 delete m_timerFactory;
67void TouchRegistry::update(const QTouchEvent *event)
69 #if TOUCHREGISTRY_DEBUG
70 UG_DEBUG << "got" << qPrintable(touchEventToString(event));
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;
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
104 QMap<QQuickItem*, QList<int>> touchIdsForItems;
106 // Build touchIdsForItems
107 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
108 if (touchInfo->isOwned() && touchInfo->watchers.isEmpty())
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);
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);
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);
147 m_inDispatchLoop = false;
150void TouchRegistry::freeEndedTouchInfos()
152 m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
153 if (touchInfo->ended()) {
154 m_touchInfoPool.freeSlot(touchInfo);
161 Extracts the touches with the given touchIds from event and send them in a
162 UnownedTouchEvent to the given item
164void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
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();
193 QTouchEvent *eventForItem = new QTouchEvent(event->type(),
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))
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)
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));
239 // Do not filter out the event. i.e., let it be handled further as
240 // we're just monitoring events
244void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
246 #if TOUCHREGISTRY_DEBUG
247 UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
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");
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,
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;
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;
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.");
306 delete candidateInfo.inactivityTimer;
307 candidateInfo.inactivityTimer = nullptr;
308 touchInfo->candidates.removeAt(i);
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();
321 if (!m_inDispatchLoop && touchInfo->ended()) {
322 m_touchInfoPool.freeSlot(touchInfo);
326void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
328 #if TOUCHREGISTRY_DEBUG
329 UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
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;
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);
357 candidateIndex = touchInfo->candidates.count() - 1;
360 // If it's the top candidate it means it's now the owner. Let
362 if (candidateIndex == 0) {
363 touchInfo->notifyCandidatesOfOwnershipResolution();
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;
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;
394 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
396 #if TOUCHREGISTRY_DEBUG
397 UG_DEBUG << "Failed to find TouchInfo for id" << id;
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;
414 // we found the guy and it's all fine.
415 rejectedCandidateIndex = i;
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());
425 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
426 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
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();
440////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
442TouchRegistry::TouchInfo::TouchInfo(int id)
447void TouchRegistry::TouchInfo::reset()
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...
458void TouchRegistry::TouchInfo::init(int id)
461 physicallyEnded = false;
466bool TouchRegistry::TouchInfo::isOwned() const
468 return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
471bool TouchRegistry::TouchInfo::ended() const
474 return physicallyEnded && (isOwned() || candidates.isEmpty());
477void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
481 #if TOUCHREGISTRY_DEBUG
482 UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
483 << " gained) to candidate" << candidates[0].item;
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;
496 QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list