on 8efbd79 help sign in
unity8/plugins/Ubuntu/Gestures/TouchGate.cpp
322 lines 10 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 "TouchGate.h"
19#include <QCoreApplication>
20#include <QDebug>
22#include <TouchOwnershipEvent.h>
23#include <TouchRegistry.h>
25#pragma GCC diagnostic push
26#pragma GCC diagnostic ignored "-pedantic"
27#include <private/qquickitem_p.h>
28#pragma GCC diagnostic pop
30#if TOUCHGATE_DEBUG
31#include <DebugHelpers.h>
32#endif
35bool TouchGate::event(QEvent *e)
37 if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
38 touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e));
39 return true;
40 } else {
41 return QQuickItem::event(e);
42 }
45void TouchGate::touchEvent(QTouchEvent *event)
47 #if TOUCHGATE_DEBUG
48 qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
49 #endif
50 event->accept();
52 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
53 bool goodToGo = true;
54 for (int i = 0; i < touchPoints.count(); ++i) {
55 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
57 if (touchPoint.state() == Qt::TouchPointPressed) {
58 Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
59 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
60 m_touchInfoMap[touchPoint.id()].ended = false;
61 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
62 }
64 goodToGo &= m_touchInfoMap.contains(touchPoint.id())
65 && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
67 if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
68 m_touchInfoMap[touchPoint.id()].ended = true;
69 }
71 }
73 if (goodToGo) {
74 if (m_storedEvents.isEmpty()) {
75 // let it pass through
76 dispatchTouchEventToTarget(event);
77 } else {
78 // Retain the event to ensure TouchGate dispatches them in order.
79 // Otherwise the current event would come before the stored ones, which are older.
80 #if TOUCHGATE_DEBUG
81 qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
82 qPrintable(oldestPendingTouchIdsString()));
83 #endif
84 storeTouchEvent(event);
85 }
86 } else {
87 // Retain events that have unowned touches
88 storeTouchEvent(event);
89 }
92void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
94 // TODO: Optimization: batch those actions as TouchOwnershipEvents
95 // might come one right after the other.
97 Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
99 TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
101 if (event->gained()) {
102 #if TOUCHGATE_DEBUG
103 qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
104 #endif
105 touchInfo.ownership = OwnershipGranted;
106 } else {
107 #if TOUCHGATE_DEBUG
108 qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
109 #endif
110 m_touchInfoMap.remove(event->touchId());
111 removeTouchFromStoredEvents(event->touchId());
112 }
114 dispatchFullyOwnedEvents();
117bool TouchGate::isTouchPointOwned(int touchId) const
119 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
122void TouchGate::storeTouchEvent(const QTouchEvent *event)
124 #if TOUCHGATE_DEBUG
125 qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
126 #endif
128 QTouchEvent *clonedEvent = new QTouchEvent(event->type(),
129 event->device(),
130 event->modifiers(),
131 event->touchPointStates(),
132 event->touchPoints());
134 m_storedEvents.append(clonedEvent);
137void TouchGate::removeTouchFromStoredEvents(int touchId)
139 int i = 0;
140 while (i < m_storedEvents.count()) {
141 QTouchEvent *event = m_storedEvents[i];
142 bool removed = removeTouchFromEvent(touchId, event);
144 if (removed && event->touchPoints().isEmpty()) {
145 m_storedEvents.removeAt(i);
146 } else {
147 ++i;
148 }
149 }
152bool TouchGate::removeTouchFromEvent(int touchId, QTouchEvent *event)
154 const QList<QTouchEvent::TouchPoint> &eventTouchPoints = event->touchPoints();
156 int indexToRemove = -1;
157 for (int i = 0; i < eventTouchPoints.count() && indexToRemove < 0; ++i) {
158 if (eventTouchPoints[i].id() == touchId) {
159 indexToRemove = i;
160 }
161 }
163 if (indexToRemove >= 0) {
164 QList<QTouchEvent::TouchPoint> newTouchPointsList = eventTouchPoints;
165 newTouchPointsList.removeAt(indexToRemove);
166 event->setTouchPoints(newTouchPointsList);
167 return true;
168 } else {
169 return false;
170 }
173void TouchGate::dispatchFullyOwnedEvents()
175 while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
176 QTouchEvent *event = m_storedEvents.first();
177 m_storedEvents.removeFirst();
178 dispatchTouchEventToTarget(event);
179 delete event;
180 }
183#if TOUCHGATE_DEBUG
184QString TouchGate::oldestPendingTouchIdsString()
186 Q_ASSERT(!m_storedEvents.isEmpty());
188 QString str;
190 auto touchPoints = m_storedEvents.first()->touchPoints();
191 for (int i = 0; i < touchPoints.count(); ++i) {
192 if (!isTouchPointOwned(touchPoints[i].id())) {
193 if (!str.isEmpty()) {
194 str.append(", ");
195 }
196 str.append(QString::number(touchPoints[i].id()));
197 }
198 }
200 return str;
202#endif
204bool TouchGate::eventIsFullyOwned(const QTouchEvent *event) const
206 auto touchPoints = event->touchPoints();
207 for (int i = 0; i < touchPoints.count(); ++i) {
208 if (!isTouchPointOwned(touchPoints[i].id())) {
209 return false;
210 }
211 }
213 return true;
216void TouchGate::setTargetItem(QQuickItem *item)
218 // TODO: changing the target item while dispatch of touch events is taking place will
219 // create a mess
221 if (item == m_targetItem.data())
222 return;
224 m_targetItem = item;
225 Q_EMIT targetItemChanged(item);
228void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
230 removeTouchInfoForEndedTouches(event);
232 if (m_targetItem.isNull()) {
233 qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
234 return;
235 }
237 QQuickItem *targetItem = m_targetItem.data();
239 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
240 #if TOUCHGATE_DEBUG
241 qDebug() << "[TouchGate] Cannot dispatch touch event to" << targetItem
242 << "because it's disabled or invisible.";
243 #endif
244 return;
245 }
247 // Map touch points to targetItem coordinates
248 QList<QTouchEvent::TouchPoint> touchPoints = event->touchPoints();
249 transformTouchPoints(touchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
250 QTouchEvent *eventForTargetItem = touchEventWithPoints(*event, touchPoints);
252 #if TOUCHGATE_DEBUG
253 qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem))
254 << "to" << targetItem;
255 #endif
257 QCoreApplication::sendEvent(targetItem, eventForTargetItem);
259 delete eventForTargetItem;
262// NB: From QQuickWindow
263void TouchGate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
265 QMatrix4x4 transformMatrix(transform);
266 for (int i=0; i<touchPoints.count(); i++) {
267 QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
268 touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
269 touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
270 touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
271 touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
272 }
275// NB: From QQuickWindow
276QTouchEvent *TouchGate::touchEventWithPoints(const QTouchEvent &event,
277 const QList<QTouchEvent::TouchPoint> &newPoints)
279 Qt::TouchPointStates eventStates = 0;
280 for (int i=0; i<newPoints.count(); i++)
281 eventStates |= newPoints[i].state();
282 // if all points have the same state, set the event type accordingly
283 QEvent::Type eventType = event.type();
284 switch (eventStates) {
285 case Qt::TouchPointPressed:
286 eventType = QEvent::TouchBegin;
287 break;
288 case Qt::TouchPointReleased:
289 eventType = QEvent::TouchEnd;
290 break;
291 default:
292 eventType = QEvent::TouchUpdate;
293 break;
294 }
296 QTouchEvent *touchEvent = new QTouchEvent(eventType);
297 touchEvent->setWindow(event.window());
298 touchEvent->setTarget(event.target());
299 touchEvent->setDevice(event.device());
300 touchEvent->setModifiers(event.modifiers());
301 touchEvent->setTouchPoints(newPoints);
302 touchEvent->setTouchPointStates(eventStates);
303 touchEvent->setTimestamp(event.timestamp());
304 touchEvent->accept();
305 return touchEvent;
308void TouchGate::removeTouchInfoForEndedTouches(QTouchEvent *event)
310 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
312 for (int i = 0; i < touchPoints.size(); ++i) {\
313 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
315 if (touchPoint.state() == Qt::TouchPointReleased) {
316 Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
317 Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
318 Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
319 m_touchInfoMap.remove(touchPoint.id());
320 }
321 }