17#include "TouchDispatcher.h"
19#include <QGuiApplication>
20#include <QScopedPointer>
22#include <private/qquickitem_p.h>
24#define TOUCHDISPATCHER_DEBUG 0
26#if TOUCHDISPATCHER_DEBUG
27#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
28#include <DebugHelpers.h>
30#define ugDebug(params) ((void)0)
33TouchDispatcher::TouchDispatcher()
34 : m_status(NoActiveTouch)
36 , m_touchMousePressTimestamp(0)
40void TouchDispatcher::setTargetItem(QQuickItem *target)
42 if (target != m_targetItem) {
43 m_targetItem = target;
44 if (m_status != NoActiveTouch) {
45 qWarning(
"[TouchDispatcher] Changing target item in the middle of a touch stream");
46 setStatus(TargetRejectedTouches);
51void TouchDispatcher::dispatch(QTouchDevice *device,
52 Qt::KeyboardModifiers modifiers,
53 const QList<QTouchEvent::TouchPoint> &touchPoints,
57 if (m_targetItem.isNull()) {
58 qWarning(
"[TouchDispatcher] Cannot dispatch touch event because target item is null");
62 QEvent::Type eventType = resolveEventType(touchPoints);
64 if (eventType == QEvent::TouchBegin) {
65 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
67 }
else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
69 if (m_status == DeliveringTouchEvents) {
70 dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp);
71 }
else if (m_status == DeliveringMouseEvents) {
72 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
74 Q_ASSERT(m_status == TargetRejectedTouches);
75 ugDebug(
"Not dispatching touch event to " << m_targetItem.data()
76 <<
"because it already rejected the touch stream.");
80 if (eventType == QEvent::TouchEnd) {
81 setStatus(NoActiveTouch);
87 qCritical() <<
"[TouchDispatcher] Unexpected event type" << eventType;
93void TouchDispatcher::dispatchTouchBegin(
95 Qt::KeyboardModifiers modifiers,
96 const QList<QTouchEvent::TouchPoint> &touchPoints,
100 Q_ASSERT(m_status == NoActiveTouch);
101 QQuickItem *targetItem = m_targetItem.data();
103 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
104 ugDebug(
"Cannot dispatch touch event to " << targetItem <<
" because it's disabled or invisible.");
109 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
110 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
112 QScopedPointer<QTouchEvent> touchEvent(
113 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
116 ugDebug(
"dispatching " << qPrintable(touchEventToString(touchEvent.data()))
117 <<
" to " << targetItem);
118 QCoreApplication::sendEvent(targetItem, touchEvent.data());
121 if (touchEvent->isAccepted()) {
122 ugDebug(
"Item accepted the touch event.");
123 setStatus(DeliveringTouchEvents);
124 }
else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
125 ugDebug(
"Item rejected the touch event. Trying a QMouseEvent");
127 QScopedPointer<QMouseEvent> mouseEvent(
128 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
130 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
132 ugDebug(
"dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
133 <<
" to " << m_targetItem.data());
134 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
135 if (mouseEvent->isAccepted()) {
136 ugDebug(
"Item accepted the QMouseEvent.");
137 setStatus(DeliveringMouseEvents);
138 m_touchMouseId = targetTouchPoints.at(0).id();
140 if (checkIfDoubleClicked(timestamp)) {
141 QScopedPointer<QMouseEvent> doubleClickEvent(
142 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
144 ugDebug(
"dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
145 <<
" to " << m_targetItem.data());
146 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
150 ugDebug(
"Item rejected the QMouseEvent.");
151 setStatus(TargetRejectedTouches);
154 ugDebug(
"Item rejected the touch event and does not accept mouse buttons.");
155 setStatus(TargetRejectedTouches);
159void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
160 QTouchDevice *device,
161 Qt::KeyboardModifiers modifiers,
162 const QList<QTouchEvent::TouchPoint> &touchPoints,
166 QQuickItem *targetItem = m_targetItem.data();
169 QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
170 transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
172 QScopedPointer<QTouchEvent> eventForTargetItem(
173 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
176 ugDebug(
"dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
177 <<
" to " << targetItem);
178 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
181void TouchDispatcher::dispatchAsMouse(
183 Qt::KeyboardModifiers modifiers,
184 const QList<QTouchEvent::TouchPoint> &touchPoints,
189 Q_ASSERT(!touchPoints.isEmpty());
191 const QTouchEvent::TouchPoint *touchMouse =
nullptr;
193 if (m_touchMouseId != -1) {
194 for (
int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
195 const auto &touchPoint = touchPoints.at(i);
196 if (touchPoint.id() == m_touchMouseId) {
197 touchMouse = &touchPoint;
201 Q_ASSERT(touchMouse);
204 qWarning(
"[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
206 m_touchMouseId = touchPoints.at(0).id();
207 touchMouse = &touchPoints.at(0);
211 for (
int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
212 const auto &touchPoint = touchPoints.at(i);
213 if (touchPoint.state() == Qt::TouchPointPressed) {
214 touchMouse = &touchPoint;
215 m_touchMouseId = touchMouse->id();
221 QEvent::Type eventType;
222 if (touchMouse->state() == Qt::TouchPointPressed) {
223 eventType = QEvent::MouseButtonPress;
224 }
else if (touchMouse->state() == Qt::TouchPointReleased) {
225 eventType = QEvent::MouseButtonRelease;
228 eventType = QEvent::MouseMove;
231 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
234 ugDebug(
"dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
235 <<
" to " << m_targetItem.data());
236 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
240QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
241 QTouchDevice *device,
242 Qt::KeyboardModifiers modifiers,
243 const QList<QTouchEvent::TouchPoint> &touchPoints,
247 Qt::TouchPointStates eventStates = 0;
248 for (
int i = 0; i < touchPoints.count(); i++)
249 eventStates |= touchPoints[i].state();
251 switch (eventStates) {
252 case Qt::TouchPointPressed:
253 eventType = QEvent::TouchBegin;
255 case Qt::TouchPointReleased:
256 eventType = QEvent::TouchEnd;
259 eventType = QEvent::TouchUpdate;
263 QTouchEvent *touchEvent =
new QTouchEvent(eventType);
264 touchEvent->setWindow(window);
265 touchEvent->setTarget(m_targetItem.data());
266 touchEvent->setDevice(device);
267 touchEvent->setModifiers(modifiers);
268 touchEvent->setTouchPoints(touchPoints);
269 touchEvent->setTouchPointStates(eventStates);
270 touchEvent->setTimestamp(timestamp);
271 touchEvent->accept();
276void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints,
const QTransform &transform)
278 QMatrix4x4 transformMatrix(transform);
279 for (
int i=0; i<touchPoints.count(); i++) {
280 QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
281 touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
282 touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
283 touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
284 touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
289QMouseEvent *TouchDispatcher::touchToMouseEvent(
290 QEvent::Type type,
const QTouchEvent::TouchPoint &p,
291 ulong timestamp, Qt::KeyboardModifiers modifiers,
292 bool transformNeeded)
294 QQuickItem *item = m_targetItem.data();
297 QMouseEvent *me =
new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
298 p.scenePos(), p.screenPos(), Qt::LeftButton,
299 (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
301 me->setAccepted(
true);
302 me->setTimestamp(timestamp);
303 QVector2D transformedVelocity = p.velocity();
304 if (transformNeeded) {
305 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
306 QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
307 transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
321bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
325 if (m_touchMousePressTimestamp == 0) {
327 m_touchMousePressTimestamp = newPressEventTimestamp;
328 doubleClicked =
false;
330 ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
331 ulong doubleClickInterval =
static_cast<ulong
>(qApp->styleHints()->
332 mouseDoubleClickInterval());
333 doubleClicked = timeBetweenPresses < doubleClickInterval;
335 m_touchMousePressTimestamp = 0;
337 m_touchMousePressTimestamp = newPressEventTimestamp;
341 return doubleClicked;
344void TouchDispatcher::setStatus(Status status)
346 if (status != m_status) {
347 #if TOUCHDISPATCHER_DEBUG
350 ugDebug(
"status = NoActiveTouch");
352 case DeliveringTouchEvents:
353 ugDebug(
"status = DeliveringTouchEvents");
355 case DeliveringMouseEvents:
356 ugDebug(
"status = DeliveringMouseEvents");
358 case TargetRejectedTouches:
359 ugDebug(
"status = TargetRejectedTouches");
362 ugDebug(
"status = " << status);
370void TouchDispatcher::reset()
372 setStatus(NoActiveTouch);
374 m_touchMousePressTimestamp =0;
377QEvent::Type TouchDispatcher::resolveEventType(
const QList<QTouchEvent::TouchPoint> &touchPoints)
379 QEvent::Type eventType;
381 Qt::TouchPointStates eventStates = 0;
382 for (
int i = 0; i < touchPoints.count(); i++)
383 eventStates |= touchPoints[i].state();
385 switch (eventStates) {
386 case Qt::TouchPointPressed:
387 eventType = QEvent::TouchBegin;
389 case Qt::TouchPointReleased:
390 eventType = QEvent::TouchEnd;
393 eventType = QEvent::TouchUpdate;