/* * Copyright (C) 2014-2015 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 #include #if TOUCHGATE_DEBUG #define ugDebug(params) qDebug().nospace() << "[TouchGate(" << (void*)this << ")] " << params #include #else // TOUCHGATE_DEBUG #define ugDebug(params) ((void)0) #endif // TOUCHGATE_DEBUG UG_USE_NAMESPACE TouchGate::TouchGate(QQuickItem *parent) : QQuickItem(parent) { connect(this, &QQuickItem::enabledChanged, this, &TouchGate::onEnabledChanged); } 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) { ugDebug("got touch event" << qPrintable(touchEventToString(event))); event->accept(); const QList &touchPoints = event->touchPoints(); QList validTouchPoints; bool ownsAllTouches = true; for (int i = 0; i < touchPoints.count(); ++i) { const QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; if (touchPoint.state() == Qt::TouchPointPressed) { // FIXME: This assert triggers frequently in make trySomething. We have verified // that it's a bug in the mouse to touch conversion of the test environment // and not in the actual product. Still, it probably should be cleaned up eventually. // 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); } if (m_touchInfoMap.contains(touchPoint.id())) { validTouchPoints.append(touchPoint); ownsAllTouches &= m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted; if (touchPoint.state() == Qt::TouchPointReleased) { m_touchInfoMap[touchPoint.id()].ended = true; } } } if (validTouchPoints.isEmpty()) { // nothing to do. return; } if (ownsAllTouches) { if (m_storedEvents.isEmpty()) { // let it pass through removeTouchInfoForEndedTouches(validTouchPoints); m_dispatcher.dispatch(event->device(), event->modifiers(), validTouchPoints, event->window(), event->timestamp()); } else { // Retain the event to ensure TouchGate dispatches them in order. // Otherwise the current event would come before the stored ones, which are older. ugDebug("Storing event because thouches " << qPrintable(oldestPendingTouchIdsString()) << " are still pending ownership."); storeTouchEvent(event->device(), event->modifiers(), validTouchPoints, event->window(), event->timestamp()); } } else { // Retain events that have unowned touches storeTouchEvent(event->device(), event->modifiers(), validTouchPoints, event->window(), event->timestamp()); } } void TouchGate::itemChange(ItemChange change, const ItemChangeData &value) { if (change == QQuickItem::ItemSceneChange) { if (value.window != nullptr) { value.window->installEventFilter(TouchRegistry::instance()); } } } void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event) { // TODO: Optimization: batch those actions as TouchOwnershipEvents // might come one right after the other. if (m_touchInfoMap.contains(event->touchId())) { TouchInfo &touchInfo = m_touchInfoMap[event->touchId()]; if (event->gained()) { ugDebug("Got ownership of touch " << event->touchId()); touchInfo.ownership = OwnershipGranted; } else { ugDebug("Lost ownership of touch " << event->touchId()); m_touchInfoMap.remove(event->touchId()); removeTouchFromStoredEvents(event->touchId()); } dispatchFullyOwnedEvents(); } else { // Ignore it. It probably happened because the TouchGate got disabled // between the time it requested ownership and the time it got it. } } bool TouchGate::isTouchPointOwned(int touchId) const { return m_touchInfoMap[touchId].ownership == OwnershipGranted; } void TouchGate::storeTouchEvent(QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QWindow *window, ulong timestamp) { ugDebug("Storing" << touchPoints); TouchEvent event(device, modifiers, touchPoints, window, timestamp); m_storedEvents.append(std::move(event)); } 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_dispatcher.targetItem()) return; m_dispatcher.setTargetItem(item); Q_EMIT targetItemChanged(item); } void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event) { removeTouchInfoForEndedTouches(event.touchPoints); m_dispatcher.dispatch(event.device, event.modifiers, event.touchPoints, event.window, event.timestamp); } 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()); } } } void TouchGate::onEnabledChanged() { ugDebug(" enabled = " << isEnabled()); if (!isEnabled()) { reset(); } } void TouchGate::reset() { m_storedEvents.clear(); m_touchInfoMap.clear(); m_dispatcher.reset(); } TouchGate::TouchEvent::TouchEvent(QTouchDevice *device, Qt::KeyboardModifiers modifiers, const QList &touchPoints, QWindow *window, ulong timestamp) : device(device) , modifiers(modifiers) , touchPoints(touchPoints) , window(window) , timestamp(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; }