/* * Copyright (C) 2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "TouchDispatcher.h" #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-pedantic" #include #pragma GCC diagnostic pop #define TOUCHDISPATCHER_DEBUG 0 #if TOUCHDISPATCHER_DEBUG #include #endif TouchDispatcher::TouchDispatcher() : m_status(NoActiveTouch) , m_touchMouseId(-1) , m_touchMousePressTimestamp(0) { } void TouchDispatcher::setTargetItem(QQuickItem *target) { if (target != m_targetItem) { m_targetItem = target; if (m_status != NoActiveTouch) { qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream"); m_status = TargetRejectedTouches; } } } void TouchDispatcher::dispatch(QEvent::Type eventType, QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QWindow *window, ulong timestamp) { if (m_targetItem.isNull()) { qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null"); return; } if (eventType == QEvent::TouchBegin) { dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp); } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) { if (m_status == DeliveringTouchEvents) { dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp); } else if (m_status == DeliveringMouseEvents) { dispatchAsMouse(device, modifiers, touchPoints, timestamp); } else { Q_ASSERT(m_status == TargetRejectedTouches); #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data() << "because it already rejected the touch stream."; #endif // Do nothing } if (eventType == QEvent::TouchEnd) { m_status = NoActiveTouch; m_touchMouseId = -1; } } else { // Should never happen qCritical() << "[TouchDispatcher] Unexpected event type" << eventType; Q_ASSERT(false); return; } } void TouchDispatcher::dispatchTouchBegin( QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QWindow *window, ulong timestamp) { Q_ASSERT(m_status == NoActiveTouch); QQuickItem *targetItem = m_targetItem.data(); if (!targetItem->isEnabled() || !targetItem->isVisible()) { #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem << "because it's disabled or invisible."; #endif return; } // Map touch points to targetItem coordinates QList targetTouchPoints = touchPoints; transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform()); QScopedPointer touchEvent( createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp)); #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data())) << "to" << targetItem; #endif QCoreApplication::sendEvent(targetItem, touchEvent.data()); if (touchEvent->isAccepted()) { #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Item accepted the touch event."; #endif m_status = DeliveringTouchEvents; } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) { #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent"; #endif // NB: Arbitrarily chose the first touch point to emulate the mouse pointer QScopedPointer mouseEvent( touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp, modifiers, false /* transformNeeded */)); Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed); #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data())) << "to" << m_targetItem.data(); #endif QCoreApplication::sendEvent(targetItem, mouseEvent.data()); if (mouseEvent->isAccepted()) { #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent."; #endif m_status = DeliveringMouseEvents; m_touchMouseId = targetTouchPoints.at(0).id(); if (checkIfDoubleClicked(timestamp)) { QScopedPointer doubleClickEvent( touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp, modifiers, false /* transformNeeded */)); #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data())) << "to" << m_targetItem.data(); #endif QCoreApplication::sendEvent(targetItem, doubleClickEvent.data()); } } else { #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent."; #endif m_status = TargetRejectedTouches; } } else { #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons."; #endif m_status = TargetRejectedTouches; } } void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType, QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QWindow *window, ulong timestamp) { QQuickItem *targetItem = m_targetItem.data(); // Map touch points to targetItem coordinates QList targetTouchPoints = touchPoints; transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform()); QScopedPointer eventForTargetItem( createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp)); #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data())) << "to" << targetItem; #endif QCoreApplication::sendEvent(targetItem, eventForTargetItem.data()); } void TouchDispatcher::dispatchAsMouse( QTouchDevice * /*device*/, Qt::KeyboardModifiers modifiers, const QList &touchPoints, ulong timestamp) { // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly Q_ASSERT(!touchPoints.isEmpty()); const QTouchEvent::TouchPoint *touchMouse = nullptr; if (m_touchMouseId != -1) { for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) { const auto &touchPoint = touchPoints.at(i); if (touchPoint.id() == m_touchMouseId) { touchMouse = &touchPoint; } } Q_ASSERT(touchMouse); if (!touchMouse) { // should not happen, but deal with it just in case. qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.", m_touchMouseId); m_touchMouseId = touchPoints.at(0).id(); touchMouse = &touchPoints.at(0); } } else { // Try to find a new touch for mouse emulation for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) { const auto &touchPoint = touchPoints.at(i); if (touchPoint.state() == Qt::TouchPointPressed) { touchMouse = &touchPoint; m_touchMouseId = touchMouse->id(); } } } if (touchMouse) { QEvent::Type eventType; if (touchMouse->state() == Qt::TouchPointPressed) { eventType = QEvent::MouseButtonPress; } if (touchMouse->state() == Qt::TouchPointReleased) { eventType = QEvent::MouseButtonRelease; m_touchMouseId = -1; } else { eventType = QEvent::MouseMove; } QScopedPointer mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers, true /* transformNeeded */)); #if TOUCHDISPATCHER_DEBUG qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data())) << "to" << m_targetItem.data(); #endif QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data()); } } QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType, QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QWindow *window, ulong timestamp) { Qt::TouchPointStates eventStates = 0; for (int i = 0; i < touchPoints.count(); i++) eventStates |= touchPoints[i].state(); // if all points have the same state, set the event type accordingly switch (eventStates) { case Qt::TouchPointPressed: eventType = QEvent::TouchBegin; break; case Qt::TouchPointReleased: eventType = QEvent::TouchEnd; break; default: eventType = QEvent::TouchUpdate; break; } QTouchEvent *touchEvent = new QTouchEvent(eventType); touchEvent->setWindow(window); touchEvent->setTarget(m_targetItem.data()); touchEvent->setDevice(device); touchEvent->setModifiers(modifiers); touchEvent->setTouchPoints(touchPoints); touchEvent->setTouchPointStates(eventStates); touchEvent->setTimestamp(timestamp); touchEvent->accept(); return touchEvent; } // NB: From QQuickWindow void TouchDispatcher::transformTouchPoints(QList &touchPoints, const QTransform &transform) { QMatrix4x4 transformMatrix(transform); for (int i=0; imapFromScene(p.scenePos()) : p.pos(), p.scenePos(), p.screenPos(), Qt::LeftButton, (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton), modifiers); me->setAccepted(true); me->setTimestamp(timestamp); QVector2D transformedVelocity = p.velocity(); if (transformNeeded) { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform()); transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D(); } // Add these later if needed: //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity); //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt); return me; } /* Copied from qquickwindow.cpp which has: Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) Under GPL 3.0 license. */ bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp) { bool doubleClicked; if (m_touchMousePressTimestamp == 0) { // just initialize the variable m_touchMousePressTimestamp = newPressEventTimestamp; doubleClicked = false; } else { ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp; ulong doubleClickInterval = static_cast(qApp->styleHints()-> mouseDoubleClickInterval()); doubleClicked = timeBetweenPresses < doubleClickInterval; if (doubleClicked) { m_touchMousePressTimestamp = 0; } else { m_touchMousePressTimestamp = newPressEventTimestamp; } } return doubleClicked; }