Track grab state in QPointingDevicePrivate::activePoints

QQuickEventPoint instances were very long-lived and got reused from one
event to the next.  That was initially done because they were "heavy"
QObjects; but it also became useful to store state in them between
events. But this is in conflict with the ubiquitous event replay
code that assumes it's OK to hold an event instance (especially
a QMouseEvent) for any length of time, and then send it to some widget,
item or window. Clearly QEventPoints must be stored in the QPointerEvent,
if we are to avoid the need for workarounds to keep such old code working.
And now they have d-pointers, so copying is cheap. But replay code
will need to detach() their QEventPoints now.

QEventPoint is useful as an object to hold state, but we now store
the truly persistent state separately in an EventPointData struct,
in QPointingDevicePrivate::activePoints. Incoming events merely
update the persistent points, then we deliver those instead.
Thus when event handler code modifies state, it will be remembered
even when the delivery is done and the QPA event is destroyed.

This gets us a step closer to supporting multiple simultaneous mice.

Within pointer events, the points are moved up to QPointerEvent itself:
 QList<QEventPoint> m_points;
This means pointCount(), point(int i) and points() can be non-virtual.
However in any QSinglePointEvent, the list only contains one point.
We hope that pessimization is worthwhile for the sake of removing
virtual functions, simplifying code in event classes themselves, and
enabling the use of the range-for loop over points() with any kind of
QPointerEvent, not just QTouchEvent. points() is a nicer API for the
sake of range-for looping; but point() is more suited to being
non-const.

In QML it's expected to be OK to emit a signal with a QPointerEvent
by value: that will involve copying the event.  But QEventPoint
instances are explicitly shared, so calling setAccepted() modifies
the instance in activePoints (EventPointData.eventPoint.d->accept);
and the grabbers are stored separately and thus preserved between events.
In code such as MouseArea { onPressed: mouse.accepted = false }
we can either continue to emit the QQuickMouseEvent wrapper
or perhaps QEvent::setAccepted() could become virtual and set
the eventpoint's accepted flag instead, so that it will survive
after the event copy that QML sees is discarded.

The grabChanged() signal is useful to keep QQuickWindow informed
when items or handlers change exclusive or passive grabbers.

When a release happens at a different location than the last move event,
Qt synthesizes an additional move.  But it would be "boring" if
QEventPoint::lastXPosition() accessors in any released eventpoint always
returned the same as the current QEventPoint::xPosition()s just because
of that; and it would mean that the velocity() must always be zero on
release, which would make it hard to use the final velocity to drive an
animation.  So now we expect the lastPositions to be different than
current positions in a released eventpoint.

De-inline some functions whose implementations might be subject to
change later on.  Improve documentation.

Since we have an accessor for pressTimestamp(), we might as well add one for
timestamp() too.  That way users get enough information to calculate
instantaneous velocity, since the plan is for velocity() to be somewhat
smoothed.

Change-Id: I2733d847139a1b1bea33c00275459dcd2a145ffc
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Shawn Rutledge 2020-09-03 21:34:31 +02:00
parent 4ceef8a3a6
commit 2692237bb1
19 changed files with 799 additions and 346 deletions

View File

