From 8e822e981d91e688799c8670f11dfdf6aaf9e0d1 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Sat, 14 Dec 2019 16:02:21 +0100 Subject: Deliver QTabletEvents to pointer handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At this time, there are not yet any specialized handlers to do anything specifically with tablet events; but we demonstrate how to use HoverHandler to detect the type of stylus in use, and how to use PointHandler to draw on a Canvas. Unfortunately, events of types TabletEnterProximity and TabletLeaveProximity are not delivered to the window, only to QGuiApplication. So HoverHandler can detect when the stylus is moved out of its parent Item (as long as it's still hovering over the tablet surface), but cannot detect when the stylus leaves the tablet completely. In Qt 5 that would require a custom application subclass (see qtbase/examples/widgets/widgets/tablet/tabletapplication.cpp). Fixes: QTBUG-79660 Change-Id: I81fdb99082dc41c0455085e6b6d3952402bf8742 Reviewed-by: Qt CI Bot Reviewed-by: Jan Arve Sæther --- src/quick/handlers/qquickhandlerpoint.cpp | 5 +- src/quick/handlers/qquickhoverhandler.cpp | 19 +++- src/quick/handlers/qquickhoverhandler_p.h | 1 + src/quick/items/qquickevents.cpp | 162 +++++++++++++++++++++++++++++- src/quick/items/qquickevents_p_p.h | 71 +++++++++++-- src/quick/items/qquickwindow.cpp | 33 +++++- src/quick/items/qquickwindow.h | 3 + 7 files changed, 274 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/quick/handlers/qquickhandlerpoint.cpp b/src/quick/handlers/qquickhandlerpoint.cpp index f3d92cf200..e6148ca072 100644 --- a/src/quick/handlers/qquickhandlerpoint.cpp +++ b/src/quick/handlers/qquickhandlerpoint.cpp @@ -120,7 +120,10 @@ void QQuickHandlerPoint::reset(const QQuickEventPoint *point) m_pressure = tp->pressure(); m_ellipseDiameters = tp->ellipseDiameters(); } else if (event->asPointerTabletEvent()) { - // TODO + m_uniqueId = event->device()->uniqueId(); + m_rotation = static_cast(point)->rotation(); + m_pressure = static_cast(point)->pressure(); + m_ellipseDiameters = QSizeF(); } else { m_uniqueId = event->device()->uniqueId(); m_rotation = 0; diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp index 1216eda477..b12d85784a 100644 --- a/src/quick/handlers/qquickhoverhandler.cpp +++ b/src/quick/handlers/qquickhoverhandler.cpp @@ -93,11 +93,22 @@ bool QQuickHoverHandler::wantsPointerEvent(QQuickPointerEvent *event) { QQuickEventPoint *point = event->point(0); if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(point) && parentContains(point)) { - // assume this is a mouse event, so there's only one point + // assume this is a mouse or tablet event, so there's only one point setPointId(point->pointId()); return true; } - setHovered(false); + + // Some hover events come from QQuickWindow::tabletEvent(). In between, + // some hover events come from QQWindowPrivate::flushFrameSynchronousEvents(), + // but those look like mouse events. If a particular HoverHandler instance + // is filtering for tablet events only (e.g. by setting + // acceptedDevices:PointerDevice.Stylus), those events should not cause + // the hovered property to transition to false prematurely. + // If a QQuickPointerTabletEvent caused the hovered property to become true, + // then only another QQuickPointerTabletEvent can make it become false. + if (!(m_hoveredTablet && event->asPointerMouseEvent())) + setHovered(false); + return false; } @@ -107,6 +118,8 @@ void QQuickHoverHandler::handleEventPoint(QQuickEventPoint *point) if (point->state() == QQuickEventPoint::Released && point->pointerEvent()->device()->pointerType() == QQuickPointerDevice::Finger) hovered = false; + else if (point->pointerEvent()->asPointerTabletEvent()) + m_hoveredTablet = true; setHovered(hovered); setPassiveGrab(point); } @@ -124,6 +137,8 @@ void QQuickHoverHandler::setHovered(bool hovered) if (m_hovered != hovered) { qCDebug(lcHoverHandler) << objectName() << "hovered" << m_hovered << "->" << hovered; m_hovered = hovered; + if (!hovered) + m_hoveredTablet = false; emit hoveredChanged(); } } diff --git a/src/quick/handlers/qquickhoverhandler_p.h b/src/quick/handlers/qquickhoverhandler_p.h index e4786bfa53..313b87217c 100644 --- a/src/quick/handlers/qquickhoverhandler_p.h +++ b/src/quick/handlers/qquickhoverhandler_p.h @@ -84,6 +84,7 @@ private: private: bool m_hovered = false; + bool m_hoveredTablet = false; }; QT_END_NAMESPACE diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 0a6faf04af..0a9abb3322 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -658,14 +658,73 @@ QQuickPointerDevice *QQuickPointerDevice::genericMouseDevice() return g_genericMouseDevice; } -QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id) -{ - auto it = g_tabletDevices->find(id); +QQuickPointerDevice *QQuickPointerDevice::tabletDevice(const QTabletEvent *event) +{ + // QTabletEvent::uniqueId() is the same for the pointy end and the eraser end of the stylus. + // We need to make those unique. QTabletEvent::PointerType only needs 2 bits' worth of storage. + // The key into g_tabletDevices just needs to be unique; we don't need to extract uniqueId + // back out of it, because QQuickPointerDevice stores that separately anyway. + // So the shift-and-add can be thought of as a sort of hash function, even though + // most of the time the result will be recognizable because the uniqueId MSBs are often 0. + qint64 key = event->uniqueId() + (qint64(event->pointerType()) << 60); + auto it = g_tabletDevices->find(key); if (it != g_tabletDevices->end()) return it.value(); - // ### Figure out how to populate the tablet devices - return nullptr; + DeviceType type = UnknownDevice; + int buttonCount = 0; + Capabilities caps = Position | Pressure | Hover; + // TODO Qt 6: we can't know for sure about XTilt or YTilt until we have a + // QTabletDevice populated with capabilities provided by QPA plugins + + switch (event->device()) { + case QTabletEvent::Stylus: + type = QQuickPointerDevice::Stylus; + buttonCount = 3; + break; + case QTabletEvent::RotationStylus: + type = QQuickPointerDevice::Stylus; + caps |= QQuickPointerDevice::Rotation; + buttonCount = 1; + break; + case QTabletEvent::Airbrush: + type = QQuickPointerDevice::Airbrush; + buttonCount = 2; + break; + case QTabletEvent::Puck: + type = QQuickPointerDevice::Puck; + buttonCount = 3; + break; + case QTabletEvent::FourDMouse: + type = QQuickPointerDevice::Mouse; + caps |= QQuickPointerDevice::Rotation; + buttonCount = 3; + break; + default: + type = QQuickPointerDevice::UnknownDevice; + break; + } + + PointerType ptype = GenericPointer; + switch (event->pointerType()) { + case QTabletEvent::Pen: + ptype = Pen; + break; + case QTabletEvent::Eraser: + ptype = Eraser; + break; + case QTabletEvent::Cursor: + ptype = Cursor; + break; + case QTabletEvent::UnknownPointer: + break; + } + + QQuickPointerDevice *device = new QQuickPointerDevice(type, ptype, caps, 1, buttonCount, + QLatin1String("tablet tool ") + QString::number(event->uniqueId()), event->uniqueId()); + + g_tabletDevices->insert(key, device); + return device; } /*! @@ -1284,6 +1343,12 @@ QVector2D QQuickEventPoint::estimatedVelocity() const QQuickPointerEvent::~QQuickPointerEvent() {} +QQuickPointerMouseEvent::QQuickPointerMouseEvent(QObject *parent, QQuickPointerDevice *device) + : QQuickSinglePointEvent(parent, device) +{ + m_point = new QQuickEventPoint(this); +} + QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) { auto ev = static_cast(event); @@ -1398,6 +1463,12 @@ void QQuickPointerTouchEvent::localize(QQuickItem *target) } #if QT_CONFIG(gestures) +QQuickPointerNativeGestureEvent::QQuickPointerNativeGestureEvent(QObject *parent, QQuickPointerDevice *device) + : QQuickSinglePointEvent(parent, device) +{ + m_point = new QQuickEventPoint(this); +} + QQuickPointerEvent *QQuickPointerNativeGestureEvent::reset(QEvent *event) { auto ev = static_cast(event); @@ -1560,6 +1631,12 @@ QQuickEventPoint *QQuickSinglePointEvent::point(int i) const \note Many platforms provide no such information. On such platforms, \c inverted always returns false. */ +QQuickPointerScrollEvent::QQuickPointerScrollEvent(QObject *parent, QQuickPointerDevice *device) + : QQuickSinglePointEvent(parent, device) +{ + m_point = new QQuickEventPoint(this); +} + QQuickPointerEvent *QQuickPointerScrollEvent::reset(QEvent *event) { m_event = static_cast(event); @@ -1832,6 +1909,81 @@ QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickIte return &m_synthMouseEvent; } +#if QT_CONFIG(tabletevent) +QQuickPointerTabletEvent::QQuickPointerTabletEvent(QObject *parent, QQuickPointerDevice *device) + : QQuickSinglePointEvent(parent, device) +{ + m_point = new QQuickEventTabletPoint(this); +} + +QQuickPointerEvent *QQuickPointerTabletEvent::reset(QEvent *event) +{ + auto ev = static_cast(event); + m_event = ev; + if (!event) + return this; + + Q_ASSERT(m_device == QQuickPointerDevice::tabletDevice(ev)); + m_device->eventDeliveryTargets().clear(); + m_button = ev->button(); + m_pressedButtons = ev->buttons(); + static_cast(m_point)->reset(ev); + return this; +} + +QQuickEventTabletPoint::QQuickEventTabletPoint(QQuickPointerTabletEvent *parent) + : QQuickEventPoint(parent) +{ +} + +void QQuickEventTabletPoint::reset(const QTabletEvent *ev) +{ + Qt::TouchPointState state = Qt::TouchPointStationary; + switch (ev->type()) { + case QEvent::TabletPress: + state = Qt::TouchPointPressed; + clearPassiveGrabbers(); + break; + case QEvent::TabletRelease: + state = Qt::TouchPointReleased; + break; + case QEvent::TabletMove: + state = Qt::TouchPointMoved; + break; + default: + break; + } + QQuickEventPoint::reset(state, ev->posF(), 1, ev->timestamp()); + m_rotation = ev->rotation(); + m_pressure = ev->pressure(); + m_tangentialPressure = ev->tangentialPressure(); + m_tilt = QVector2D(ev->xTilt(), ev->yTilt()); +} + +bool QQuickPointerTabletEvent::isPressEvent() const +{ + auto me = static_cast(m_event); + return me->type() == QEvent::TabletPress; +} + +bool QQuickPointerTabletEvent::isUpdateEvent() const +{ + auto me = static_cast(m_event); + return me->type() == QEvent::TabletMove; +} + +bool QQuickPointerTabletEvent::isReleaseEvent() const +{ + auto me = static_cast(m_event); + return me->type() == QEvent::TabletRelease; +} + +QTabletEvent *QQuickPointerTabletEvent::asTabletEvent() const +{ + return static_cast(m_event); +} +#endif // QT_CONFIG(tabletevent) + #if QT_CONFIG(gestures) bool QQuickPointerNativeGestureEvent::isPressEvent() const { diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index 4615ce43d2..3a8028678e 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -476,8 +476,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickSinglePointEvent : public QQuickPointerEvent { Q_OBJECT public: - QQuickSinglePointEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr) - : QQuickPointerEvent(parent, device), m_point(new QQuickEventPoint(this)) { } + QQuickSinglePointEvent(QObject *parent, QQuickPointerDevice *device) + : QQuickPointerEvent(parent, device) { } void localize(QQuickItem *target) override; int pointCount() const override { return 1; } @@ -491,7 +491,7 @@ public: bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const override; protected: - QQuickEventPoint *m_point; + QQuickEventPoint *m_point = nullptr; Q_DISABLE_COPY(QQuickSinglePointEvent) }; @@ -505,8 +505,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerMouseEvent : public QQuickSinglePointE QML_ADDED_IN_MINOR_VERSION(12) public: - QQuickPointerMouseEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr) - : QQuickSinglePointEvent(parent, device) { } + QQuickPointerMouseEvent(QObject *parent, QQuickPointerDevice *device); QQuickPointerEvent *reset(QEvent *) override; bool isPressEvent() const override; @@ -568,6 +567,60 @@ private: Q_DISABLE_COPY(QQuickPointerTouchEvent) }; +#if QT_CONFIG(tabletevent) +class Q_QUICK_PRIVATE_EXPORT QQuickEventTabletPoint : public QQuickEventPoint +{ + Q_OBJECT + Q_PROPERTY(qreal rotation READ rotation) + Q_PROPERTY(qreal pressure READ pressure) + Q_PROPERTY(qreal tangentialPressure READ tangentialPressure) + Q_PROPERTY(QVector2D tilt READ tilt) + + QML_NAMED_ELEMENT(EventTabletPoint) + QML_UNCREATABLE("EventTouchPoint is only available as a member of PointerEvent.") + QML_ADDED_IN_MINOR_VERSION(15) + +public: + QQuickEventTabletPoint(QQuickPointerTabletEvent *parent); + + void reset(const QTabletEvent *e); + + qreal rotation() const { return m_rotation; } + qreal pressure() const { return m_pressure; } + qreal tangentialPressure() const { return m_tangentialPressure; } + QVector2D tilt() const { return m_tilt; } + +private: + qreal m_rotation; + qreal m_pressure; + qreal m_tangentialPressure; + QVector2D m_tilt; + + friend class QQuickPointerTouchEvent; + + Q_DISABLE_COPY(QQuickEventTabletPoint) +}; + +class Q_QUICK_PRIVATE_EXPORT QQuickPointerTabletEvent : public QQuickSinglePointEvent +{ + Q_OBJECT +public: + QQuickPointerTabletEvent(QObject *parent, QQuickPointerDevice *device); + + QQuickPointerEvent *reset(QEvent *) override; + bool isPressEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; + QQuickPointerTabletEvent *asPointerTabletEvent() override { return this; } + const QQuickPointerTabletEvent *asPointerTabletEvent() const override { return this; } + const QQuickEventTabletPoint *tabletPoint() const { return static_cast(m_point); } + + QTabletEvent *asTabletEvent() const; + + Q_DISABLE_COPY(QQuickPointerTabletEvent) +}; +#endif // QT_CONFIG(tabletevent) + #if QT_CONFIG(gestures) class Q_QUICK_PRIVATE_EXPORT QQuickPointerNativeGestureEvent : public QQuickSinglePointEvent { @@ -576,8 +629,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerNativeGestureEvent : public QQuickSing Q_PROPERTY(qreal value READ value CONSTANT) public: - QQuickPointerNativeGestureEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr) - : QQuickSinglePointEvent(parent, device) { } + QQuickPointerNativeGestureEvent(QObject *parent, QQuickPointerDevice *device); QQuickPointerEvent *reset(QEvent *) override; bool isPressEvent() const override; @@ -606,8 +658,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPointerScrollEvent : public QQuickSinglePoint QML_ADDED_IN_MINOR_VERSION(14) public: - QQuickPointerScrollEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr) - : QQuickSinglePointEvent(parent, device) { } + QQuickPointerScrollEvent(QObject *parent, QQuickPointerDevice *device); QQuickPointerEvent *reset(QEvent *) override; void localize(QQuickItem *target) override; @@ -712,7 +763,7 @@ public: static QQuickPointerDevice *touchDevice(const QTouchDevice *d); static QList touchDevices(); static QQuickPointerDevice *genericMouseDevice(); - static QQuickPointerDevice *tabletDevice(qint64); + static QQuickPointerDevice *tabletDevice(const QTabletEvent *event); QVector &eventDeliveryTargets() { return m_eventDeliveryTargets; } diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 6c63b83511..dda0cfef3e 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -93,6 +93,7 @@ Q_LOGGING_CATEGORY(DBG_TOUCH, "qt.quick.touch") Q_LOGGING_CATEGORY(DBG_TOUCH_TARGET, "qt.quick.touch.target") Q_LOGGING_CATEGORY(DBG_MOUSE, "qt.quick.mouse") Q_LOGGING_CATEGORY(DBG_MOUSE_TARGET, "qt.quick.mouse.target") +Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet") Q_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target") Q_LOGGING_CATEGORY(lcGestureTarget, "qt.quick.gesture.target") Q_LOGGING_CATEGORY(DBG_HOVER_TRACE, "qt.quick.hover.trace") @@ -2122,6 +2123,17 @@ void QQuickWindow::wheelEvent(QWheelEvent *event) } #endif // wheelevent +#if QT_CONFIG(tabletevent) +/*! \reimp */ +void QQuickWindow::tabletEvent(QTabletEvent *event) +{ + Q_D(QQuickWindow); + qCDebug(lcTablet) << event; + // TODO Qt 6: make sure TabletEnterProximity and TabletLeaveProximity are delivered here + d->deliverPointerEvent(d->pointerEventInstance(event)); +} +#endif // tabletevent + bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) { qCDebug(DBG_TOUCH) << event; @@ -2400,8 +2412,12 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevic #endif ev = new QQuickPointerTouchEvent(q, device); break; + case QQuickPointerDevice::Stylus: + case QQuickPointerDevice::Airbrush: + case QQuickPointerDevice::Puck: + ev = new QQuickPointerTabletEvent(q, device); + break; default: - // TODO tablet event types break; } pointerEventInstances << ev; @@ -2432,7 +2448,15 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QEvent *event) con case QEvent::TouchCancel: dev = QQuickPointerDevice::touchDevice(static_cast(event)->device()); break; - // TODO tablet event types +#if QT_CONFIG(tabletevent) + case QEvent::TabletPress: + case QEvent::TabletMove: + case QEvent::TabletRelease: + case QEvent::TabletEnterProximity: + case QEvent::TabletLeaveProximity: + dev = QQuickPointerDevice::tabletDevice(static_cast(event)); + break; +#endif #if QT_CONFIG(gestures) case QEvent::NativeGesture: dev = QQuickPointerDevice::touchDevice(static_cast(event)->device()); @@ -2467,6 +2491,11 @@ void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event) deliverTouchEvent(event->asPointerTouchEvent()); } else { deliverSinglePointEventUntilAccepted(event); + // If any handler got interested in the tablet event, we don't want to receive a synth-mouse event from QtGui + // TODO Qt 6: QTabletEvent will be accepted by default, like other events + if (event->asPointerTabletEvent() && + (!event->point(0)->passiveGrabbers().isEmpty() || event->point(0)->exclusiveGrabber())) + event->setAccepted(true); } event->reset(nullptr); diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h index d22bba4512..097627374c 100644 --- a/src/quick/items/qquickwindow.h +++ b/src/quick/items/qquickwindow.h @@ -252,6 +252,9 @@ protected: #if QT_CONFIG(wheelevent) void wheelEvent(QWheelEvent *) override; #endif +#if QT_CONFIG(tabletevent) + void tabletEvent(QTabletEvent *) override; +#endif private Q_SLOTS: void maybeUpdate(); -- cgit v1.2.3