on 545d96e help sign in
unity8/plugins/Ubuntu/Gestures/TouchGate.cpp
347 lines 11 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 TouchEvent clonedEvent(event);
129 m_storedEvents.append(std::move(clonedEvent));
132void TouchGate::removeTouchFromStoredEvents(int touchId)
134 int i = 0;
135 while (i < m_storedEvents.count()) {
136 TouchEvent &event = m_storedEvents[i];
137 bool removed = event.removeTouch(touchId);
139 if (removed && event.touchPoints.isEmpty()) {
140 m_storedEvents.removeAt(i);
141 } else {
142 ++i;
143 }
144 }
147void TouchGate::dispatchFullyOwnedEvents()
149 while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
150 TouchEvent event = m_storedEvents.takeFirst();
151 dispatchTouchEventToTarget(event);
152 }
155#if TOUCHGATE_DEBUG
156QString TouchGate::oldestPendingTouchIdsString()
158 Q_ASSERT(!m_storedEvents.isEmpty());
160 QString str;
162 const auto &touchPoints = m_storedEvents.first().touchPoints;
163 for (int i = 0; i < touchPoints.count(); ++i) {
164 if (!isTouchPointOwned(touchPoints[i].id())) {
165 if (!str.isEmpty()) {
166 str.append(", ");
167 }
168 str.append(QString::number(touchPoints[i].id()));
169 }
170 }
172 return str;
174#endif
176bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const
178 for (int i = 0; i < event.touchPoints.count(); ++i) {
179 if (!isTouchPointOwned(event.touchPoints[i].id())) {
180 return false;
181 }
182 }
184 return true;
187void TouchGate::setTargetItem(QQuickItem *item)
189 // TODO: changing the target item while dispatch of touch events is taking place will
190 // create a mess
192 if (item == m_targetItem.data())
193 return;
195 m_targetItem = item;
196 Q_EMIT targetItemChanged(item);
199void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
201 dispatchTouchEventToTarget(event.eventType,
202 event.device,
203 event.modifiers,
204 event.touchPoints,
205 event.target,
206 event.window,
207 event.timestamp);
210void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
212 dispatchTouchEventToTarget(event->type(),
213 event->device(),
214 event->modifiers(),
215 event->touchPoints(),
216 event->target(),
217 event->window(),
218 event->timestamp());
221void TouchGate::dispatchTouchEventToTarget(QEvent::Type eventType,
222 QTouchDevice *device,
223 Qt::KeyboardModifiers modifiers,
224 const QList<QTouchEvent::TouchPoint> &touchPoints,
225 QObject *target,
226 QWindow *window,
227 ulong timestamp)
229 removeTouchInfoForEndedTouches(touchPoints);
231 if (m_targetItem.isNull()) {
232 qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
233 return;
234 }
236 QQuickItem *targetItem = m_targetItem.data();
238 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
239 #if TOUCHGATE_DEBUG
240 qDebug() << "[TouchGate] Cannot dispatch touch event to" << targetItem
241 << "because it's disabled or invisible.";
242 #endif
243 return;
244 }
246 // Map touch points to targetItem coordinates
247 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
248 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
249 QTouchEvent *eventForTargetItem = createQTouchEvent(eventType, device, modifiers, targetTouchPoints,
250 target, window, timestamp);
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 }
275QTouchEvent *TouchGate::createQTouchEvent(QEvent::Type eventType,
276 QTouchDevice *device,
277 Qt::KeyboardModifiers modifiers,
278 const QList<QTouchEvent::TouchPoint> &touchPoints,
279 QObject *target,
280 QWindow *window,
281 ulong timestamp)
283 Qt::TouchPointStates eventStates = 0;
284 for (int i = 0; i < touchPoints.count(); i++)
285 eventStates |= touchPoints[i].state();
286 // if all points have the same state, set the event type accordingly
287 switch (eventStates) {
288 case Qt::TouchPointPressed:
289 eventType = QEvent::TouchBegin;
290 break;
291 case Qt::TouchPointReleased:
292 eventType = QEvent::TouchEnd;
293 break;
294 default:
295 eventType = QEvent::TouchUpdate;
296 break;
297 }
299 QTouchEvent *touchEvent = new QTouchEvent(eventType);
300 touchEvent->setWindow(window);
301 touchEvent->setTarget(target);
302 touchEvent->setDevice(device);
303 touchEvent->setModifiers(modifiers);
304 touchEvent->setTouchPoints(touchPoints);
305 touchEvent->setTouchPointStates(eventStates);
306 touchEvent->setTimestamp(timestamp);
307 touchEvent->accept();
308 return touchEvent;
311void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
313 for (int i = 0; i < touchPoints.size(); ++i) {\
314 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
316 if (touchPoint.state() == Qt::TouchPointReleased) {
317 Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
318 Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
319 Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
320 m_touchInfoMap.remove(touchPoint.id());
321 }
322 }
325TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
326 : eventType(event->type())
327 , device(event->device())
328 , modifiers(event->modifiers())
329 , touchPoints(event->touchPoints())
330 , target(event->target())
331 , window(event->window())
332 , timestamp(event->timestamp())
336bool TouchGate::TouchEvent::removeTouch(int touchId)
338 bool removed = false;
339 for (int i = 0; i < touchPoints.count() && !removed; ++i) {
340 if (touchPoints[i].id() == touchId) {
341 touchPoints.removeAt(i);
342 removed = true;
343 }
344 }
346 return removed;