@ -62,14 +62,7 @@
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcPointerGrab, "qt.pointer.grab")
static const QString pointDeviceName(const QEventPoint &point)
{
const auto device = point.device();
QString deviceName = (device ? device->name() : QLatin1String("null device"));
deviceName.resize(16, u' '); // shorten, and align in case of sequential output
return deviceName;
}
Q_LOGGING_CATEGORY(lcEPDetach, "qt.pointer.eventpoint.detach")
/*!
\class QEnterEvent
@ -321,6 +314,9 @@ int QEventPoint::id() const
QPointingDeviceUniqueId QEventPoint::uniqueId() const
{ return d->uniqueId; }
ulong QEventPoint::timestamp() const
{ return d->timestamp; }
ulong QEventPoint::pressTimestamp() const
{ return d->pressTimestamp; }
@ -396,8 +392,8 @@ QPointF QEventPoint::startNormalizedPos() const
\obsolete
Deprecated since Qt 6.0. Use globalLastPosition() instead.
Returns the normalized position of this touch point from the
previous touch event.
Returns the normalized position of this point from the previous press or
move event.
The coordinates are normalized to QInputDevice::availableVirtualGeometry(),
i.e. (0, 0) is the top-left corner and (1, 1) is the bottom-right corner.
@ -429,7 +425,84 @@ void QMutableEventPoint::detach()
{
if (d->refCount == 1)
return; // no need: there is only one QEventPoint using it
qCDebug(lcEPDetach) << "detaching: refCount" << d->refCount << this;
d = new QEventPointPrivate(*d);
d->refCount = 1;
}
/*! \internal
Update current state from the given \a other point, assuming that this
instance contains state from the previous event and \a other contains new
values that came in from a device.
That is: global position and other valuators will be updated, but
the following properties will not be updated:
\li other properties that are not likely to be set after a fresh touchpoint
has been received from a device
\li properties that should be persistent between events (such as grabbers)
*/
void QMutableEventPoint::updateFrom(const QEventPoint &other)
{
detach();
setPressure(other.pressure());
switch (other.state()) {
case QEventPoint::State::Pressed:
setGlobalPressPosition(other.globalPosition());
setGlobalLastPosition(other.globalPosition());
if (pressure() < 0)
setPressure(1);
break;
case QEventPoint::State::Released:
if (globalPosition() != other.globalPosition())
setGlobalLastPosition(globalPosition());
setPressure(0);
break;
case QEventPoint::State::Stationary:
// Stationary points might not be delivered down to the receiving item
// and get their position transformed, keep the old values instead.
if (other.velocity() != velocity() ||
!qFuzzyCompare(other.pressure(), pressure())) {
setStationaryWithModifiedProperty();
}
Q_FALLTHROUGH();
default: // update or stationary
if (globalPosition() != other.globalPosition())
setGlobalLastPosition(globalPosition());
if (pressure() < 0)
setPressure(1);
break;
}
setState(other.state());
setPosition(other.position());
setScenePosition(other.scenePosition());
setGlobalPosition(other.globalPosition());
setEllipseDiameters(other.ellipseDiameters());
setRotation(other.rotation());
setVelocity(other.velocity());
}
/*! \internal
Set the timestamp from the event that updated this point's positions.
*/
void QMutableEventPoint::setTimestamp(const ulong t)
{
// On mouse press, if the mouse has moved from its last-known location,
// QGuiApplicationPrivate::processMouseEvent() sends first a mouse move and
// then a press. Both events will get the same timestamp. So we need to set
// the press timestamp and position even when the timestamp isn't advancing.
if (state() == QEventPoint::State::Pressed) {
d->pressTimestamp = t;
d->globalPressPos = d->globalPos;
}
if (d->timestamp == t)
return;
detach();
d->timestamp = t;
}
/*! \internal
@ -470,8 +543,9 @@ void QMutableEventPoint::detach()
/*!
\internal
*/
QPointerEvent::QPointerEvent(QEvent::Type type, const QPointingDevice *dev, Qt::KeyboardModifiers modifiers)
: QInputEvent(type, QEvent::PointerEventTag{}, dev, modifiers)
QPointerEvent::QPointerEvent(QEvent::Type type, const QPointingDevice *dev,
Qt::KeyboardModifiers modifiers, const QList<QEventPoint> &points)
: QInputEvent(type, QEvent::PointerEventTag{}, dev, modifiers), m_points(points)
{
}
@ -489,6 +563,16 @@ const QPointingDevice *QPointerEvent::pointingDevice() const
return static_cast<const QPointingDevice *>(m_dev);
}
/*! \internal
Sets the timestamp for this event and its points().
*/
void QPointerEvent::setTimestamp(ulong timestamp)
{
QInputEvent::setTimestamp(timestamp);
for (auto &p : m_points)
QMutableEventPoint::from(p).setTimestamp(timestamp);
}
/*!
Returns the object which has been set to receive all future update events
and the release event containing the given \a point.
@ -497,7 +581,13 @@ const QPointingDevice *QPointerEvent::pointingDevice() const
*/
QObject *QPointerEvent::exclusiveGrabber(const QEventPoint &point) const
{
return point.d->exclusiveGrabber.data();
Q_ASSERT(pointingDevice());
auto persistentPoint = QPointingDevicePrivate::get(pointingDevice())->queryPointById(point.id());
if (Q_UNLIKELY(!persistentPoint)) {
qWarning() << "point is not in activePoints" << point;
return nullptr;
}
return persistentPoint->exclusiveGrabber;
}
/*!
@ -509,15 +599,9 @@ QObject *QPointerEvent::exclusiveGrabber(const QEventPoint &point) const
*/
void QPointerEvent::setExclusiveGrabber(const QEventPoint &point, QObject *exclusiveGrabber)
{
if (point.d->exclusiveGrabber == exclusiveGrabber)
return;
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << pointDeviceName(point) << "point" << point.id() << point.state()
<< "@" << point.scenePosition()
<< ": grab" << point.d->exclusiveGrabber << "->" << exclusiveGrabber;
}
point.d->exclusiveGrabber = exclusiveGrabber;
point.d->globalGrabPos = point.d->globalPos;
Q_ASSERT(pointingDevice());
auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice()));
devPriv->setExclusiveGrabber(this, point, exclusiveGrabber);
}
/*!
@ -530,7 +614,13 @@ void QPointerEvent::setExclusiveGrabber(const QEventPoint &point, QObject *exclu
*/
QList<QPointer<QObject> > QPointerEvent::passiveGrabbers(const QEventPoint &point) const
{
return point.d->passiveGrabbers;
Q_ASSERT(pointingDevice());
auto persistentPoint = QPointingDevicePrivate::get(pointingDevice())->queryPointById(point.id());
if (Q_UNLIKELY(!persistentPoint)) {
qWarning() << "point is not in activePoints" << point;
return {};
}
return persistentPoint->passiveGrabbers;
}
/*!
@ -544,14 +634,9 @@ QList<QPointer<QObject> > QPointerEvent::passiveGrabbers(const QEventPoint &poin
*/
bool QPointerEvent::addPassiveGrabber(const QEventPoint &point, QObject *grabber)
{
if (point.d->passiveGrabbers.contains(grabber))
return false;
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << pointDeviceName(point) << "point" << point.id() << point.state()
<< ": grab (passive)" << grabber;
}
point.d->passiveGrabbers << grabber;
return true;
Q_ASSERT(pointingDevice());
auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice()));
return devPriv->addPassiveGrabber(this, point, grabber);
}
/*!
@ -564,12 +649,9 @@ bool QPointerEvent::addPassiveGrabber(const QEventPoint &point, QObject *grabber
*/
bool QPointerEvent::removePassiveGrabber(const QEventPoint &point, QObject *grabber)
{
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << pointDeviceName(point) << "point" << point.id() << point.state()
<< ": removing passive grabber" << grabber;
}
point.d->passiveGrabbers.removeOne(grabber);
return false;
Q_ASSERT(pointingDevice());
auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice()));
return devPriv->removePassiveGrabber(this, point, grabber);
}
/*!
@ -581,11 +663,9 @@ bool QPointerEvent::removePassiveGrabber(const QEventPoint &point, QObject *grab
*/
void QPointerEvent::clearPassiveGrabbers(const QEventPoint &point)
{
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << pointDeviceName(point) << "point" << point.id() << point.state()
<< ": clearing" << point.d->passiveGrabbers;
}
point.d->passiveGrabbers.clear();
Q_ASSERT(pointingDevice());
auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice()));
devPriv->clearPassiveGrabbers(this, point);
}
/*!
@ -678,23 +758,38 @@ void QPointerEvent::clearPassiveGrabbers(const QEventPoint &point)
QSinglePointEvent::QSinglePointEvent(QEvent::Type type, const QPointingDevice *dev, const QPointF &localPos, const QPointF &scenePos,
const QPointF &globalPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
: QPointerEvent(type, dev, modifiers),
m_point(0, dev),
m_button(button),
m_mouseState(buttons),
m_source(Qt::MouseEventNotSynthesized),
m_doubleClick(false),
m_reserved(0)
{
QMutableEventPoint &mut = QMutableEventPoint::from(m_point);
if (button == Qt::NoButton)
bool isPress = (button != Qt::NoButton && (button | buttons) == buttons);
bool isWheel = (type == QEvent::Type::Wheel);
auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice()));
auto epd = devPriv->pointById(0);
QMutableEventPoint &mut = QMutableEventPoint::from(epd->eventPoint);
Q_ASSERT(mut.device() == dev);
// mut is now a reference to a non-detached instance that lives in QPointingDevicePrivate::activePoints.
// Update persistent info in that instance.
if (isPress || isWheel)
mut.setGlobalLastPosition(globalPos);
else
mut.setGlobalLastPosition(mut.globalPosition());
mut.setGlobalPosition(globalPos);
if (isWheel && mut.state() != QEventPoint::State::Updated)
mut.setGlobalPressPosition(globalPos);
if (button == Qt::NoButton || isWheel)
mut.setState(QEventPoint::State::Updated); // stationary only happens with touch events, not single-point events
else if ((button | buttons) == buttons)
else if (isPress)
mut.setState(QEventPoint::State::Pressed);
else
mut.setState(QEventPoint::State::Released);
mut.setPosition(localPos);
mut.setScenePosition(scenePos);
mut.setGlobalPosition(globalPos);
// Now detach, and update the detached instance with ephemeral state.
mut.detach();
mut.setPosition(localPos);
m_points.append(mut);
}
/*!
@ -2653,7 +2748,7 @@ QTabletEvent::QTabletEvent(Type type, const QPointingDevice *dev, const QPointF
m_z(z),
m_tangential(tangentialPressure)
{
QMutableEventPoint &mut = QMutableEventPoint::from(m_point);
QMutableEventPoint &mut = QMutableEventPoint::from(point(0));
mut.setPressure(pressure);
mut.setRotation(rotation);
}
@ -3795,7 +3890,7 @@ static inline void formatTouchEvent(QDebug d, const QTouchEvent &t)
d << " device: " << t.device()->name();
d << " states: ";
QtDebugUtils::formatQFlags(d, t.touchPointStates());
d << ", " << t.touchPoints().size() << " points: " << t.touchPoints() << ')';
d << ", " << t.points().size() << " points: " << t.points() << ')';
}
static void formatUnicodeString(QDebug d, const QString &s)
@ -4033,12 +4128,23 @@ static void formatTabletEvent(QDebug d, const QTabletEvent *e)
# endif // QT_CONFIG(tabletevent)
QDebug operator<<(QDebug dbg, const QEventPoint *tp)
{
if (!tp) {
dbg << "QEventPoint(0x0)";
return dbg;
}
return operator<<(dbg, *tp);
}
QDebug operator<<(QDebug dbg, const QEventPoint &tp)
{
QDebugStateSaver saver(dbg);
dbg.nospace();
dbg << "QEventPoint(" << tp.id() << " (";
dbg << "QEventPoint(" << tp.id() << " ts " << tp.timestamp() << " (";
QtDebugUtils::formatQPoint(dbg, tp.position());
dbg << " scene ";
QtDebugUtils::formatQPoint(dbg, tp.scenePosition());
dbg << " global ";
QtDebugUtils::formatQPoint(dbg, tp.globalPosition());
dbg << ") ";
@ -4387,15 +4493,15 @@ QWindowStateChangeEvent::~QWindowStateChangeEvent()
The pointCount() and point() functions can be used to access and iterate individual
touch points.
The touchPoints() function returns a list of all touch points contained in the event.
The points() function returns a list of all touch points contained in the event.
Note that this list may be empty, for example in case of a QEvent::TouchCancel event.
Each point is an instance of the QEventPoint class. The QEventPoint::State enum
describes the different states that a touch point may have.
\note The list of touchPoints() will never be partial: A touch event will always contain a touch
\note The list of points() will never be partial: A touch event will always contain a touch
point for each existing physical touch contacts targetting the window or widget to which the
event is sent. For instance, assuming that all touches target the same window or widget, an
event with a condition of touchPoints().count()==2 is guaranteed to imply that the number of
event with a condition of points().count()==2 is guaranteed to imply that the number of
fingers touching the touchscreen or touchpad is exactly two.
\section1 Event Delivery and Propagation
@ -4481,11 +4587,10 @@ QTouchEvent::QTouchEvent(QEvent::Type eventType,
const QPointingDevice *device,
Qt::KeyboardModifiers modifiers,
const QList<QEventPoint> &touchPoints)
: QPointerEvent(eventType, device, modifiers),
m_target(nullptr),
m_touchPoints(touchPoints)
: QPointerEvent(eventType, device, modifiers, touchPoints),
m_target(nullptr)
{
for (QEventPoint &point : m_touchPoints) {
for (QEventPoint &point : m_points) {
m_touchPointStates |= point.state();
QMutableEventPoint::from(point).setDevice(device);
}
@ -4505,12 +4610,11 @@ QTouchEvent::QTouchEvent(QEvent::Type eventType,
Qt::KeyboardModifiers modifiers,
QEventPoint::States touchPointStates,
const QList<QEventPoint> &touchPoints)
: QPointerEvent(eventType, device, modifiers),
: QPointerEvent(eventType, device, modifiers, touchPoints),
m_target(nullptr),
m_touchPointStates(touchPointStates),
m_touchPoints(touchPoints)
m_touchPointStates(touchPointStates)
{
for (QEventPoint &point : m_touchPoints)
for (QEventPoint &point : m_points)
QMutableEventPoint::from(point).setDevice(device);
}
@ -4558,6 +4662,8 @@ bool QTouchEvent::isReleaseEvent() const
*/
/*! \fn const QList<QEventPoint> &QTouchEvent::touchPoints() const
\obsolete
Deprecated since Qt 6.0. Use points() instead.
Returns a reference to the list of touch points contained in the touch event.
@ -4672,14 +4778,14 @@ bool QTouchEvent::isReleaseEvent() const
*/
/*! \fn QPointF QEventPoint::lastPosition() const
Returns the position of this point from the previous event,
Returns the position of this point from the previous press or move event,
relative to the widget or QGraphicsItem that received the event.
\sa position(), pressPosition()
*/
/*! \fn QPointF QEventPoint::sceneLastPosition() const
Returns the scene position of this point from the previous event.
Returns the scene position of this point from the previous press or move event.
The scene position is the position in QGraphicsScene coordinates
if the QTouchEvent is handled by a QGraphicsItem::touchEvent()
@ -4722,6 +4828,12 @@ bool QTouchEvent::isReleaseEvent() const
\sa QInputDevice::capabilities(), QInputEvent::device()
*/
/*! \fn ulong QEventPoint::timestamp() const
Returns the most recent time at which this point was included in a QPointerEvent.
\sa QPointerEvent::timestamp()
*/
/*!
\class QScrollPrepareEvent
\since 4.8
@ -4952,6 +5064,18 @@ Qt::ApplicationState QApplicationStateChangeEvent::applicationState() const
return m_applicationState;
}
/*! \internal
Add the given \a point.
*/
void QMutableTouchEvent::addPoint(const QEventPoint &point)
{
m_points.append(point);
auto &added = m_points.last();
if (!added.device())
QMutableEventPoint::from(added).setDevice(pointingDevice());
m_touchPointStates |= point.state();
}
/*!
\class QPointingDeviceUniqueId
\since 5.8

View File

@ -79,7 +79,7 @@ public:
inline Qt::KeyboardModifiers modifiers() const { return m_modState; }
inline void setModifiers(Qt::KeyboardModifiers modifiers) { m_modState = modifiers; }
inline ulong timestamp() const { return m_timeStamp; }
inline void setTimestamp(ulong timestamp) { m_timeStamp = timestamp; }
virtual void setTimestamp(ulong timestamp) { m_timeStamp = timestamp; }
protected:
QInputEvent(Type type, PointerEventTag, const QInputDevice *m_dev, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
@ -156,6 +156,7 @@ public:
const QPointingDevice *device() const;
int id() const;
QPointingDeviceUniqueId uniqueId() const;
ulong timestamp() const;
ulong pressTimestamp() const;
qreal timeHeld() const;
qreal pressure() const;
@ -172,20 +173,24 @@ private:
};
#ifndef QT_NO_DEBUG_STREAM
Q_GUI_EXPORT QDebug operator<<(QDebug, const QEventPoint *);
Q_GUI_EXPORT QDebug operator<<(QDebug, const QEventPoint &);
#endif
class Q_GUI_EXPORT QPointerEvent : public QInputEvent
{
public:
explicit QPointerEvent(Type type, const QPointingDevice *dev, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
explicit QPointerEvent(Type type, const QPointingDevice *dev,
Qt::KeyboardModifiers modifiers = Qt::NoModifier, const QList<QEventPoint> &points = {});
virtual ~QPointerEvent();
const QPointingDevice *pointingDevice() const;
QPointingDevice::PointerType pointerType() const {
return pointingDevice() ? pointingDevice()->pointerType() : QPointingDevice::PointerType::Unknown;
}
virtual int pointCount() const = 0;
virtual const QEventPoint &point(int i) const = 0;
void setTimestamp(ulong timestamp) override;
qsizetype pointCount() const { return m_points.count(); }
QEventPoint &point(qsizetype i) { return m_points[i]; }
const QList<QEventPoint> &points() const { return m_points; }
virtual bool isPressEvent() const { return false; }
virtual bool isUpdateEvent() const { return false; }
virtual bool isReleaseEvent() const { return false; }
@ -195,6 +200,9 @@ public:
void clearPassiveGrabbers(const QEventPoint &point);
bool addPassiveGrabber(const QEventPoint &point, QObject *grabber);
bool removePassiveGrabber(const QEventPoint &point, QObject *grabber);
protected:
QList<QEventPoint> m_points;
};
class Q_GUI_EXPORT QSinglePointEvent : public QPointerEvent
@ -204,22 +212,22 @@ public:
const QPointF &scenePos, const QPointF &globalPos,
Qt::MouseButton button = Qt::NoButton, Qt::MouseButtons buttons = Qt::NoButton,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
int pointCount() const override { return 1; }
const QEventPoint &point(int i) const override { Q_ASSERT(i == 0); return m_point; }
inline Qt::MouseButton button() const { return m_button; }
inline Qt::MouseButtons buttons() const { return m_mouseState; }
inline QPointF position() const { return m_point.position(); }
inline QPointF scenePosition() const { return m_point.scenePosition(); }
inline QPointF globalPosition() const { return m_point.globalPosition(); }
inline QPointF position() const
{ Q_ASSERT(!m_points.isEmpty()); return m_points.first().position(); }
inline QPointF scenePosition() const
{ Q_ASSERT(!m_points.isEmpty()); return m_points.first().scenePosition(); }
inline QPointF globalPosition() const
{ Q_ASSERT(!m_points.isEmpty()); return m_points.first().globalPosition(); }
bool isPressEvent() const override;
bool isUpdateEvent() const override;
bool isReleaseEvent() const override;
protected:
QEventPoint m_point;
Qt::MouseButton m_button = Qt::NoButton;
Qt::MouseButtons m_mouseState = Qt::NoButton;
quint32 m_source : 8; // actually Qt::MouseEventSource
@ -400,8 +408,8 @@ public:
QT_DEPRECATED_VERSION_X_6_0("use pointingDevice().uniqueId()")
inline qint64 uniqueId() const { return pointingDevice() ? pointingDevice()->uniqueId().numericId() : -1; }
#endif
inline qreal pressure() const { return point(0).pressure(); }
inline qreal rotation() const { return point(0).rotation(); }
inline qreal pressure() const { Q_ASSERT(!points().isEmpty()); return points().first().pressure(); }
inline qreal rotation() const { Q_ASSERT(!points().isEmpty()); return points().first().rotation(); }
inline int z() const { return m_z; }
inline qreal tangentialPressure() const { return m_tangential; }
inline int xTilt() const { return m_xTilt; }
@ -950,12 +958,12 @@ public:
#endif
~QTouchEvent();
int pointCount() const override { return m_touchPoints.count(); }
const QEventPoint &point(int i) const override { return m_touchPoints.at(i); }
inline QObject *target() const { return m_target; }
inline QEventPoint::States touchPointStates() const { return m_touchPointStates; }
const QList<QEventPoint> &touchPoints() const { return m_touchPoints; }
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use points()")
const QList<QEventPoint> &touchPoints() const { return points(); }
#endif
bool isPressEvent() const override;
bool isUpdateEvent() const override;
bool isReleaseEvent() const override;
@ -963,7 +971,7 @@ public:
protected:
QObject *m_target = nullptr;
QEventPoint::States m_touchPointStates = QEventPoint::State::Unknown;
QList<QEventPoint> m_touchPoints;
quint32 m_reserved : 24;
};
class Q_GUI_EXPORT QScrollPrepareEvent : public QEvent

View File

@ -54,10 +54,12 @@
#include <QtGui/private/qtguiglobal_p.h>
#include <QtCore/qurl.h>
#include <QtGui/qevent.h>
#include <QtGui/private/qpointingdevice_p.h>
#include <QtGui/qwindow.h>
QT_BEGIN_NAMESPACE
class QPointingDevice;
struct QEventPointPrivate {
QEventPointPrivate(int id, const QPointingDevice *device)
: device(device), pointId(id) { }
@ -70,14 +72,14 @@ struct QEventPointPrivate {
}
const QPointingDevice *device = nullptr;
QPointer<QWindow> window;
QPointer<QObject> target;
QPointF pos, scenePos, globalPos,
globalPressPos, globalGrabPos, globalLastPos;
qreal pressure = 1;
qreal rotation = 0;
QSizeF ellipseDiameters = QSizeF(0, 0);
QVector2D velocity;
QPointer<QObject> exclusiveGrabber;
QList<QPointer <QObject> > passiveGrabbers;
ulong timestamp = 0;
ulong pressTimestamp = 0;
QPointingDeviceUniqueId uniqueId;
@ -106,10 +108,14 @@ public:
d->pos = position;
}
void updateFrom(const QEventPoint &other);
static QMutableEventPoint *from(QEventPoint *me) { return static_cast<QMutableEventPoint *>(me); }
static QMutableEventPoint &from(QEventPoint &me) { return static_cast<QMutableEventPoint &>(me); }
static const QMutableEventPoint &constFrom(const QEventPoint &me) { return static_cast<const QMutableEventPoint &>(me); }
void detach();
bool stationaryWithModifiedProperty() const { return d->stationaryWithModifiedProperty; }
@ -118,7 +124,7 @@ public:
void setDevice(const QPointingDevice *device) { d->device = device; }
void setTimestamp(const ulong t) { d->timestamp = t; }
void setTimestamp(const ulong t);
void setPressTimestamp(const ulong t) { d->pressTimestamp = t; }
@ -157,6 +163,14 @@ public:
void setVelocity(const QVector2D &v) { d->velocity = v; }
void setStationaryWithModifiedProperty(bool s = true) { d->stationaryWithModifiedProperty = s; }
QWindow *window() const { return d->window.data(); }
void setWindow(const QPointer<QWindow> &w) { d->window = w; }
QObject *target() const { return d->target.data(); }
void setTarget(const QPointer<QObject> &t) { d->target = t; }
};
static_assert(sizeof(QMutableEventPoint) == sizeof(QEventPoint));
@ -164,7 +178,7 @@ static_assert(sizeof(QMutableEventPoint) == sizeof(QEventPoint));
class Q_GUI_EXPORT QMutableTouchEvent : public QTouchEvent
{
public:
QMutableTouchEvent(QEvent::Type eventType,
QMutableTouchEvent(QEvent::Type eventType = QEvent::TouchBegin,
const QPointingDevice *device = nullptr,
Qt::KeyboardModifiers modifiers = Qt::NoModifier,
const QList<QEventPoint> &touchPoints = QList<QEventPoint>()) :
@ -176,7 +190,7 @@ public:
void setTarget(QObject *target) { m_target = target; }
QList<QEventPoint> &touchPoints() { return m_touchPoints; }
void addPoint(const QEventPoint &point);
};
static_assert(sizeof(QMutableTouchEvent) == sizeof(QTouchEvent));
@ -188,7 +202,7 @@ public:
static QMutableSinglePointEvent &from(QSinglePointEvent &e) { return static_cast<QMutableSinglePointEvent &>(e); }
QMutableEventPoint &mutablePoint() { return QMutableEventPoint::from(m_point); }
QMutableEventPoint &mutablePoint() { return QMutableEventPoint::from(point(0)); }
void setSource(Qt::MouseEventSource s) { m_source = s; }

View File

@ -59,6 +59,7 @@
#include <QtCore/qmutex.h>
#include <QtCore/private/qthread_p.h>
#include <QtCore/private/qlocking_p.h>
#include <QtCore/private/qflatmap_p.h>
#include <QtCore/qdir.h>
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qnumeric.h>
@ -155,7 +156,7 @@ bool QGuiApplicationPrivate::highDpiScalingUpdated = false;
QPointer<QWindow> QGuiApplicationPrivate::currentDragWindow;
QList<QGuiApplicationPrivate::TabletPointData> QGuiApplicationPrivate::tabletDevicePoints;
QList<QGuiApplicationPrivate::TabletPointData> QGuiApplicationPrivate::tabletDevicePoints; // TODO remove
QPlatformIntegration *QGuiApplicationPrivate::platform_integration = nullptr;
QPlatformTheme *QGuiApplicationPrivate::platform_theme = nullptr;
@ -177,10 +178,7 @@ QString *QGuiApplicationPrivate::desktopFileName = nullptr;
QPalette *QGuiApplicationPrivate::app_pal = nullptr; // default application palette
ulong QGuiApplicationPrivate::mousePressTime = 0;
Qt::MouseButton QGuiApplicationPrivate::mousePressButton = Qt::NoButton;
int QGuiApplicationPrivate::mousePressX = 0; // TODO use QPointF and store it in QPointingDevicePrivate
int QGuiApplicationPrivate::mousePressY = 0;
static int mouseDoubleClickDistance = -1;
static int touchDoubleTapDistance = -1;
@ -716,8 +714,6 @@ QGuiApplication::~QGuiApplication()
QGuiApplicationPrivate::highDpiScalingUpdated = false;
QGuiApplicationPrivate::currentDragWindow = nullptr;
QGuiApplicationPrivate::tabletDevicePoints.clear();
QGuiApplicationPrivate::mousePressTime = 0;
QGuiApplicationPrivate::mousePressX = QGuiApplicationPrivate::mousePressY = 0;
}
QGuiApplicationPrivate::QGuiApplicationPrivate(int &argc, char **argv, int flags)
@ -1195,6 +1191,7 @@ QString QGuiApplication::platformName()
}
Q_LOGGING_CATEGORY(lcQpaPluginLoading, "qt.qpa.plugin");
Q_LOGGING_CATEGORY(lcPtrDispatch, "qt.pointer.dispatch");
static void init_platform(const QString &pluginNamesWithArguments, const QString &platformPluginPath, const QString &platformThemeName, int &argc, char **argv)
{
@ -2132,9 +2129,13 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
QEvent::Type type = QEvent::None;
Qt::MouseButton button = Qt::NoButton;
QWindow *window = e->window.data();
const QPointingDevice *device = static_cast<const QPointingDevice *>(e->device);
Q_ASSERT(device);
QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(device));
bool positionChanged = QGuiApplicationPrivate::lastCursorPosition != e->globalPos;
bool mouseMove = false;
bool mousePress = false;
QPointF globalPoint = e->globalPos;
if (e->enhancedMouseEvent()) {
type = e->buttonType;
@ -2207,27 +2208,24 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
modifier_buttons = e->modifiers;
QPointF localPoint = e->localPos;
QPointF globalPoint = e->globalPos;
const QPointF lastGlobalPosition = QGuiApplicationPrivate::lastCursorPosition;
bool doubleClick = false;
auto persistentEPD = devPriv->pointById(0);
const auto &persistentPoint = QMutableEventPoint::from(persistentEPD->eventPoint);
if (mouseMove) {
QGuiApplicationPrivate::lastCursorPosition = globalPoint;
const auto doubleClickDistance = (e->device && e->device->type() == QInputDevice::DeviceType::Mouse ?
mouseDoubleClickDistance : touchDoubleTapDistance);
if (qAbs(globalPoint.x() - mousePressX) > doubleClickDistance ||
qAbs(globalPoint.y() - mousePressY) > doubleClickDistance)
const auto pressPos = persistentPoint.globalPressPosition();
if (qAbs(globalPoint.x() - pressPos.x()) > doubleClickDistance ||
qAbs(globalPoint.y() - pressPos.y()) > doubleClickDistance)
mousePressButton = Qt::NoButton;
} else {
mouse_buttons = e->buttons;
if (mousePress) {
ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval());
doubleClick = e->timestamp - mousePressTime < doubleClickInterval && button == mousePressButton;
mousePressTime = e->timestamp;
doubleClick = e->timestamp - persistentPoint.pressTimestamp() < doubleClickInterval && button == mousePressButton;
mousePressButton = button;
const QPoint point = QGuiApplicationPrivate::lastCursorPosition.toPoint();
mousePressX = point.x();
mousePressY = point.y();
}
}
@ -2252,7 +2250,6 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
if (!window)
return;
const QPointingDevice *device = static_cast<const QPointingDevice *>(e->device);
#ifndef QT_NO_CURSOR
if (!e->synthetic()) {
if (const QScreen *screen = window->screen())
@ -2268,11 +2265,8 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
#endif
QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source, device);
// ev now contains a detached copy of the QEventPoint from QPointingDevicePrivate::activePoints
ev.setTimestamp(e->timestamp);
QMutableEventPoint &mutPt = QMutableSinglePointEvent::from(ev).mutablePoint();
mutPt.setGlobalLastPosition(lastGlobalPosition);
mutPt.setGlobalPressPosition(QPointF(mousePressX, mousePressY));
if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
// a modal window is blocking this window, don't allow mouse events through
return;
@ -2330,6 +2324,10 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
QGuiApplication::sendSpontaneousEvent(window, &dblClickEvent);
}
}
if (type == QEvent::MouseButtonRelease && e->buttons == Qt::NoButton) {
ev.setExclusiveGrabber(persistentPoint, nullptr);
ev.clearPassiveGrabbers(persistentPoint);
}
}
void QGuiApplicationPrivate::processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e)
@ -2806,38 +2804,28 @@ void QGuiApplicationPrivate::processContextMenuEvent(QWindowSystemInterfacePriva
}
#endif
Q_GUI_EXPORT size_t qHash(const QGuiApplicationPrivate::ActiveTouchPointsKey &k, size_t seed)
{
return (qHash(k.device) + k.touchPointId) ^ seed;
}
Q_GUI_EXPORT bool operator==(const QGuiApplicationPrivate::ActiveTouchPointsKey &a,
const QGuiApplicationPrivate::ActiveTouchPointsKey &b)
{
return a.device == b.device
&& a.touchPointId == b.touchPointId;
}
void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::TouchEvent *e)
{
QGuiApplicationPrivate *d = self;
modifier_buttons = e->modifiers;
const QPointingDevice *device = static_cast<const QPointingDevice *>(e->device);
QPointingDevice *device = const_cast<QPointingDevice *>(static_cast<const QPointingDevice *>(e->device));
QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(device);
if (e->touchType == QEvent::TouchCancel) {
// The touch sequence has been canceled (e.g. by the compositor).
// Send the TouchCancel to all windows with active touches and clean up.
QTouchEvent touchEvent(QEvent::TouchCancel, device, e->modifiers);
touchEvent.setTimestamp(e->timestamp);
QHash<ActiveTouchPointsKey, ActiveTouchPointsValue>::const_iterator it
= self->activeTouchPoints.constBegin(), ite = self->activeTouchPoints.constEnd();
QSet<QWindow *> windowsNeedingCancel;
while (it != ite) {
QWindow *w = it->window.data();
for (auto &epd : devPriv->activePoints.values()) {
auto &mut = QMutableEventPoint::from(const_cast<QEventPoint &>(epd.eventPoint));
QWindow *w = mut.window();
if (w)
windowsNeedingCancel.insert(w);
++it;
mut.setWindow(nullptr);
mut.setTarget(nullptr);
}
for (QSet<QWindow *>::const_iterator winIt = windowsNeedingCancel.constBegin(),
winItEnd = windowsNeedingCancel.constEnd(); winIt != winItEnd; ++winIt) {
QGuiApplication::sendSpontaneousEvent(*winIt, &touchEvent);
@ -2863,7 +2851,6 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
}
self->synthesizedMousePoints.clear();
}
self->activeTouchPoints.clear();
self->lastTouchType = e->touchType;
return;
}
@ -2874,110 +2861,88 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
self->lastTouchType = e->touchType;
QWindow *window = e->window.data();
// TODO get rid of this QPair; we don't need to accumulate combined states here anymore
typedef QPair<QEventPoint::States, QList<QEventPoint> > StatesAndTouchPoints;
QHash<QWindow *, StatesAndTouchPoints> windowsNeedingEvents;
bool stationaryTouchPointChangedProperty = false;
QPointer<QWindow> window = e->window; // the platform hopefully tells us which window received the event
QVarLengthArray<QMutableTouchEvent, 2> touchEvents;
for (int i = 0; i < e->points.count(); ++i) {
QMutableEventPoint touchPoint = QMutableEventPoint::from(e->points[i]);
// For each temporary QEventPoint from the QPA TouchEvent:
// - update the persistent QEventPoint in QPointingDevicePrivate::activePoints with current values
// - determine which window to deliver it to
// - add it to the QTouchEvent instance for that window (QMutableTouchEvent::target() will be QWindow*, for now)
for (auto &tempPt : e->points) {
// update state
QPointer<QWindow> w;
QEventPoint previousTouchPoint;
ActiveTouchPointsKey touchInfoKey(device, touchPoint.id());
ActiveTouchPointsValue &touchInfo = d->activeTouchPoints[touchInfoKey];
switch (touchPoint.state()) {
auto epd = devPriv->pointById(tempPt.id());
auto &mut = QMutableEventPoint::from(const_cast<QEventPoint &>(epd->eventPoint));
epd->eventPoint.setAccepted(false);
switch (tempPt.state()) {
case QEventPoint::State::Pressed:
if (e->device && e->device->type() == QInputDevice::DeviceType::TouchPad) {
// on touch-pads, send all touch points to the same widget
w = d->activeTouchPoints.isEmpty()
? QPointer<QWindow>()
: d->activeTouchPoints.constBegin().value().window;
}
if (!w) {
// determine which window this event will go to
if (!window)
window = QGuiApplication::topLevelAt(touchPoint.globalPosition().toPoint());
if (!window)
continue;
w = window;
}
touchInfo.window = w;
touchPoint.setGlobalPressPosition(touchPoint.globalPosition());
touchPoint.setGlobalLastPosition(touchPoint.globalPosition());
if (touchPoint.pressure() < 0)
touchPoint.setPressure(1);
touchInfo.touchPoint = touchPoint;
// On touchpads, send all touch points to the same window.
if (!window && e->device && e->device->type() == QInputDevice::DeviceType::TouchPad)
window = devPriv->firstActiveWindow();
// If the QPA event didn't tell us which window, find the one under the touchpoint position.
if (!window)
window = QGuiApplication::topLevelAt(tempPt.globalPosition().toPoint());
mut.setWindow(window);
break;
case QEventPoint::State::Released:
w = touchInfo.window;
if (!w)
continue;
previousTouchPoint = touchInfo.touchPoint;
touchPoint.setGlobalPressPosition(previousTouchPoint.globalPressPosition());
touchPoint.setGlobalLastPosition(previousTouchPoint.globalPosition());
touchPoint.setPressure(0);
if (Q_UNLIKELY(window != mut.window())) {
qCWarning(lcPtrDispatch) << "delivering touch release to same window" << mut.window() << "not" << window.data();
window = mut.window();
}
break;
default:
w = touchInfo.window;
if (!w)
continue;
previousTouchPoint = touchInfo.touchPoint;
touchPoint.setGlobalPressPosition(previousTouchPoint.globalPressPosition());
touchPoint.setGlobalLastPosition(previousTouchPoint.globalPosition());
if (touchPoint.pressure() < 0)
touchPoint.setPressure(1);
// Stationary points might not be delivered down to the receiving item
// and get their position transformed, keep the old values instead.
if (touchPoint.state() == QEventPoint::State::Stationary) {
if (touchInfo.touchPoint.velocity() != touchPoint.velocity()) {
touchInfo.touchPoint.setVelocity(touchPoint.velocity());
touchPoint.setStationaryWithModifiedProperty();
stationaryTouchPointChangedProperty = true;
}
if (!qFuzzyCompare(touchInfo.touchPoint.pressure(), touchPoint.pressure())) {
touchInfo.touchPoint.setPressure(touchPoint.pressure());
touchPoint.setStationaryWithModifiedProperty();
stationaryTouchPointChangedProperty = true;
}
} else {
touchInfo.touchPoint = touchPoint;
default: // update or stationary
if (Q_UNLIKELY(window != mut.window())) {
qCWarning(lcPtrDispatch) << "delivering touch update to same window" << mut.window() << "not" << window.data();
window = mut.window();
}
break;
}
// If we somehow still don't have a window, we can't deliver this touchpoint. (should never happen)
if (Q_UNLIKELY(!window)) {
qCWarning(lcPtrDispatch) << "skipping" << &tempPt << ": no target window";
continue;
}
mut.updateFrom(tempPt);
Q_ASSERT(w.data() != nullptr);
Q_ASSERT(window.data() != nullptr);
// make the *scene* position the same as the *global* position
// Note: touchPoint is a reference to the one from activeTouchPoints, so we can modify it.
touchPoint.setScenePosition(touchPoint.globalPosition());
mut.setScenePosition(tempPt.globalPosition());
StatesAndTouchPoints &maskAndPoints = windowsNeedingEvents[w.data()];
maskAndPoints.first |= touchPoint.state();
maskAndPoints.second.append(touchPoint);
// store the scene position as local position, for now
mut.setPosition(window->mapFromGlobal(tempPt.globalPosition()));
// setTimeStamp has side effects, so we do it last
mut.setTimestamp(e->timestamp);
// add the touchpoint to the event that will be delivered to the window
bool added = false;
for (QMutableTouchEvent &ev : touchEvents) {
if (ev.target() == window.data()) {
ev.addPoint(mut);
added = true;
break;
}
}
if (!added) {
QMutableTouchEvent mte(e->touchType, device, e->modifiers, {mut});
mte.setTimestamp(e->timestamp);
mte.setTarget(window.data());
touchEvents.append(mte);
}
}
if (windowsNeedingEvents.isEmpty())
if (touchEvents.isEmpty())
return;
QHash<QWindow *, StatesAndTouchPoints>::ConstIterator it = windowsNeedingEvents.constBegin();
const QHash<QWindow *, StatesAndTouchPoints>::ConstIterator end = windowsNeedingEvents.constEnd();
for (; it != end; ++it) {
QWindow *w = it.key();
for (QMutableTouchEvent &touchEvent : touchEvents) {
QWindow *window = static_cast<QWindow *>(touchEvent.target());
auto &points = touchEvent.points();
QEvent::Type eventType;
switch (it.value().first) {
switch (touchEvent.touchPointStates()) {
case QEventPoint::State::Pressed:
eventType = QEvent::TouchBegin;
break;
@ -2994,32 +2959,22 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
break;
}
if (w->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
// a modal window is blocking this window, don't allow touch events through
// QTBUG-37371 temporary fix; TODO: revisit in 5.4 when we have a forwarding solution
if (eventType == QEvent::TouchEnd) {
// QTBUG-37371 temporary fix; TODO: revisit when we have a forwarding solution
if (touchEvent.type() == QEvent::TouchEnd) {
// but don't leave dangling state: e.g.
// QQuickWindowPrivate::itemForTouchPointId needs to be cleared.
QTouchEvent touchEvent(QEvent::TouchCancel,
device,
e->modifiers);
QTouchEvent touchEvent(QEvent::TouchCancel, device, e->modifiers);
touchEvent.setTimestamp(e->timestamp);
QGuiApplication::sendSpontaneousEvent(w, &touchEvent);
QGuiApplication::sendSpontaneousEvent(window, &touchEvent);
}
continue;
}
const auto &touchpoints = it.value().second;
QMutableTouchEvent touchEvent(eventType, device, e->modifiers, touchpoints);
touchEvent.setTimestamp(e->timestamp);
QGuiApplication::sendSpontaneousEvent(window, &touchEvent);
for (QEventPoint &pt : touchEvent.touchPoints()) {
auto &touchPoint = QMutableEventPoint::from(pt);
touchPoint.setPosition(w->mapFromGlobal(touchPoint.globalPosition()));
}
QGuiApplication::sendSpontaneousEvent(w, &touchEvent);
if (!e->synthetic() && !touchEvent.isAccepted() && qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) {
// exclude devices which generate their own mouse events
if (!(touchEvent.device()->capabilities().testFlag(QInputDevice::Capability::MouseEmulation))) {
@ -3042,15 +2997,15 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
Qt::MouseButton button = mouseType == QEvent::MouseMove ? Qt::NoButton : Qt::LeftButton;
Qt::MouseButtons buttons = mouseType == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton;
const auto &points = touchEvent.touchPoints();
for (const QEventPoint &touchPoint : points) {
if (touchPoint.id() == m_fakeMouseSourcePointId) {
if (eventType != QEvent::TouchEnd)
self->synthesizedMousePoints.insert(w, SynthesizedMouseData(
touchPoint.position(), touchPoint.globalPosition(), w));
self->synthesizedMousePoints.insert(window, SynthesizedMouseData(
touchPoint.position(), touchPoint.globalPosition(), window));
// All touch events that are not accepted by the application will be translated to
// left mouse button events instead (see AA_SynthesizeMouseForUnhandledTouchEvents docs).
QWindowSystemInterfacePrivate::MouseEvent fake(w, e->timestamp,
// TODO why go through QPA? Why not just send a QMouseEvent right from here?
QWindowSystemInterfacePrivate::MouseEvent fake(window, e->timestamp,
touchPoint.position(),
touchPoint.globalPosition(),
buttons,
@ -3069,13 +3024,11 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
}
}
// Remove released points from the hash table only after the event is
// delivered. When the receiver is a widget, QApplication will access
// activeTouchPoints during delivery and therefore nothing can be removed
// before sending the event.
// Remove released points from QPointingDevicePrivate::activePoints only after the event is
// delivered. Widgets and Qt Quick are allowed to access them at any time before this.
for (const QEventPoint &touchPoint : e->points) {
if (touchPoint.state() == QEventPoint::State::Released)
d->activeTouchPoints.remove(ActiveTouchPointsKey(device, touchPoint.id()));
devPriv->removePointById(touchPoint.id());
}
}

View File

@ -227,10 +227,7 @@ public:
virtual bool isWindowBlocked(QWindow *window, QWindow **blockingWindow = nullptr) const;
virtual bool popupActive() { return false; }
static ulong mousePressTime;
static Qt::MouseButton mousePressButton;
static int mousePressX;
static int mousePressY;
static QPointF lastCursorPosition;
static QWindow *currentMouseWindow;
static QWindow *currentMousePressWindow;
@ -286,17 +283,6 @@ public:
void saveState();
#endif
struct ActiveTouchPointsKey {
ActiveTouchPointsKey(const QPointingDevice *dev, int id) : device(dev), touchPointId(id) { }
const QPointingDevice *device;
int touchPointId;
};
struct ActiveTouchPointsValue {
QPointer<QWindow> window;
QPointer<QObject> target;
QMutableEventPoint touchPoint;
};
QHash<ActiveTouchPointsKey, ActiveTouchPointsValue> activeTouchPoints;
QEvent::Type lastTouchType;
struct SynthesizedMouseData {
SynthesizedMouseData(const QPointF &p, const QPointF &sp, QWindow *w)
@ -364,11 +350,6 @@ private:
static qreal m_maxDevicePixelRatio;
};
Q_GUI_EXPORT size_t qHash(const QGuiApplicationPrivate::ActiveTouchPointsKey &k, size_t seed = 0);
Q_GUI_EXPORT bool operator==(const QGuiApplicationPrivate::ActiveTouchPointsKey &a,
const QGuiApplicationPrivate::ActiveTouchPointsKey &b);
// ----------------- QPlatformInterface -----------------
namespace QPlatformInterface::Private {

View File

@ -49,6 +49,7 @@
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcQpaInputDevices)
Q_DECLARE_LOGGING_CATEGORY(lcPointerGrab)
/*!
\class QPointingDevice
@ -353,6 +354,157 @@ const QPointingDevice *QPointingDevicePrivate::queryTabletDevice(QInputDevice::D
return nullptr;
}
/*! \internal
Returns the active EventPointData instance with the given \a id, if available,
or \c nullptr if not.
*/
QPointingDevicePrivate::EventPointData *QPointingDevicePrivate::queryPointById(int id) const
{
auto it = activePoints.find(id);
if (it == activePoints.end())
return nullptr;
return &it.value();
}
/*! \internal
Returns the active EventPointData instance with the given \a id, if available;
if not, appends a new instance and returns it.
*/
QPointingDevicePrivate::EventPointData *QPointingDevicePrivate::pointById(int id) const
{
auto it = activePoints.find(id);
if (it == activePoints.end()) {
Q_Q(const QPointingDevice);
QPointingDevicePrivate::EventPointData epd;
QMutableEventPoint::from(epd.eventPoint).setId(id);
QMutableEventPoint::from(epd.eventPoint).setDevice(q);
return &activePoints.insert(id, epd).first.value();
}
return &it.value();
}
/*! \internal
Remove the active EventPointData instance with the given \a id.
*/
void QPointingDevicePrivate::removePointById(int id)
{
activePoints.remove(id);
}
/*!
\internal
Find the first non-null target (widget) via QMutableEventPoint::target()
in the active points. This is the widget that will receive any event that
comes from a touchpad, even if some of the touchpoints fall spatially on
other windows.
*/
QObject *QPointingDevicePrivate::firstActiveTarget() const
{
for (auto &pt : activePoints.values()) {
if (auto target = QMutableEventPoint::constFrom(pt.eventPoint).target())
return target;
}
return nullptr;
}
/*! \internal
Find the first non-null QWindow instance via QMutableEventPoint::window()
in the active points. This is the window that will receive any event that
comes from a touchpad, even if some of the touchpoints fall spatially on
other windows.
*/
QWindow *QPointingDevicePrivate::firstActiveWindow() const
{
for (auto &pt : activePoints.values()) {
if (auto window = QMutableEventPoint::constFrom(pt.eventPoint).window())
return window;
}
return nullptr;
}
void QPointingDevicePrivate::setExclusiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *exclusiveGrabber)
{
Q_Q(QPointingDevice);
auto persistentPoint = queryPointById(point.id());
if (!persistentPoint) {
qWarning() << "point is not in activePoints" << point;
return;
}
if (persistentPoint->exclusiveGrabber == exclusiveGrabber)
return;
auto oldGrabber = persistentPoint->exclusiveGrabber;
persistentPoint->exclusiveGrabber = exclusiveGrabber;
if (oldGrabber)
emit q->grabChanged(oldGrabber, QPointingDevice::UngrabExclusive, event, point);
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << name << "point" << point.id() << point.state()
<< "@" << point.scenePosition()
<< ": grab" << oldGrabber << "->" << exclusiveGrabber;
}
QMutableEventPoint::from(persistentPoint->eventPoint).setGlobalGrabPosition(point.globalPosition());
if (exclusiveGrabber)
emit q->grabChanged(exclusiveGrabber, QPointingDevice::GrabExclusive, event, point);
}
bool QPointingDevicePrivate::addPassiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *grabber)
{
Q_Q(QPointingDevice);
auto persistentPoint = queryPointById(point.id());
if (!persistentPoint) {
qWarning() << "point is not in activePoints" << point;
return false;
}
if (persistentPoint->passiveGrabbers.contains(grabber))
return false;
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << name << "point" << point.id() << point.state()
<< ": grab (passive)" << grabber;
}
persistentPoint->passiveGrabbers << grabber;
emit q->grabChanged(grabber, QPointingDevice::GrabPassive, event, point);
return true;
}
bool QPointingDevicePrivate::removePassiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *grabber)
{
Q_Q(QPointingDevice);
auto persistentPoint = queryPointById(point.id());
if (!persistentPoint) {
qWarning() << "point is not in activePoints" << point;
return false;
}
int i = persistentPoint->passiveGrabbers.indexOf(grabber);
if (i >= 0) {
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << name << "point" << point.id() << point.state()
<< ": removing passive grabber" << grabber;
}
emit q->grabChanged(grabber, QPointingDevice::UngrabPassive, event, point);
persistentPoint->passiveGrabbers.removeAt(i);
return true;
}
return false;
}
void QPointingDevicePrivate::clearPassiveGrabbers(const QPointerEvent *event, const QEventPoint &point)
{
Q_Q(QPointingDevice);
auto persistentPoint = queryPointById(point.id());
if (!persistentPoint) {
qWarning() << "point is not in activePoints" << point;
return;
}
if (persistentPoint->passiveGrabbers.isEmpty())
return;
if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) {
qCDebug(lcPointerGrab) << name << "point" << point.id() << point.state()
<< ": clearing" << persistentPoint->passiveGrabbers;
}
for (auto g : persistentPoint->passiveGrabbers)
emit q->grabChanged(g, QPointingDevice::UngrabPassive, event, point);
persistentPoint->passiveGrabbers.clear();
}
/*!
\internal
Finds the device instance belonging to the drawing or eraser end of a particular stylus,

View File

@ -47,6 +47,8 @@
QT_BEGIN_NAMESPACE
class QDebug;
class QEventPoint;
class QPointerEvent;
class QPointingDevicePrivate;
class QScreen;
@ -100,6 +102,17 @@ public:
Q_DECLARE_FLAGS(PointerTypes, PointerType)
Q_FLAG(PointerTypes)
enum GrabTransition : quint8 {
GrabPassive = 0x01,
UngrabPassive = 0x02,
CancelGrabPassive = 0x03,
OverrideGrabPassive = 0x04,
GrabExclusive = 0x10,
UngrabExclusive = 0x20,
CancelGrabExclusive = 0x30,
};
Q_ENUM(GrabTransition)
QPointingDevice();
~QPointingDevice();
QPointingDevice(const QString &name, qint64 systemId, QInputDevice::DeviceType devType,
@ -126,6 +139,9 @@ public:
bool operator==(const QPointingDevice &other) const;
Q_SIGNALS:
void grabChanged(QObject *grabber, GrabTransition transition, const QPointerEvent *event, const QEventPoint &point) const;
protected:
QPointingDevice(QPointingDevicePrivate &d, QObject *parent = nullptr);

View File

@ -51,14 +51,17 @@
// We mean it.
//
#include <QtGui/private/qevent_p.h>
#include <QtGui/qpointingdevice.h>
#include <QtGui/private/qtguiglobal_p.h>
#include <QtGui/private/qinputdevice_p.h>
#include <QtGui/qpointingdevice.h>
#include <QtCore/private/qflatmap_p.h>
QT_BEGIN_NAMESPACE
class Q_GUI_EXPORT QPointingDevicePrivate : public QInputDevicePrivate
{
Q_DECLARE_PUBLIC(QPointingDevice)
public:
QPointingDevicePrivate(const QString &name, qint64 id, QInputDevice::DeviceType type,
QPointingDevice::PointerType pType, QPointingDevice::Capabilities caps,
@ -71,8 +74,31 @@ public:
pointerType(pType)
{
pointingDeviceType = true;
activePoints.reserve(maxPoints);
}
/*! \internal
This struct (stored in activePoints) holds persistent state between event deliveries.
*/
struct EventPointData {
QEventPoint eventPoint;
QPointer<QObject> exclusiveGrabber;
QList<QPointer <QObject> > passiveGrabbers;
};
EventPointData *queryPointById(int id) const;
EventPointData *pointById(int id) const;
void removePointById(int id);
QObject *firstActiveTarget() const;
QWindow *firstActiveWindow() const;
void setExclusiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *exclusiveGrabber);
bool addPassiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *grabber);
bool removePassiveGrabber(const QPointerEvent *event, const QEventPoint &point, QObject *grabber);
void clearPassiveGrabbers(const QPointerEvent *event, const QEventPoint &point);
using EventPointMap = QFlatMap<int, EventPointData>;
mutable EventPointMap activePoints;
void * extra = nullptr; // QPA plugins can store platform-specific stuff here
QPointingDeviceUniqueId uniqueId;
quint32 toolId = 0; // only for Wacom tablets

