aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquickevents.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquickevents.cpp')
-rw-r--r--src/quick/items/qquickevents.cpp508
1 files changed, 507 insertions, 1 deletions
diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp
index 14c0adf393..86d1ddac6c 100644
--- a/src/quick/items/qquickevents.cpp
+++ b/src/quick/items/qquickevents.cpp
@@ -38,9 +38,15 @@
****************************************************************************/
#include "qquickevents_p_p.h"
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+#include <private/qdebug_p.h>
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcPointerEvents, "qt.quick.pointer.events")
+
/*!
\qmltype KeyEvent
\instantiates QQuickKeyEvent
@@ -379,7 +385,7 @@ Item {
\qmlproperty point QtQuick::WheelEvent::pixelDelta
This property holds the delta in screen pixels and is available in platforms that
- have high-resolution trackpads, such as OS X.
+ have high-resolution trackpads, such as \macos.
The x and y cordinate of this property holds the delta in horizontal and
vertical orientation. The value should be used directly to scroll content on screen.
@@ -437,4 +443,504 @@ Item {
\l inverted always returns false.
*/
+typedef QHash<QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash;
+Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices)
+
+Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice,
+ (QQuickPointerDevice::Mouse,
+ QQuickPointerDevice::GenericPointer,
+ QQuickPointerDevice::Position | QQuickPointerDevice::Scroll | QQuickPointerDevice::Hover,
+ 1, 3, QLatin1String("core pointer"), 0))
+
+typedef QHash<qint64, QQuickPointerDevice *> PointerDeviceForDeviceIdHash;
+Q_GLOBAL_STATIC(PointerDeviceForDeviceIdHash, g_tabletDevices)
+
+QQuickPointerDevice *QQuickPointerDevice::touchDevice(QTouchDevice *d)
+{
+ if (g_touchDevices->contains(d))
+ return g_touchDevices->value(d);
+
+ QQuickPointerDevice::DeviceType type = QQuickPointerDevice::TouchScreen;
+ QString name;
+ int maximumTouchPoints = 10;
+ QQuickPointerDevice::Capabilities caps = QQuickPointerDevice::Capabilities(QTouchDevice::Position);
+ if (d) {
+ QQuickPointerDevice::Capabilities caps =
+ static_cast<QQuickPointerDevice::Capabilities>(static_cast<int>(d->capabilities()) & 0x0F);
+ if (d->type() == QTouchDevice::TouchPad) {
+ type = QQuickPointerDevice::TouchPad;
+ caps |= QQuickPointerDevice::Scroll;
+ }
+ name = d->name();
+ maximumTouchPoints = d->maximumTouchPoints();
+ } else {
+ qWarning() << "QQuickWindowPrivate::touchDevice: creating touch device from nullptr device in QTouchEvent";
+ }
+
+ QQuickPointerDevice *dev = new QQuickPointerDevice(type, QQuickPointerDevice::Finger,
+ caps, maximumTouchPoints, 0, name, 0);
+ g_touchDevices->insert(d, dev);
+ return dev;
+}
+
+QList<QQuickPointerDevice*> QQuickPointerDevice::touchDevices()
+{
+ return g_touchDevices->values();
+}
+
+QQuickPointerDevice *QQuickPointerDevice::genericMouseDevice()
+{
+ return g_genericMouseDevice;
+}
+
+QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id)
+{
+ auto it = g_tabletDevices->find(id);
+ if (it != g_tabletDevices->end())
+ return it.value();
+
+ // ### Figure out how to populate the tablet devices
+ return nullptr;
+}
+
+void QQuickEventPoint::reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp)
+{
+ m_scenePos = scenePos;
+ m_pointId = pointId;
+ m_valid = true;
+ m_accept = false;
+ m_state = static_cast<QQuickEventPoint::State>(state);
+ m_timestamp = timestamp;
+ if (state == Qt::TouchPointPressed)
+ m_pressTimestamp = timestamp;
+ // TODO calculate velocity
+}
+
+QQuickItem *QQuickEventPoint::grabber() const
+{
+ return m_grabber.data();
+}
+
+void QQuickEventPoint::setGrabber(QQuickItem *grabber)
+{
+ m_grabber = QPointer<QQuickItem>(grabber);
+}
+
+void QQuickEventPoint::setAccepted(bool accepted)
+{
+ if (m_accept != accepted) {
+ qCDebug(lcPointerEvents) << this << m_accept << "->" << accepted;
+ m_accept = accepted;
+ }
+}
+
+QQuickEventTouchPoint::QQuickEventTouchPoint(QQuickPointerTouchEvent *parent)
+ : QQuickEventPoint(parent), m_rotation(0), m_pressure(0)
+{}
+
+void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong timestamp)
+{
+ QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp);
+ m_rotation = tp.rotation();
+ m_pressure = tp.pressure();
+ m_uniqueId = tp.uniqueId();
+}
+
+/*!
+ \internal
+ \class QQuickPointerEvent
+
+ QQuickPointerEvent is used as a long-lived object to store data related to
+ an event from a pointing device, such as a mouse, touch or tablet event,
+ during event delivery. It also provides properties which may be used later
+ to expose the event to QML, the same as is done with QQuickMouseEvent,
+ QQuickTouchPoint, QQuickKeyEvent, etc. Since only one event can be
+ delivered at a time, this class is effectively a singleton. We don't worry
+ about the QObject overhead because the instances are long-lived: we don't
+ dynamically create and destroy objects of this type for each event.
+*/
+
+QQuickPointerEvent::~QQuickPointerEvent()
+{}
+
+QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event)
+{
+ auto ev = static_cast<QMouseEvent*>(event);
+ m_event = ev;
+ if (!event)
+ return this;
+
+ m_device = QQuickPointerDevice::genericMouseDevice();
+ m_button = ev->button();
+ m_pressedButtons = ev->buttons();
+ Qt::TouchPointState state = Qt::TouchPointStationary;
+ switch (ev->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonDblClick:
+ state = Qt::TouchPointPressed;
+ break;
+ case QEvent::MouseButtonRelease:
+ state = Qt::TouchPointReleased;
+ break;
+ case QEvent::MouseMove:
+ state = Qt::TouchPointMoved;
+ break;
+ default:
+ break;
+ }
+ m_mousePoint->reset(state, ev->windowPos(), 0, ev->timestamp()); // mouse is 0
+ return this;
+}
+
+QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event)
+{
+ auto ev = static_cast<QTouchEvent*>(event);
+ m_event = ev;
+ if (!event)
+ return this;
+
+ m_device = QQuickPointerDevice::touchDevice(ev->device());
+ m_button = Qt::NoButton;
+ m_pressedButtons = Qt::NoButton;
+
+ const QList<QTouchEvent::TouchPoint> &tps = ev->touchPoints();
+ int newPointCount = tps.count();
+ m_touchPoints.reserve(newPointCount);
+
+ for (int i = m_touchPoints.size(); i < newPointCount; ++i)
+ m_touchPoints.insert(i, new QQuickEventTouchPoint(this));
+
+ // Make sure the grabbers are right from one event to the next
+ QVector<QQuickItem*> grabbers;
+ // Copy all grabbers, because the order of points might have changed in the event.
+ // The ID is all that we can rely on (release might remove the first point etc).
+ for (int i = 0; i < newPointCount; ++i) {
+ QQuickItem *grabber = nullptr;
+ if (auto point = pointById(tps.at(i).id()))
+ grabber = point->grabber();
+ grabbers.append(grabber);
+ }
+
+ for (int i = 0; i < newPointCount; ++i) {
+ auto point = m_touchPoints.at(i);
+ point->reset(tps.at(i), ev->timestamp());
+ if (point->state() == QQuickEventPoint::Pressed) {
+ if (grabbers.at(i))
+ qWarning() << "TouchPointPressed without previous release event" << point;
+ point->setGrabber(nullptr);
+ } else {
+ point->setGrabber(grabbers.at(i));
+ }
+ }
+ m_pointCount = newPointCount;
+ return this;
+}
+
+QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const {
+ if (i == 0)
+ return m_mousePoint;
+ return nullptr;
+}
+
+QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const {
+ if (i >= 0 && i < m_pointCount)
+ return m_touchPoints.at(i);
+ return nullptr;
+}
+
+QQuickEventPoint::QQuickEventPoint(QQuickPointerEvent *parent)
+ : QObject(parent), m_pointId(0), m_grabber(nullptr), m_timestamp(0), m_pressTimestamp(0),
+ m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false)
+{
+ Q_UNUSED(m_reserved);
+}
+
+QQuickPointerEvent *QQuickEventPoint::pointerEvent() const
+{
+ return static_cast<QQuickPointerEvent *>(parent());
+}
+
+bool QQuickPointerMouseEvent::allPointsAccepted() const {
+ return m_mousePoint->isAccepted();
+}
+
+QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) const
+{
+ auto event = static_cast<QMouseEvent *>(m_event);
+ event->setLocalPos(localPos);
+ return event;
+}
+
+QVector<QQuickItem *> QQuickPointerMouseEvent::grabbers() const
+{
+ QVector<QQuickItem *> result;
+ if (QQuickItem *grabber = m_mousePoint->grabber())
+ result << grabber;
+ return result;
+}
+
+void QQuickPointerMouseEvent::clearGrabbers() const {
+ m_mousePoint->setGrabber(nullptr);
+}
+
+bool QQuickPointerMouseEvent::isPressEvent() const
+{
+ auto me = static_cast<QMouseEvent*>(m_event);
+ return ((me->type() == QEvent::MouseButtonPress || me->type() == QEvent::MouseButtonDblClick) &&
+ (me->buttons() & me->button()) == me->buttons());
+}
+
+bool QQuickPointerTouchEvent::allPointsAccepted() const {
+ for (int i = 0; i < m_pointCount; ++i) {
+ if (!m_touchPoints.at(i)->isAccepted())
+ return false;
+ }
+ return true;
+}
+
+QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const
+{
+ QVector<QQuickItem *> result;
+ for (int i = 0; i < m_pointCount; ++i) {
+ auto point = m_touchPoints.at(i);
+ if (QQuickItem *grabber = point->grabber()) {
+ if (!result.contains(grabber))
+ result << grabber;
+ }
+ }
+ return result;
+}
+
+void QQuickPointerTouchEvent::clearGrabbers() const {
+ for (auto point: m_touchPoints)
+ point->setGrabber(nullptr);
+}
+
+bool QQuickPointerTouchEvent::isPressEvent() const
+{
+ return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointPressed;
+}
+
+QVector<QPointF> QQuickPointerEvent::unacceptedPressedPointScenePositions() const
+{
+ QVector<QPointF> points;
+ for (int i = 0; i < pointCount(); ++i) {
+ if (!point(i)->isAccepted() && point(i)->state() == QQuickEventPoint::Pressed)
+ points << point(i)->scenePos();
+ }
+ return points;
+}
+
+/*!
+ \internal
+ Populate the reusable synth-mouse event from one touchpoint.
+ It's required that isTouchEvent() be true when this is called.
+ If the touchpoint cannot be found, this returns nullptr.
+ Ownership of the event is NOT transferred to the caller.
+*/
+QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickItem *relativeTo) const {
+ const QTouchEvent::TouchPoint *p = touchPointById(pointID);
+ if (!p)
+ return nullptr;
+ QEvent::Type type;
+ Qt::MouseButton buttons = Qt::LeftButton;
+ switch (p->state()) {
+ case Qt::TouchPointPressed:
+ type = QEvent::MouseButtonPress;
+ break;
+ case Qt::TouchPointMoved:
+ case Qt::TouchPointStationary:
+ type = QEvent::MouseMove;
+ break;
+ case Qt::TouchPointReleased:
+ type = QEvent::MouseButtonRelease;
+ buttons = Qt::NoButton;
+ break;
+ default:
+ Q_ASSERT(false);
+ return nullptr;
+ }
+ m_synthMouseEvent = QMouseEvent(type, relativeTo->mapFromScene(p->scenePos()),
+ p->scenePos(), p->screenPos(), Qt::LeftButton, buttons, m_event->modifiers());
+ m_synthMouseEvent.setAccepted(true);
+ m_synthMouseEvent.setTimestamp(m_event->timestamp());
+ // In the future we will try to always have valid velocity in every QQuickEventPoint.
+ // QQuickFlickablePrivate::handleMouseMoveEvent() checks for QTouchDevice::Velocity
+ // and if it is set, then it does not need to do its own velocity calculations.
+ // That's probably the only usecase for this, so far. Some day Flickable should handle
+ // pointer events, and then passing touchpoint velocity via QMouseEvent will be obsolete.
+ // Conveniently (by design), QTouchDevice::Velocity == QQuickPointerDevice.Velocity
+ // so that we don't need to convert m_device->capabilities().
+ if (m_device)
+ QGuiApplicationPrivate::setMouseEventCapsAndVelocity(&m_synthMouseEvent, m_device->capabilities(), p->velocity());
+ QGuiApplicationPrivate::setMouseEventSource(&m_synthMouseEvent, Qt::MouseEventSynthesizedByQt);
+ return &m_synthMouseEvent;
+}
+
+/*!
+ \internal
+ Returns a pointer to the QQuickEventPoint which has the \a pointId as
+ \l {QQuickEventPoint::pointId}{pointId}.
+ Returns nullptr if there is no point with that ID.
+
+ \fn QQuickPointerEvent::pointById(quint64 pointId) const
+*/
+QQuickEventPoint *QQuickPointerMouseEvent::pointById(quint64 pointId) const {
+ if (m_mousePoint && pointId == m_mousePoint->pointId())
+ return m_mousePoint;
+ return nullptr;
+}
+
+QQuickEventPoint *QQuickPointerTouchEvent::pointById(quint64 pointId) const {
+ auto it = std::find_if(m_touchPoints.constBegin(), m_touchPoints.constEnd(),
+ [pointId](const QQuickEventTouchPoint *tp) { return tp->pointId() == pointId; } );
+ if (it != m_touchPoints.constEnd())
+ return *it;
+ return nullptr;
+}
+
+
+/*!
+ \internal
+ Returns a pointer to the original TouchPoint which has the same
+ \l {QTouchEvent::TouchPoint::id}{id} as \a pointId, if the original event is a
+ QTouchEvent, and if that point is found. Otherwise, returns nullptr.
+*/
+const QTouchEvent::TouchPoint *QQuickPointerTouchEvent::touchPointById(int pointId) const {
+ const QTouchEvent *ev = asTouchEvent();
+ if (!ev)
+ return nullptr;
+ const QList<QTouchEvent::TouchPoint> &tps = ev->touchPoints();
+ auto it = std::find_if(tps.constBegin(), tps.constEnd(),
+ [pointId](QTouchEvent::TouchPoint const& tp) { return tp.id() == pointId; } );
+ // return the pointer to the actual TP in QTouchEvent::_touchPoints
+ return (it == tps.constEnd() ? nullptr : it.operator->());
+}
+
+/*!
+ \internal
+ Make a new QTouchEvent, giving it a subset of the original touch points.
+
+ Returns a nullptr if all points are stationary or there are no points inside the item.
+*/
+QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool isFiltering) const
+{
+ QList<QTouchEvent::TouchPoint> touchPoints;
+ Qt::TouchPointStates eventStates;
+ // TODO maybe add QQuickItem::mapVector2DFromScene(QVector2D) to avoid needing QQuickItemPrivate here
+ // Or else just document that velocity is always scene-relative and is not scaled and rotated with the item
+ // but that would require changing tst_qquickwindow::touchEvent_velocity(): it expects transformed velocity
+
+ QMatrix4x4 transformMatrix(QQuickItemPrivate::get(item)->windowToItemTransform());
+ for (int i = 0; i < m_pointCount; ++i) {
+ auto p = m_touchPoints.at(i);
+ if (p->isAccepted())
+ continue;
+ bool isGrabber = p->grabber() == item;
+ bool isPressInside = p->state() == QQuickEventPoint::Pressed && item->contains(item->mapFromScene(p->scenePos()));
+ if (!(isGrabber || isPressInside || isFiltering))
+ continue;
+
+ const QTouchEvent::TouchPoint *tp = touchPointById(p->pointId());
+ if (tp) {
+ eventStates |= tp->state();
+ QTouchEvent::TouchPoint tpCopy = *tp;
+ tpCopy.setPos(item->mapFromScene(tpCopy.scenePos()));
+ tpCopy.setLastPos(item->mapFromScene(tpCopy.lastScenePos()));
+ tpCopy.setStartPos(item->mapFromScene(tpCopy.startScenePos()));
+ tpCopy.setRect(item->mapRectFromScene(tpCopy.sceneRect()));
+ tpCopy.setVelocity(transformMatrix.mapVector(tpCopy.velocity()).toVector2D());
+ touchPoints << tpCopy;
+ }
+ }
+
+ if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty())
+ return nullptr;
+
+ // if all points have the same state, set the event type accordingly
+ const QTouchEvent &event = *asTouchEvent();
+ QEvent::Type eventType = event.type();
+ 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(event.window());
+ touchEvent->setTarget(item);
+ touchEvent->setDevice(event.device());
+ touchEvent->setModifiers(event.modifiers());
+ touchEvent->setTouchPoints(touchPoints);
+ touchEvent->setTouchPointStates(eventStates);
+ touchEvent->setTimestamp(event.timestamp());
+ touchEvent->accept();
+ return touchEvent;
+}
+
+QTouchEvent *QQuickPointerTouchEvent::asTouchEvent() const
+{
+ return static_cast<QTouchEvent *>(m_event);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+
+Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice *dev) {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ if (!dev) {
+ dbg << "QQuickPointerDevice(0)";
+ return dbg;
+ }
+ dbg << "QQuickPointerDevice("<< dev->name() << ' ';
+ QtDebugUtils::formatQEnum(dbg, dev->type());
+ dbg << ' ';
+ QtDebugUtils::formatQEnum(dbg, dev->pointerType());
+ dbg << " caps:";
+ QtDebugUtils::formatQFlags(dbg, dev->capabilities());
+ if (dev->type() == QQuickPointerDevice::TouchScreen ||
+ dev->type() == QQuickPointerDevice::TouchPad)
+ dbg << " maxTouchPoints:" << dev->maximumTouchPoints();
+ else
+ dbg << " buttonCount:" << dev->buttonCount();
+ dbg << ')';
+ return dbg;
+}
+
+Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *event) {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ dbg << "QQuickPointerEvent(dev:";
+ QtDebugUtils::formatQEnum(dbg, event->device()->type());
+ if (event->buttons() != Qt::NoButton) {
+ dbg << " buttons:";
+ QtDebugUtils::formatQEnum(dbg, event->buttons());
+ }
+ dbg << " [";
+ int c = event->pointCount();
+ for (int i = 0; i < c; ++i)
+ dbg << event->point(i) << ' ';
+ dbg << "])";
+ return dbg;
+}
+
+Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickEventPoint *event) {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ dbg << "QQuickEventPoint(valid:" << event->isValid() << " accepted:" << event->isAccepted()
+ << " state:";
+ QtDebugUtils::formatQEnum(dbg, event->state());
+ dbg << " scenePos:" << event->scenePos() << " id:" << hex << event->pointId() << dec
+ << " timeHeld:" << event->timeHeld() << ')';
+ return dbg;
+}
+
+#endif
+
QT_END_NAMESPACE