/* * 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 "TouchGate.h" #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-pedantic" #include #pragma GCC diagnostic pop #if TOUCHGATE_DEBUG #include #endif bool TouchGate::event(QEvent *e) { if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) { touchOwnershipEvent(static_cast(e)); return true; } else { return QQuickItem::event(e); } } void TouchGate::touchEvent(QTouchEvent *event) { #if TOUCHGATE_DEBUG qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event)); #endif event->accept(); const QList &touchPoints = event->touchPoints(); bool goodToGo = true; for (int i = 0; i < touchPoints.count(); ++i) { const QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; if (touchPoint.state() == Qt::TouchPointPressed) { Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id())); m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested; m_touchInfoMap[touchPoint.id()].ended = false; TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this); } goodToGo &= m_touchInfoMap.contains(touchPoint.id()) && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted; if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) { m_touchInfoMap[touchPoint.id()].ended = true; } } if (goodToGo) { if (m_storedEvents.isEmpty()) { // let it pass through dispatchTouchEventToTarget(event); } else { // Retain the event to ensure TouchGate dispatches them in order. // Otherwise the current event would come before the stored ones, which are older. #if TOUCHGATE_DEBUG qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.", qPrintable(oldestPendingTouchIdsString())); #endif storeTouchEvent(event); } } else { // Retain events that have unowned touches storeTouchEvent(event); } } void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event) { // TODO: Optimization: batch those actions as TouchOwnershipEvents // might come one right after the other. Q_ASSERT(m_touchInfoMap.contains(event->touchId())); TouchInfo &touchInfo = m_touchInfoMap[event->touchId()]; if (event->gained()) { #if TOUCHGATE_DEBUG qDebug() << "[TouchGate] Got ownership of touch " << event->touchId(); #endif touchInfo.ownership = OwnershipGranted; } else { #if TOUCHGATE_DEBUG qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId(); #endif m_touchInfoMap.remove(event->touchId()); removeTouchFromStoredEvents(event->touchId()); } dispatchFullyOwnedEvents(); } bool TouchGate::isTouchPointOwned(int touchId) const { return m_touchInfoMap[touchId].ownership == OwnershipGranted; } void TouchGate::storeTouchEvent(const QTouchEvent *event) { #if TOUCHGATE_DEBUG qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event)); #endif TouchEvent clonedEvent(event); m_storedEvents.append(std::move(clonedEvent)); } void TouchGate::removeTouchFromStoredEvents(int touchId) { int i = 0; while (i < m_storedEvents.count()) { TouchEvent &event = m_storedEvents[i]; bool removed = event.removeTouch(touchId); if (removed && event.touchPoints.isEmpty()) { m_storedEvents.removeAt(i); } else { ++i; } } } void TouchGate::dispatchFullyOwnedEvents() { while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) { TouchEvent event = m_storedEvents.takeFirst(); dispatchTouchEventToTarget(event); } } #if TOUCHGATE_DEBUG QString TouchGate::oldestPendingTouchIdsString() { Q_ASSERT(!m_storedEvents.isEmpty()); QString str; const auto &touchPoints = m_storedEvents.first().touchPoints; for (int i = 0; i < touchPoints.count(); ++i) { if (!isTouchPointOwned(touchPoints[i].id())) { if (!str.isEmpty()) { str.append(", "); } str.append(QString::number(touchPoints[i].id())); } } return str; } #endif bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const { for (int i = 0; i < event.touchPoints.count(); ++i) { if (!isTouchPointOwned(event.touchPoints[i].id())) { return false; } } return true; } void TouchGate::setTargetItem(QQuickItem *item) { // TODO: changing the target item while dispatch of touch events is taking place will // create a mess if (item == m_targetItem.data()) return; m_targetItem = item; Q_EMIT targetItemChanged(item); } void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event) { dispatchTouchEventToTarget(event.eventType, event.device, event.modifiers, event.touchPoints, event.target, event.window, event.timestamp); } void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event) { dispatchTouchEventToTarget(event->type(), event->device(), event->modifiers(), event->touchPoints(), event->target(), event->window(), event->timestamp()); } void TouchGate::dispatchTouchEventToTarget(QEvent::Type eventType, QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QObject *target, QWindow *window, ulong timestamp) { removeTouchInfoForEndedTouches(touchPoints); if (m_targetItem.isNull()) { qWarning("[TouchGate] Cannot dispatch touch event because target item is null"); return; } QQuickItem *targetItem = m_targetItem.data(); if (!targetItem->isEnabled() || !targetItem->isVisible()) { #if TOUCHGATE_DEBUG qDebug() << "[TouchGate] 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()); QTouchEvent *eventForTargetItem = createQTouchEvent(eventType, device, modifiers, targetTouchPoints, target, window, timestamp); #if TOUCHGATE_DEBUG qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem)) << "to" << targetItem; #endif QCoreApplication::sendEvent(targetItem, eventForTargetItem); delete eventForTargetItem; } // NB: From QQuickWindow void TouchGate::transformTouchPoints(QList &touchPoints, const QTransform &transform) { QMatrix4x4 transformMatrix(transform); for (int i=0; i &touchPoints, QObject *target, 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(target); touchEvent->setDevice(device); touchEvent->setModifiers(modifiers); touchEvent->setTouchPoints(touchPoints); touchEvent->setTouchPointStates(eventStates); touchEvent->setTimestamp(timestamp); touchEvent->accept(); return touchEvent; } void TouchGate::removeTouchInfoForEndedTouches(const QList &touchPoints) { for (int i = 0; i < touchPoints.size(); ++i) {\ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); if (touchPoint.state() == Qt::TouchPointReleased) { Q_ASSERT(m_touchInfoMap.contains(touchPoint.id())); Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended); Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted); m_touchInfoMap.remove(touchPoint.id()); } } } TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event) : eventType(event->type()) , device(event->device()) , modifiers(event->modifiers()) , touchPoints(event->touchPoints()) , target(event->target()) , window(event->window()) , timestamp(event->timestamp()) { } bool TouchGate::TouchEvent::removeTouch(int touchId) { bool removed = false; for (int i = 0; i < touchPoints.count() && !removed; ++i) { if (touchPoints[i].id() == touchId) { touchPoints.removeAt(i); removed = true; } } return removed; }