diff options
Diffstat (limited to 'src/quick/items/qquickevents.cpp')
-rw-r--r-- | src/quick/items/qquickevents.cpp | 1020 |
1 files changed, 961 insertions, 59 deletions
diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 448b63c347..8653d758de 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -38,8 +38,11 @@ ****************************************************************************/ #include "qquickevents_p_p.h" +#include <QtCore/qmap.h> #include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qtouchdevice_p.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <private/qdebug_p.h> @@ -445,10 +448,96 @@ Item { \l inverted always returns false. */ -typedef QHash<QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash; +/*! + \qmltype PointerDevice + \instantiates QQuickPointerDevice + \inqmlmodule QtQuick + \ingroup qtquick-input-events + + \brief Provides information about a pointing device + + A pointing device can be a mouse, a touchscreen, or a stylus on a graphics + tablet. + + \sa PointerEvent, PointerHandler +*/ + +/*! + \readonly + \qmlproperty enumeration QtQuick::PointerDevice::type + + This property holds the type of the pointing device. + + Valid values are: + + \value DeviceType.UnknownDevice + the device cannot be identified + \value DeviceType.Mouse + a mouse + \value DeviceType.TouchScreen + a touchscreen providing absolute coordinates + \value DeviceType.TouchPad + a trackpad or touchpad providing relative coordinates + \value DeviceType.Stylus + a pen-like device + \value DeviceType.Airbrush + a stylus with a thumbwheel to adjust + \l {QTabletEvent::tangentialPressure}{tangentialPressure} + \value DeviceType.Puck + a device that is similar to a flat mouse with a + transparent circle with cross-hairs (same as \l QTabletEvent::Puck) + + \sa QTouchDevice::DeviceType +*/ + +/*! + \readonly + \qmlproperty enumeration QtQuick::PointerDevice::capabilities + + This property holds a bitwise combination of the capabilities of the + pointing device. It tells you under which conditions events are sent, + and which properties of PointerEvent are expected to be valid. + + Valid values are: + + \value CapabilityFlag.Position + the \l {QtQuick::EventPoint::position}{position} and + \l {QtQuick::EventPoint::scenePosition}{scenePosition} properties + \value CapabilityFlag.Area + the \l {QtQuick::EventTouchPoint::ellipseDiameters}{ellipseDiameters} property + \value CapabilityFlag.Pressure + the \l {QtQuick::EventTouchPoint::pressure}{pressure} property + \value CapabilityFlag.Velocity + the \l {QtQuick::PointerEvent::velocity}{velocity} property + \value CapabilityFlag.Scroll + a \l {QtQuick::PointerDevice::DeviceType::Mouse}{Mouse} has a wheel, or the + operating system recognizes scroll gestures on a + \l {QtQuick::PointerDevice::DeviceType::TouchPad}{TouchPad} + \value CapabilityFlag.Hover + events are sent even when no button is pressed, or the finger or stylus + is not in contact with the surface + \value CapabilityFlag.Rotation + the \l {QtQuick::EventTouchPoint::rotation}{rotation} property + \value CapabilityFlag.XTilt + horizontal angle between a stylus and the axis perpendicular to the surface + \value CapabilityFlag.YTilt + vertical angle between a stylus and the axis perpendicular to the surface + + \sa QTouchDevice::capabilities +*/ + +typedef QHash<const QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash; Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices) -Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, +struct ConstructableQQuickPointerDevice : public QQuickPointerDevice +{ + ConstructableQQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, + int maxPoints, int buttonCount, const QString &name, + qint64 uniqueId = 0) + : QQuickPointerDevice(devType, pType, caps, maxPoints, buttonCount, name, uniqueId) {} + +}; +Q_GLOBAL_STATIC_WITH_ARGS(ConstructableQQuickPointerDevice, g_genericMouseDevice, (QQuickPointerDevice::Mouse, QQuickPointerDevice::GenericPointer, QQuickPointerDevice::Position | QQuickPointerDevice::Scroll | QQuickPointerDevice::Hover, @@ -457,7 +546,23 @@ Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, typedef QHash<qint64, QQuickPointerDevice *> PointerDeviceForDeviceIdHash; Q_GLOBAL_STATIC(PointerDeviceForDeviceIdHash, g_tabletDevices) -QQuickPointerDevice *QQuickPointerDevice::touchDevice(QTouchDevice *d) +// debugging helpers +static const char *pointStateString(const QQuickEventPoint *point) +{ + static const QMetaEnum stateMetaEnum = point->metaObject()->enumerator(point->metaObject()->indexOfEnumerator("State")); + return stateMetaEnum.valueToKey(point->state()); +} + +static const QString pointDeviceName(const QQuickEventPoint *point) +{ + auto device = static_cast<const QQuickPointerEvent *>(point->parent())->device(); + QString deviceName = (device ? device->name() : QLatin1String("null device")); + deviceName.resize(16, ' '); // shorten, and align in case of sequential output + return deviceName; +} + + +QQuickPointerDevice *QQuickPointerDevice::touchDevice(const QTouchDevice *d) { if (g_touchDevices->contains(d)) return g_touchDevices->value(d); @@ -505,37 +610,358 @@ QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id) return nullptr; } -void QQuickEventPoint::reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp) +/*! + \qmltype EventPoint + \qmlabstract + \instantiates QQuickEventPoint + \inqmlmodule QtQuick + \ingroup qtquick-input-events + \brief Provides information about an individual point within a PointerEvent + + A PointerEvent contains an EventPoint for each point of contact: one corresponding + to the mouse cursor, or one for each finger touching a touchscreen. + + \sa PointerEvent, PointerHandler +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::position + + This property holds the coordinates of the position supplied by the event, + relative to the upper-left corner of the Item which has the PointerHandler. + If a contact patch is available from the pointing device, this point + represents its centroid. +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::scenePosition + + This property holds the coordinates of the position supplied by the event, + relative to the scene. If a contact patch is available from the \l device, + this point represents its centroid. +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::scenePressPosition + + This property holds the scene-relative position at which the press event + (on a touch device) or most recent change in QQuickPointerEvent::buttons() + (on a mouse or tablet stylus) occurred. +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::sceneGrabPosition + + This property holds the scene-relative position at which the EventPoint was + located when setGrabber() was called most recently. +*/ + +/*! + \readonly + \qmlproperty vector2d QtQuick::EventPoint::velocity + + This property holds average recent velocity: how fast and in which + direction the event point has been moving recently. +*/ + +/*! + \readonly + \qmlproperty int QtQuick::EventPoint::state + + This property tells what the user is currently doing at this point. + + It can be one of: + \value Pressed + The user's finger is now pressing a touchscreen, button or stylus + which was not pressed already + \value Updated + The touchpoint or position is being moved, with no change in pressed state + \value Stationary + The touchpoint or position is not being moved, and there is also + no change in pressed state + \value Released + The user's finger has now released a touch point, button or stylus + which was pressed +*/ + +/*! + \readonly + \qmlproperty int QtQuick::EventPoint::pointId + + This property holds the ID of the event, if any. + + Touchpoints have automatically-incrementing IDs: each time the user + presses a finger against the touchscreen, it will be a larger number. + In other cases, it will be -1. + + \sa PointerDevice.uniqueId +*/ + +/*! + \readonly + \qmlproperty bool QtQuick::EventPoint::accepted + + Setting \a accepted to true prevents the event from being propagated to + Items below the PointerHandler's Item. + + Generally, if the handler acts on the mouse event, then it should be + accepted so that items lower in the stacking order do not also respond to + the same event. +*/ + +/*! + \readonly + \qmlproperty real QtQuick::EventPoint::timeHeld + + This property holds the amount of time in seconds that the button or touchpoint has + been held. It can be used to detect a "long press", and can drive an + animation to show progress toward activation of the "long press" action. +*/ + +void QQuickEventPoint::reset(Qt::TouchPointState state, const QPointF &scenePos, int pointId, ulong timestamp, const QVector2D &velocity) { 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) + if (state == Qt::TouchPointPressed) { m_pressTimestamp = timestamp; - // TODO calculate velocity + m_scenePressPos = scenePos; + } + m_velocity = (Q_LIKELY(velocity.isNull()) ? estimatedVelocity() : velocity); +} + +void QQuickEventPoint::localizePosition(QQuickItem *target) +{ + if (target) + m_pos = target->mapFromScene(scenePosition()); + else + m_pos = QPointF(); +} + +/*! + If this point has an exclusive grabber, returns a pointer to it; else + returns null, if there is no grabber. The grabber could be either + an Item or a PointerHandler. +*/ +QObject *QQuickEventPoint::exclusiveGrabber() const +{ + return m_exclusiveGrabber.data(); +} + +/*! + Set the given Item or PointerHandler as the exclusive grabber of this point. + If there was already an exclusive grab, it will be canceled. If there + were passive grabbers, they will continue to lurk, but the exclusive grab + is a behavioral override of the passive grab as long as it remains. + If you already know whether the grabber is to be an Item or a PointerHandler, + you should instead call setGrabberItem() or setGrabberPointerHandler(), + because it is slightly more efficient. +*/ +void QQuickEventPoint::setExclusiveGrabber(QObject *grabber) +{ + if (QQuickPointerHandler *phGrabber = qmlobject_cast<QQuickPointerHandler *>(grabber)) + setGrabberPointerHandler(phGrabber, true); + else + setGrabberItem(static_cast<QQuickItem *>(grabber)); +} + +/*! + If the exclusive grabber of this point is an Item, returns a + pointer to that Item; else returns null, if there is no grabber or if + the grabber is a PointerHandler. +*/ +QQuickItem *QQuickEventPoint::grabberItem() const +{ + return (m_grabberIsHandler ? nullptr : static_cast<QQuickItem *>(m_exclusiveGrabber.data())); +} + +/*! + Set the given Item \a grabber as the exclusive grabber of this point. + If there was already an exclusive grab, it will be canceled. If there + were passive grabbers, they will continue to lurk, but the exclusive grab + is a behavioral override of the passive grab as long as it remains. +*/ +void QQuickEventPoint::setGrabberItem(QQuickItem *grabber) +{ + if (grabber != m_exclusiveGrabber.data()) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab" << m_exclusiveGrabber << "->" << grabber; + } + QQuickPointerHandler *oldGrabberHandler = grabberPointerHandler(); + QQuickItem *oldGrabberItem = grabberItem(); + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = false; + m_sceneGrabPos = m_scenePos; + if (oldGrabberHandler) + oldGrabberHandler->onGrabChanged(oldGrabberHandler, CancelGrabExclusive, this); + else if (oldGrabberItem && oldGrabberItem != grabber && grabber && pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) + passiveGrabber->onGrabChanged(passiveGrabber, OverrideGrabPassive, this); + } } -QQuickItem *QQuickEventPoint::grabber() const +/*! + If the exclusive grabber of this point is a PointerHandler, returns a + pointer to that handler; else returns null, if there is no grabber or if + the grabber is an Item. +*/ +QQuickPointerHandler *QQuickEventPoint::grabberPointerHandler() const { - return m_grabber.data(); + return (m_grabberIsHandler ? static_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data()) : nullptr); } -void QQuickEventPoint::setGrabber(QQuickItem *grabber) +/*! + Set the given PointerHandler \a grabber as grabber of this point. If \a + exclusive is true, it will override any other grabs; if false, \a grabber + will be added to the list of passive grabbers of this point. +*/ +void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber, bool exclusive) { - if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled()) && m_grabber.data() != grabber) { - auto device = static_cast<const QQuickPointerEvent *>(parent())->device(); - static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State")); - QString deviceName = (device ? device->name() : QLatin1String("null device")); - deviceName.resize(16, ' '); // shorten, and align in case of sequential output - qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state()) - << ": grab" << m_grabber << "->" << grabber; + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + if (exclusive) { + if (m_exclusiveGrabber != grabber) + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (exclusive)" << m_exclusiveGrabber << "->" << grabber; + } else { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (passive)" << grabber; + } + } + if (exclusive) { + if (grabber != m_exclusiveGrabber.data()) { + if (grabber) { + // set variables before notifying the new grabber + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + grabber->onGrabChanged(grabber, GrabExclusive, this); + for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) { + if (passiveGrabber != grabber) + passiveGrabber->onGrabChanged(grabber, OverrideGrabPassive, this); + } + } else if (QQuickPointerHandler *oldGrabberPointerHandler = qmlobject_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data())) { + oldGrabberPointerHandler->onGrabChanged(oldGrabberPointerHandler, UngrabExclusive, this); + } else if (!m_exclusiveGrabber.isNull()) { + // If there is a previous grabber and it's not a PointerHandler, it must be an Item. + QQuickItem *oldGrabberItem = static_cast<QQuickItem *>(m_exclusiveGrabber.data()); + // If this point came from a touchscreen, notify that previous grabber Item that it's losing its touch grab. + if (pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + } + // set variables after notifying the old grabber + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + } + } else { + if (!grabber) { + qDebug() << "can't set passive grabber to null"; + return; + } + auto ptr = QPointer<QQuickPointerHandler>(grabber); + if (!m_passiveGrabbers.contains(ptr)) { + m_passiveGrabbers.append(ptr); + grabber->onGrabChanged(grabber, GrabPassive, this); + } } - m_grabber = QPointer<QQuickItem>(grabber); } +/*! + If this point has an existing exclusive grabber (Item or PointerHandler), + inform the grabber that its grab is canceled, and remove it as grabber. + This normally happens when the grab is stolen by another Item. +*/ +void QQuickEventPoint::cancelExclusiveGrab() +{ + if (m_exclusiveGrabber.isNull()) + qWarning("cancelGrab: no grabber"); + else + cancelExclusiveGrabImpl(); +} + +void QQuickEventPoint::cancelExclusiveGrabImpl(QTouchEvent *cancelEvent) +{ + if (m_exclusiveGrabber.isNull()) + return; + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (exclusive)" << m_exclusiveGrabber << "-> nullptr"; + } + if (auto handler = grabberPointerHandler()) { + handler->onGrabChanged(handler, CancelGrabExclusive, this); + } else if (auto item = grabberItem()) { + if (cancelEvent) + QCoreApplication::sendEvent(item, cancelEvent); + else + item->touchUngrabEvent(); + } + m_exclusiveGrabber.clear(); +} + +/*! + If this point has the given \a handler as a passive grabber, + inform the grabber that its grab is canceled, and remove it as grabber. + This normally happens when another Item or PointerHandler does an exclusive grab. +*/ +void QQuickEventPoint::cancelPassiveGrab(QQuickPointerHandler *handler) +{ + if (removePassiveGrabber(handler)) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (passive)" << handler << "removed"; + } + handler->onGrabChanged(handler, CancelGrabPassive, this); + } +} + +/*! + If this point has the given \a handler as a passive grabber, remove it as grabber. + Returns true if it was removed, false if it wasn't a grabber. +*/ +bool QQuickEventPoint::removePassiveGrabber(QQuickPointerHandler *handler) +{ + return m_passiveGrabbers.removeOne(handler); +} + +/*! + If the given \a handler is grabbing this point passively, exclusively + or both, cancel the grab and remove it as grabber. + This normally happens when the handler decides that the behavior of this + point can no longer satisfy the handler's behavioral constraints within + the remainder of the gesture which the user is performing: for example + the handler tries to detect a tap but a drag is occurring instead, or + it tries to detect a drag in one direction but the drag is going in + another direction. In such cases the handler no longer needs or wants + to be informed of any further movements of this point. +*/ +void QQuickEventPoint::cancelAllGrabs(QQuickPointerHandler *handler) +{ + if (m_exclusiveGrabber == handler) { + handler->onGrabChanged(handler, CancelGrabExclusive, this); + m_exclusiveGrabber.clear(); + } + cancelPassiveGrab(handler); +} + +/*! + Sets this point as \a accepted (true) or rejected (false). + + During delivery of the current event to the Items in the scene, each Item + or Pointer Handler should accept the points for which it is taking + responsibility. As soon as all points within the event are accepted, event + propagation stops. However accepting the point does not imply any kind of + grab, passive or exclusive. + + \sa setExclusiveGrabber, QQuickPointerHandler::setPassiveGrab, QQuickPointerHandler::setExclusiveGrab +*/ void QQuickEventPoint::setAccepted(bool accepted) { if (m_accept != accepted) { @@ -544,18 +970,175 @@ void QQuickEventPoint::setAccepted(bool accepted) } } + +/*! + \qmltype EventTouchPoint + \qmlabstract + \instantiates QQuickEventTouchPoint + \inqmlmodule QtQuick + \ingroup qtquick-input-events + \brief Provides information about an individual touch point within a PointerEvent + + \sa PointerEvent, PointerHandler +*/ + +/*! + \readonly + \qmlproperty QPointerUniqueId QtQuick::EventTouchPoint::uniqueId + + This property holds the unique ID of the fiducial or stylus in use, if any. + + On touchscreens that can track physical objects (such as knobs or game + pieces) in addition to fingers, each object usually has a unique ID. + Likewise, each stylus that can be used with a graphics tablet usually has a + unique serial number. Qt so far only supports numeric IDs. You can get the + actual number as uniqueId.numeric, but that is a device-specific detail. + In the future, there may be support for non-numeric IDs, so you should + not assume that the number is meaningful. + + If you need to identify specific objects, your application should provide + UI for registering objects and mapping them to functionality: allow the + user to select a meaning, virtual tool, or action, prompt the user to bring + the object into proximity, and store a mapping from uniqueId to its + purpose, for example in \l Settings. +*/ + +/*! + \readonly + \qmlproperty qreal QtQuick::EventTouchPoint::rotation + + This property holds the rotation angle of the stylus on a graphics tablet + or the contact patch of a touchpoint on a touchscreen. + + It is valid only with certain tablet stylus devices and touchscreens that + can measure the rotation angle. Otherwise, it will be zero. +*/ + +/*! + \readonly + \qmlproperty qreal QtQuick::EventTouchPoint::pressure + + This property tells how hard the user is pressing the stylus on a graphics + tablet or the finger against a touchscreen, in the range from \c 0 (no + measurable pressure) to \c 1.0 (maximum pressure which the device can + measure). + + It is valid only with certain tablets and touchscreens that can measure + pressure. Otherwise, it will be \c 1.0 when pressed. +*/ + +/*! + \readonly + \qmlproperty size QtQuick::EventTouchPoint::ellipseDiameters + + This property holds the diameters of the contact patch, if the event + comes from a touchpoint and the \l device provides this information. + + A touchpoint is modeled as an elliptical area where the finger is pressed + against the touchscreen. (In fact, it could also be modeled as a bitmap; but + in that case we expect an elliptical bounding estimate to be fitted to the + contact patch before the event is sent.) The harder the user presses, the + larger the contact patch; so, these diameters provide an alternate way of + detecting pressure, in case the device does not include a separate pressure + sensor. The ellipse is centered on \l scenePos (\l pos in the PointerHandler's + Item's local coordinates). The \l rotation property provides the + rotation of the ellipse, if known. It is expected that if the \l rotation + is zero, the verticalDiameter of the ellipse is the larger one (the major axis), + because of the usual hand position, reaching upward or outward across the surface. + + If the contact patch is unknown, or the \l device is not a touchscreen, + these values will be zero. +*/ + 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); + QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp, tp.velocity()); + m_exclusiveGrabber.clear(); + m_passiveGrabbers.clear(); m_rotation = tp.rotation(); m_pressure = tp.pressure(); + m_ellipseDiameters = tp.ellipseDiameters(); m_uniqueId = tp.uniqueId(); } +struct PointVelocityData { + QVector2D velocity; + QPointF pos; + ulong timestamp; +}; + +typedef QMap<quint64, PointVelocityData*> PointDataForPointIdMap; +Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData) +static const int PointVelocityAgeLimit = 500; // milliseconds + +/*! + \internal + Estimates the velocity based on a weighted average of all previous velocities. + The older the velocity is, the less significant it becomes for the estimate. +*/ +QVector2D QQuickEventPoint::estimatedVelocity() const +{ + PointVelocityData *prevPoint = g_previousPointData->value(m_pointId); + if (!prevPoint) { + // cleanup events older than PointVelocityAgeLimit + auto end = g_previousPointData->end(); + for (auto it = g_previousPointData->begin(); it != end; ) { + PointVelocityData *data = it.value(); + if (m_timestamp - data->timestamp > PointVelocityAgeLimit) { + it = g_previousPointData->erase(it); + delete data; + } else { + ++it; + } + } + // TODO optimize: stop this dynamic memory thrashing + prevPoint = new PointVelocityData; + prevPoint->velocity = QVector2D(); + prevPoint->timestamp = 0; + prevPoint->pos = QPointF(); + g_previousPointData->insert(m_pointId, prevPoint); + } + const ulong timeElapsed = m_timestamp - prevPoint->timestamp; + if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint + return m_velocity; + + QVector2D newVelocity; + if (prevPoint->timestamp != 0) + newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed; + + // VERY simple kalman filter: does a weighted average + // where the older velocities get less and less significant + static const float KalmanGain = 0.7f; + QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain); + + prevPoint->velocity = filteredVelocity; + prevPoint->pos = m_scenePos; + prevPoint->timestamp = m_timestamp; + return filteredVelocity; +} + +/*! + \qmltype PointerEvent + \instantiates QQuickPointerEvent + \inqmlmodule QtQuick + \ingroup qtquick-input-events + + \brief Provides information about an event from a pointing device + + A PointerEvent is an event describing contact or movement across a surface, + provided by a mouse, a touchpoint (single finger on a touchscreen), or a + stylus on a graphics tablet. The \l device property provides more + information about where the event came from. + + \sa PointerHandler + + \image touchpoint-metrics.png +*/ + /*! \internal \class QQuickPointerEvent @@ -570,6 +1153,68 @@ void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong times dynamically create and destroy objects of this type for each event. */ +/*! + \readonly + \qmlproperty enumeration QtQuick::PointerEvent::button + + This property holds the \l {Qt::MouseButton}{button} that caused the event, + if any. If the \l device does not have buttons, or the event is a hover + event, it will be \c Qt.NoButton. +*/ + +/*! + \readonly + \qmlproperty int QtQuick::PointerEvent::buttons + + This property holds the combination of mouse or stylus + \l {Qt::MouseButton}{buttons} pressed when the event was generated. For move + events, this is all buttons that are pressed down. For press events, this + includes the button that caused the event, as well as any others that were + already held. For release events, this excludes the button that caused the + event. +*/ + +/*! + \readonly + \qmlproperty int QtQuick::PointerEvent::modifiers + + This property holds the \l {Qt::KeyboardModifier}{keyboard modifier} flags + that existed immediately before the event occurred. + + It contains a bitwise combination of the following flags: + \value Qt.NoModifier + No modifier key is pressed. + \value Qt.ShiftModifier + A Shift key on the keyboard is pressed. + \value Qt.ControlModifier + A Ctrl key on the keyboard is pressed. + \value Qt.AltModifier + An Alt key on the keyboard is pressed. + \value Qt.MetaModifier + A Meta key on the keyboard is pressed. + \value Qt.KeypadModifier + A keypad button is pressed. + + For example, to react to a Shift key + Left mouse button click: + \qml + Item { + TapHandler { + onTapped: { + if ((event.button == Qt.LeftButton) && (event.modifiers & Qt.ShiftModifier)) + doSomething(); + } + } + } + \endqml +*/ + +/*! + \readonly + \qmlproperty PointerDevice QtQuick::PointerEvent::device + + This property holds the device that generated the event. +*/ + QQuickPointerEvent::~QQuickPointerEvent() {} @@ -581,11 +1226,14 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) return this; m_device = QQuickPointerDevice::genericMouseDevice(); + m_device->eventDeliveryTargets().clear(); m_button = ev->button(); m_pressedButtons = ev->buttons(); Qt::TouchPointState state = Qt::TouchPointStationary; switch (ev->type()) { case QEvent::MouseButtonPress: + m_mousePoint->clearPassiveGrabbers(); + Q_FALLTHROUGH(); case QEvent::MouseButtonDblClick: state = Qt::TouchPointPressed; break; @@ -598,10 +1246,15 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) default: break; } - m_mousePoint->reset(state, ev->windowPos(), 0, ev->timestamp()); // mouse is 0 + m_mousePoint->reset(state, ev->windowPos(), quint64(1) << 24, ev->timestamp()); // mouse has device ID 1 return this; } +void QQuickPointerMouseEvent::localize(QQuickItem *target) +{ + m_mousePoint->localizePosition(target); +} + QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) { auto ev = static_cast<QTouchEvent*>(event); @@ -610,6 +1263,7 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) return this; m_device = QQuickPointerDevice::touchDevice(ev->device()); + m_device->eventDeliveryTargets().clear(); m_button = Qt::NoButton; m_pressedButtons = Qt::NoButton; @@ -620,32 +1274,93 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) 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. + // Make sure the grabbers and on-pressed values are right from one event to the next + struct ToPreserve { + int pointId; // just for double-checking + ulong pressTimestamp; + QPointF scenePressPos; + QPointF sceneGrabPos; + QObject * grabber; + QVector <QPointer <QQuickPointerHandler> > passiveGrabbers; + + ToPreserve() : pointId(0), pressTimestamp(0), grabber(nullptr) {} + }; + QVector<ToPreserve> preserves(newPointCount); // jar of pickled touchpoints, in order of points in the _new_ event + + // Copy stuff we need to preserve, 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); + int pid = tps.at(i).id(); + if (auto point = pointById(pid)) { + preserves[i].pointId = pid; + preserves[i].pressTimestamp = point->m_pressTimestamp; + preserves[i].scenePressPos = point->scenePressPosition(); + preserves[i].sceneGrabPos = point->sceneGrabPosition(); + preserves[i].grabber = point->exclusiveGrabber(); + preserves[i].passiveGrabbers = point->passiveGrabbers(); + } } for (int i = 0; i < newPointCount; ++i) { auto point = m_touchPoints.at(i); point->reset(tps.at(i), ev->timestamp()); + const auto &preserved = preserves.at(i); if (point->state() == QQuickEventPoint::Pressed) { - if (grabbers.at(i)) + if (preserved.grabber) qWarning() << "TouchPointPressed without previous release event" << point; - point->setGrabber(nullptr); + point->setGrabberItem(nullptr); + point->clearPassiveGrabbers(); } else { - point->setGrabber(grabbers.at(i)); + // Restore the grabbers without notifying (don't call onGrabChanged) + Q_ASSERT(preserved.pointId == 0 || preserved.pointId == point->pointId()); + point->m_pressTimestamp = preserved.pressTimestamp; + point->m_scenePressPos = preserved.scenePressPos; + point->m_sceneGrabPos = preserved.sceneGrabPos; + point->m_exclusiveGrabber = preserved.grabber; + point->m_grabberIsHandler = (qmlobject_cast<QQuickPointerHandler *>(point->m_exclusiveGrabber) != nullptr); + point->m_passiveGrabbers = preserved.passiveGrabbers; } } m_pointCount = newPointCount; return this; } +void QQuickPointerTouchEvent::localize(QQuickItem *target) +{ + for (auto point : qAsConst(m_touchPoints)) + point->localizePosition(target); +} + +QQuickPointerEvent *QQuickPointerNativeGestureEvent::reset(QEvent *event) +{ + auto ev = static_cast<QNativeGestureEvent*>(event); + m_event = ev; + if (!event) + return this; + + m_device = QQuickPointerDevice::touchDevice(ev->device()); + m_device->eventDeliveryTargets().clear(); + Qt::TouchPointState state = Qt::TouchPointMoved; + switch (type()) { + case Qt::BeginNativeGesture: + state = Qt::TouchPointPressed; + break; + case Qt::EndNativeGesture: + state = Qt::TouchPointReleased; + break; + default: + break; + } + quint64 deviceId = QTouchDevicePrivate::get(const_cast<QTouchDevice *>(ev->device()))->id; // a bit roundabout since QTouchDevice::mTouchDeviceId is protected + m_gesturePoint->reset(state, ev->windowPos(), deviceId << 24, ev->timestamp()); + return this; +} + +void QQuickPointerNativeGestureEvent::localize(QQuickItem *target) +{ + m_gesturePoint->localizePosition(target); +} + QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const { if (i == 0) return m_mousePoint; @@ -658,9 +1373,15 @@ QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const { return nullptr; } +QQuickEventPoint *QQuickPointerNativeGestureEvent::point(int i) const { + if (i == 0) + return m_gesturePoint; + 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) + : QObject(parent), m_pointId(0), m_exclusiveGrabber(nullptr), m_timestamp(0), m_pressTimestamp(0), + m_state(QQuickEventPoint::Released), m_accept(false), m_grabberIsHandler(false) { Q_UNUSED(m_reserved); } @@ -674,6 +1395,15 @@ bool QQuickPointerMouseEvent::allPointsAccepted() const { return m_mousePoint->isAccepted(); } +bool QQuickPointerMouseEvent::allUpdatedPointsAccepted() const { + return m_mousePoint->state() == QQuickEventPoint::Pressed || m_mousePoint->isAccepted(); +} + +bool QQuickPointerMouseEvent::allPointsGrabbed() const +{ + return m_mousePoint->exclusiveGrabber() != nullptr; +} + QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) const { auto event = static_cast<QMouseEvent *>(m_event); @@ -681,16 +1411,31 @@ QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) cons return event; } -QVector<QQuickItem *> QQuickPointerMouseEvent::grabbers() const +/*! + Returns the exclusive grabber of this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerMouseEvent::exclusiveGrabbers() const { - QVector<QQuickItem *> result; - if (QQuickItem *grabber = m_mousePoint->grabber()) + QVector<QObject *> result; + if (QObject *grabber = m_mousePoint->exclusiveGrabber()) result << grabber; return result; } +/*! + Remove all passive and exclusive grabbers of this event, without notifying. +*/ void QQuickPointerMouseEvent::clearGrabbers() const { - m_mousePoint->setGrabber(nullptr); + m_mousePoint->setGrabberItem(nullptr); + m_mousePoint->clearPassiveGrabbers(); +} + +/*! + Returns whether the given \a handler is the exclusive grabber of this event. +*/ +bool QQuickPointerMouseEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ + return m_mousePoint->exclusiveGrabber() == handler; } bool QQuickPointerMouseEvent::isPressEvent() const @@ -700,6 +1445,24 @@ bool QQuickPointerMouseEvent::isPressEvent() const (me->buttons() & me->button()) == me->buttons()); } +bool QQuickPointerMouseEvent::isDoubleClickEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return (me->type() == QEvent::MouseButtonDblClick); +} + +bool QQuickPointerMouseEvent::isUpdateEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return me->type() == QEvent::MouseMove; +} + +bool QQuickPointerMouseEvent::isReleaseEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return me && me->type() == QEvent::MouseButtonRelease; +} + bool QQuickPointerTouchEvent::allPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { if (!m_touchPoints.at(i)->isAccepted()) @@ -708,12 +1471,32 @@ bool QQuickPointerTouchEvent::allPointsAccepted() const { return true; } -QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const -{ - QVector<QQuickItem *> result; +bool QQuickPointerTouchEvent::allUpdatedPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { auto point = m_touchPoints.at(i); - if (QQuickItem *grabber = point->grabber()) { + if (point->state() != QQuickEventPoint::Pressed && !point->isAccepted()) + return false; + } + return true; +} + +bool QQuickPointerTouchEvent::allPointsGrabbed() const +{ + for (int i = 0; i < m_pointCount; ++i) { + if (!m_touchPoints.at(i)->exclusiveGrabber()) + return false; + } + return true; +} + +/*! + Returns the exclusive grabbers of all points in this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerTouchEvent::exclusiveGrabbers() const +{ + QVector<QObject *> result; + for (int i = 0; i < m_pointCount; ++i) { + if (QObject *grabber = m_touchPoints.at(i)->exclusiveGrabber()) { if (!result.contains(grabber)) result << grabber; } @@ -721,9 +1504,27 @@ QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const return result; } +/*! + Remove all passive and exclusive grabbers of all touchpoints in this event, + without notifying. +*/ void QQuickPointerTouchEvent::clearGrabbers() const { + for (auto point: m_touchPoints) { + point->setGrabberItem(nullptr); + point->clearPassiveGrabbers(); + } +} + +/*! + Returns whether the given \a handler is the exclusive grabber of any + touchpoint within this event. +*/ +bool QQuickPointerTouchEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ for (auto point: m_touchPoints) - point->setGrabber(nullptr); + if (point->exclusiveGrabber() == handler) + return true; + return false; } bool QQuickPointerTouchEvent::isPressEvent() const @@ -731,12 +1532,22 @@ bool QQuickPointerTouchEvent::isPressEvent() const return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointPressed; } +bool QQuickPointerTouchEvent::isUpdateEvent() const +{ + return static_cast<QTouchEvent*>(m_event)->touchPointStates() & (Qt::TouchPointMoved | Qt::TouchPointStationary); +} + +bool QQuickPointerTouchEvent::isReleaseEvent() const +{ + return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointReleased; +} + 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(); + points << point(i)->scenePosition(); } return points; } @@ -788,20 +1599,78 @@ QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickIte } /*! + Returns the exclusive grabber of this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerNativeGestureEvent::exclusiveGrabbers() const +{ + QVector<QObject *> result; + if (QObject *grabber = m_gesturePoint->exclusiveGrabber()) + result << grabber; + return result; +} + +/*! + Remove all passive and exclusive grabbers of this event, without notifying. +*/ +void QQuickPointerNativeGestureEvent::clearGrabbers() const { + m_gesturePoint->setGrabberItem(nullptr); + m_gesturePoint->clearPassiveGrabbers(); +} + +/*! + Returns whether the given \a handler is the exclusive grabber of this event. +*/ +bool QQuickPointerNativeGestureEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ + return m_gesturePoint->exclusiveGrabber() == handler; +} + +bool QQuickPointerNativeGestureEvent::isPressEvent() const +{ + return type() == Qt::BeginNativeGesture; +} + +bool QQuickPointerNativeGestureEvent::isUpdateEvent() const +{ + switch (type()) { + case Qt::BeginNativeGesture: + case Qt::EndNativeGesture: + return false; + default: + return true; + } +} + +bool QQuickPointerNativeGestureEvent::isReleaseEvent() const +{ + return type() == Qt::EndNativeGesture; +} + +Qt::NativeGestureType QQuickPointerNativeGestureEvent::type() const +{ + return static_cast<QNativeGestureEvent *>(m_event)->gestureType(); +} + +qreal QQuickPointerNativeGestureEvent::value() const +{ + return static_cast<QNativeGestureEvent *>(m_event)->value(); +} + +/*! \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 + \fn QQuickPointerEvent::pointById(int pointId) const */ -QQuickEventPoint *QQuickPointerMouseEvent::pointById(quint64 pointId) const { +QQuickEventPoint *QQuickPointerMouseEvent::pointById(int pointId) const { if (m_mousePoint && pointId == m_mousePoint->pointId()) return m_mousePoint; return nullptr; } -QQuickEventPoint *QQuickPointerTouchEvent::pointById(quint64 pointId) const { +QQuickEventPoint *QQuickPointerTouchEvent::pointById(int 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()) @@ -809,6 +1678,12 @@ QQuickEventPoint *QQuickPointerTouchEvent::pointById(quint64 pointId) const { return nullptr; } +QQuickEventPoint *QQuickPointerNativeGestureEvent::pointById(int pointId) const { + if (m_gesturePoint && pointId == m_gesturePoint->pointId()) + return m_gesturePoint; + return nullptr; +} + /*! \internal @@ -831,7 +1706,12 @@ const QTouchEvent::TouchPoint *QQuickPointerTouchEvent::touchPointById(int point \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. + Returns a nullptr if all points are stationary, or there are no points inside the item, + or none of the points were pressed inside and the item was not grabbing any of them + and isFiltering is false. When isFiltering is true, it is assumed that the item + cares about all points which are inside its bounds, because most filtering items + need to monitor eventpoint movements until a drag threshold is exceeded or the + requirements for a gesture to be recognized are met in some other way. */ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool isFiltering) const { @@ -841,19 +1721,24 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i // 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 + bool anyPressOrReleaseInside = false; + bool anyGrabber = false; QMatrix4x4 transformMatrix(QQuickItemPrivate::get(item)->windowToItemTransform()); for (int i = 0; i < m_pointCount; ++i) { auto p = m_touchPoints.at(i); if (p->isAccepted()) continue; // include points where item is the grabber - bool isGrabber = p->grabber() == item; - // include newly pressed points inside the bounds - bool isPressInside = p->state() == QQuickEventPoint::Pressed && item->contains(item->mapFromScene(p->scenePos())); + bool isGrabber = p->exclusiveGrabber() == item; + if (isGrabber) + anyGrabber = true; + // include points inside the bounds if no other item is the grabber or if the item is filtering + bool isInside = item->contains(item->mapFromScene(p->scenePosition())); + bool hasAnotherGrabber = p->exclusiveGrabber() && p->exclusiveGrabber() != item; // filtering: (childMouseEventFilter) include points that are grabbed by children of the target item bool grabberIsChild = false; - auto parent = p->grabber(); + auto parent = p->grabberItem(); while (isFiltering && parent) { if (parent == item) { grabberIsChild = true; @@ -862,11 +1747,11 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i parent = parent->parentItem(); } - // when filtering, send points that are grabbed by a child and points that are not grabbed but inside - bool filterRelevant = isFiltering && (grabberIsChild || (!p->grabber() && item->contains(item->mapFromScene(p->scenePos())))); - if (!(isGrabber || isPressInside || filterRelevant)) + bool filterRelevant = isFiltering && grabberIsChild; + if (!(isGrabber || (isInside && (!hasAnotherGrabber || isFiltering)) || filterRelevant)) continue; - + if ((p->state() == QQuickEventPoint::Pressed || p->state() == QQuickEventPoint::Released) && isInside) + anyPressOrReleaseInside = true; const QTouchEvent::TouchPoint *tp = touchPointById(p->pointId()); if (tp) { eventStates |= tp->state(); @@ -880,7 +1765,9 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i } } - if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty()) + // Now touchPoints will have only points which are inside the item. + // But if none of them were just pressed inside, and the item has no other reason to care, ignore them anyway. + if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty() || (!anyPressOrReleaseInside && !anyGrabber && !isFiltering)) return nullptr; // if all points have the same state, set the event type accordingly @@ -915,6 +1802,19 @@ QTouchEvent *QQuickPointerTouchEvent::asTouchEvent() const return static_cast<QTouchEvent *>(m_event); } +bool QQuickPointerNativeGestureEvent::allPointsAccepted() const { + return m_gesturePoint->isAccepted(); +} + +bool QQuickPointerNativeGestureEvent::allUpdatedPointsAccepted() const { + return m_gesturePoint->state() == QQuickEventPoint::Pressed || m_gesturePoint->isAccepted(); +} + +bool QQuickPointerNativeGestureEvent::allPointsGrabbed() const +{ + return m_gesturePoint->exclusiveGrabber() != nullptr; +} + #ifndef QT_NO_DEBUG_STREAM Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice *dev) { @@ -942,7 +1842,9 @@ Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice * Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *event) { QDebugStateSaver saver(dbg); dbg.nospace(); - dbg << "QQuickPointerEvent(dev:"; + dbg << "QQuickPointerEvent("; + dbg << event->timestamp(); + dbg << " dev:"; QtDebugUtils::formatQEnum(dbg, event->device()->type()); if (event->buttons() != Qt::NoButton) { dbg << " buttons:"; @@ -959,10 +1861,10 @@ Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *e Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickEventPoint *event) { QDebugStateSaver saver(dbg); dbg.nospace(); - dbg << "QQuickEventPoint(valid:" << event->isValid() << " accepted:" << event->isAccepted() + dbg << "QQuickEventPoint(accepted:" << event->isAccepted() << " state:"; QtDebugUtils::formatQEnum(dbg, event->state()); - dbg << " scenePos:" << event->scenePos() << " id:" << hex << event->pointId() << dec + dbg << " scenePos:" << event->scenePosition() << " id:" << hex << event->pointId() << dec << " timeHeld:" << event->timeHeld() << ')'; return dbg; } |