/* * Copyright (C) 2013-2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Daniel d'Andrada * Gerry Boland */ // local #include "application.h" #include "mirbuffersgtexture.h" #include "session.h" #include "mirsurfaceitem.h" #include "mirshell.h" #include "logging.h" #include "ubuntukeyboardinfo.h" // mirserver #include "surfaceobserver.h" // common #include // Qt #include #include #include #include #include #include #include #include // Mir #include #include #include namespace mg = mir::graphics; namespace qtmir { namespace { // Would be better if QMouseEvent had nativeModifiers MirInputEventModifiers getMirModifiersFromQt(Qt::KeyboardModifiers mods) { MirInputEventModifiers m_mods = mir_input_event_modifier_none; if (mods & Qt::ShiftModifier) m_mods |= mir_input_event_modifier_shift; if (mods & Qt::ControlModifier) m_mods |= mir_input_event_modifier_ctrl; if (mods & Qt::AltModifier) m_mods |= mir_input_event_modifier_alt; if (mods & Qt::MetaModifier) m_mods |= mir_input_event_modifier_meta; return m_mods; } mir::EventUPtr makeMirEvent(QMouseEvent *qtEvent, MirPointerAction action) { auto timestamp = std::chrono::milliseconds(qtEvent->timestamp()); auto modifiers = getMirModifiersFromQt(qtEvent->modifiers()); MirPointerButtons buttons = 0; if (qtEvent->buttons() & Qt::LeftButton) buttons |= mir_pointer_button_primary; if (qtEvent->buttons() & Qt::RightButton) buttons |= mir_pointer_button_secondary; if (qtEvent->buttons() & Qt::MidButton) buttons |= mir_pointer_button_tertiary; return mir::events::make_event(0 /*DeviceID */, timestamp, modifiers, action, buttons, qtEvent->x(), qtEvent->y(), 0, 0); } mir::EventUPtr makeMirEvent(QHoverEvent *qtEvent, MirPointerAction action) { auto timestamp = std::chrono::milliseconds(qtEvent->timestamp()); MirPointerButtons buttons = 0; return mir::events::make_event(0 /*DeviceID */, timestamp, mir_input_event_modifier_none, action, buttons, qtEvent->posF().x(), qtEvent->posF().y(), 0, 0); } mir::EventUPtr makeMirEvent(QKeyEvent *qtEvent) { MirKeyboardAction action = mir_keyboard_action_down; switch (qtEvent->type()) { case QEvent::KeyPress: action = mir_keyboard_action_down; break; case QEvent::KeyRelease: action = mir_keyboard_action_up; break; default: break; } if (qtEvent->isAutoRepeat()) action = mir_keyboard_action_repeat; return mir::events::make_event(0 /* DeviceID */, std::chrono::milliseconds(qtEvent->timestamp()), action, qtEvent->nativeVirtualKey(), qtEvent->nativeScanCode(), qtEvent->nativeModifiers()); } mir::EventUPtr makeMirEvent(Qt::KeyboardModifiers qmods, const QList &qtTouchPoints, Qt::TouchPointStates /* qtTouchPointStates */, ulong qtTimestamp) { auto modifiers = getMirModifiersFromQt(qmods); auto ev = mir::events::make_event(0, std::chrono::milliseconds(qtTimestamp), modifiers); for (int i = 0; i < qtTouchPoints.count(); ++i) { auto touchPoint = qtTouchPoints.at(i); auto id = touchPoint.id(); MirTouchAction action = mir_touch_action_change; if (touchPoint.state() == Qt::TouchPointReleased) { action = mir_touch_action_up; } if (touchPoint.state() == Qt::TouchPointPressed) { action = mir_touch_action_down; } MirTouchTooltype tooltype = mir_touch_tooltype_finger; if (touchPoint.flags() & QTouchEvent::TouchPoint::Pen) tooltype = mir_touch_tooltype_stylus; mir::events::add_touch(*ev, id, action, tooltype, touchPoint.pos().x(), touchPoint.pos().y(), touchPoint.pressure(), touchPoint.rect().width(), touchPoint.rect().height(), 0 /* size */); } return ev; } } // namespace { class QMirSurfaceTextureProvider : public QSGTextureProvider { Q_OBJECT public: QMirSurfaceTextureProvider() : t(0) { } ~QMirSurfaceTextureProvider() { delete t; } QSGTexture *texture() const { if (t) t->setFiltering(smooth ? QSGTexture::Linear : QSGTexture::Nearest); return t; } bool smooth; MirBufferSGTexture *t; public Q_SLOTS: void invalidate() { delete t; t = 0; } }; MirSurfaceItem::MirSurfaceItem(std::shared_ptr surface, SessionInterface* session, MirShell *shell, std::shared_ptr observer, QQuickItem *parent) : MirSurfaceItemInterface(parent) , m_surface(surface) , m_session(session) , m_shell(shell) , m_firstFrameDrawn(false) , m_live(true) , m_orientationAngle(Angle0) , m_textureProvider(nullptr) , m_lastTouchEvent(nullptr) { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::MirSurfaceItem"; m_surfaceObserver = observer; if (observer) { connect(observer.get(), &SurfaceObserver::framesPosted, this, &MirSurfaceItem::surfaceDamaged); connect(observer.get(), &SurfaceObserver::attributeChanged, this, &MirSurfaceItem::onAttributeChanged); observer->setListener(this); } setSmooth(true); setFlag(QQuickItem::ItemHasContents, true); //so scene graph will render this item setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton | Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::ExtraButton3 | Qt::ExtraButton4 | Qt::ExtraButton5 | Qt::ExtraButton6 | Qt::ExtraButton7 | Qt::ExtraButton8 | Qt::ExtraButton9 | Qt::ExtraButton10 | Qt::ExtraButton11 | Qt::ExtraButton12 | Qt::ExtraButton13); setAcceptHoverEvents(true); // fetch surface geometry setImplicitSize(static_cast(m_surface->size().width.as_float()), static_cast(m_surface->size().height.as_float())); if (!UbuntuKeyboardInfo::instance()) { new UbuntuKeyboardInfo; } // Ensure C++ (MirSurfaceManager) retains ownership of this object // TODO: Investigate if having the Javascript engine have ownership of this object // might create a less error-prone API design (concern: QML forgets to call "release()" // for a surface, and thus Mir will not release the surface buffers etc.) QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); connect(&m_frameDropperTimer, &QTimer::timeout, this, &MirSurfaceItem::dropPendingBuffers); // Rationale behind the frame dropper and its interval value: // // We want to give ample room for Qt scene graph to have a chance to fetch and render // the next pending buffer before we take the drastic action of dropping it (so don't set // it anywhere close to our target render interval). // // We also want to guarantee a minimal frames-per-second (fps) frequency for client applications // as they get stuck on swap_buffers() if there's no free buffer to swap to yet (ie, they // are all pending consumption by the compositor, us). But on the other hand, we don't want // that minimal fps to be too high as that would mean this timer would be triggered way too often // for nothing causing unnecessary overhead as actually dropping frames from an app should // in practice rarely happen. m_frameDropperTimer.setInterval(200); m_frameDropperTimer.setSingleShot(false); m_updateMirSurfaceSizeTimer.setSingleShot(true); m_updateMirSurfaceSizeTimer.setInterval(1); connect(&m_updateMirSurfaceSizeTimer, &QTimer::timeout, this, &MirSurfaceItem::updateMirSurfaceSize); connect(this, &QQuickItem::widthChanged, this, &MirSurfaceItem::scheduleMirSurfaceSizeUpdate); connect(this, &QQuickItem::heightChanged, this, &MirSurfaceItem::scheduleMirSurfaceSizeUpdate); // FIXME - setting surface unfocused immediately breaks camera & video apps, but is // technically the correct thing to do (surface should be unfocused until shell focuses it) //m_surface->configure(mir_surface_attrib_focus, mir_surface_unfocused); connect(this, &QQuickItem::activeFocusChanged, this, &MirSurfaceItem::updateMirSurfaceFocus); if (m_session) { connect(m_session.data(), &Session::stateChanged, this, &MirSurfaceItem::onSessionStateChanged); } } MirSurfaceItem::~MirSurfaceItem() { if (m_session) { m_session->setSurface(nullptr); } qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::~MirSurfaceItem - this=" << this; QMutexLocker locker(&m_mutex); m_surface->remove_observer(m_surfaceObserver); if (m_textureProvider) m_textureProvider->deleteLater(); delete m_lastTouchEvent; } // For QML to destroy this surface void MirSurfaceItem::release() { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::release - this=" << this; if (m_session) { m_session->setSurface(nullptr); } deleteLater(); } SessionInterface* MirSurfaceItem::session() const { return m_session.data(); } MirSurfaceItem::Type MirSurfaceItem::type() const { return static_cast(m_surface->type()); } MirSurfaceItem::State MirSurfaceItem::state() const { return static_cast(m_surface->state()); } MirSurfaceItem::OrientationAngle MirSurfaceItem::orientationAngle() const { return m_orientationAngle; } void MirSurfaceItem::setOrientationAngle(MirSurfaceItem::OrientationAngle angle) { qCDebug(QTMIR_SURFACES, "MirSurfaceItem::setOrientationAngle(%d)", angle); if (m_orientationAngle == angle) return; MirOrientation mirOrientation; switch (angle) { case Angle0: mirOrientation = mir_orientation_normal; break; case Angle90: mirOrientation = mir_orientation_right; break; case Angle180: mirOrientation = mir_orientation_inverted; break; case Angle270: mirOrientation = mir_orientation_left; break; default: qCWarning(QTMIR_SURFACES, "Unsupported orientation angle: %d", angle); return; } m_surface->set_orientation(mirOrientation); m_orientationAngle = angle; Q_EMIT orientationAngleChanged(angle); } QString MirSurfaceItem::name() const { //FIXME - how to listen to change in this property? return QString::fromStdString(m_surface->name()); } bool MirSurfaceItem::live() const { return m_live; } // Called from the rendering (scene graph) thread QSGTextureProvider *MirSurfaceItem::textureProvider() const { const_cast(this)->ensureProvider(); return m_textureProvider; } void MirSurfaceItem::ensureProvider() { if (!m_textureProvider) { m_textureProvider = new QMirSurfaceTextureProvider(); connect(window(), SIGNAL(sceneGraphInvalidated()), m_textureProvider, SLOT(invalidate()), Qt::DirectConnection); } } void MirSurfaceItem::surfaceDamaged() { if (!m_firstFrameDrawn) { m_firstFrameDrawn = true; Q_EMIT firstFrameDrawn(); } scheduleTextureUpdate(); } bool MirSurfaceItem::updateTexture() // called by rendering thread (scene graph) { QMutexLocker locker(&m_mutex); ensureProvider(); bool textureUpdated = false; const void* const userId = (void*)123; auto renderables = m_surface->generate_renderables(userId); if (m_surface->buffers_ready_for_compositor(userId) > 0 && renderables.size() > 0) { if (!m_textureProvider->t) { m_textureProvider->t = new MirBufferSGTexture(renderables[0]->buffer()); } else { // Avoid holding two buffers for the compositor at the same time. Thus free the current // before acquiring the next m_textureProvider->t->freeBuffer(); m_textureProvider->t->setBuffer(renderables[0]->buffer()); } textureUpdated = true; } if (m_surface->buffers_ready_for_compositor(userId) > 0) { QTimer::singleShot(0, this, SLOT(update())); // restart the frame dropper so that we have enough time to render the next frame. // queued since the timer lives in a different thread QMetaObject::invokeMethod(&m_frameDropperTimer, "start", Qt::QueuedConnection); } m_textureProvider->smooth = smooth(); return textureUpdated; } QSGNode *MirSurfaceItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) // called by render thread { if (!m_surface) { delete oldNode; return 0; } bool textureUpdated = updateTexture(); if (!m_textureProvider->t) { delete oldNode; return 0; } QSGDefaultImageNode *node = static_cast(oldNode); if (!node) { node = new QSGDefaultImageNode; node->setTexture(m_textureProvider->t); node->setMipmapFiltering(QSGTexture::None); node->setHorizontalWrapMode(QSGTexture::ClampToEdge); node->setVerticalWrapMode(QSGTexture::ClampToEdge); node->setSubSourceRect(QRectF(0, 0, 1, 1)); } else { if (textureUpdated) { node->markDirty(QSGNode::DirtyMaterial); } } node->setTargetRect(QRectF(0, 0, width(), height())); node->setInnerTargetRect(QRectF(0, 0, width(), height())); node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); node->setAntialiasing(antialiasing()); node->update(); return node; } void MirSurfaceItem::mousePressEvent(QMouseEvent *event) { if (type() == InputMethod) { // FIXME: Hack to get the VKB use case working while we don't have the proper solution in place. if (isMouseInsideUbuntuKeyboard(event)) { auto ev = makeMirEvent(event, mir_pointer_action_button_down); m_surface->consume(*ev); event->accept(); } else { event->ignore(); } } else { auto ev = makeMirEvent(event, mir_pointer_action_button_down); m_surface->consume(*ev); event->accept(); } } void MirSurfaceItem::mouseMoveEvent(QMouseEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_motion); m_surface->consume(*ev); event->accept(); } void MirSurfaceItem::mouseReleaseEvent(QMouseEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_button_up); m_surface->consume(*ev); event->accept(); } void MirSurfaceItem::wheelEvent(QWheelEvent *event) { Q_UNUSED(event); } void MirSurfaceItem::hoverEnterEvent(QHoverEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_enter); m_surface->consume(*ev); event->accept(); } void MirSurfaceItem::hoverLeaveEvent(QHoverEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_leave); m_surface->consume(*ev); event->accept(); } void MirSurfaceItem::hoverMoveEvent(QHoverEvent *event) { auto ev = makeMirEvent(event, mir_pointer_action_motion); m_surface->consume(*ev); event->accept(); } void MirSurfaceItem::keyPressEvent(QKeyEvent *qtEvent) { auto ev = makeMirEvent(qtEvent); m_surface->consume(*ev); qtEvent->accept(); } void MirSurfaceItem::keyReleaseEvent(QKeyEvent *qtEvent) { auto ev = makeMirEvent(qtEvent); m_surface->consume(*ev); qtEvent->accept(); } QString MirSurfaceItem::appId() const { QString appId; if (session() && session()->application()) { appId = session()->application()->appId(); } else { appId.append("-"); } return appId; } void MirSurfaceItem::endCurrentTouchSequence(ulong timestamp) { Q_ASSERT(m_lastTouchEvent); Q_ASSERT(m_lastTouchEvent->type != QEvent::TouchEnd); Q_ASSERT(m_lastTouchEvent->touchPoints.count() > 0); TouchEvent touchEvent = *m_lastTouchEvent; touchEvent.timestamp = timestamp; // Remove all already released touch points int i = 0; while (i < touchEvent.touchPoints.count()) { if (touchEvent.touchPoints[i].state() == Qt::TouchPointReleased) { touchEvent.touchPoints.removeAt(i); } else { ++i; } } // And release the others one by one as Mir expects one press/release per event while (touchEvent.touchPoints.count() > 0) { touchEvent.touchPoints[0].setState(Qt::TouchPointReleased); touchEvent.updateTouchPointStatesAndType(); auto ev = makeMirEvent(touchEvent.modifiers, touchEvent.touchPoints, touchEvent.touchPointStates, touchEvent.timestamp); m_surface->consume(*ev); *m_lastTouchEvent = touchEvent; touchEvent.touchPoints.removeAt(0); } } void MirSurfaceItem::validateAndDeliverTouchEvent(int eventType, ulong timestamp, Qt::KeyboardModifiers mods, const QList &touchPoints, Qt::TouchPointStates touchPointStates) { if (eventType == QEvent::TouchBegin && m_lastTouchEvent && m_lastTouchEvent->type != QEvent::TouchEnd) { qCWarning(QTMIR_SURFACES) << qPrintable(QString("MirSurfaceItem(%1) - Got a QEvent::TouchBegin while " "there's still an active/unfinished touch sequence.").arg(appId())); // Qt forgot to end the last touch sequence. Let's do it ourselves. endCurrentTouchSequence(timestamp); } auto ev = makeMirEvent(mods, touchPoints, touchPointStates, timestamp); m_surface->consume(*ev); if (!m_lastTouchEvent) { m_lastTouchEvent = new TouchEvent; } m_lastTouchEvent->type = eventType; m_lastTouchEvent->timestamp = timestamp; m_lastTouchEvent->touchPoints = touchPoints; m_lastTouchEvent->touchPointStates = touchPointStates; } void MirSurfaceItem::touchEvent(QTouchEvent *event) { bool accepted = processTouchEvent(event->type(), event->timestamp(), event->modifiers(), event->touchPoints(), event->touchPointStates()); event->setAccepted(accepted); } bool MirSurfaceItem::processTouchEvent( int eventType, ulong timestamp, Qt::KeyboardModifiers mods, const QList &touchPoints, Qt::TouchPointStates touchPointStates) { bool accepted = true; if (type() == InputMethod && eventType == QEvent::TouchBegin) { // FIXME: Hack to get the VKB use case working while we don't have the proper solution in place. if (hasTouchInsideUbuntuKeyboard(touchPoints)) { validateAndDeliverTouchEvent(eventType, timestamp, mods, touchPoints, touchPointStates); } else { accepted = false; } } else { // NB: If we are getting QEvent::TouchUpdate or QEvent::TouchEnd it's because we've // previously accepted the corresponding QEvent::TouchBegin validateAndDeliverTouchEvent(eventType, timestamp, mods, touchPoints, touchPointStates); } return accepted; } bool MirSurfaceItem::hasTouchInsideUbuntuKeyboard(const QList &touchPoints) { UbuntuKeyboardInfo *ubuntuKeyboardInfo = UbuntuKeyboardInfo::instance(); for (int i = 0; i < touchPoints.count(); ++i) { QPoint pos = touchPoints.at(i).pos().toPoint(); if (pos.x() >= ubuntuKeyboardInfo->x() && pos.x() <= (ubuntuKeyboardInfo->x() + ubuntuKeyboardInfo->width()) && pos.y() >= ubuntuKeyboardInfo->y() && pos.y() <= (ubuntuKeyboardInfo->y() + ubuntuKeyboardInfo->height())) { return true; } } return false; } bool MirSurfaceItem::isMouseInsideUbuntuKeyboard(const QMouseEvent *event) { UbuntuKeyboardInfo *ubuntuKeyboardInfo = UbuntuKeyboardInfo::instance(); const QPointF &pos = event->localPos(); return pos.x() >= ubuntuKeyboardInfo->x() && pos.x() <= (ubuntuKeyboardInfo->x() + ubuntuKeyboardInfo->width()) && pos.y() >= ubuntuKeyboardInfo->y() && pos.y() <= (ubuntuKeyboardInfo->y() + ubuntuKeyboardInfo->height()); } void MirSurfaceItem::setType(const Type &type) { if (this->type() != type) { m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_type, static_cast(type)); } } void MirSurfaceItem::setState(const State &state) { if (this->state() != state) { m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_state, static_cast(state)); } } void MirSurfaceItem::setLive(bool live) { if (m_live != live) { m_live = live; Q_EMIT liveChanged(m_live); } } void MirSurfaceItem::onAttributeChanged(const MirSurfaceAttrib attribute, const int /*value*/) { switch (attribute) { case mir_surface_attrib_type: Q_EMIT typeChanged(); break; case mir_surface_attrib_state: Q_EMIT stateChanged(); break; default: break; } } void MirSurfaceItem::scheduleMirSurfaceSizeUpdate() { if (clientIsRunning() && !m_updateMirSurfaceSizeTimer.isActive()) { m_updateMirSurfaceSizeTimer.start(); } } void MirSurfaceItem::updateMirSurfaceSize() { int mirWidth = m_surface->size().width.as_int(); int mirHeight = m_surface->size().height.as_int(); int qmlWidth = (int)width(); int qmlHeight = (int)height(); bool mirSizeIsDifferent = qmlWidth != mirWidth || qmlHeight != mirHeight; const char *didResize = clientIsRunning() && mirSizeIsDifferent ? "surface resized" : "surface NOT resized"; qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::updateMirSurfaceSize" << "surface =" << this << ", old (" << mirWidth << "," << mirHeight << ")" << ", new (" << qmlWidth << "," << qmlHeight << ")" << didResize; if (clientIsRunning() && mirSizeIsDifferent) { mir::geometry::Size newMirSize(qmlWidth, qmlHeight); m_surface->resize(newMirSize); setImplicitSize(qmlWidth, qmlHeight); } } void MirSurfaceItem::updateMirSurfaceFocus(bool focused) { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::updateMirSurfaceFocus" << focused; if (focused) { m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_focus, mir_surface_focused); } else { m_shell->set_surface_attribute(m_session->session(), m_surface, mir_surface_attrib_focus, mir_surface_unfocused); } } void MirSurfaceItem::dropPendingBuffers() { QMutexLocker locker(&m_mutex); const void* const userId = (void*)123; // TODO: Multimonitor support while (m_surface->buffers_ready_for_compositor(userId) > 0) { // The line below looks like an innocent, effect-less, getter. But as this // method returns a unique_pointer, not holding its reference causes the // buffer to be destroyed/released straight away. for (auto const & item : m_surface->generate_renderables(userId)) item->buffer(); qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::dropPendingBuffers()" << "surface =" << this << "buffer dropped." << m_surface->buffers_ready_for_compositor(userId) << "left."; } } void MirSurfaceItem::stopFrameDropper() { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::stopFrameDropper surface = " << this; QMutexLocker locker(&m_mutex); m_frameDropperTimer.stop(); } void MirSurfaceItem::startFrameDropper() { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::startFrameDropper surface = " << this; QMutexLocker locker(&m_mutex); if (!m_frameDropperTimer.isActive()) { m_frameDropperTimer.start(); } } void MirSurfaceItem::scheduleTextureUpdate() { QMutexLocker locker(&m_mutex); // Notify QML engine that this needs redrawing, schedules call to updatePaintItem update(); // restart the frame dropper so that we have enough time to render the next frame. m_frameDropperTimer.start(); } void MirSurfaceItem::setSession(SessionInterface *session) { m_session = session; } void MirSurfaceItem::onSessionStateChanged(SessionInterface::State state) { switch (state) { case SessionInterface::State::Running: syncSurfaceSizeWithItemSize(); break; default: break; } } void MirSurfaceItem::syncSurfaceSizeWithItemSize() { int mirWidth = m_surface->size().width.as_int(); int mirHeight = m_surface->size().width.as_int(); if ((int)width() != mirWidth || (int)height() != mirHeight) { qCDebug(QTMIR_SURFACES) << "MirSurfaceItem::syncSurfaceSizeWithItemSize()"; mir::geometry::Size newMirSize((int)width(), (int)height()); m_surface->resize(newMirSize); setImplicitSize(width(), height()); } } bool MirSurfaceItem::clientIsRunning() const { return (m_session && (m_session->state() == Session::State::Running || m_session->state() == Session::State::Starting)) || !m_session; } void MirSurfaceItem::TouchEvent::updateTouchPointStatesAndType() { touchPointStates = 0; for (int i = 0; i < touchPoints.count(); ++i) { touchPointStates |= touchPoints.at(i).state(); } if (touchPointStates == Qt::TouchPointReleased) { type = QEvent::TouchEnd; } else if (touchPointStates == Qt::TouchPointPressed) { type = QEvent::TouchBegin; } else { type = QEvent::TouchUpdate; } } } // namespace qtmir #include "mirsurfaceitem.moc"