@@ -0,0 +1,324 @@
2+ * Copyright (C) 2014 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 "TouchDispatcher.h"
19+#include <QScopedPointer>
21+#pragma GCC diagnostic push
22+#pragma GCC diagnostic ignored "-pedantic"
23+#include <private/qquickitem_p.h>
24+#pragma GCC diagnostic pop
26+#define TOUCHDISPATCHER_DEBUG 0
28+#if TOUCHDISPATCHER_DEBUG
29+#include <DebugHelpers.h>
32+TouchDispatcher::TouchDispatcher()
33+ : m_status(NoActiveTouch)
38+void TouchDispatcher::setTargetItem(QQuickItem *target)
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;
49+void TouchDispatcher::dispatch(QEvent::Type eventType,
50+ QTouchDevice *device,
51+ Qt::KeyboardModifiers modifiers,
52+ const QList<QTouchEvent::TouchPoint> &touchPoints,
56+ if (m_targetItem.isNull()) {
57+ qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
61+ if (eventType == QEvent::TouchBegin) {
62+ dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
64+ } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
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);
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.";
79+ if (eventType == QEvent::TouchEnd) {
80+ m_status = NoActiveTouch;
85+ // Should never happen
86+ qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
92+void TouchDispatcher::dispatchTouchBegin(
93+ QTouchDevice *device,
94+ Qt::KeyboardModifiers modifiers,
95+ const QList<QTouchEvent::TouchPoint> &touchPoints,
99+ Q_ASSERT(m_status == NoActiveTouch);
100+ QQuickItem *targetItem = m_targetItem.data();
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.";
110+ // Map touch points to targetItem coordinates
111+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
112+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
114+ QScopedPointer<QTouchEvent> touchEvent(
115+ createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
118+ #if TOUCHDISPATCHER_DEBUG
119+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
120+ << "to" << targetItem;
122+ QCoreApplication::sendEvent(targetItem, touchEvent.data());
125+ if (touchEvent->isAccepted()) {
126+ #if TOUCHDISPATCHER_DEBUG
127+ qDebug() << "[TouchDispatcher] Item accepted the touch event.";
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";
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);
140+ #if TOUCHDISPATCHER_DEBUG
141+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
142+ << "to" << m_targetItem.data();
144+ QCoreApplication::sendEvent(targetItem, mouseEvent.data());
145+ if (mouseEvent->isAccepted()) {
146+ #if TOUCHDISPATCHER_DEBUG
147+ qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
149+ m_status = DeliveringMouseEvents;
150+ m_touchMouseId = targetTouchPoints.at(0).id();
152+ #if TOUCHDISPATCHER_DEBUG
153+ qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
155+ m_status = TargetRejectedTouches;
158+ #if TOUCHDISPATCHER_DEBUG
159+ qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
161+ m_status = TargetRejectedTouches;
165+void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
166+ QTouchDevice *device,
167+ Qt::KeyboardModifiers modifiers,
168+ const QList<QTouchEvent::TouchPoint> &touchPoints,
172+ QQuickItem *targetItem = m_targetItem.data();
174+ // Map touch points to targetItem coordinates
175+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
176+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
178+ QScopedPointer<QTouchEvent> eventForTargetItem(
179+ createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
182+ #if TOUCHDISPATCHER_DEBUG
183+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
184+ << "to" << targetItem;
186+ QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
189+void TouchDispatcher::dispatchAsMouse(
190+ QTouchDevice * /*device*/,
191+ Qt::KeyboardModifiers modifiers,
192+ const QList<QTouchEvent::TouchPoint> &touchPoints,
195+ // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
197+ Q_ASSERT(!touchPoints.isEmpty());
199+ const QTouchEvent::TouchPoint *touchMouse = nullptr;
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;
209+ Q_ASSERT(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.",
214+ m_touchMouseId = touchPoints.at(0).id();
215+ touchMouse = &touchPoints.at(0);
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();
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;
236+ eventType = QEvent::MouseMove;
239+ QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
240+ true /* transformNeeded */));
242+ #if TOUCHDISPATCHER_DEBUG
243+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
244+ << "to" << m_targetItem.data();
246+ QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
250+QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
251+ QTouchDevice *device,
252+ Qt::KeyboardModifiers modifiers,
253+ const QList<QTouchEvent::TouchPoint> &touchPoints,
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;
265+ case Qt::TouchPointReleased:
266+ eventType = QEvent::TouchEnd;
269+ eventType = QEvent::TouchUpdate;
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();
285+// NB: From QQuickWindow
286+void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
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());
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)
304+ QQuickItem *item = m_targetItem.data();
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),
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();
320+ // Add these later if needed:
321+ //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
322+ //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);