on xenial help sign in
Make TouchGate synthesize QMouseEvents for mouse-based target items

As the dispatch code grew considerably because of that it was moved out of TouchGate, into a separate object called TouchDispatcher. Moved some common test code out of tst_DirectionalDragArea. Also added TouchGate to cover Greeter's MouseArea, so that it only triggers hint animations and tease launcher once we are sure that a left-edge drag won't take place. ie: don't tease launcher while it's being dragged at the same time. Fixes: 1381067 Approved by: Albert Astals Cid

commit0706621a7eec48969e71a6430f85cb8a07383fb8 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateThu 30 Oct 2014, 21:41 (12 years ago) parent37b678649e495c
15 files changed+685−199
libs/UbuntuGestures/DebugHelpers.cpp+27 libs/UbuntuGestures/DebugHelpers.h+2 plugins/Ubuntu/Gestures/CMakeLists.txt+1 plugins/Ubuntu/Gestures/TouchDispatcher.cpp+324 plugins/Ubuntu/Gestures/TouchDispatcher.h+86 plugins/Ubuntu/Gestures/TouchGate.cpp+7−103 plugins/Ubuntu/Gestures/TouchGate.h+5−21 qml/Greeter/Greeter.qml+5 qml/Launcher/Launcher.qml+1−1 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+1 tests/plugins/Ubuntu/Gestures/GestureTest.cpp+70 tests/plugins/Ubuntu/Gestures/GestureTest.h+40−1 tests/plugins/Ubuntu/Gestures/empty.qml+22 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+4−73 tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+90
libs/UbuntuGestures/DebugHelpers.cpp+27
@@ -16,6 +16,7 @@
1616
1717#include "DebugHelpers.h"
1818#include <QTouchEvent>
19+#include <QMouseEvent>
1920
2021QString touchPointStateToString(Qt::TouchPointState state)
2122{
@@ -66,3 +67,29 @@ QString touchEventToString(const QTouchEvent *ev)
6667
6768 return message;
6869}
70+
71+QString mouseEventToString(const QMouseEvent *ev)
72+{
73+ QString message;
74+
75+ switch (ev->type()) {
76+ case QEvent::MouseButtonPress:
77+ message.append("MouseButtonPress ");
78+ break;
79+ case QEvent::MouseButtonRelease:
80+ message.append("MouseButtonRelease ");
81+ break;
82+ case QEvent::MouseButtonDblClick:
83+ message.append("MouseButtonDblClick ");
84+ break;
85+ case QEvent::MouseMove:
86+ message.append("MouseButtonMove ");
87+ break;
88+ default:
89+ message.append("INVALID_MOUSE_EVENT_TYPE ");
90+ }
91+
92+ message.append(QString("pos(%1, %2)").arg(ev->x()).arg(ev->y()));
93+
94+ return message;
95+}
libs/UbuntuGestures/DebugHelpers.h+2
@@ -21,9 +21,11 @@
2121
2222#include "UbuntuGesturesGlobal.h"
2323
24+class QMouseEvent;
2425class QTouchEvent;
2526
2627UBUNTUGESTURES_EXPORT QString touchPointStateToString(Qt::TouchPointState state);
2728UBUNTUGESTURES_EXPORT QString touchEventToString(const QTouchEvent *ev);
29+UBUNTUGESTURES_EXPORT QString mouseEventToString(const QMouseEvent *ev);
2830
2931#endif // UBUNTUGESTURES_DEBUG_HELPER_H
plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -8,6 +8,7 @@ set(UbuntuGesturesQml_SOURCES
88 DirectionalDragArea.cpp
99 PressedOutsideNotifier.cpp
1010 TimeSource.cpp
11+ TouchDispatcher.cpp
1112 TouchGate.cpp
1213)
1314
plugins/Ubuntu/Gestures/TouchDispatcher.cppnew file+324
@@ -0,0 +1,324 @@
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+ */
16+
17+#include "TouchDispatcher.h"
18+
19+#include <QScopedPointer>
20+
21+#pragma GCC diagnostic push
22+#pragma GCC diagnostic ignored "-pedantic"
23+#include <private/qquickitem_p.h>
24+#pragma GCC diagnostic pop
25+
26+#define TOUCHDISPATCHER_DEBUG 0
27+
28+#if TOUCHDISPATCHER_DEBUG
29+#include <DebugHelpers.h>
30+#endif
31+
32+TouchDispatcher::TouchDispatcher()
33+ : m_status(NoActiveTouch)
34+ , m_touchMouseId(-1)
35+{
36+}
37+
38+void TouchDispatcher::setTargetItem(QQuickItem *target)
39+{
40+ if (target != m_targetItem) {
41+ m_targetItem = target;
42+ if (m_status != NoActiveTouch) {
43+ qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
44+ m_status = TargetRejectedTouches;
45+ }
46+ }
47+}
48+
49+void TouchDispatcher::dispatch(QEvent::Type eventType,
50+ QTouchDevice *device,
51+ Qt::KeyboardModifiers modifiers,
52+ const QList<QTouchEvent::TouchPoint> &touchPoints,
53+ QWindow *window,
54+ ulong timestamp)
55+{
56+ if (m_targetItem.isNull()) {
57+ qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
58+ return;
59+ }
60+
61+ if (eventType == QEvent::TouchBegin) {
62+ dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
63+
64+ } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
65+
66+ if (m_status == DeliveringTouchEvents) {
67+ dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp);
68+ } else if (m_status == DeliveringMouseEvents) {
69+ dispatchAsMouse(device, modifiers, touchPoints, timestamp);
70+ } else {
71+ Q_ASSERT(m_status == TargetRejectedTouches);
72+ #if TOUCHDISPATCHER_DEBUG
73+ qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
74+ << "because it already rejected the touch stream.";
75+ #endif
76+ // Do nothing
77+ }
78+
79+ if (eventType == QEvent::TouchEnd) {
80+ m_status = NoActiveTouch;
81+ m_touchMouseId = -1;
82+ }
83+
84+ } else {
85+ // Should never happen
86+ qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
87+ Q_ASSERT(false);
88+ return;
89+ }
90+}
91+
92+void TouchDispatcher::dispatchTouchBegin(
93+ QTouchDevice *device,
94+ Qt::KeyboardModifiers modifiers,
95+ const QList<QTouchEvent::TouchPoint> &touchPoints,
96+ QWindow *window,
97+ ulong timestamp)
98+{
99+ Q_ASSERT(m_status == NoActiveTouch);
100+ QQuickItem *targetItem = m_targetItem.data();
101+
102+ if (!targetItem->isEnabled() || !targetItem->isVisible()) {
103+ #if TOUCHDISPATCHER_DEBUG
104+ qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
105+ << "because it's disabled or invisible.";
106+ #endif
107+ return;
108+ }
109+
110+ // Map touch points to targetItem coordinates
111+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
112+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
113+
114+ QScopedPointer<QTouchEvent> touchEvent(
115+ createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
116+
117+
118+ #if TOUCHDISPATCHER_DEBUG
119+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
120+ << "to" << targetItem;
121+ #endif
122+ QCoreApplication::sendEvent(targetItem, touchEvent.data());
123+
124+
125+ if (touchEvent->isAccepted()) {
126+ #if TOUCHDISPATCHER_DEBUG
127+ qDebug() << "[TouchDispatcher] Item accepted the touch event.";
128+ #endif
129+ m_status = DeliveringTouchEvents;
130+ } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
131+ #if TOUCHDISPATCHER_DEBUG
132+ qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
133+ #endif
134+ // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
135+ QScopedPointer<QMouseEvent> mouseEvent(
136+ touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
137+ modifiers, false /* transformNeeded */));
138+ Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
139+
140+ #if TOUCHDISPATCHER_DEBUG
141+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
142+ << "to" << m_targetItem.data();
143+ #endif
144+ QCoreApplication::sendEvent(targetItem, mouseEvent.data());
145+ if (mouseEvent->isAccepted()) {
146+ #if TOUCHDISPATCHER_DEBUG
147+ qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
148+ #endif
149+ m_status = DeliveringMouseEvents;
150+ m_touchMouseId = targetTouchPoints.at(0).id();
151+ } else {
152+ #if TOUCHDISPATCHER_DEBUG
153+ qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
154+ #endif
155+ m_status = TargetRejectedTouches;
156+ }
157+ } else {
158+ #if TOUCHDISPATCHER_DEBUG
159+ qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
160+ #endif
161+ m_status = TargetRejectedTouches;
162+ }
163+}
164+
165+void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
166+ QTouchDevice *device,
167+ Qt::KeyboardModifiers modifiers,
168+ const QList<QTouchEvent::TouchPoint> &touchPoints,
169+ QWindow *window,
170+ ulong timestamp)
171+{
172+ QQuickItem *targetItem = m_targetItem.data();
173+
174+ // Map touch points to targetItem coordinates
175+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
176+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
177+
178+ QScopedPointer<QTouchEvent> eventForTargetItem(
179+ createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
180+
181+
182+ #if TOUCHDISPATCHER_DEBUG
183+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
184+ << "to" << targetItem;
185+ #endif
186+ QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
187+}
188+
189+void TouchDispatcher::dispatchAsMouse(
190+ QTouchDevice * /*device*/,
191+ Qt::KeyboardModifiers modifiers,
192+ const QList<QTouchEvent::TouchPoint> &touchPoints,
193+ ulong timestamp)
194+{
195+ // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
196+
197+ Q_ASSERT(!touchPoints.isEmpty());
198+
199+ const QTouchEvent::TouchPoint *touchMouse = nullptr;
200+
201+ if (m_touchMouseId != -1) {
202+ for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
203+ const auto &touchPoint = touchPoints.at(i);
204+ if (touchPoint.id() == m_touchMouseId) {
205+ touchMouse = &touchPoint;
206+ }
207+ }
208+
209+ Q_ASSERT(touchMouse);
210+ if (!touchMouse) {
211+ // should not happen, but deal with it just in case.
212+ qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
213+ m_touchMouseId);
214+ m_touchMouseId = touchPoints.at(0).id();
215+ touchMouse = &touchPoints.at(0);
216+ }
217+ } else {
218+ // Try to find a new touch for mouse emulation
219+ for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
220+ const auto &touchPoint = touchPoints.at(i);
221+ if (touchPoint.state() == Qt::TouchPointPressed) {
222+ touchMouse = &touchPoint;
223+ m_touchMouseId = touchMouse->id();
224+ }
225+ }
226+ }
227+
228+ if (touchMouse) {
229+ QEvent::Type eventType;
230+ if (touchMouse->state() == Qt::TouchPointPressed) {
231+ eventType = QEvent::MouseButtonPress;
232+ } if (touchMouse->state() == Qt::TouchPointReleased) {
233+ eventType = QEvent::MouseButtonRelease;
234+ m_touchMouseId = -1;
235+ } else {
236+ eventType = QEvent::MouseMove;
237+ }
238+
239+ QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
240+ true /* transformNeeded */));
241+
242+ #if TOUCHDISPATCHER_DEBUG
243+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
244+ << "to" << m_targetItem.data();
245+ #endif
246+ QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
247+ }
248+}
249+
250+QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
251+ QTouchDevice *device,
252+ Qt::KeyboardModifiers modifiers,
253+ const QList<QTouchEvent::TouchPoint> &touchPoints,
254+ QWindow *window,
255+ ulong timestamp)
256+{
257+ Qt::TouchPointStates eventStates = 0;
258+ for (int i = 0; i < touchPoints.count(); i++)
259+ eventStates |= touchPoints[i].state();
260+ // if all points have the same state, set the event type accordingly
261+ switch (eventStates) {
262+ case Qt::TouchPointPressed:
263+ eventType = QEvent::TouchBegin;
264+ break;
265+ case Qt::TouchPointReleased:
266+ eventType = QEvent::TouchEnd;
267+ break;
268+ default:
269+ eventType = QEvent::TouchUpdate;
270+ break;
271+ }
272+
273+ QTouchEvent *touchEvent = new QTouchEvent(eventType);
274+ touchEvent->setWindow(window);
275+ touchEvent->setTarget(m_targetItem.data());
276+ touchEvent->setDevice(device);
277+ touchEvent->setModifiers(modifiers);
278+ touchEvent->setTouchPoints(touchPoints);
279+ touchEvent->setTouchPointStates(eventStates);
280+ touchEvent->setTimestamp(timestamp);
281+ touchEvent->accept();
282+ return touchEvent;
283+}
284+
285+// NB: From QQuickWindow
286+void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
287+{
288+ QMatrix4x4 transformMatrix(transform);
289+ for (int i=0; i<touchPoints.count(); i++) {
290+ QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
291+ touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
292+ touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
293+ touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
294+ touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
295+ }
296+}
297+
298+// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
299+QMouseEvent *TouchDispatcher::touchToMouseEvent(
300+ QEvent::Type type, const QTouchEvent::TouchPoint &p,
301+ ulong timestamp, Qt::KeyboardModifiers modifiers,
302+ bool transformNeeded)
303+{
304+ QQuickItem *item = m_targetItem.data();
305+
306+ // The touch point local position and velocity are not yet transformed.
307+ QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
308+ p.scenePos(), p.screenPos(), Qt::LeftButton,
309+ (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
310+ modifiers);
311+ me->setAccepted(true);
312+ me->setTimestamp(timestamp);
313+ QVector2D transformedVelocity = p.velocity();
314+ if (transformNeeded) {
315+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
316+ QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
317+ transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
318+ }
319+
320+ // Add these later if needed:
321+ //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
322+ //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
323+ return me;
324+}
plugins/Ubuntu/Gestures/TouchDispatcher.hnew file+86
@@ -0,0 +1,86 @@
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+ */
16+
17+#ifndef UBUNTU_TOUCH_DISPATCHER_H
18+#define UBUNTU_TOUCH_DISPATCHER_H
19+
20+#include "UbuntuGesturesQmlGlobal.h"
21+
22+#include <QPointer>
23+#include <QQuickItem>
24+
25+/*
26+ Dispatches touches to the given target, converting the touch point
27+ coordinates accordingly.
28+
29+ Also takes care of synthesizing mouse events in case the target
30+ doesn't work with touch events.
31+ */
32+class UBUNTUGESTURESQML_EXPORT TouchDispatcher {
33+public:
34+ TouchDispatcher();
35+
36+ void setTargetItem(QQuickItem *target);
37+ QQuickItem *targetItem() { return m_targetItem; }
38+
39+ void dispatch(QEvent::Type eventType,
40+ QTouchDevice *device,
41+ Qt::KeyboardModifiers modifiers,
42+ const QList<QTouchEvent::TouchPoint> &touchPoints,
43+ QWindow *window,
44+ ulong timestamp);
45+private:
46+ void dispatchTouchBegin(
47+ QTouchDevice *device,
48+ Qt::KeyboardModifiers modifiers,
49+ const QList<QTouchEvent::TouchPoint> &touchPoints,
50+ QWindow *window,
51+ ulong timestamp);
52+ void dispatchAsTouch(QEvent::Type eventType,
53+ QTouchDevice *device,
54+ Qt::KeyboardModifiers modifiers,
55+ const QList<QTouchEvent::TouchPoint> &touchPoints,
56+ QWindow *window,
57+ ulong timestamp);
58+ void dispatchAsMouse(
59+ QTouchDevice *device,
60+ Qt::KeyboardModifiers modifiers,
61+ const QList<QTouchEvent::TouchPoint> &touchPoints,
62+ ulong timestamp);
63+
64+ static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
65+ QTouchEvent *createQTouchEvent(QEvent::Type eventType,
66+ QTouchDevice *device,
67+ Qt::KeyboardModifiers modifiers,
68+ const QList<QTouchEvent::TouchPoint> &touchPoints,
69+ QWindow *window,
70+ ulong timestamp);
71+ QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p,
72+ ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true);
73+
74+ QPointer<QQuickItem> m_targetItem;
75+
76+ enum {
77+ NoActiveTouch,
78+ DeliveringTouchEvents,
79+ DeliveringMouseEvents,
80+ TargetRejectedTouches
81+ } m_status;
82+
83+ int m_touchMouseId;
84+};
85+
86+#endif // UBUNTU_TOUCH_DISPATCHER_H
plugins/Ubuntu/Gestures/TouchGate.cpp+7−103
@@ -22,16 +22,10 @@
2222#include <TouchOwnershipEvent.h>
2323#include <TouchRegistry.h>
2424
25−#pragma GCC diagnostic push
26−#pragma GCC diagnostic ignored "-pedantic"
27−#include <private/qquickitem_p.h>
28−#pragma GCC diagnostic pop
29−
3025#if TOUCHGATE_DEBUG
3126#include <DebugHelpers.h>
3227#endif
3328
34−
3529bool TouchGate::event(QEvent *e)
3630{
3731 if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
@@ -189,125 +183,35 @@ void TouchGate::setTargetItem(QQuickItem *item)
189183 // TODO: changing the target item while dispatch of touch events is taking place will
190184 // create a mess
191185
192− if (item == m_targetItem.data())
186+ if (item == m_dispatcher.targetItem())
193187 return;
194188
195− m_targetItem = item;
189+ m_dispatcher.setTargetItem(item);
196190 Q_EMIT targetItemChanged(item);
197191}
198192
199193void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
200194{
201− dispatchTouchEventToTarget(event.eventType,
195+ removeTouchInfoForEndedTouches(event.touchPoints);
196+ m_dispatcher.dispatch(event.eventType,
202197 event.device,
203198 event.modifiers,
204199 event.touchPoints,
205− event.target,
206200 event.window,
207201 event.timestamp);
208202}
209203
210204void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
211205{
212− dispatchTouchEventToTarget(event->type(),
206+ removeTouchInfoForEndedTouches(event->touchPoints());
207+ m_dispatcher.dispatch(event->type(),
213208 event->device(),
214209 event->modifiers(),
215210 event->touchPoints(),
216− event->target(),
217211 event->window(),
218212 event->timestamp());
219213}
220214
221−void 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)
228−{
229− removeTouchInfoForEndedTouches(touchPoints);
230−
231− if (m_targetItem.isNull()) {
232− qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
233− return;
234− }
235−
236− QQuickItem *targetItem = m_targetItem.data();
237−
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− }
245−
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);
251−
252− #if TOUCHGATE_DEBUG
253− qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem))
254− << "to" << targetItem;
255− #endif
256−
257− QCoreApplication::sendEvent(targetItem, eventForTargetItem);
258−
259− delete eventForTargetItem;
260−}
261−
262−// NB: From QQuickWindow
263−void TouchGate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
264−{
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− }
273−}
274−
275−QTouchEvent *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)
282−{
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− }
298−
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;
309−}
310−
311215void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
312216{
313217 for (int i = 0; i < touchPoints.size(); ++i) {\
@@ -327,7 +231,7 @@ TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
327231 , device(event->device())
328232 , modifiers(event->modifiers())
329233 , touchPoints(event->touchPoints())
330− , target(event->target())
234+ , target(qobject_cast<QQuickItem*>(event->target()))
331235 , window(event->window())
332236 , timestamp(event->timestamp())
333237{
plugins/Ubuntu/Gestures/TouchGate.h+5−21
@@ -18,10 +18,10 @@
1818#define UBUNTU_TOUCH_GATE_H
1919
2020#include "UbuntuGesturesQmlGlobal.h"
21+#include "TouchDispatcher.h"
2122
2223#include <QQuickItem>
2324#include <QList>
24−#include <QPointer>
2525#include <QMap>
2626
2727#define TOUCHGATE_DEBUG 0
@@ -47,7 +47,7 @@ class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
4747public:
4848 bool event(QEvent *e) override;
4949
50− QQuickItem *targetItem() { return m_targetItem; }
50+ QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
5151 void setTargetItem(QQuickItem *item);
5252
5353Q_SIGNALS:
@@ -66,7 +66,7 @@ private:
6666 QTouchDevice *device;
6767 Qt::KeyboardModifiers modifiers;
6868 QList<QTouchEvent::TouchPoint> touchPoints;
69− QObject *target;
69+ QQuickItem *target;
7070 QWindow *window;
7171 ulong timestamp;
7272 };
@@ -80,23 +80,7 @@ private:
8080
8181 void dispatchTouchEventToTarget(const TouchEvent &event);
8282 void dispatchTouchEventToTarget(QTouchEvent* event);
83− void dispatchTouchEventToTarget(
84− QEvent::Type eventType,
85− QTouchDevice *device,
86− Qt::KeyboardModifiers modifiers,
87− const QList<QTouchEvent::TouchPoint> &touchPoints,
88− QObject *target,
89− QWindow *window,
90− ulong timestamp);
91−
92− void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
93− static QTouchEvent *createQTouchEvent(QEvent::Type eventType,
94− QTouchDevice *device,
95− Qt::KeyboardModifiers modifiers,
96− const QList<QTouchEvent::TouchPoint> &touchPoints,
97− QObject *target,
98− QWindow *window,
99− ulong timestamp);
83+
10084 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
10185
10286 #if TOUCHGATE_DEBUG
@@ -118,7 +102,7 @@ private:
118102 };
119103 QMap<int, TouchInfo> m_touchInfoMap;
120104
121− QPointer<QQuickItem> m_targetItem;
105+ TouchDispatcher m_dispatcher;
122106
123107 friend class tst_TouchGate;
124108};
qml/Greeter/Greeter.qml+5
@@ -129,6 +129,11 @@ Showable {
129129 direction: Direction.Leftwards
130130 }
131131 }
132+ TouchGate {
133+ targetItem: dragHandle
134+ anchors.fill: targetItem
135+ enabled: targetITem.enabled
136+ }
132137
133138 Loader {
134139 id: greeterContentLoader
qml/Launcher/Launcher.qml+1−1
@@ -64,7 +64,7 @@ Item {
6464 }
6565
6666 function tease() {
67− if (available) {
67+ if (available && !dragArea.dragging) {
6868 teaseTimer.start();
6969 }
7070 }
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -45,6 +45,7 @@ endmacro(add_gesture_test)
4545
4646add_gesture_ui_test(DirectionalDragArea)
4747add_gesture_ui_test(PressedOutsideNotifier)
48+add_gesture_ui_test(TouchDispatcher)
4849add_gesture_ui_test(TouchGate)
4950add_gesture_test(Damper)
5051add_gesture_test(AxisVelocityCalculator)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+70
@@ -21,6 +21,8 @@
2121#include <QQuickView>
2222#include <QtTest>
2323
24+#include <TouchRegistry.h>
25+
2426GestureTest::GestureTest(const QString &qmlFilename)
2527 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
2628{
@@ -44,11 +46,79 @@ void GestureTest::init()
4446 m_view->show();
4547 QVERIFY(QTest::qWaitForWindowExposed(m_view));
4648 QVERIFY(m_view->rootObject() != 0);
49+
50+ m_touchRegistry = new TouchRegistry;
51+ m_view->installEventFilter(m_touchRegistry);
52+
4753 qApp->processEvents();
4854}
4955
5056void GestureTest::cleanup()
5157{
58+ m_view->removeEventFilter(m_touchRegistry);
59+ delete m_touchRegistry;
60+ m_touchRegistry = nullptr;
61+
5262 delete m_view;
5363 m_view = nullptr;
5464}
65+
66+////////////////////////// TouchMemento /////////////////////////////
67+
68+TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
69+ : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
70+{
71+
72+}
73+
74+bool TouchMemento::containsTouchWithId(int touchId) const
75+{
76+ for (int i = 0; i < touchPoints.count(); ++i) {
77+ if (touchPoints.at(i).id() == touchId) {
78+ return true;
79+ }
80+ }
81+ return false;
82+}
83+
84+////////////////////////// DummyItem /////////////////////////////
85+
86+DummyItem::DummyItem(QQuickItem *parent)
87+ : QQuickItem(parent)
88+{
89+ touchEventHandler = defaultTouchEventHandler;
90+ mousePressEventHandler = defaultMouseEventHandler;
91+ mouseMoveEventHandler = defaultMouseEventHandler;
92+ mouseReleaseEventHandler = defaultMouseEventHandler;
93+}
94+
95+void DummyItem::touchEvent(QTouchEvent *event)
96+{
97+ touchEvents.append(TouchMemento(event));
98+ touchEventHandler(event);
99+}
100+
101+void DummyItem::mousePressEvent(QMouseEvent *event)
102+{
103+ mousePressEventHandler(event);
104+}
105+
106+void DummyItem::mouseMoveEvent(QMouseEvent *event)
107+{
108+ mouseMoveEventHandler(event);
109+}
110+
111+void DummyItem::mouseReleaseEvent(QMouseEvent *event)
112+{
113+ mouseReleaseEventHandler(event);
114+}
115+
116+void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
117+{
118+ event->accept();
119+}
120+
121+void DummyItem::defaultMouseEventHandler(QMouseEvent *event)
122+{
123+ event->accept();
124+}
tests/plugins/Ubuntu/Gestures/GestureTest.h+40−1
@@ -17,14 +17,52 @@
1717#ifndef GESTURETEST_H
1818#endif // GESTURETEST_H
1919
20−#include <QObject>
20+#include <QQuickItem>
21+#include <QTouchEvent>
2122
2223class QQuickView;
2324class QTouchDevice;
2425
26+class TouchRegistry;
27+
28+// C++ std lib
29+#include <functional>
30+
2531/*
2632 The common stuff among tests come here
2733 */
34+
35+class TouchMemento {
36+public:
37+ TouchMemento(const QTouchEvent *touchEvent);
38+ Qt::TouchPointStates touchPointStates;
39+ QList<QTouchEvent::TouchPoint> touchPoints;
40+
41+ bool containsTouchWithId(int touchId) const;
42+};
43+
44+class DummyItem : public QQuickItem
45+{
46+ Q_OBJECT
47+public:
48+ DummyItem(QQuickItem *parent = 0);
49+
50+ QList<TouchMemento> touchEvents;
51+ std::function<void(QTouchEvent*)> touchEventHandler;
52+ std::function<void(QMouseEvent*)> mousePressEventHandler;
53+ std::function<void(QMouseEvent*)> mouseMoveEventHandler;
54+ std::function<void(QMouseEvent*)> mouseReleaseEventHandler;
55+protected:
56+ void touchEvent(QTouchEvent *event) override;
57+
58+ void mousePressEvent(QMouseEvent *event) override;
59+ void mouseMoveEvent(QMouseEvent *event) override;
60+ void mouseReleaseEvent(QMouseEvent *event) override;
61+private:
62+ static void defaultTouchEventHandler(QTouchEvent *event);
63+ static void defaultMouseEventHandler(QMouseEvent *event);
64+};
65+
2866class GestureTest : public QObject
2967{
3068 Q_OBJECT
@@ -40,6 +78,7 @@ protected Q_SLOTS:
4078protected:
4179 QTouchDevice *m_device;
4280 QQuickView *m_view;
81+ TouchRegistry *m_touchRegistry;
4382 QString m_qmlFilename;
4483};
4584
tests/plugins/Ubuntu/Gestures/empty.qmlnew file+22
@@ -0,0 +1,22 @@
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+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Gestures 0.1
19+
20+Item {
21+ width: 720; height: 720
22+}
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+4−73
@@ -21,8 +21,6 @@
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
2323
24−// C++ std lib
25−#include <functional>
2624
2725#include <DirectionalDragArea.h>
2826#include <TouchRegistry.h>
@@ -31,28 +29,6 @@
3129
3230using namespace UbuntuGestures;
3331
34−class TouchMemento {
35−public:
36− TouchMemento(const QTouchEvent *touchEvent);
37− Qt::TouchPointStates touchPointStates;
38− QList<QTouchEvent::TouchPoint> touchPoints;
39−
40− bool containsTouchWithId(int touchId) const;
41−};
42−
43−class DummyItem : public QQuickItem
44−{
45− Q_OBJECT
46−public:
47− DummyItem(QQuickItem *parent = 0);
48−
49− QList<TouchMemento> touchEvents;
50− std::function<void(QTouchEvent*)> touchEventHandler;
51−protected:
52− void touchEvent(QTouchEvent *event) override;
53−private:
54− static void defaultTouchEventHandler(QTouchEvent *event);
55−};
5632
5733class ComplexFakeTimer : public FakeTimer
5834{
@@ -149,7 +125,6 @@ private Q_SLOTS:
149125
150126private:
151127 void passTime(qint64 timeSpanMs);
152− TouchRegistry *touchRegistry;
153128 ComplexFakeTimer *fakeTimer;
154129 QSharedPointer<FakeTimeSource> fakeTimeSource;
155130};
@@ -171,19 +146,12 @@ void tst_DirectionalDragArea::init()
171146 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
172147 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
173148
174− touchRegistry = new TouchRegistry;
175− m_view->installEventFilter(touchRegistry);
176−
177149 fakeTimeSource.reset(new FakeTimeSource);
178150 fakeTimer = new ComplexFakeTimer(fakeTimeSource);
179151}
180152
181153void tst_DirectionalDragArea::cleanup()
182154{
183− m_view->removeEventFilter(touchRegistry);
184− delete touchRegistry;
185− touchRegistry = nullptr;
186−
187155 delete fakeTimer;
188156 fakeTimer = 0;
189157
@@ -1015,7 +983,7 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1015983 QPoint touchPos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1016984
1017985 dummyItem->touchEventHandler = [&](QTouchEvent *event) {
1018− touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
986+ m_touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
1019987 event->ignore();
1020988 };
1021989
@@ -1023,7 +991,7 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1023991
1024992 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1025993
1026− touchRegistry->requestTouchOwnership(0, dummyItem);
994+ m_touchRegistry->requestTouchOwnership(0, dummyItem);
1027995
1028996 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1029997
@@ -1172,7 +1140,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
11721140
11731141 // edgeDragArea should be an undecided candidate
11741142 {
1175− auto touchInfo = touchRegistry->findTouchInfo(0);
1143+ auto touchInfo = m_touchRegistry->findTouchInfo(0);
11761144 QCOMPARE(touchInfo->candidates.size(), 1);
11771145 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
11781146 QCOMPARE(touchInfo->candidates.at(0).undecided, true);
@@ -1188,7 +1156,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
11881156
11891157 // edgeDragArea should no longer be a candidate
11901158 {
1191− auto touchInfo = touchRegistry->findTouchInfo(0);
1159+ auto touchInfo = m_touchRegistry->findTouchInfo(0);
11921160 QCOMPARE(touchInfo->candidates.size(), 0);
11931161 }
11941162
@@ -1289,43 +1257,6 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
12891257 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
12901258}
12911259
1292−////////////////////////// TouchMemento /////////////////////////////
1293−
1294−TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
1295− : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
1296−{
1297−
1298−}
1299−
1300−bool TouchMemento::containsTouchWithId(int touchId) const
1301−{
1302− for (int i = 0; i < touchPoints.count(); ++i) {
1303− if (touchPoints.at(i).id() == touchId) {
1304− return true;
1305− }
1306− }
1307− return false;
1308−}
1309−
1310−////////////////////////// DummyItem /////////////////////////////
1311−
1312−DummyItem::DummyItem(QQuickItem *parent)
1313− : QQuickItem(parent)
1314−{
1315− touchEventHandler = defaultTouchEventHandler;
1316−}
1317−
1318−void DummyItem::touchEvent(QTouchEvent *event)
1319−{
1320− touchEvents.append(TouchMemento(event));
1321− touchEventHandler(event);
1322−}
1323−
1324−void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
1325−{
1326− event->accept();
1327−}
1328−
13291260QTEST_MAIN(tst_DirectionalDragArea)
13301261
13311262#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cppnew file+90
@@ -0,0 +1,90 @@
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+ */
16+
17+#include <QtTest>
18+#include <QQuickView>
19+
20+#include "GestureTest.h"
21+
22+#include <TouchDispatcher.h>
23+
24+class tst_TouchDispatcher : public GestureTest
25+{
26+ Q_OBJECT
27+public:
28+ tst_TouchDispatcher();
29+private Q_SLOTS:
30+ void sendMouseEventIfTouchIgnored_data();
31+ void sendMouseEventIfTouchIgnored();
32+};
33+
34+tst_TouchDispatcher::tst_TouchDispatcher()
35+ : GestureTest(QStringLiteral("empty.qml"))
36+{
37+}
38+
39+void tst_TouchDispatcher::sendMouseEventIfTouchIgnored_data()
40+{
41+ QTest::addColumn<bool>("itemAcceptsTouch");
42+ QTest::addColumn<int>("acceptedMouseButtons");
43+ QTest::addColumn<bool>("shouldGetMousePress");
44+
45+ QTest::newRow("accepts touch and doesn't accept mouse buttons") << true << (int)Qt::NoButton << false;
46+ QTest::newRow("ignores touch and accepts mouse buttons") << false << (int)Qt::LeftButton << true;
47+ QTest::newRow("ignores touch and doesn't accept mouse buttons") << false << (int)Qt::NoButton << false;
48+}
49+
50+void tst_TouchDispatcher::sendMouseEventIfTouchIgnored()
51+{
52+ QFETCH(bool, itemAcceptsTouch);
53+ QFETCH(int, acceptedMouseButtons);
54+ QFETCH(bool, shouldGetMousePress);
55+ DummyItem *dummyItem = new DummyItem(m_view->rootObject());
56+
57+ TouchDispatcher touchDispatcher;
58+ touchDispatcher.setTargetItem(dummyItem);
59+
60+ QList<QTouchEvent::TouchPoint> touchPoints;
61+ QTouchEvent::TouchPoint touchPoint;
62+ touchPoint.setId(0);
63+ touchPoint.setState(Qt::TouchPointPressed);
64+ touchPoints.append(touchPoint);
65+
66+ bool gotTouchEvent = false;
67+ bool gotMousePressEvent = false;
68+
69+ dummyItem->setAcceptedMouseButtons((Qt::MouseButtons)acceptedMouseButtons);
70+
71+ dummyItem->touchEventHandler = [&](QTouchEvent *event) {
72+ gotTouchEvent = true;
73+ event->setAccepted(itemAcceptsTouch);
74+ };
75+
76+ dummyItem->mousePressEventHandler = [&](QMouseEvent *event) {
77+ gotMousePressEvent = true;
78+ event->accept();
79+ };
80+
81+ touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view,
82+ 12345 /* timestamp */);
83+
84+ QVERIFY(gotTouchEvent);
85+ QCOMPARE(gotMousePressEvent, shouldGetMousePress);
86+}
87+
88+QTEST_MAIN(tst_TouchDispatcher)
89+
90+#include "tst_TouchDispatcher.moc"