Lomiri
Loading...
Searching...
No Matches
TouchGate.cpp
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 */
16
17#include "TouchGate.h"
18
19#include <QCoreApplication>
20#include <QDebug>
21#include <QQuickWindow>
22
23#include <LomiriGestures/private/touchownershipevent_p.h>
24#include <LomiriGestures/private/touchregistry_p.h>
25
26#if TOUCHGATE_DEBUG
27#define ugDebug(params) qDebug().nospace() << "[TouchGate(" << (void*)this << ")] " << params
28#include <LomiriGestures/private/debughelpers_p.h>
29#else // TOUCHGATE_DEBUG
30#define ugDebug(params) ((void)0)
31#endif // TOUCHGATE_DEBUG
32
33UG_USE_NAMESPACE
34
35TouchGate::TouchGate(QQuickItem *parent)
36 : QQuickItem(parent)
37{
38 connect(this, &QQuickItem::enabledChanged,
39 this, &TouchGate::onEnabledChanged);
40}
41
42bool TouchGate::event(QEvent *e)
43{
44 if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
45 touchOwnershipEvent(static_cast<TouchOwnershipEvent*>(e));
46 return true;
47 } else {
48 return QQuickItem::event(e);
49 }
50}
51
52void TouchGate::touchEvent(QTouchEvent *event)
53{
54 ugDebug("got touch event" << qPrintable(touchEventToString(event)));
55 event->accept();
56
57 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
58 QList<QTouchEvent::TouchPoint> validTouchPoints;
59 bool ownsAllTouches = true;
60 for (int i = 0; i < touchPoints.count(); ++i) {
61 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
62
63 if (touchPoint.state() == Qt::TouchPointPressed) {
64// FIXME: This assert triggers frequently in make trySomething. We have verified
65// that it's a bug in the mouse to touch conversion of the test environment
66// and not in the actual product. Still, it probably should be cleaned up eventually.
67// Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
68 m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
69 m_touchInfoMap[touchPoint.id()].ended = false;
70 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
71 }
72
73 if (m_touchInfoMap.contains(touchPoint.id())) {
74 validTouchPoints.append(touchPoint);
75
76 ownsAllTouches &= m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
77
78 if (touchPoint.state() == Qt::TouchPointReleased) {
79 m_touchInfoMap[touchPoint.id()].ended = true;
80 }
81 }
82
83 }
84
85 if (validTouchPoints.isEmpty()) {
86 // nothing to do.
87 return;
88 }
89
90 if (ownsAllTouches) {
91 if (m_storedEvents.isEmpty()) {
92 // let it pass through
93 removeTouchInfoForEndedTouches(validTouchPoints);
94 m_dispatcher.dispatch(event->device(), event->modifiers(), validTouchPoints,
95 event->window(), event->timestamp());
96 } else {
97 // Retain the event to ensure TouchGate dispatches them in order.
98 // Otherwise the current event would come before the stored ones, which are older.
99 ugDebug("Storing event because thouches " << qPrintable(oldestPendingTouchIdsString())
100 << " are still pending ownership.");
101 storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
102 event->window(), event->timestamp());
103 }
104 } else {
105 // Retain events that have unowned touches
106 storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
107 event->window(), event->timestamp());
108 }
109}
110
111void TouchGate::itemChange(ItemChange change, const ItemChangeData &value)
112{
113 if (change == QQuickItem::ItemSceneChange) {
114 if (value.window != nullptr) {
115 value.window->installEventFilter(TouchRegistry::instance());
116 }
117 }
118}
119
120void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
121{
122 // TODO: Optimization: batch those actions as TouchOwnershipEvents
123 // might come one right after the other.
124
125 if (m_touchInfoMap.contains(event->touchId())) {
126 TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
127
128 if (event->gained()) {
129 ugDebug("Got ownership of touch " << event->touchId());
130 touchInfo.ownership = OwnershipGranted;
131 } else {
132 ugDebug("Lost ownership of touch " << event->touchId());
133 m_touchInfoMap.remove(event->touchId());
134 removeTouchFromStoredEvents(event->touchId());
135 }
136
137 dispatchFullyOwnedEvents();
138 } else {
139 // Ignore it. It probably happened because the TouchGate got disabled
140 // between the time it requested ownership and the time it got it.
141 }
142}
143
144bool TouchGate::isTouchPointOwned(int touchId) const
145{
146 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
147}
148
149void TouchGate::storeTouchEvent(QTouchDevice *device,
150 Qt::KeyboardModifiers modifiers,
151 const QList<QTouchEvent::TouchPoint> &touchPoints,
152 QWindow *window,
153 ulong timestamp)
154{
155 ugDebug("Storing" << touchPoints);
156 TouchEvent event(device, modifiers, touchPoints, window, timestamp);
157 m_storedEvents.append(std::move(event));
158}
159
160void TouchGate::removeTouchFromStoredEvents(int touchId)
161{
162 int i = 0;
163 while (i < m_storedEvents.count()) {
164 TouchEvent &event = m_storedEvents[i];
165 bool removed = event.removeTouch(touchId);
166
167 if (removed && event.touchPoints.isEmpty()) {
168 m_storedEvents.removeAt(i);
169 } else {
170 ++i;
171 }
172 }
173}
174
175void TouchGate::dispatchFullyOwnedEvents()
176{
177 while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
178 TouchEvent event = m_storedEvents.takeFirst();
179 dispatchTouchEventToTarget(event);
180 }
181}
182
183#if TOUCHGATE_DEBUG
184QString TouchGate::oldestPendingTouchIdsString()
185{
186 Q_ASSERT(!m_storedEvents.isEmpty());
187
188 QString str;
189
190 const 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 }
199
200 return str;
201}
202#endif
203
204bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const
205{
206 for (int i = 0; i < event.touchPoints.count(); ++i) {
207 if (!isTouchPointOwned(event.touchPoints[i].id())) {
208 return false;
209 }
210 }
211
212 return true;
213}
214
215void TouchGate::setTargetItem(QQuickItem *item)
216{
217 // TODO: changing the target item while dispatch of touch events is taking place will
218 // create a mess
219
220 if (item == m_dispatcher.targetItem())
221 return;
222
223 m_dispatcher.setTargetItem(item);
224 Q_EMIT targetItemChanged(item);
225}
226
227void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
228{
229 removeTouchInfoForEndedTouches(event.touchPoints);
230 m_dispatcher.dispatch(event.device,
231 event.modifiers,
232 event.touchPoints,
233 event.window,
234 event.timestamp);
235}
236
237void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
238{
239 for (int i = 0; i < touchPoints.size(); ++i) {\
240 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
241
242 if (touchPoint.state() == Qt::TouchPointReleased) {
243 Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
244 Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
245 Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
246 m_touchInfoMap.remove(touchPoint.id());
247 }
248 }
249}
250
251void TouchGate::onEnabledChanged()
252{
253 ugDebug(" enabled = " << isEnabled());
254 if (!isEnabled()) {
255 reset();
256 }
257}
258
259void TouchGate::reset()
260{
261 m_storedEvents.clear();
262 m_touchInfoMap.clear();
263 m_dispatcher.reset();
264}
265
266TouchGate::TouchEvent::TouchEvent(QTouchDevice *device,
267 Qt::KeyboardModifiers modifiers,
268 const QList<QTouchEvent::TouchPoint> &touchPoints,
269 QWindow *window,
270 ulong timestamp)
271 : device(device)
272 , modifiers(modifiers)
273 , touchPoints(touchPoints)
274 , window(window)
275 , timestamp(timestamp)
276{
277}
278
279bool TouchGate::TouchEvent::removeTouch(int touchId)
280{
281 bool removed = false;
282 for (int i = 0; i < touchPoints.count() && !removed; ++i) {
283 if (touchPoints[i].id() == touchId) {
284 touchPoints.removeAt(i);
285 removed = true;
286 }
287 }
288
289 return removed;
290}