View File

@ -630,6 +630,33 @@ void QWindowSystemInterface::registerInputDevice(const QInputDevice *device)
QInputDevicePrivate::registerDevice(device);
}
/*!
\internal
Convert a list of \l QWindowSystemInterface::TouchPoint \a points to a list
of \em temporary QEventPoint instances, scaled (but not localized)
for delivery to the given \a window.
This is called from QWindowSystemInterface::handleTouchEvent():
that is too early to update the QEventPoint instances in QPointingDevice,
because we want those to hold "current" state from the applcation's
point of view. The QWindowSystemInterfacePrivate::TouchEvent, to which
the returned touchpoints will "belong", might go through the queue before
being processed; the application doesn't see the equivalent QTouchEvent
until later on. Therefore the responsibility to update the QEventPoint
instances in QPointingDevice is in QGuiApplication, not here.
QGuiApplicationPrivate::processMouseEvent() also calls this function
when it synthesizes a touch event from a mouse event. But that's outside
the normal use case.
It might be better if we change all the platform plugins to create
temporary instances of QEventPoint directly, and remove
QWindowSystemInterface::TouchPoint completely. Then we will no longer need
this function either. But that's only possible as long as QEventPoint
remains a Q_GADGET, not a QObject, so that it continues to be small and
suitable for temporary stack allocation. QEventPoint is a little bigger
than QWindowSystemInterface::TouchPoint, though.
*/
QList<QEventPoint>
QWindowSystemInterfacePrivate::fromNativeTouchPoints(const QList<QWindowSystemInterface::TouchPoint> &points,
const QWindow *window, QEvent::Type *type)

View File

@ -155,6 +155,8 @@ public:
Qt::MouseEventSource source = Qt::MouseEventNotSynthesized,
bool inverted = false);
// A very-temporary QPA touchpoint which gets converted to a QEventPoint as early as possible
// in QWindowSystemInterfacePrivate::fromNativeTouchPoints()
struct TouchPoint {
TouchPoint() : id(0), uniqueId(-1), pressure(0), rotation(0), state(QEventPoint::State::Stationary) { }
int id; // for application use

View File

@ -5829,8 +5829,10 @@ void QGraphicsScenePrivate::updateTouchPointsForItem(QGraphicsItem *item, QTouch
const QTransform mapFromScene =
item->d_ptr->genericMapFromSceneTransform(static_cast<const QWidget *>(touchEvent->target()));
for (QEventPoint &pt : QMutableTouchEvent::from(touchEvent)->touchPoints())
for (int i = 0; i < touchEvent->pointCount(); ++i) {
auto &pt = QMutableEventPoint::from(touchEvent->point(i));
QMutableEventPoint::from(pt).setPosition(mapFromScene.map(pt.scenePosition()));
}
}
int QGraphicsScenePrivate::findClosestTouchPointId(const QPointF &scenePos)

View File

@ -310,7 +310,8 @@ inline int q_round_bound(qreal d) //### (int)(qreal) INT_MAX != INT_MAX for sing
void QGraphicsViewPrivate::translateTouchEvent(QGraphicsViewPrivate *d, QTouchEvent *touchEvent)
{
for (QEventPoint &pt : QMutableTouchEvent::from(touchEvent)->touchPoints()) {
for (int i = 0; i < touchEvent->pointCount(); ++i) {
auto &pt = touchEvent->point(i);
// the scene will set the item local pos, startPos, lastPos, and rect before delivering to
// an item, but for now those functions are returning the view's local coordinates
QMutableEventPoint::from(pt).setScenePosition(d->mapToScene(pt.position()));

View File

@ -80,6 +80,7 @@
#include <QtGui/qinputmethod.h>
#include <QtGui/private/qwindow_p.h>
#include <QtGui/qpointingdevice.h>
#include <QtGui/private/qpointingdevice_p.h>
#include <qpa/qplatformtheme.h>
#if QT_CONFIG(whatsthis)
#include <QtWidgets/QWhatsThis>
@ -3190,7 +3191,7 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
bool acceptTouchEvents = widget->testAttribute(Qt::WA_AcceptTouchEvents);
if (acceptTouchEvents && e->spontaneous()) {
const QPoint localPos = touchEvent->touchPoints()[0].position().toPoint();
const QPoint localPos = touchEvent->points()[0].position().toPoint();
QApplicationPrivate::giveFocusAccordingToFocusPolicy(widget, e, localPos);
}
@ -3228,8 +3229,10 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
QPoint offset = widget->pos();
widget = widget->parentWidget();
touchEvent->setTarget(widget);
for (QEventPoint &pt : touchEvent->touchPoints())
QMutableEventPoint::from(pt).setPosition(pt.position() + offset);
for (int i = 0; i < touchEvent->pointCount(); ++i) {
auto &pt = QMutableEventPoint::from(touchEvent->point(i));
pt.setPosition(pt.position() + offset);
}
}
#ifndef QT_NO_GESTURES
@ -3487,8 +3490,10 @@ void QApplicationPrivate::closePopup(QWidget *popup)
if (popupGrabOk) {
popupGrabOk = false;
if (popup->geometry().contains(QPoint(QGuiApplicationPrivate::mousePressX,
QGuiApplicationPrivate::mousePressY))
// TODO on multi-seat window systems, we have to know which mouse
auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice());
auto mousePressPos = devPriv->pointById(0)->eventPoint.globalPressPosition();
if (popup->geometry().contains(mousePressPos.toPoint())
|| popup->testAttribute(Qt::WA_NoMouseReplay)) {
// mouse release event or inside
qt_replay_popup_mouse_event = false;
@ -3876,9 +3881,9 @@ bool QApplicationPrivate::updateTouchPointsForWidget(QWidget *widget, QTouchEven
{
bool containsPress = false;
for (QEventPoint &pt : QMutableTouchEvent::from(touchEvent)->touchPoints()) {
const QPointF screenPos = pt.globalPosition();
QMutableEventPoint::from(pt).setPosition(widget->mapFromGlobal(screenPos));
for (int i = 0; i < touchEvent->pointCount(); ++i) {
auto &pt = QMutableEventPoint::from(touchEvent->point(i));
pt.setPosition(widget->mapFromGlobal(pt.globalPosition()));
if (pt.state() == QEventPoint::State::Pressed)
containsPress = true;
@ -3906,25 +3911,23 @@ void QApplicationPrivate::cleanupMultitouch_sys()
QWidget *QApplicationPrivate::findClosestTouchPointTarget(const QPointingDevice *device, const QEventPoint &touchPoint)
{
const QPointF screenPos = touchPoint.globalPosition();
const QPointF globalPos = touchPoint.globalPosition();
int closestTouchPointId = -1;
QObject *closestTarget = nullptr;
qreal closestDistance = qreal(0.);
QHash<ActiveTouchPointsKey, ActiveTouchPointsValue>::const_iterator it = activeTouchPoints.constBegin(),
ite = activeTouchPoints.constEnd();
while (it != ite) {
if (it.key().device == device && it.key().touchPointId != touchPoint.id()) {
const QEventPoint &touchPoint = it->touchPoint;
qreal dx = screenPos.x() - touchPoint.globalPosition().x();
qreal dy = screenPos.y() - touchPoint.globalPosition().y();
qreal closestDistance = 0;
const QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(device);
for (const auto &pair : devPriv->activePoints) {
const auto &pt = pair.second.eventPoint;
if (pt.id() != touchPoint.id()) {
qreal dx = globalPos.x() - pt.globalPosition().x();
qreal dy = globalPos.y() - pt.globalPosition().y();
qreal distance = dx * dx + dy * dy;
if (closestTouchPointId == -1 || distance < closestDistance) {
closestTouchPointId = touchPoint.id();
closestTouchPointId = pt.id();
closestDistance = distance;
closestTarget = it.value().target.data();
closestTarget = static_cast<const QMutableEventPoint &>(pt).target();
}
}
++it;
}
return static_cast<QWidget *>(closestTarget);
}
@ -3934,16 +3937,14 @@ void QApplicationPrivate::activateImplicitTouchGrab(QWidget *widget, QTouchEvent
if (touchEvent->type() != QEvent::TouchBegin)
return;
for (int i = 0, tc = touchEvent->touchPoints().count(); i < tc; ++i) {
const QEventPoint &touchPoint = touchEvent->touchPoints().at(i);
activeTouchPoints[QGuiApplicationPrivate::ActiveTouchPointsKey(
touchEvent->pointingDevice(), touchPoint.id())].target = widget;
}
for (int i = 0; i < touchEvent->pointCount(); ++i)
QMutableEventPoint::from(touchEvent->point(i)).setTarget(widget);
// TODO setExclusiveGrabber() to be consistent with Qt Quick?
}
bool QApplicationPrivate::translateRawTouchEvent(QWidget *window,
const QPointingDevice *device,
const QList<QEventPoint> &touchPoints,
QList<QEventPoint> &touchPoints,
ulong timestamp)
{
QApplicationPrivate *d = self;
@ -3951,26 +3952,17 @@ bool QApplicationPrivate::translateRawTouchEvent(QWidget *window,
typedef QPair<QEventPoint::State, QList<QEventPoint> > StatesAndTouchPoints;
QHash<QWidget *, StatesAndTouchPoints> widgetsNeedingEvents;
for (int i = 0; i < touchPoints.count(); ++i) {
QEventPoint touchPoint = touchPoints.at(i);
for (auto &touchPoint : touchPoints) {
// update state
QPointer<QObject> target;
ActiveTouchPointsKey touchInfoKey(device, touchPoint.id());
ActiveTouchPointsValue &touchInfo = d->activeTouchPoints[touchInfoKey];
if (touchPoint.state() == QEventPoint::State::Pressed) {
if (device->type() == QInputDevice::DeviceType::TouchPad && !d->activeTouchPoints.isEmpty()) {
// on touch-pads, send all touch points to the same widget
if (device->type() == QInputDevice::DeviceType::TouchPad) {
// on touchpads, send all touch points to the same widget:
// pick the first non-null target if possible
for (const auto &a : d->activeTouchPoints.values()) {
if (a.target) {
target = a.target;
break;
}
}
target = QPointingDevicePrivate::get(device)->firstActiveTarget();
}
if (!target) {
if (target.isNull()) {
// determine which widget this event will go to
if (!window)
window = QApplication::topLevelAt(touchPoint.globalPosition().toPoint());
@ -3990,13 +3982,13 @@ bool QApplicationPrivate::translateRawTouchEvent(QWidget *window,
}
}
touchInfo.target = target;
QMutableEventPoint::from(touchPoint).setTarget(target);
} else {
target = touchInfo.target;
target = QMutableEventPoint::from(touchPoint).target();
if (!target)
continue;
}
Q_ASSERT(target.data() != nullptr);
Q_ASSERT(!target.isNull());
QWidget *targetWidget = static_cast<QWidget *>(target.data());
@ -4084,14 +4076,14 @@ void QApplicationPrivate::translateTouchCancel(const QPointingDevice *device, ul
{
QMutableTouchEvent touchEvent(QEvent::TouchCancel, device, QGuiApplication::keyboardModifiers());
touchEvent.setTimestamp(timestamp);
QHash<ActiveTouchPointsKey, ActiveTouchPointsValue>::const_iterator it
= self->activeTouchPoints.constBegin(), ite = self->activeTouchPoints.constEnd();
QSet<QWidget *> widgetsNeedingCancel;
while (it != ite) {
QWidget *widget = static_cast<QWidget *>(it->target.data());
if (widget)
widgetsNeedingCancel.insert(widget);
++it;
const QPointingDevicePrivate *devPriv = QPointingDevicePrivate::get(device);
for (const auto &pair : devPriv->activePoints) {
const auto &pt = pair.second.eventPoint;
QObject *target = static_cast<const QMutableEventPoint &>(pt).target();
if (target && target->isWidgetType())
widgetsNeedingCancel.insert(static_cast<QWidget *>(target));
}
for (QSet<QWidget *>::const_iterator widIt = widgetsNeedingCancel.constBegin(),
widItEnd = widgetsNeedingCancel.constEnd(); widIt != widItEnd; ++widIt) {

View File

@ -252,7 +252,7 @@ public:
void activateImplicitTouchGrab(QWidget *widget, QTouchEvent *touchBeginEvent);
static bool translateRawTouchEvent(QWidget *widget,
const QPointingDevice *device,
const QList<QEventPoint> &touchPoints,
QList<QEventPoint> &touchPoints,
ulong timestamp);
static void translateTouchCancel(const QPointingDevice *device, ulong timestamp);

View File

@ -696,7 +696,9 @@ void QWidgetWindow::handleTouchEvent(QTouchEvent *event)
// events instead, which QWidgetWindow::handleMouseEvent will forward correctly:
event->ignore();
} else {
event->setAccepted(QApplicationPrivate::translateRawTouchEvent(m_widget, event->pointingDevice(), event->touchPoints(), event->timestamp()));
event->setAccepted(QApplicationPrivate::translateRawTouchEvent(m_widget, event->pointingDevice(),
const_cast<QList<QEventPoint> &>(event->points()),
event->timestamp()));
}
}

View File

@ -9,4 +9,5 @@ qt_add_test(tst_qmouseevent
tst_qmouseevent.cpp
PUBLIC_LIBRARIES
Qt::Gui
Qt::GuiPrivate
)

View File

@ -1,4 +1,4 @@
CONFIG += testcase
TARGET = tst_qmouseevent
QT += testlib
QT += testlib gui_private
SOURCES += tst_qmouseevent.cpp

View File

@ -30,13 +30,19 @@
#include <QtTest/QtTest>
#include <qevent.h>
#include <qwindow.h>
#include <QtGui/private/qpointingdevice_p.h>
Q_LOGGING_CATEGORY(lcTests, "qt.gui.tests")
class MouseEventWidget : public QWindow
{
public:
MouseEventWidget(QWindow *parent = 0) : QWindow(parent)
MouseEventWidget(QWindow *parent = nullptr) : QWindow(parent)
{
setGeometry(100, 100, 100, 100);
}
bool grabExclusive = false;
bool grabPassive = false;
bool mousePressEventRecieved;
bool mouseReleaseEventRecieved;
int mousePressButton;
@ -45,15 +51,31 @@ public:
int mouseReleaseButton;
int mouseReleaseButtons;
int mouseReleaseModifiers;
ulong pressTimestamp;
protected:
void mousePressEvent(QMouseEvent *e) override
{
const auto &firstPoint = e->point(0);
if (e->type() == QEvent::MouseButtonPress) {
auto firstPoint = e->points().first();
QCOMPARE(e->exclusiveGrabber(firstPoint), nullptr);
QVERIFY(e->passiveGrabbers(firstPoint).isEmpty());
QCOMPARE(firstPoint.timeHeld(), 0);
QCOMPARE(firstPoint.pressTimestamp(), e->timestamp());
pressTimestamp = e->timestamp();
}
QWindow::mousePressEvent(e);
mousePressButton = e->button();
mousePressButtons = e->buttons();
mousePressModifiers = e->modifiers();
mousePressEventRecieved = true;
e->accept();
// It's not normal for QWindow to be the grabber, but that's easier to test
// without needing to create child ojects.
if (grabExclusive)
e->setExclusiveGrabber(firstPoint, this);
if (grabPassive)
e->addPassiveGrabber(firstPoint, this);
}
void mouseReleaseEvent(QMouseEvent *e) override
{
@ -80,6 +102,8 @@ private slots:
void checkMousePressEvent();
void checkMouseReleaseEvent_data();
void checkMouseReleaseEvent();
void grabbers_data();
void grabbers();
private:
MouseEventWidget* testMouseWidget;
@ -87,7 +111,7 @@ private:
void tst_QMouseEvent::initTestCase()
{
testMouseWidget = new MouseEventWidget(0);
testMouseWidget = new MouseEventWidget;
testMouseWidget->show();
}
@ -218,5 +242,42 @@ void tst_QMouseEvent::checkMouseReleaseEvent()
QCOMPARE(testMouseWidget->mouseReleaseModifiers, modifiers);
}
void tst_QMouseEvent::grabbers_data()
{
QTest::addColumn<bool>("grabExclusive");
QTest::addColumn<bool>("grabPassive");
QTest::newRow("no grab") << false << false;
QTest::newRow("exclusive") << true << false;
QTest::newRow("passive") << false << true;
}
void tst_QMouseEvent::grabbers()
{
QFETCH(bool, grabExclusive);
QFETCH(bool, grabPassive);
testMouseWidget->grabExclusive = grabExclusive;
testMouseWidget->grabPassive = grabPassive;
QTest::mousePress(testMouseWidget, Qt::LeftButton, Qt::KeyboardModifiers(), {10, 10});
auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice());
QCOMPARE(devPriv->activePoints.count(), 1);
// Ensure that grabbers are persistent between events, within the stored touchpoints
auto firstEPD = devPriv->pointById(0);
QCOMPARE(firstEPD->eventPoint.pressTimestamp(), testMouseWidget->pressTimestamp);
QCOMPARE(firstEPD->exclusiveGrabber, grabExclusive ? testMouseWidget : nullptr);
QCOMPARE(firstEPD->passiveGrabbers.count(), grabPassive ? 1 : 0);
if (grabPassive)
QCOMPARE(firstEPD->passiveGrabbers.first(), testMouseWidget);
// Ensure that grabbers are forgotten after release delivery
QTest::mouseRelease(testMouseWidget, Qt::LeftButton, Qt::KeyboardModifiers(), {10, 10});
QTRY_COMPARE(firstEPD->exclusiveGrabber, nullptr);
QCOMPARE(firstEPD->passiveGrabbers.count(), 0);
}
QTEST_MAIN(tst_QMouseEvent)
#include "tst_qmouseevent.moc"

View File

@ -171,6 +171,7 @@ public:
{
switch (event->type()) {
case QEvent::TouchBegin:
qCDebug(lcTests) << this << event;
if (seenTouchBegin) qWarning("TouchBegin: already seen a TouchBegin");
if (seenTouchUpdate) qWarning("TouchBegin: TouchUpdate cannot happen before TouchBegin");
if (seenTouchEnd) qWarning("TouchBegin: TouchEnd cannot happen before TouchBegin");
@ -182,6 +183,7 @@ public:
delete this;
break;
case QEvent::TouchUpdate:
qCDebug(lcTests) << this << event;
if (!seenTouchBegin) qWarning("TouchUpdate: have not seen TouchBegin");
if (seenTouchEnd) qWarning("TouchUpdate: TouchEnd cannot happen before TouchUpdate");
seenTouchUpdate = seenTouchBegin && !seenTouchEnd;
@ -192,6 +194,7 @@ public:
delete this;
break;
case QEvent::TouchEnd:
qCDebug(lcTests) << this << event;
if (!seenTouchBegin) qWarning("TouchEnd: have not seen TouchBegin");
if (seenTouchEnd) qWarning("TouchEnd: already seen a TouchEnd");
seenTouchEnd = seenTouchBegin && !seenTouchEnd;
@ -208,6 +211,32 @@ public:
}
};
struct GrabberWindow : public QWindow
{
bool grabExclusive = false;
bool grabPassive = false;
void touchEvent(QTouchEvent *ev) override {
qCDebug(lcTests) << ev;
const auto &firstPoint = ev->point(0);
switch (ev->type()) {
case QEvent::TouchBegin: {
QCOMPARE(ev->exclusiveGrabber(firstPoint), nullptr);
QVERIFY(ev->passiveGrabbers(firstPoint).isEmpty());
// It's not normal for QWindow to be the grabber, but that's easier to test
// without needing to create child ojects.
if (grabExclusive)
ev->setExclusiveGrabber(firstPoint, this);
if (grabPassive)
ev->addPassiveGrabber(firstPoint, this);
break;
}
default:
break;
}
}
};
class tst_QTouchEvent : public QObject
{
Q_OBJECT
@ -233,6 +262,8 @@ private slots:
void touchBeginWithGraphicsWidget();
void testQGuiAppDelivery();
void testMultiDevice();
void grabbers_data();
void grabbers();
private:
QPointingDevice *touchScreenDevice;
@ -246,6 +277,7 @@ tst_QTouchEvent::tst_QTouchEvent()
, touchPadDevice(QTest::createTouchDevice(QInputDevice::DeviceType::TouchPad))
{
QInputDevicePrivate::get(touchPadDevice)->setAvailableVirtualGeometry(QRect(50, 50, 500, 500));
QInputDevicePrivate::get(secondaryTouchScreenDevice)->name = QLatin1String("secondary touchscreen");
}
void tst_QTouchEvent::cleanup()
@ -670,7 +702,7 @@ void tst_QTouchEvent::basicRawEventTranslation()
// this should be translated to a TouchBegin
QEventPoint rawTouchPoint(0, QEventPoint::State::Pressed, QPointF(), screenPos);
const ulong timestamp = 1234;
ulong timestamp = 1234;
QWindow *window = touchWidget.windowHandle();
QList<QWindowSystemInterface::TouchPoint> nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(QList<QEventPoint>() << rawTouchPoint, window);
@ -704,7 +736,7 @@ void tst_QTouchEvent::basicRawEventTranslation()
rawTouchPoint = QEventPoint(0, QEventPoint::State::Updated, QPointF(), screenPos + delta);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(QList<QEventPoint>() << rawTouchPoint, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(touchWidget.seenTouchBegin);
QVERIFY(touchWidget.seenTouchUpdate);
@ -732,7 +764,7 @@ void tst_QTouchEvent::basicRawEventTranslation()
rawTouchPoint = QEventPoint(0, QEventPoint::State::Released, QPointF(), screenPos + delta + delta);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(QList<QEventPoint>() << rawTouchPoint, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(touchWidget.seenTouchBegin);
QVERIFY(touchWidget.seenTouchUpdate);
@ -765,10 +797,12 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchScreen()
touchWidget.setGeometry(100, 100, 400, 300);
tst_QTouchEventWidget leftWidget(&touchWidget);
leftWidget.setObjectName("leftWidget");
leftWidget.setAttribute(Qt::WA_AcceptTouchEvents);
leftWidget.setGeometry(0, 100, 100, 100);
tst_QTouchEventWidget rightWidget(&touchWidget);
rightWidget.setObjectName("rightWidget");
rightWidget.setAttribute(Qt::WA_AcceptTouchEvents);
rightWidget.setGeometry(300, 100, 100, 100);
@ -783,13 +817,14 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchScreen()
QPointF centerScreenPos = touchWidget.mapToGlobal(centerPos.toPoint());
// generate TouchBegins on both leftWidget and rightWidget
ulong timestamp = 0;
auto rawTouchPoints = QList<QEventPoint>()
<< QEventPoint(0, QEventPoint::State::Pressed, QPointF(), leftScreenPos)
<< QEventPoint(1, QEventPoint::State::Pressed, QPointF(), rightScreenPos);
QWindow *window = touchWidget.windowHandle();
QList<QWindowSystemInterface::TouchPoint> nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(!touchWidget.seenTouchBegin);
QVERIFY(!touchWidget.seenTouchUpdate);
@ -847,7 +882,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchScreen()
<< QEventPoint(1, QEventPoint::State::Updated, QPointF(), centerScreenPos);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(!touchWidget.seenTouchBegin);
QVERIFY(!touchWidget.seenTouchUpdate);
@ -904,7 +939,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchScreen()
<< QEventPoint(1, QEventPoint::State::Released, QPointF(), centerScreenPos);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(!touchWidget.seenTouchBegin);
QVERIFY(!touchWidget.seenTouchUpdate);
@ -923,13 +958,13 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchScreen()
QCOMPARE(leftTouchPoint.state(), rawTouchPoints[0].state());
QCOMPARE(leftTouchPoint.position(), QPointF(leftWidget.mapFromParent(centerPos.toPoint())));
QCOMPARE(leftTouchPoint.pressPosition(), leftPos);
QCOMPARE(leftTouchPoint.lastPosition(), leftTouchPoint.position());
QCOMPARE(leftTouchPoint.lastPosition(), leftPos);
QCOMPARE(leftTouchPoint.scenePosition(), centerScreenPos);
QCOMPARE(leftTouchPoint.scenePressPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.sceneLastPosition(), leftTouchPoint.scenePosition());
QCOMPARE(leftTouchPoint.sceneLastPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.globalPosition(), centerScreenPos);
QCOMPARE(leftTouchPoint.globalPressPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.globalLastPosition(), leftTouchPoint.globalPosition());
QCOMPARE(leftTouchPoint.globalLastPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.position(), leftWidget.mapFromParent(centerPos.toPoint()));
QCOMPARE(leftTouchPoint.scenePosition(), centerScreenPos);
QCOMPARE(leftTouchPoint.globalPosition(), centerScreenPos);
@ -941,13 +976,13 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchScreen()
QCOMPARE(rightTouchPoint.state(), rawTouchPoints[1].state());
QCOMPARE(rightTouchPoint.position(), QPointF(rightWidget.mapFromParent(centerPos.toPoint())));
QCOMPARE(rightTouchPoint.pressPosition(), rightPos);
QCOMPARE(rightTouchPoint.lastPosition(), rightTouchPoint.position());
QCOMPARE(rightTouchPoint.lastPosition(), rightPos);
QCOMPARE(rightTouchPoint.scenePosition(), centerScreenPos);
QCOMPARE(rightTouchPoint.scenePressPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.sceneLastPosition(), rightTouchPoint.scenePosition());
QCOMPARE(rightTouchPoint.sceneLastPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.globalPosition(), centerScreenPos);
QCOMPARE(rightTouchPoint.globalPressPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.globalLastPosition(), rightTouchPoint.globalPosition());
QCOMPARE(rightTouchPoint.globalLastPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.position(), rightWidget.mapFromParent(centerPos.toPoint()));
QCOMPARE(rightTouchPoint.scenePosition(), centerScreenPos);
QCOMPARE(rightTouchPoint.globalPosition(), centerScreenPos);
@ -1116,6 +1151,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
QPointF rightScreenPos = rightWidget.mapToGlobal(rightPos.toPoint());
QPointF centerScreenPos = touchWidget.mapToGlobal(centerPos.toPoint());
ulong timestamp = 0;
QList<QMutableEventPoint> rawTouchPoints;
rawTouchPoints.append(QMutableEventPoint(0));
rawTouchPoints.append(QMutableEventPoint(1));
@ -1132,7 +1168,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
QWindow *window = touchWidget.windowHandle();
QList<QWindowSystemInterface::TouchPoint> nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchPadDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchPadDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(!touchWidget.seenTouchBegin);
QVERIFY(!touchWidget.seenTouchUpdate);
@ -1148,7 +1184,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
{
QEventPoint leftTouchPoint = leftWidget.touchBeginPoints.at(0);
qCDebug(lcTests) << "lastNormalizedPositions after press" << leftWidget.lastNormalizedPositions;
qCDebug(lcTests) << "leftTouchPoint" << leftTouchPoint;
qCDebug(lcTests) << "leftTouchPoint" << &leftTouchPoint;
QCOMPARE(leftTouchPoint.id(), rawTouchPoints[0].id());
QCOMPARE(leftTouchPoint.state(), rawTouchPoints[0].state());
QCOMPARE(leftTouchPoint.position(), leftPos);
@ -1168,7 +1204,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
QCOMPARE(leftTouchPoint.pressure(), qreal(1.));
QEventPoint rightTouchPoint = leftWidget.touchBeginPoints.at(1);
qCDebug(lcTests) << "rightTouchPoint" << rightTouchPoint;
qCDebug(lcTests) << "rightTouchPoint" << &rightTouchPoint;
QCOMPARE(rightTouchPoint.id(), rawTouchPoints[1].id());
QCOMPARE(rightTouchPoint.state(), rawTouchPoints[1].state());
QCOMPARE(rightTouchPoint.position(), QPointF(leftWidget.mapFromGlobal(rightScreenPos.toPoint())));
@ -1194,7 +1230,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
rawTouchPoints[1].setGlobalPosition(centerScreenPos);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchPadDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchPadDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(!touchWidget.seenTouchBegin);
QVERIFY(!touchWidget.seenTouchUpdate);
@ -1256,7 +1292,7 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
rawTouchPoints[1].setState(QEventPoint::State::Released);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchPadDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchPadDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(!touchWidget.seenTouchBegin);
QVERIFY(!touchWidget.seenTouchUpdate);
@ -1277,13 +1313,14 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
QCOMPARE(leftTouchPoint.state(), rawTouchPoints[0].state());
QCOMPARE(leftTouchPoint.position(), QPointF(leftWidget.mapFromParent(centerPos.toPoint())));
QCOMPARE(leftTouchPoint.pressPosition(), leftPos);
QCOMPARE(leftTouchPoint.lastPosition(), leftTouchPoint.position());
// it didn't move, so lastPosition is from the last time it moved
QCOMPARE(leftTouchPoint.lastPosition(), leftPos);
QCOMPARE(leftTouchPoint.scenePosition(), centerScreenPos);
QCOMPARE(leftTouchPoint.scenePressPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.sceneLastPosition(), leftTouchPoint.scenePosition());
QCOMPARE(leftTouchPoint.sceneLastPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.globalPosition(), centerScreenPos);
QCOMPARE(leftTouchPoint.globalPressPosition(), leftScreenPos);
QCOMPARE(leftTouchPoint.globalLastPosition(), leftTouchPoint.globalPosition());
QCOMPARE(leftTouchPoint.globalLastPosition(), leftScreenPos);
QVERIFY(qAbs(leftWidget.lastNormalizedPositions.at(0).x() - 0.5) < 0.05); // 0.498, might depend on window frame size
QCOMPARE(leftTouchPoint.position(), leftWidget.mapFromParent(centerPos.toPoint()));
QCOMPARE(leftTouchPoint.scenePosition(), centerScreenPos);
@ -1297,13 +1334,13 @@ void tst_QTouchEvent::multiPointRawEventTranslationOnTouchPad()
QCOMPARE(rightTouchPoint.state(), rawTouchPoints[1].state());
QCOMPARE(rightTouchPoint.position(), QPointF(leftWidget.mapFromParent(centerPos.toPoint())));
QCOMPARE(rightTouchPoint.pressPosition(), QPointF(leftWidget.mapFromGlobal(rightScreenPos.toPoint())));
QCOMPARE(rightTouchPoint.lastPosition(), rightTouchPoint.position());
QCOMPARE(rightTouchPoint.lastPosition(), QPointF(leftWidget.mapFromGlobal(rightScreenPos.toPoint())));
QCOMPARE(rightTouchPoint.scenePosition(), centerScreenPos);
QCOMPARE(rightTouchPoint.scenePressPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.sceneLastPosition(), rightTouchPoint.scenePosition());
QCOMPARE(rightTouchPoint.sceneLastPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.globalPosition(), centerScreenPos);
QCOMPARE(rightTouchPoint.globalPressPosition(), rightScreenPos);
QCOMPARE(rightTouchPoint.globalLastPosition(), rightTouchPoint.globalPosition());
QCOMPARE(rightTouchPoint.globalLastPosition(), rightScreenPos);
QVERIFY(qAbs(leftWidget.lastNormalizedPositions.at(1).x() - 0.5) < 0.05); // 0.498, might depend on window frame size
QCOMPARE(rightTouchPoint.position(), leftWidget.mapFromParent(centerPos.toPoint()));
QCOMPARE(rightTouchPoint.scenePosition(), centerScreenPos);
@ -1341,7 +1378,7 @@ void tst_QTouchEvent::basicRawEventTranslationOfIds()
QMutableEventPoint &p0 = rawTouchPoints[0];
QMutableEventPoint &p1 = rawTouchPoints[1];
const ulong timestamp = 1234;
ulong timestamp = 1234;
QWindow *window = touchWidget.windowHandle();
QList<QWindowSystemInterface::TouchPoint> nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
@ -1366,7 +1403,7 @@ void tst_QTouchEvent::basicRawEventTranslationOfIds()
}
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(touchWidget.seenTouchBegin);
QVERIFY(touchWidget.seenTouchUpdate);
@ -1381,7 +1418,7 @@ void tst_QTouchEvent::basicRawEventTranslationOfIds()
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(touchWidget.seenTouchBegin);
QVERIFY(touchWidget.seenTouchUpdate);
@ -1395,7 +1432,7 @@ void tst_QTouchEvent::basicRawEventTranslationOfIds()
p1.setId(42); // new id
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(touchWidget.seenTouchBegin);
QVERIFY(touchWidget.seenTouchUpdate);
@ -1409,7 +1446,7 @@ void tst_QTouchEvent::basicRawEventTranslationOfIds()
p1.setState(QEventPoint::State::Released);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(touchWidget.seenTouchBegin);
QVERIFY(touchWidget.seenTouchUpdate);
@ -1594,6 +1631,7 @@ void tst_QTouchEvent::deleteInRawEventTranslation()
QPointF centerScreenPos = centerWidget->mapToGlobal(centerPos.toPoint());
QPointF rightScreenPos = rightWidget->mapToGlobal(rightPos.toPoint());
ulong timestamp = 0;
QList<QMutableEventPoint> rawTouchPoints;
rawTouchPoints.append(QMutableEventPoint(0));
rawTouchPoints.append(QMutableEventPoint(1));
@ -1609,7 +1647,7 @@ void tst_QTouchEvent::deleteInRawEventTranslation()
QWindow *window = touchWidget.windowHandle();
QList<QWindowSystemInterface::TouchPoint> nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
QVERIFY(leftWidget.isNull());
QVERIFY(!centerWidget.isNull());
@ -1621,7 +1659,7 @@ void tst_QTouchEvent::deleteInRawEventTranslation()
rawTouchPoints[2].setState(QEventPoint::State::Updated);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
// generate end events on all widget, the right widget should die
@ -1630,7 +1668,7 @@ void tst_QTouchEvent::deleteInRawEventTranslation()
rawTouchPoints[2].setState(QEventPoint::State::Released);
nativeTouchPoints =
QWindowSystemInterfacePrivate::toNativeTouchPoints(rawTouchPoints, window);
QWindowSystemInterface::handleTouchEvent(window, 0, touchScreenDevice, nativeTouchPoints);
QWindowSystemInterface::handleTouchEvent(window, ++timestamp, touchScreenDevice, nativeTouchPoints);
QCoreApplication::processEvents();
}
@ -1739,7 +1777,7 @@ bool WindowTouchEventFilter::eventFilter(QObject *, QEvent *event)
TouchInfo &td = d[te->pointingDevice()];
if (event->type() == QEvent::TouchBegin)
td.points.clear();
td.points.append(te->touchPoints());
td.points.append(te->points());
td.lastSeenType = event->type();
}
return false;
@ -1841,16 +1879,69 @@ void tst_QTouchEvent::testMultiDevice()
QCOMPARE(filter.d.value(deviceTwo).points.count(), 2);
QCOMPARE(filter.d.value(touchScreenDevice).points.at(0).globalPosition(), area0.center());
QCOMPARE(filter.d.value(touchScreenDevice).points.at(0).ellipseDiameters(), area0.size());
// This fails because QGuiApplicationPrivate::processTouchEvent() sends synth-mouse events
// as QPA events, but with the device being the touchscreen; then processMouseEvent eventually
// updates the touchscreen's QEventPoint in activePoints from an eventpoint that
// came from a QPA mouse event, which of course does not have ellipse diameters.
// Perhaps we should send the synth-mouse events more directly, bypassing QPA?
// QCOMPARE(filter.d.value(touchScreenDevice).points.at(0).ellipseDiameters(), area0.size());
QCOMPARE(filter.d.value(touchScreenDevice).points.at(0).state(), pointsOne[0].state);
QCOMPARE(filter.d.value(deviceTwo).points.at(0).globalPosition(), area0.center());
QCOMPARE(filter.d.value(deviceTwo).points.at(0).ellipseDiameters(), area0.size());
// QCOMPARE(filter.d.value(deviceTwo).points.at(0).ellipseDiameters(), area0.size());
QCOMPARE(filter.d.value(deviceTwo).points.at(0).state(), pointsTwo[0].state);
QCOMPARE(filter.d.value(deviceTwo).points.at(1).globalPosition(), area1.center());
QCOMPARE(filter.d.value(deviceTwo).points.at(1).state(), pointsTwo[1].state);
}
void tst_QTouchEvent::grabbers_data()
{
QTest::addColumn<bool>("grabExclusive");
QTest::addColumn<bool>("grabPassive");
QTest::newRow("no grab") << false << false;
QTest::newRow("exclusive") << true << false;
QTest::newRow("passive") << false << true;
}
void tst_QTouchEvent::grabbers()
{
QFETCH(bool, grabExclusive);
QFETCH(bool, grabPassive);
GrabberWindow w;
w.grabExclusive = grabExclusive;
w.grabPassive = grabPassive;
w.setGeometry(100, 100, 100, 100);
w.show();
QVERIFY(QTest::qWaitForWindowExposed(&w));
auto devPriv = QPointingDevicePrivate::get(touchScreenDevice);
devPriv->activePoints.clear(); // in case other tests left dangling state
QList<QWindowSystemInterface::TouchPoint> points;
QWindowSystemInterface::TouchPoint tp;
tp.id = 0;
tp.state = QEventPoint::State::Pressed;
tp.area = QRectF(120, 120, 20, 20);
points.append(tp);
QWindowSystemInterface::handleTouchEvent(&w, touchScreenDevice, points); // TouchBegin
QCoreApplication::processEvents();
QCOMPARE(devPriv->activePoints.count(), 1);
// Ensure that grabbers are persistent between events, within the stored touchpoints
QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, grabExclusive ? &w : nullptr);
QCOMPARE(devPriv->pointById(0)->passiveGrabbers.count(), grabPassive ? 1 : 0);
if (grabPassive)
QCOMPARE(devPriv->pointById(0)->passiveGrabbers.first(), &w);
// Ensure that eventpoints are forgotten after release delivery
points.first().state = QEventPoint::State::Released;
QWindowSystemInterface::handleTouchEvent(&w, touchScreenDevice, points); // TouchEnd
QCoreApplication::processEvents();
QTRY_COMPARE(devPriv->activePoints.count(), 0);
}
QTEST_MAIN(tst_QTouchEvent)
#include "tst_qtouchevent.moc"