diff options
Diffstat (limited to 'src/quick/items')
56 files changed, 2473 insertions, 590 deletions
diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index 5674326d6c..9ac7422a39 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -40,6 +40,7 @@ #include "qquickcontext2d_p.h" #include "qquickcontext2dcommandbuffer_p.h" #include "qquickcanvasitem_p.h" +#include <private/qtquickglobal_p.h> #include <private/qquickcontext2dtexture_p.h> #include <private/qquickitem_p.h> #if QT_CONFIG(quick_shadereffect) @@ -129,8 +130,6 @@ QT_BEGIN_NAMESPACE Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); -#define DEGREES(t) ((t) * 180.0 / M_PI) - #define CHECK_CONTEXT(r) if (!r || !r->d()->context || !r->d()->context->bufferValid()) \ THROW_GENERIC_ERROR("Not a Context2D object"); @@ -138,7 +137,7 @@ Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); THROW_GENERIC_ERROR("Not a Context2D object"); #define qClamp(val, min, max) qMin(qMax(val, min), max) #define CHECK_RGBA(c) (c == '-' || c == '.' || (c >=0 && c <= 9)) -QColor qt_color_from_string(const QV4::Value &name) +Q_QUICK_PRIVATE_EXPORT QColor qt_color_from_string(const QV4::Value &name) { QByteArray str = name.toQString().toUtf8(); @@ -905,7 +904,7 @@ struct QQuickJSContext2DPixelData : public QV4::Object V4_NEEDS_DESTROY static QV4::ReturnedValue getIndexed(const QV4::Managed *m, uint index, bool *hasProperty); - static void putIndexed(QV4::Managed *m, uint index, const QV4::Value &value); + static bool putIndexed(QV4::Managed *m, uint index, const QV4::Value &value); static void proto_get_length(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData); }; @@ -929,9 +928,9 @@ struct QQuickJSContext2DImageData : public QV4::Object static void method_get_height(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData); static void method_get_data(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData); - static void markObjects(QV4::Heap::Base *that, QV4::ExecutionEngine *engine) { - static_cast<QQuickJSContext2DImageData::Data *>(that)->pixelData.mark(engine); - QV4::Object::markObjects(that, engine); + static void markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack) { + static_cast<QQuickJSContext2DImageData::Data *>(that)->pixelData.mark(markStack); + QV4::Object::markObjects(that, markStack); } }; @@ -1643,7 +1642,7 @@ void QQuickJSContext2DPrototype::method_createConicalGradient(const QV4::Builtin if (callData->argc >= 3) { qreal x = callData->args[0].toNumber(); qreal y = callData->args[1].toNumber(); - qreal angle = DEGREES(callData->args[2].toNumber()); + qreal angle = qRadiansToDegrees(callData->args[2].toNumber()); if (!qt_is_finite(x) || !qt_is_finite(y)) { THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createConicalGradient(): Incorrect arguments"); } @@ -3083,13 +3082,13 @@ QV4::ReturnedValue QQuickJSContext2DPixelData::getIndexed(const QV4::Managed *m, return QV4::Encode::undefined(); } -void QQuickJSContext2DPixelData::putIndexed(QV4::Managed *m, uint index, const QV4::Value &value) +bool QQuickJSContext2DPixelData::putIndexed(QV4::Managed *m, uint index, const QV4::Value &value) { Q_ASSERT(m->as<QQuickJSContext2DPixelData>()); QV4::ExecutionEngine *v4 = static_cast<QQuickJSContext2DPixelData *>(m)->engine(); QV4::Scope scope(v4); if (scope.hasException()) - return; + return false; QV4::Scoped<QQuickJSContext2DPixelData> r(scope, static_cast<QQuickJSContext2DPixelData *>(m)); @@ -3115,7 +3114,10 @@ void QQuickJSContext2DPixelData::putIndexed(QV4::Managed *m, uint index, const Q *pixel = qRgba(qRed(*pixel), qGreen(*pixel), qBlue(*pixel), v); break; } + return true; } + + return false; } /*! \qmlmethod CanvasImageData QtQuick::Context2D::createImageData(real sw, real sh) @@ -3367,7 +3369,7 @@ void QQuickContext2D::rotate(qreal angle) return; QTransform newTransform =state.matrix; - newTransform.rotate(DEGREES(angle)); + newTransform.rotate(qRadiansToDegrees(angle)); if (!newTransform.isInvertible()) { state.invertibleCTM = false; @@ -3376,7 +3378,7 @@ void QQuickContext2D::rotate(qreal angle) state.matrix = newTransform; buffer()->updateMatrix(state.matrix); - m_path = QTransform().rotate(-DEGREES(angle)).map(m_path); + m_path = QTransform().rotate(-qRadiansToDegrees(angle)).map(m_path); } void QQuickContext2D::shear(qreal h, qreal v) @@ -3773,8 +3775,8 @@ void QQuickContext2D::arc(qreal xc, qreal yc, qreal radius, qreal sar, qreal ear antiClockWise = !antiClockWise; //end hack - float sa = DEGREES(sar); - float ea = DEGREES(ear); + float sa = qRadiansToDegrees(sar); + float ea = qRadiansToDegrees(ear); double span = 0; diff --git a/src/quick/items/qquickanimatedimage_p.h b/src/quick/items/qquickanimatedimage_p.h index d0d8ec7c5c..94e44f27cd 100644 --- a/src/quick/items/qquickanimatedimage_p.h +++ b/src/quick/items/qquickanimatedimage_p.h @@ -97,7 +97,6 @@ Q_SIGNALS: void playingChanged(); void pausedChanged(); void frameChanged(); - void sourceSizeChanged(); void frameCountChanged(); private Q_SLOTS: diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 448b63c347..8653d758de 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -38,8 +38,11 @@ ****************************************************************************/ #include "qquickevents_p_p.h" +#include <QtCore/qmap.h> #include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qtouchdevice_p.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <private/qdebug_p.h> @@ -445,10 +448,96 @@ Item { \l inverted always returns false. */ -typedef QHash<QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash; +/*! + \qmltype PointerDevice + \instantiates QQuickPointerDevice + \inqmlmodule QtQuick + \ingroup qtquick-input-events + + \brief Provides information about a pointing device + + A pointing device can be a mouse, a touchscreen, or a stylus on a graphics + tablet. + + \sa PointerEvent, PointerHandler +*/ + +/*! + \readonly + \qmlproperty enumeration QtQuick::PointerDevice::type + + This property holds the type of the pointing device. + + Valid values are: + + \value DeviceType.UnknownDevice + the device cannot be identified + \value DeviceType.Mouse + a mouse + \value DeviceType.TouchScreen + a touchscreen providing absolute coordinates + \value DeviceType.TouchPad + a trackpad or touchpad providing relative coordinates + \value DeviceType.Stylus + a pen-like device + \value DeviceType.Airbrush + a stylus with a thumbwheel to adjust + \l {QTabletEvent::tangentialPressure}{tangentialPressure} + \value DeviceType.Puck + a device that is similar to a flat mouse with a + transparent circle with cross-hairs (same as \l QTabletEvent::Puck) + + \sa QTouchDevice::DeviceType +*/ + +/*! + \readonly + \qmlproperty enumeration QtQuick::PointerDevice::capabilities + + This property holds a bitwise combination of the capabilities of the + pointing device. It tells you under which conditions events are sent, + and which properties of PointerEvent are expected to be valid. + + Valid values are: + + \value CapabilityFlag.Position + the \l {QtQuick::EventPoint::position}{position} and + \l {QtQuick::EventPoint::scenePosition}{scenePosition} properties + \value CapabilityFlag.Area + the \l {QtQuick::EventTouchPoint::ellipseDiameters}{ellipseDiameters} property + \value CapabilityFlag.Pressure + the \l {QtQuick::EventTouchPoint::pressure}{pressure} property + \value CapabilityFlag.Velocity + the \l {QtQuick::PointerEvent::velocity}{velocity} property + \value CapabilityFlag.Scroll + a \l {QtQuick::PointerDevice::DeviceType::Mouse}{Mouse} has a wheel, or the + operating system recognizes scroll gestures on a + \l {QtQuick::PointerDevice::DeviceType::TouchPad}{TouchPad} + \value CapabilityFlag.Hover + events are sent even when no button is pressed, or the finger or stylus + is not in contact with the surface + \value CapabilityFlag.Rotation + the \l {QtQuick::EventTouchPoint::rotation}{rotation} property + \value CapabilityFlag.XTilt + horizontal angle between a stylus and the axis perpendicular to the surface + \value CapabilityFlag.YTilt + vertical angle between a stylus and the axis perpendicular to the surface + + \sa QTouchDevice::capabilities +*/ + +typedef QHash<const QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash; Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices) -Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, +struct ConstructableQQuickPointerDevice : public QQuickPointerDevice +{ + ConstructableQQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, + int maxPoints, int buttonCount, const QString &name, + qint64 uniqueId = 0) + : QQuickPointerDevice(devType, pType, caps, maxPoints, buttonCount, name, uniqueId) {} + +}; +Q_GLOBAL_STATIC_WITH_ARGS(ConstructableQQuickPointerDevice, g_genericMouseDevice, (QQuickPointerDevice::Mouse, QQuickPointerDevice::GenericPointer, QQuickPointerDevice::Position | QQuickPointerDevice::Scroll | QQuickPointerDevice::Hover, @@ -457,7 +546,23 @@ Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, typedef QHash<qint64, QQuickPointerDevice *> PointerDeviceForDeviceIdHash; Q_GLOBAL_STATIC(PointerDeviceForDeviceIdHash, g_tabletDevices) -QQuickPointerDevice *QQuickPointerDevice::touchDevice(QTouchDevice *d) +// debugging helpers +static const char *pointStateString(const QQuickEventPoint *point) +{ + static const QMetaEnum stateMetaEnum = point->metaObject()->enumerator(point->metaObject()->indexOfEnumerator("State")); + return stateMetaEnum.valueToKey(point->state()); +} + +static const QString pointDeviceName(const QQuickEventPoint *point) +{ + auto device = static_cast<const QQuickPointerEvent *>(point->parent())->device(); + QString deviceName = (device ? device->name() : QLatin1String("null device")); + deviceName.resize(16, ' '); // shorten, and align in case of sequential output + return deviceName; +} + + +QQuickPointerDevice *QQuickPointerDevice::touchDevice(const QTouchDevice *d) { if (g_touchDevices->contains(d)) return g_touchDevices->value(d); @@ -505,37 +610,358 @@ QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id) return nullptr; } -void QQuickEventPoint::reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp) +/*! + \qmltype EventPoint + \qmlabstract + \instantiates QQuickEventPoint + \inqmlmodule QtQuick + \ingroup qtquick-input-events + \brief Provides information about an individual point within a PointerEvent + + A PointerEvent contains an EventPoint for each point of contact: one corresponding + to the mouse cursor, or one for each finger touching a touchscreen. + + \sa PointerEvent, PointerHandler +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::position + + This property holds the coordinates of the position supplied by the event, + relative to the upper-left corner of the Item which has the PointerHandler. + If a contact patch is available from the pointing device, this point + represents its centroid. +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::scenePosition + + This property holds the coordinates of the position supplied by the event, + relative to the scene. If a contact patch is available from the \l device, + this point represents its centroid. +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::scenePressPosition + + This property holds the scene-relative position at which the press event + (on a touch device) or most recent change in QQuickPointerEvent::buttons() + (on a mouse or tablet stylus) occurred. +*/ + +/*! + \readonly + \qmlproperty point QtQuick::EventPoint::sceneGrabPosition + + This property holds the scene-relative position at which the EventPoint was + located when setGrabber() was called most recently. +*/ + +/*! + \readonly + \qmlproperty vector2d QtQuick::EventPoint::velocity + + This property holds average recent velocity: how fast and in which + direction the event point has been moving recently. +*/ + +/*! + \readonly + \qmlproperty int QtQuick::EventPoint::state + + This property tells what the user is currently doing at this point. + + It can be one of: + \value Pressed + The user's finger is now pressing a touchscreen, button or stylus + which was not pressed already + \value Updated + The touchpoint or position is being moved, with no change in pressed state + \value Stationary + The touchpoint or position is not being moved, and there is also + no change in pressed state + \value Released + The user's finger has now released a touch point, button or stylus + which was pressed +*/ + +/*! + \readonly + \qmlproperty int QtQuick::EventPoint::pointId + + This property holds the ID of the event, if any. + + Touchpoints have automatically-incrementing IDs: each time the user + presses a finger against the touchscreen, it will be a larger number. + In other cases, it will be -1. + + \sa PointerDevice.uniqueId +*/ + +/*! + \readonly + \qmlproperty bool QtQuick::EventPoint::accepted + + Setting \a accepted to true prevents the event from being propagated to + Items below the PointerHandler's Item. + + Generally, if the handler acts on the mouse event, then it should be + accepted so that items lower in the stacking order do not also respond to + the same event. +*/ + +/*! + \readonly + \qmlproperty real QtQuick::EventPoint::timeHeld + + This property holds the amount of time in seconds that the button or touchpoint has + been held. It can be used to detect a "long press", and can drive an + animation to show progress toward activation of the "long press" action. +*/ + +void QQuickEventPoint::reset(Qt::TouchPointState state, const QPointF &scenePos, int pointId, ulong timestamp, const QVector2D &velocity) { m_scenePos = scenePos; m_pointId = pointId; - m_valid = true; m_accept = false; m_state = static_cast<QQuickEventPoint::State>(state); m_timestamp = timestamp; - if (state == Qt::TouchPointPressed) + if (state == Qt::TouchPointPressed) { m_pressTimestamp = timestamp; - // TODO calculate velocity + m_scenePressPos = scenePos; + } + m_velocity = (Q_LIKELY(velocity.isNull()) ? estimatedVelocity() : velocity); +} + +void QQuickEventPoint::localizePosition(QQuickItem *target) +{ + if (target) + m_pos = target->mapFromScene(scenePosition()); + else + m_pos = QPointF(); +} + +/*! + If this point has an exclusive grabber, returns a pointer to it; else + returns null, if there is no grabber. The grabber could be either + an Item or a PointerHandler. +*/ +QObject *QQuickEventPoint::exclusiveGrabber() const +{ + return m_exclusiveGrabber.data(); +} + +/*! + Set the given Item or PointerHandler as the exclusive grabber of this point. + If there was already an exclusive grab, it will be canceled. If there + were passive grabbers, they will continue to lurk, but the exclusive grab + is a behavioral override of the passive grab as long as it remains. + If you already know whether the grabber is to be an Item or a PointerHandler, + you should instead call setGrabberItem() or setGrabberPointerHandler(), + because it is slightly more efficient. +*/ +void QQuickEventPoint::setExclusiveGrabber(QObject *grabber) +{ + if (QQuickPointerHandler *phGrabber = qmlobject_cast<QQuickPointerHandler *>(grabber)) + setGrabberPointerHandler(phGrabber, true); + else + setGrabberItem(static_cast<QQuickItem *>(grabber)); +} + +/*! + If the exclusive grabber of this point is an Item, returns a + pointer to that Item; else returns null, if there is no grabber or if + the grabber is a PointerHandler. +*/ +QQuickItem *QQuickEventPoint::grabberItem() const +{ + return (m_grabberIsHandler ? nullptr : static_cast<QQuickItem *>(m_exclusiveGrabber.data())); +} + +/*! + Set the given Item \a grabber as the exclusive grabber of this point. + If there was already an exclusive grab, it will be canceled. If there + were passive grabbers, they will continue to lurk, but the exclusive grab + is a behavioral override of the passive grab as long as it remains. +*/ +void QQuickEventPoint::setGrabberItem(QQuickItem *grabber) +{ + if (grabber != m_exclusiveGrabber.data()) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab" << m_exclusiveGrabber << "->" << grabber; + } + QQuickPointerHandler *oldGrabberHandler = grabberPointerHandler(); + QQuickItem *oldGrabberItem = grabberItem(); + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = false; + m_sceneGrabPos = m_scenePos; + if (oldGrabberHandler) + oldGrabberHandler->onGrabChanged(oldGrabberHandler, CancelGrabExclusive, this); + else if (oldGrabberItem && oldGrabberItem != grabber && grabber && pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) + passiveGrabber->onGrabChanged(passiveGrabber, OverrideGrabPassive, this); + } } -QQuickItem *QQuickEventPoint::grabber() const +/*! + If the exclusive grabber of this point is a PointerHandler, returns a + pointer to that handler; else returns null, if there is no grabber or if + the grabber is an Item. +*/ +QQuickPointerHandler *QQuickEventPoint::grabberPointerHandler() const { - return m_grabber.data(); + return (m_grabberIsHandler ? static_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data()) : nullptr); } -void QQuickEventPoint::setGrabber(QQuickItem *grabber) +/*! + Set the given PointerHandler \a grabber as grabber of this point. If \a + exclusive is true, it will override any other grabs; if false, \a grabber + will be added to the list of passive grabbers of this point. +*/ +void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber, bool exclusive) { - if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled()) && m_grabber.data() != grabber) { - auto device = static_cast<const QQuickPointerEvent *>(parent())->device(); - static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State")); - QString deviceName = (device ? device->name() : QLatin1String("null device")); - deviceName.resize(16, ' '); // shorten, and align in case of sequential output - qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state()) - << ": grab" << m_grabber << "->" << grabber; + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + if (exclusive) { + if (m_exclusiveGrabber != grabber) + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (exclusive)" << m_exclusiveGrabber << "->" << grabber; + } else { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (passive)" << grabber; + } + } + if (exclusive) { + if (grabber != m_exclusiveGrabber.data()) { + if (grabber) { + // set variables before notifying the new grabber + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + grabber->onGrabChanged(grabber, GrabExclusive, this); + for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) { + if (passiveGrabber != grabber) + passiveGrabber->onGrabChanged(grabber, OverrideGrabPassive, this); + } + } else if (QQuickPointerHandler *oldGrabberPointerHandler = qmlobject_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data())) { + oldGrabberPointerHandler->onGrabChanged(oldGrabberPointerHandler, UngrabExclusive, this); + } else if (!m_exclusiveGrabber.isNull()) { + // If there is a previous grabber and it's not a PointerHandler, it must be an Item. + QQuickItem *oldGrabberItem = static_cast<QQuickItem *>(m_exclusiveGrabber.data()); + // If this point came from a touchscreen, notify that previous grabber Item that it's losing its touch grab. + if (pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + } + // set variables after notifying the old grabber + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + } + } else { + if (!grabber) { + qDebug() << "can't set passive grabber to null"; + return; + } + auto ptr = QPointer<QQuickPointerHandler>(grabber); + if (!m_passiveGrabbers.contains(ptr)) { + m_passiveGrabbers.append(ptr); + grabber->onGrabChanged(grabber, GrabPassive, this); + } } - m_grabber = QPointer<QQuickItem>(grabber); } +/*! + If this point has an existing exclusive grabber (Item or PointerHandler), + inform the grabber that its grab is canceled, and remove it as grabber. + This normally happens when the grab is stolen by another Item. +*/ +void QQuickEventPoint::cancelExclusiveGrab() +{ + if (m_exclusiveGrabber.isNull()) + qWarning("cancelGrab: no grabber"); + else + cancelExclusiveGrabImpl(); +} + +void QQuickEventPoint::cancelExclusiveGrabImpl(QTouchEvent *cancelEvent) +{ + if (m_exclusiveGrabber.isNull()) + return; + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (exclusive)" << m_exclusiveGrabber << "-> nullptr"; + } + if (auto handler = grabberPointerHandler()) { + handler->onGrabChanged(handler, CancelGrabExclusive, this); + } else if (auto item = grabberItem()) { + if (cancelEvent) + QCoreApplication::sendEvent(item, cancelEvent); + else + item->touchUngrabEvent(); + } + m_exclusiveGrabber.clear(); +} + +/*! + If this point has the given \a handler as a passive grabber, + inform the grabber that its grab is canceled, and remove it as grabber. + This normally happens when another Item or PointerHandler does an exclusive grab. +*/ +void QQuickEventPoint::cancelPassiveGrab(QQuickPointerHandler *handler) +{ + if (removePassiveGrabber(handler)) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (passive)" << handler << "removed"; + } + handler->onGrabChanged(handler, CancelGrabPassive, this); + } +} + +/*! + If this point has the given \a handler as a passive grabber, remove it as grabber. + Returns true if it was removed, false if it wasn't a grabber. +*/ +bool QQuickEventPoint::removePassiveGrabber(QQuickPointerHandler *handler) +{ + return m_passiveGrabbers.removeOne(handler); +} + +/*! + If the given \a handler is grabbing this point passively, exclusively + or both, cancel the grab and remove it as grabber. + This normally happens when the handler decides that the behavior of this + point can no longer satisfy the handler's behavioral constraints within + the remainder of the gesture which the user is performing: for example + the handler tries to detect a tap but a drag is occurring instead, or + it tries to detect a drag in one direction but the drag is going in + another direction. In such cases the handler no longer needs or wants + to be informed of any further movements of this point. +*/ +void QQuickEventPoint::cancelAllGrabs(QQuickPointerHandler *handler) +{ + if (m_exclusiveGrabber == handler) { + handler->onGrabChanged(handler, CancelGrabExclusive, this); + m_exclusiveGrabber.clear(); + } + cancelPassiveGrab(handler); +} + +/*! + Sets this point as \a accepted (true) or rejected (false). + + During delivery of the current event to the Items in the scene, each Item + or Pointer Handler should accept the points for which it is taking + responsibility. As soon as all points within the event are accepted, event + propagation stops. However accepting the point does not imply any kind of + grab, passive or exclusive. + + \sa setExclusiveGrabber, QQuickPointerHandler::setPassiveGrab, QQuickPointerHandler::setExclusiveGrab +*/ void QQuickEventPoint::setAccepted(bool accepted) { if (m_accept != accepted) { @@ -544,18 +970,175 @@ void QQuickEventPoint::setAccepted(bool accepted) } } + +/*! + \qmltype EventTouchPoint + \qmlabstract + \instantiates QQuickEventTouchPoint + \inqmlmodule QtQuick + \ingroup qtquick-input-events + \brief Provides information about an individual touch point within a PointerEvent + + \sa PointerEvent, PointerHandler +*/ + +/*! + \readonly + \qmlproperty QPointerUniqueId QtQuick::EventTouchPoint::uniqueId + + This property holds the unique ID of the fiducial or stylus in use, if any. + + On touchscreens that can track physical objects (such as knobs or game + pieces) in addition to fingers, each object usually has a unique ID. + Likewise, each stylus that can be used with a graphics tablet usually has a + unique serial number. Qt so far only supports numeric IDs. You can get the + actual number as uniqueId.numeric, but that is a device-specific detail. + In the future, there may be support for non-numeric IDs, so you should + not assume that the number is meaningful. + + If you need to identify specific objects, your application should provide + UI for registering objects and mapping them to functionality: allow the + user to select a meaning, virtual tool, or action, prompt the user to bring + the object into proximity, and store a mapping from uniqueId to its + purpose, for example in \l Settings. +*/ + +/*! + \readonly + \qmlproperty qreal QtQuick::EventTouchPoint::rotation + + This property holds the rotation angle of the stylus on a graphics tablet + or the contact patch of a touchpoint on a touchscreen. + + It is valid only with certain tablet stylus devices and touchscreens that + can measure the rotation angle. Otherwise, it will be zero. +*/ + +/*! + \readonly + \qmlproperty qreal QtQuick::EventTouchPoint::pressure + + This property tells how hard the user is pressing the stylus on a graphics + tablet or the finger against a touchscreen, in the range from \c 0 (no + measurable pressure) to \c 1.0 (maximum pressure which the device can + measure). + + It is valid only with certain tablets and touchscreens that can measure + pressure. Otherwise, it will be \c 1.0 when pressed. +*/ + +/*! + \readonly + \qmlproperty size QtQuick::EventTouchPoint::ellipseDiameters + + This property holds the diameters of the contact patch, if the event + comes from a touchpoint and the \l device provides this information. + + A touchpoint is modeled as an elliptical area where the finger is pressed + against the touchscreen. (In fact, it could also be modeled as a bitmap; but + in that case we expect an elliptical bounding estimate to be fitted to the + contact patch before the event is sent.) The harder the user presses, the + larger the contact patch; so, these diameters provide an alternate way of + detecting pressure, in case the device does not include a separate pressure + sensor. The ellipse is centered on \l scenePos (\l pos in the PointerHandler's + Item's local coordinates). The \l rotation property provides the + rotation of the ellipse, if known. It is expected that if the \l rotation + is zero, the verticalDiameter of the ellipse is the larger one (the major axis), + because of the usual hand position, reaching upward or outward across the surface. + + If the contact patch is unknown, or the \l device is not a touchscreen, + these values will be zero. +*/ + QQuickEventTouchPoint::QQuickEventTouchPoint(QQuickPointerTouchEvent *parent) : QQuickEventPoint(parent), m_rotation(0), m_pressure(0) {} void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong timestamp) { - QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp); + QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp, tp.velocity()); + m_exclusiveGrabber.clear(); + m_passiveGrabbers.clear(); m_rotation = tp.rotation(); m_pressure = tp.pressure(); + m_ellipseDiameters = tp.ellipseDiameters(); m_uniqueId = tp.uniqueId(); } +struct PointVelocityData { + QVector2D velocity; + QPointF pos; + ulong timestamp; +}; + +typedef QMap<quint64, PointVelocityData*> PointDataForPointIdMap; +Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData) +static const int PointVelocityAgeLimit = 500; // milliseconds + +/*! + \internal + Estimates the velocity based on a weighted average of all previous velocities. + The older the velocity is, the less significant it becomes for the estimate. +*/ +QVector2D QQuickEventPoint::estimatedVelocity() const +{ + PointVelocityData *prevPoint = g_previousPointData->value(m_pointId); + if (!prevPoint) { + // cleanup events older than PointVelocityAgeLimit + auto end = g_previousPointData->end(); + for (auto it = g_previousPointData->begin(); it != end; ) { + PointVelocityData *data = it.value(); + if (m_timestamp - data->timestamp > PointVelocityAgeLimit) { + it = g_previousPointData->erase(it); + delete data; + } else { + ++it; + } + } + // TODO optimize: stop this dynamic memory thrashing + prevPoint = new PointVelocityData; + prevPoint->velocity = QVector2D(); + prevPoint->timestamp = 0; + prevPoint->pos = QPointF(); + g_previousPointData->insert(m_pointId, prevPoint); + } + const ulong timeElapsed = m_timestamp - prevPoint->timestamp; + if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint + return m_velocity; + + QVector2D newVelocity; + if (prevPoint->timestamp != 0) + newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed; + + // VERY simple kalman filter: does a weighted average + // where the older velocities get less and less significant + static const float KalmanGain = 0.7f; + QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain); + + prevPoint->velocity = filteredVelocity; + prevPoint->pos = m_scenePos; + prevPoint->timestamp = m_timestamp; + return filteredVelocity; +} + +/*! + \qmltype PointerEvent + \instantiates QQuickPointerEvent + \inqmlmodule QtQuick + \ingroup qtquick-input-events + + \brief Provides information about an event from a pointing device + + A PointerEvent is an event describing contact or movement across a surface, + provided by a mouse, a touchpoint (single finger on a touchscreen), or a + stylus on a graphics tablet. The \l device property provides more + information about where the event came from. + + \sa PointerHandler + + \image touchpoint-metrics.png +*/ + /*! \internal \class QQuickPointerEvent @@ -570,6 +1153,68 @@ void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong times dynamically create and destroy objects of this type for each event. */ +/*! + \readonly + \qmlproperty enumeration QtQuick::PointerEvent::button + + This property holds the \l {Qt::MouseButton}{button} that caused the event, + if any. If the \l device does not have buttons, or the event is a hover + event, it will be \c Qt.NoButton. +*/ + +/*! + \readonly + \qmlproperty int QtQuick::PointerEvent::buttons + + This property holds the combination of mouse or stylus + \l {Qt::MouseButton}{buttons} pressed when the event was generated. For move + events, this is all buttons that are pressed down. For press events, this + includes the button that caused the event, as well as any others that were + already held. For release events, this excludes the button that caused the + event. +*/ + +/*! + \readonly + \qmlproperty int QtQuick::PointerEvent::modifiers + + This property holds the \l {Qt::KeyboardModifier}{keyboard modifier} flags + that existed immediately before the event occurred. + + It contains a bitwise combination of the following flags: + \value Qt.NoModifier + No modifier key is pressed. + \value Qt.ShiftModifier + A Shift key on the keyboard is pressed. + \value Qt.ControlModifier + A Ctrl key on the keyboard is pressed. + \value Qt.AltModifier + An Alt key on the keyboard is pressed. + \value Qt.MetaModifier + A Meta key on the keyboard is pressed. + \value Qt.KeypadModifier + A keypad button is pressed. + + For example, to react to a Shift key + Left mouse button click: + \qml + Item { + TapHandler { + onTapped: { + if ((event.button == Qt.LeftButton) && (event.modifiers & Qt.ShiftModifier)) + doSomething(); + } + } + } + \endqml +*/ + +/*! + \readonly + \qmlproperty PointerDevice QtQuick::PointerEvent::device + + This property holds the device that generated the event. +*/ + QQuickPointerEvent::~QQuickPointerEvent() {} @@ -581,11 +1226,14 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) return this; m_device = QQuickPointerDevice::genericMouseDevice(); + m_device->eventDeliveryTargets().clear(); m_button = ev->button(); m_pressedButtons = ev->buttons(); Qt::TouchPointState state = Qt::TouchPointStationary; switch (ev->type()) { case QEvent::MouseButtonPress: + m_mousePoint->clearPassiveGrabbers(); + Q_FALLTHROUGH(); case QEvent::MouseButtonDblClick: state = Qt::TouchPointPressed; break; @@ -598,10 +1246,15 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) default: break; } - m_mousePoint->reset(state, ev->windowPos(), 0, ev->timestamp()); // mouse is 0 + m_mousePoint->reset(state, ev->windowPos(), quint64(1) << 24, ev->timestamp()); // mouse has device ID 1 return this; } +void QQuickPointerMouseEvent::localize(QQuickItem *target) +{ + m_mousePoint->localizePosition(target); +} + QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) { auto ev = static_cast<QTouchEvent*>(event); @@ -610,6 +1263,7 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) return this; m_device = QQuickPointerDevice::touchDevice(ev->device()); + m_device->eventDeliveryTargets().clear(); m_button = Qt::NoButton; m_pressedButtons = Qt::NoButton; @@ -620,32 +1274,93 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) for (int i = m_touchPoints.size(); i < newPointCount; ++i) m_touchPoints.insert(i, new QQuickEventTouchPoint(this)); - // Make sure the grabbers are right from one event to the next - QVector<QQuickItem*> grabbers; - // Copy all grabbers, because the order of points might have changed in the event. + // Make sure the grabbers and on-pressed values are right from one event to the next + struct ToPreserve { + int pointId; // just for double-checking + ulong pressTimestamp; + QPointF scenePressPos; + QPointF sceneGrabPos; + QObject * grabber; + QVector <QPointer <QQuickPointerHandler> > passiveGrabbers; + + ToPreserve() : pointId(0), pressTimestamp(0), grabber(nullptr) {} + }; + QVector<ToPreserve> preserves(newPointCount); // jar of pickled touchpoints, in order of points in the _new_ event + + // Copy stuff we need to preserve, because the order of points might have changed in the event. // The ID is all that we can rely on (release might remove the first point etc). for (int i = 0; i < newPointCount; ++i) { - QQuickItem *grabber = nullptr; - if (auto point = pointById(tps.at(i).id())) - grabber = point->grabber(); - grabbers.append(grabber); + int pid = tps.at(i).id(); + if (auto point = pointById(pid)) { + preserves[i].pointId = pid; + preserves[i].pressTimestamp = point->m_pressTimestamp; + preserves[i].scenePressPos = point->scenePressPosition(); + preserves[i].sceneGrabPos = point->sceneGrabPosition(); + preserves[i].grabber = point->exclusiveGrabber(); + preserves[i].passiveGrabbers = point->passiveGrabbers(); + } } for (int i = 0; i < newPointCount; ++i) { auto point = m_touchPoints.at(i); point->reset(tps.at(i), ev->timestamp()); + const auto &preserved = preserves.at(i); if (point->state() == QQuickEventPoint::Pressed) { - if (grabbers.at(i)) + if (preserved.grabber) qWarning() << "TouchPointPressed without previous release event" << point; - point->setGrabber(nullptr); + point->setGrabberItem(nullptr); + point->clearPassiveGrabbers(); } else { - point->setGrabber(grabbers.at(i)); + // Restore the grabbers without notifying (don't call onGrabChanged) + Q_ASSERT(preserved.pointId == 0 || preserved.pointId == point->pointId()); + point->m_pressTimestamp = preserved.pressTimestamp; + point->m_scenePressPos = preserved.scenePressPos; + point->m_sceneGrabPos = preserved.sceneGrabPos; + point->m_exclusiveGrabber = preserved.grabber; + point->m_grabberIsHandler = (qmlobject_cast<QQuickPointerHandler *>(point->m_exclusiveGrabber) != nullptr); + point->m_passiveGrabbers = preserved.passiveGrabbers; } } m_pointCount = newPointCount; return this; } +void QQuickPointerTouchEvent::localize(QQuickItem *target) +{ + for (auto point : qAsConst(m_touchPoints)) + point->localizePosition(target); +} + +QQuickPointerEvent *QQuickPointerNativeGestureEvent::reset(QEvent *event) +{ + auto ev = static_cast<QNativeGestureEvent*>(event); + m_event = ev; + if (!event) + return this; + + m_device = QQuickPointerDevice::touchDevice(ev->device()); + m_device->eventDeliveryTargets().clear(); + Qt::TouchPointState state = Qt::TouchPointMoved; + switch (type()) { + case Qt::BeginNativeGesture: + state = Qt::TouchPointPressed; + break; + case Qt::EndNativeGesture: + state = Qt::TouchPointReleased; + break; + default: + break; + } + quint64 deviceId = QTouchDevicePrivate::get(const_cast<QTouchDevice *>(ev->device()))->id; // a bit roundabout since QTouchDevice::mTouchDeviceId is protected + m_gesturePoint->reset(state, ev->windowPos(), deviceId << 24, ev->timestamp()); + return this; +} + +void QQuickPointerNativeGestureEvent::localize(QQuickItem *target) +{ + m_gesturePoint->localizePosition(target); +} + QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const { if (i == 0) return m_mousePoint; @@ -658,9 +1373,15 @@ QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const { return nullptr; } +QQuickEventPoint *QQuickPointerNativeGestureEvent::point(int i) const { + if (i == 0) + return m_gesturePoint; + return nullptr; +} + QQuickEventPoint::QQuickEventPoint(QQuickPointerEvent *parent) - : QObject(parent), m_pointId(0), m_grabber(nullptr), m_timestamp(0), m_pressTimestamp(0), - m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false) + : QObject(parent), m_pointId(0), m_exclusiveGrabber(nullptr), m_timestamp(0), m_pressTimestamp(0), + m_state(QQuickEventPoint::Released), m_accept(false), m_grabberIsHandler(false) { Q_UNUSED(m_reserved); } @@ -674,6 +1395,15 @@ bool QQuickPointerMouseEvent::allPointsAccepted() const { return m_mousePoint->isAccepted(); } +bool QQuickPointerMouseEvent::allUpdatedPointsAccepted() const { + return m_mousePoint->state() == QQuickEventPoint::Pressed || m_mousePoint->isAccepted(); +} + +bool QQuickPointerMouseEvent::allPointsGrabbed() const +{ + return m_mousePoint->exclusiveGrabber() != nullptr; +} + QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) const { auto event = static_cast<QMouseEvent *>(m_event); @@ -681,16 +1411,31 @@ QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) cons return event; } -QVector<QQuickItem *> QQuickPointerMouseEvent::grabbers() const +/*! + Returns the exclusive grabber of this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerMouseEvent::exclusiveGrabbers() const { - QVector<QQuickItem *> result; - if (QQuickItem *grabber = m_mousePoint->grabber()) + QVector<QObject *> result; + if (QObject *grabber = m_mousePoint->exclusiveGrabber()) result << grabber; return result; } +/*! + Remove all passive and exclusive grabbers of this event, without notifying. +*/ void QQuickPointerMouseEvent::clearGrabbers() const { - m_mousePoint->setGrabber(nullptr); + m_mousePoint->setGrabberItem(nullptr); + m_mousePoint->clearPassiveGrabbers(); +} + +/*! + Returns whether the given \a handler is the exclusive grabber of this event. +*/ +bool QQuickPointerMouseEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ + return m_mousePoint->exclusiveGrabber() == handler; } bool QQuickPointerMouseEvent::isPressEvent() const @@ -700,6 +1445,24 @@ bool QQuickPointerMouseEvent::isPressEvent() const (me->buttons() & me->button()) == me->buttons()); } +bool QQuickPointerMouseEvent::isDoubleClickEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return (me->type() == QEvent::MouseButtonDblClick); +} + +bool QQuickPointerMouseEvent::isUpdateEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return me->type() == QEvent::MouseMove; +} + +bool QQuickPointerMouseEvent::isReleaseEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return me && me->type() == QEvent::MouseButtonRelease; +} + bool QQuickPointerTouchEvent::allPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { if (!m_touchPoints.at(i)->isAccepted()) @@ -708,12 +1471,32 @@ bool QQuickPointerTouchEvent::allPointsAccepted() const { return true; } -QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const -{ - QVector<QQuickItem *> result; +bool QQuickPointerTouchEvent::allUpdatedPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { auto point = m_touchPoints.at(i); - if (QQuickItem *grabber = point->grabber()) { + if (point->state() != QQuickEventPoint::Pressed && !point->isAccepted()) + return false; + } + return true; +} + +bool QQuickPointerTouchEvent::allPointsGrabbed() const +{ + for (int i = 0; i < m_pointCount; ++i) { + if (!m_touchPoints.at(i)->exclusiveGrabber()) + return false; + } + return true; +} + +/*! + Returns the exclusive grabbers of all points in this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerTouchEvent::exclusiveGrabbers() const +{ + QVector<QObject *> result; + for (int i = 0; i < m_pointCount; ++i) { + if (QObject *grabber = m_touchPoints.at(i)->exclusiveGrabber()) { if (!result.contains(grabber)) result << grabber; } @@ -721,9 +1504,27 @@ QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const return result; } +/*! + Remove all passive and exclusive grabbers of all touchpoints in this event, + without notifying. +*/ void QQuickPointerTouchEvent::clearGrabbers() const { + for (auto point: m_touchPoints) { + point->setGrabberItem(nullptr); + point->clearPassiveGrabbers(); + } +} + +/*! + Returns whether the given \a handler is the exclusive grabber of any + touchpoint within this event. +*/ +bool QQuickPointerTouchEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ for (auto point: m_touchPoints) - point->setGrabber(nullptr); + if (point->exclusiveGrabber() == handler) + return true; + return false; } bool QQuickPointerTouchEvent::isPressEvent() const @@ -731,12 +1532,22 @@ bool QQuickPointerTouchEvent::isPressEvent() const return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointPressed; } +bool QQuickPointerTouchEvent::isUpdateEvent() const +{ + return static_cast<QTouchEvent*>(m_event)->touchPointStates() & (Qt::TouchPointMoved | Qt::TouchPointStationary); +} + +bool QQuickPointerTouchEvent::isReleaseEvent() const +{ + return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointReleased; +} + QVector<QPointF> QQuickPointerEvent::unacceptedPressedPointScenePositions() const { QVector<QPointF> points; for (int i = 0; i < pointCount(); ++i) { if (!point(i)->isAccepted() && point(i)->state() == QQuickEventPoint::Pressed) - points << point(i)->scenePos(); + points << point(i)->scenePosition(); } return points; } @@ -788,20 +1599,78 @@ QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickIte } /*! + Returns the exclusive grabber of this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerNativeGestureEvent::exclusiveGrabbers() const +{ + QVector<QObject *> result; + if (QObject *grabber = m_gesturePoint->exclusiveGrabber()) + result << grabber; + return result; +} + +/*! + Remove all passive and exclusive grabbers of this event, without notifying. +*/ +void QQuickPointerNativeGestureEvent::clearGrabbers() const { + m_gesturePoint->setGrabberItem(nullptr); + m_gesturePoint->clearPassiveGrabbers(); +} + +/*! + Returns whether the given \a handler is the exclusive grabber of this event. +*/ +bool QQuickPointerNativeGestureEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ + return m_gesturePoint->exclusiveGrabber() == handler; +} + +bool QQuickPointerNativeGestureEvent::isPressEvent() const +{ + return type() == Qt::BeginNativeGesture; +} + +bool QQuickPointerNativeGestureEvent::isUpdateEvent() const +{ + switch (type()) { + case Qt::BeginNativeGesture: + case Qt::EndNativeGesture: + return false; + default: + return true; + } +} + +bool QQuickPointerNativeGestureEvent::isReleaseEvent() const +{ + return type() == Qt::EndNativeGesture; +} + +Qt::NativeGestureType QQuickPointerNativeGestureEvent::type() const +{ + return static_cast<QNativeGestureEvent *>(m_event)->gestureType(); +} + +qreal QQuickPointerNativeGestureEvent::value() const +{ + return static_cast<QNativeGestureEvent *>(m_event)->value(); +} + +/*! \internal Returns a pointer to the QQuickEventPoint which has the \a pointId as \l {QQuickEventPoint::pointId}{pointId}. Returns nullptr if there is no point with that ID. - \fn QQuickPointerEvent::pointById(quint64 pointId) const + \fn QQuickPointerEvent::pointById(int pointId) const */ -QQuickEventPoint *QQuickPointerMouseEvent::pointById(quint64 pointId) const { +QQuickEventPoint *QQuickPointerMouseEvent::pointById(int pointId) const { if (m_mousePoint && pointId == m_mousePoint->pointId()) return m_mousePoint; return nullptr; } -QQuickEventPoint *QQuickPointerTouchEvent::pointById(quint64 pointId) const { +QQuickEventPoint *QQuickPointerTouchEvent::pointById(int pointId) const { auto it = std::find_if(m_touchPoints.constBegin(), m_touchPoints.constEnd(), [pointId](const QQuickEventTouchPoint *tp) { return tp->pointId() == pointId; } ); if (it != m_touchPoints.constEnd()) @@ -809,6 +1678,12 @@ QQuickEventPoint *QQuickPointerTouchEvent::pointById(quint64 pointId) const { return nullptr; } +QQuickEventPoint *QQuickPointerNativeGestureEvent::pointById(int pointId) const { + if (m_gesturePoint && pointId == m_gesturePoint->pointId()) + return m_gesturePoint; + return nullptr; +} + /*! \internal @@ -831,7 +1706,12 @@ const QTouchEvent::TouchPoint *QQuickPointerTouchEvent::touchPointById(int point \internal Make a new QTouchEvent, giving it a subset of the original touch points. - Returns a nullptr if all points are stationary or there are no points inside the item. + Returns a nullptr if all points are stationary, or there are no points inside the item, + or none of the points were pressed inside and the item was not grabbing any of them + and isFiltering is false. When isFiltering is true, it is assumed that the item + cares about all points which are inside its bounds, because most filtering items + need to monitor eventpoint movements until a drag threshold is exceeded or the + requirements for a gesture to be recognized are met in some other way. */ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool isFiltering) const { @@ -841,19 +1721,24 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i // Or else just document that velocity is always scene-relative and is not scaled and rotated with the item // but that would require changing tst_qquickwindow::touchEvent_velocity(): it expects transformed velocity + bool anyPressOrReleaseInside = false; + bool anyGrabber = false; QMatrix4x4 transformMatrix(QQuickItemPrivate::get(item)->windowToItemTransform()); for (int i = 0; i < m_pointCount; ++i) { auto p = m_touchPoints.at(i); if (p->isAccepted()) continue; // include points where item is the grabber - bool isGrabber = p->grabber() == item; - // include newly pressed points inside the bounds - bool isPressInside = p->state() == QQuickEventPoint::Pressed && item->contains(item->mapFromScene(p->scenePos())); + bool isGrabber = p->exclusiveGrabber() == item; + if (isGrabber) + anyGrabber = true; + // include points inside the bounds if no other item is the grabber or if the item is filtering + bool isInside = item->contains(item->mapFromScene(p->scenePosition())); + bool hasAnotherGrabber = p->exclusiveGrabber() && p->exclusiveGrabber() != item; // filtering: (childMouseEventFilter) include points that are grabbed by children of the target item bool grabberIsChild = false; - auto parent = p->grabber(); + auto parent = p->grabberItem(); while (isFiltering && parent) { if (parent == item) { grabberIsChild = true; @@ -862,11 +1747,11 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i parent = parent->parentItem(); } - // when filtering, send points that are grabbed by a child and points that are not grabbed but inside - bool filterRelevant = isFiltering && (grabberIsChild || (!p->grabber() && item->contains(item->mapFromScene(p->scenePos())))); - if (!(isGrabber || isPressInside || filterRelevant)) + bool filterRelevant = isFiltering && grabberIsChild; + if (!(isGrabber || (isInside && (!hasAnotherGrabber || isFiltering)) || filterRelevant)) continue; - + if ((p->state() == QQuickEventPoint::Pressed || p->state() == QQuickEventPoint::Released) && isInside) + anyPressOrReleaseInside = true; const QTouchEvent::TouchPoint *tp = touchPointById(p->pointId()); if (tp) { eventStates |= tp->state(); @@ -880,7 +1765,9 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i } } - if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty()) + // Now touchPoints will have only points which are inside the item. + // But if none of them were just pressed inside, and the item has no other reason to care, ignore them anyway. + if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty() || (!anyPressOrReleaseInside && !anyGrabber && !isFiltering)) return nullptr; // if all points have the same state, set the event type accordingly @@ -915,6 +1802,19 @@ QTouchEvent *QQuickPointerTouchEvent::asTouchEvent() const return static_cast<QTouchEvent *>(m_event); } +bool QQuickPointerNativeGestureEvent::allPointsAccepted() const { + return m_gesturePoint->isAccepted(); +} + +bool QQuickPointerNativeGestureEvent::allUpdatedPointsAccepted() const { + return m_gesturePoint->state() == QQuickEventPoint::Pressed || m_gesturePoint->isAccepted(); +} + +bool QQuickPointerNativeGestureEvent::allPointsGrabbed() const +{ + return m_gesturePoint->exclusiveGrabber() != nullptr; +} + #ifndef QT_NO_DEBUG_STREAM Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice *dev) { @@ -942,7 +1842,9 @@ Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice * Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *event) { QDebugStateSaver saver(dbg); dbg.nospace(); - dbg << "QQuickPointerEvent(dev:"; + dbg << "QQuickPointerEvent("; + dbg << event->timestamp(); + dbg << " dev:"; QtDebugUtils::formatQEnum(dbg, event->device()->type()); if (event->buttons() != Qt::NoButton) { dbg << " buttons:"; @@ -959,10 +1861,10 @@ Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *e Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickEventPoint *event) { QDebugStateSaver saver(dbg); dbg.nospace(); - dbg << "QQuickEventPoint(valid:" << event->isValid() << " accepted:" << event->isAccepted() + dbg << "QQuickEventPoint(accepted:" << event->isAccepted() << " state:"; QtDebugUtils::formatQEnum(dbg, event->state()); - dbg << " scenePos:" << event->scenePos() << " id:" << hex << event->pointId() << dec + dbg << " scenePos:" << event->scenePosition() << " id:" << hex << event->pointId() << dec << " timeHeld:" << event->timeHeld() << ')'; return dbg; } diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index 3735d68a85..09a63febdc 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -66,8 +66,10 @@ QT_BEGIN_NAMESPACE class QQuickPointerDevice; class QQuickPointerEvent; class QQuickPointerMouseEvent; +class QQuickPointerNativeGestureEvent; class QQuickPointerTabletEvent; class QQuickPointerTouchEvent; +class QQuickPointerHandler; class QQuickKeyEvent : public QObject { @@ -251,12 +253,17 @@ private: class Q_QUICK_PRIVATE_EXPORT QQuickEventPoint : public QObject { Q_OBJECT - Q_PROPERTY(QPointF scenePos READ scenePos) + Q_PROPERTY(QQuickPointerEvent *event READ pointerEvent) + Q_PROPERTY(QPointF position READ position) + Q_PROPERTY(QPointF scenePosition READ scenePosition) + Q_PROPERTY(QPointF scenePressPosition READ scenePressPosition) + Q_PROPERTY(QPointF sceneGrabPosition READ sceneGrabPosition) Q_PROPERTY(State state READ state) - Q_PROPERTY(quint64 pointId READ pointId) + Q_PROPERTY(int pointId READ pointId) Q_PROPERTY(qreal timeHeld READ timeHeld) + Q_PROPERTY(QVector2D velocity READ velocity) Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted) - Q_PROPERTY(QQuickItem *grabber READ grabber WRITE setGrabber) + Q_PROPERTY(QObject *exclusiveGrabber READ exclusiveGrabber WRITE setExclusiveGrabber) public: enum State { @@ -264,37 +271,80 @@ public: Updated = Qt::TouchPointMoved, Stationary = Qt::TouchPointStationary, Released = Qt::TouchPointReleased - // Canceled = Qt::TouchPointReleased << 1 // 0x10 // TODO maybe }; - Q_ENUM(State) + Q_DECLARE_FLAGS(States, State) + Q_FLAG(States) + + enum GrabState { + GrabPassive = 0x01, + UngrabPassive = 0x02, + CancelGrabPassive = 0x03, + OverrideGrabPassive = 0x04, + GrabExclusive = 0x10, + UngrabExclusive = 0x20, + CancelGrabExclusive = 0x30, + }; + Q_ENUM(GrabState) QQuickEventPoint(QQuickPointerEvent *parent); - void reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp); - - void invalidate() { m_valid = false; } + void reset(Qt::TouchPointState state, const QPointF &scenePosition, int pointId, ulong timestamp, const QVector2D &velocity = QVector2D()); + void localizePosition(QQuickItem *target); QQuickPointerEvent *pointerEvent() const; - QPointF scenePos() const { return m_scenePos; } + QPointF position() const { return m_pos; } + QPointF scenePosition() const { return m_scenePos; } + QPointF scenePressPosition() const { return m_scenePressPos; } + QPointF sceneGrabPosition() const { return m_sceneGrabPos; } + QVector2D velocity() const { return m_velocity; } State state() const { return m_state; } - quint64 pointId() const { return m_pointId; } - bool isValid() const { return m_valid; } + int pointId() const { return m_pointId; } qreal timeHeld() const { return (m_timestamp - m_pressTimestamp) / 1000.0; } bool isAccepted() const { return m_accept; } void setAccepted(bool accepted = true); - QQuickItem *grabber() const; - void setGrabber(QQuickItem *grabber); + QObject *exclusiveGrabber() const; + void setExclusiveGrabber(QObject *exclusiveGrabber); + + QQuickItem *grabberItem() const; + Q_DECL_DEPRECATED QQuickItem *grabber() const { return grabberItem(); } + void setGrabberItem(QQuickItem *exclusiveGrabber); + + QQuickPointerHandler *grabberPointerHandler() const; + void setGrabberPointerHandler(QQuickPointerHandler *exclusiveGrabber, bool exclusive = false); + + void cancelExclusiveGrab(); + void cancelPassiveGrab(QQuickPointerHandler *handler); + bool removePassiveGrabber(QQuickPointerHandler *handler); + void cancelAllGrabs(QQuickPointerHandler *handler); + + QVector<QPointer <QQuickPointerHandler> > passiveGrabbers() const { return m_passiveGrabbers; } + void setPassiveGrabbers(const QVector<QPointer <QQuickPointerHandler> > &grabbers) { m_passiveGrabbers = grabbers; } + void clearPassiveGrabbers() { m_passiveGrabbers.clear(); } + +protected: + void cancelExclusiveGrabImpl(QTouchEvent *cancelEvent = nullptr); private: + QVector2D estimatedVelocity() const; + +protected: + QPointF m_pos; QPointF m_scenePos; - quint64 m_pointId; - QPointer<QQuickItem> m_grabber; + QPointF m_scenePressPos; + QPointF m_sceneGrabPos; + QVector2D m_velocity; + int m_pointId; + QPointer<QObject> m_exclusiveGrabber; + QVector<QPointer <QQuickPointerHandler> > m_passiveGrabbers; ulong m_timestamp; ulong m_pressTimestamp; State m_state; - bool m_valid : 1; bool m_accept : 1; - int m_reserved : 30; + bool m_grabberIsHandler : 1; + int m_reserved : 29; + + friend class QQuickPointerTouchEvent; + friend class QQuickWindowPrivate; Q_DISABLE_COPY(QQuickEventPoint) }; @@ -304,6 +354,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickEventTouchPoint : public QQuickEventPoint Q_OBJECT Q_PROPERTY(qreal rotation READ rotation) Q_PROPERTY(qreal pressure READ pressure) + Q_PROPERTY(QSizeF ellipseDiameters READ ellipseDiameters) Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId) public: @@ -313,13 +364,17 @@ public: qreal rotation() const { return m_rotation; } qreal pressure() const { return m_pressure; } + QSizeF ellipseDiameters() const { return m_ellipseDiameters; } QPointingDeviceUniqueId uniqueId() const { return m_uniqueId; } private: qreal m_rotation; qreal m_pressure; + QSizeF m_ellipseDiameters; QPointingDeviceUniqueId m_uniqueId; + friend class QQuickPointerTouchEvent; + Q_DISABLE_COPY(QQuickEventTouchPoint) }; @@ -350,25 +405,33 @@ public: // property accessors public: // helpers for C++ only (during event delivery) virtual QQuickPointerEvent *reset(QEvent *ev) = 0; + virtual void localize(QQuickItem *target) = 0; virtual bool isPressEvent() const = 0; + virtual bool isDoubleClickEvent() const { return false; } + virtual bool isUpdateEvent() const = 0; + virtual bool isReleaseEvent() const = 0; virtual QQuickPointerMouseEvent *asPointerMouseEvent() { return nullptr; } virtual QQuickPointerTouchEvent *asPointerTouchEvent() { return nullptr; } virtual QQuickPointerTabletEvent *asPointerTabletEvent() { return nullptr; } + virtual QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() { return nullptr; } virtual const QQuickPointerMouseEvent *asPointerMouseEvent() const { return nullptr; } virtual const QQuickPointerTouchEvent *asPointerTouchEvent() const { return nullptr; } virtual const QQuickPointerTabletEvent *asPointerTabletEvent() const { return nullptr; } - bool isValid() const { return m_event != nullptr; } + virtual const QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() const { return nullptr; } virtual bool allPointsAccepted() const = 0; + virtual bool allUpdatedPointsAccepted() const = 0; + virtual bool allPointsGrabbed() const = 0; bool isAccepted() { return m_event->isAccepted(); } void setAccepted(bool accepted) { m_event->setAccepted(accepted); } QVector<QPointF> unacceptedPressedPointScenePositions() const; virtual int pointCount() const = 0; virtual QQuickEventPoint *point(int i) const = 0; - virtual QQuickEventPoint *pointById(quint64 pointId) const = 0; - virtual QVector<QQuickItem *> grabbers() const = 0; + virtual QQuickEventPoint *pointById(int pointId) const = 0; + virtual QVector<QObject *> exclusiveGrabbers() const = 0; virtual void clearGrabbers() const = 0; + virtual bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const = 0; ulong timestamp() const { return m_event->timestamp(); } @@ -389,15 +452,22 @@ public: : QQuickPointerEvent(parent, device), m_mousePoint(new QQuickEventPoint(this)) { } QQuickPointerEvent *reset(QEvent *) override; + void localize(QQuickItem *target) override; bool isPressEvent() const override; + bool isDoubleClickEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; QQuickPointerMouseEvent *asPointerMouseEvent() override { return this; } const QQuickPointerMouseEvent *asPointerMouseEvent() const override { return this; } int pointCount() const override { return 1; } QQuickEventPoint *point(int i) const override; - QQuickEventPoint *pointById(quint64 pointId) const override; + QQuickEventPoint *pointById(int pointId) const override; bool allPointsAccepted() const override; - QVector<QQuickItem *> grabbers() const override; + bool allUpdatedPointsAccepted() const override; + bool allPointsGrabbed() const override; + QVector<QObject *> exclusiveGrabbers() const override; void clearGrabbers() const override; + bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const override; QMouseEvent *asMouseEvent(const QPointF& localPos) const; @@ -418,16 +488,22 @@ public: { } QQuickPointerEvent *reset(QEvent *) override; + void localize(QQuickItem *target) override; bool isPressEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; QQuickPointerTouchEvent *asPointerTouchEvent() override { return this; } const QQuickPointerTouchEvent *asPointerTouchEvent() const override { return this; } int pointCount() const override { return m_pointCount; } QQuickEventPoint *point(int i) const override; - QQuickEventPoint *pointById(quint64 pointId) const override; + QQuickEventPoint *pointById(int pointId) const override; const QTouchEvent::TouchPoint *touchPointById(int pointId) const; bool allPointsAccepted() const override; - QVector<QQuickItem *> grabbers() const override; + bool allUpdatedPointsAccepted() const override; + bool allPointsGrabbed() const override; + QVector<QObject *> exclusiveGrabbers() const override; void clearGrabbers() const override; + bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const override; QMouseEvent *syntheticMouseEvent(int pointID, QQuickItem *relativeTo) const; QTouchEvent *touchEventForItem(QQuickItem *item, bool isFiltering = false) const; @@ -442,6 +518,42 @@ private: Q_DISABLE_COPY(QQuickPointerTouchEvent) }; +class Q_QUICK_PRIVATE_EXPORT QQuickPointerNativeGestureEvent : public QQuickPointerEvent +{ + Q_OBJECT + Q_PROPERTY(Qt::NativeGestureType type READ type CONSTANT) + Q_PROPERTY(qreal value READ value CONSTANT) + +public: + QQuickPointerNativeGestureEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr) + : QQuickPointerEvent(parent, device), m_gesturePoint(new QQuickEventPoint(this)) { } + + QQuickPointerEvent *reset(QEvent *) override; + void localize(QQuickItem *target) override; + bool isPressEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; + QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() override { return this; } + const QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() const override { return this; } + int pointCount() const override { return 1; } + QQuickEventPoint *point(int i) const override; + QQuickEventPoint *pointById(int pointId) const override; + bool allPointsAccepted() const override; + bool allUpdatedPointsAccepted() const override; + bool allPointsGrabbed() const override; + QVector<QObject *> exclusiveGrabbers() const override; + void clearGrabbers() const override; + bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const override; + Qt::NativeGestureType type() const; + qreal value() const; + +private: + QQuickEventPoint *m_gesturePoint; + + Q_DISABLE_COPY(QQuickPointerNativeGestureEvent) +}; + + // ### Qt 6: move this to qtbase, replace QTouchDevice and the enums in QTabletEvent class Q_QUICK_PRIVATE_EXPORT QQuickPointerDevice : public QObject { @@ -497,13 +609,6 @@ public: Q_ENUM(CapabilityFlag) Q_FLAG(Capabilities) - QQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, int maxPoints, int buttonCount, const QString &name, qint64 uniqueId = 0) - : m_deviceType(devType), m_pointerType(pType), m_capabilities(caps) - , m_maximumTouchPoints(maxPoints), m_buttonCount(buttonCount), m_name(name) - , m_uniqueId(QPointingDeviceUniqueId::fromNumericId(uniqueId)) - { - } - DeviceType type() const { return m_deviceType; } PointerType pointerType() const { return m_pointerType; } Capabilities capabilities() const { return m_capabilities; } @@ -513,11 +618,22 @@ public: QString name() const { return m_name; } QPointingDeviceUniqueId uniqueId() const { return m_uniqueId; } - static QQuickPointerDevice *touchDevice(QTouchDevice *d); + static QQuickPointerDevice *touchDevice(const QTouchDevice *d); static QList<QQuickPointerDevice *> touchDevices(); static QQuickPointerDevice *genericMouseDevice(); static QQuickPointerDevice *tabletDevice(qint64); + QVector<QQuickPointerHandler *> &eventDeliveryTargets() { return m_eventDeliveryTargets; } + +private: + QQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, int maxPoints, int buttonCount, const QString &name, qint64 uniqueId = 0) + : m_deviceType(devType), m_pointerType(pType), m_capabilities(caps) + , m_maximumTouchPoints(maxPoints), m_buttonCount(buttonCount), m_name(name) + , m_uniqueId(QPointingDeviceUniqueId::fromNumericId(uniqueId)) + { + } + ~QQuickPointerDevice() { } + private: DeviceType m_deviceType; PointerType m_pointerType; @@ -526,8 +642,10 @@ private: int m_buttonCount; QString m_name; QPointingDeviceUniqueId m_uniqueId; + QVector<QQuickPointerHandler *> m_eventDeliveryTargets; // during delivery, handlers which have already seen the event Q_DISABLE_COPY(QQuickPointerDevice) + friend struct ConstructableQQuickPointerDevice; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPointerDevice::DeviceTypes) diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index c75a682f4a..ee5f177855 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -44,6 +44,7 @@ #include "qquickwindow_p.h" #include "qquickevents_p_p.h" +#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquicktransition_p.h> #include <private/qqmlglobal_p.h> @@ -255,6 +256,7 @@ QQuickFlickablePrivate::QQuickFlickablePrivate() , flickBoost(1.0), fixupMode(Normal), vTime(0), visibleArea(0) , flickableDirection(QQuickFlickable::AutoFlickDirection) , boundsBehavior(QQuickFlickable::DragAndOvershootBounds) + , boundsMovement(QQuickFlickable::FollowBoundsBehavior) , rebound(0) { } @@ -269,6 +271,7 @@ void QQuickFlickablePrivate::init() qmlobject_connect(&velocityTimeline, QQuickTimeLine, SIGNAL(completed()), q, QQuickFlickable, SLOT(velocityTimelineCompleted())) q->setAcceptedMouseButtons(Qt::LeftButton); + q->setAcceptTouchEvents(false); // rely on mouse events synthesized from touch q->setFiltersChildMouseEvents(true); QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem); viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry); @@ -564,18 +567,6 @@ void QQuickFlickablePrivate::updateBeginningEnd() visibleArea->updateVisible(); } -/* -XXXTODO add docs describing moving, dragging, flicking properties, e.g. - -When the user starts dragging the Flickable, the dragging and moving properties -will be true. - -If the velocity is sufficient when the drag is ended, flicking may begin. - -The moving properties will remain true until all dragging and flicking -is finished. -*/ - /*! \qmlsignal QtQuick::Flickable::dragStarted() @@ -1559,6 +1550,8 @@ void QQuickFlickablePrivate::replayDelayedPress() // If we have the grab, release before delivering the event if (QQuickWindow *w = q->window()) { + QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(w); + wpriv->allowChildEventFiltering = false; // don't allow re-filtering during replay replayingPressEvent = true; if (w->mouseGrabberItem() == q) q->ungrabMouse(); @@ -1566,6 +1559,7 @@ void QQuickFlickablePrivate::replayDelayedPress() // Use the event handler that will take care of finding the proper item to propagate the event QCoreApplication::sendEvent(w, mouseEvent.data()); replayingPressEvent = false; + wpriv->allowChildEventFiltering = true; } } } @@ -1574,16 +1568,18 @@ void QQuickFlickablePrivate::replayDelayedPress() void QQuickFlickablePrivate::setViewportX(qreal x) { Q_Q(QQuickFlickable); - if (pixelAligned) - x = -Round(-x); - - contentItem->setX(x); - if (contentItem->x() != x) - return; // reentered + qreal effectiveX = pixelAligned ? -Round(-x) : x; const qreal maxX = q->maxXExtent(); const qreal minX = q->minXExtent(); + if (boundsMovement == int(QQuickFlickable::StopAtBounds)) + effectiveX = qBound(maxX, effectiveX, minX); + + contentItem->setX(effectiveX); + if (contentItem->x() != effectiveX) + return; // reentered + qreal overshoot = 0.0; if (x <= maxX) overshoot = maxX - x; @@ -1599,16 +1595,18 @@ void QQuickFlickablePrivate::setViewportX(qreal x) void QQuickFlickablePrivate::setViewportY(qreal y) { Q_Q(QQuickFlickable); - if (pixelAligned) - y = -Round(-y); - - contentItem->setY(y); - if (contentItem->y() != y) - return; // reentered + qreal effectiveY = pixelAligned ? -Round(-y) : y; const qreal maxY = q->maxYExtent(); const qreal minY = q->minYExtent(); + if (boundsMovement == int(QQuickFlickable::StopAtBounds)) + effectiveY = qBound(maxY, effectiveY, minY); + + contentItem->setY(effectiveY); + if (contentItem->y() != effectiveY) + return; // reentered + qreal overshoot = 0.0; if (y <= maxY) overshoot = maxY - y; @@ -1869,8 +1867,9 @@ QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren() beyond the Flickable's boundaries, or overshoot the Flickable's boundaries when flicked. - This enables the feeling that the edges of the view are soft, - rather than a hard physical boundary. + When the \l boundsMovement is \c Flickable.FollowBoundsBehavior, a value + other than \c Flickable.StopAtBounds will give a feeling that the edges of + the view are soft, rather than a hard physical boundary. The \c boundsBehavior can be one of: @@ -1886,7 +1885,7 @@ QQmlListProperty<QQuickItem> QQuickFlickable::flickableChildren() boundary when flicked. \endlist - \sa horizontalOvershoot, verticalOvershoot + \sa horizontalOvershoot, verticalOvershoot, boundsMovement */ QQuickFlickable::BoundsBehavior QQuickFlickable::boundsBehavior() const { @@ -2697,7 +2696,11 @@ void QQuickFlickablePrivate::updateVelocity() The value is negative when the content is dragged or flicked beyond the beginning, and positive when beyond the end; \c 0.0 otherwise. - \sa verticalOvershoot, boundsBehavior + Whether the values are reported for dragging and/or flicking is determined by + \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement + is \c Flickable.StopAtBounds. + + \sa verticalOvershoot, boundsBehavior, boundsMovement */ qreal QQuickFlickable::horizontalOvershoot() const { @@ -2714,7 +2717,11 @@ qreal QQuickFlickable::horizontalOvershoot() const The value is negative when the content is dragged or flicked beyond the beginning, and positive when beyond the end; \c 0.0 otherwise. - \sa horizontalOvershoot, boundsBehavior + Whether the values are reported for dragging and/or flicking is determined by + \l boundsBehavior. The overshoot distance is reported even when \l boundsMovement + is \c Flickable.StopAtBounds. + + \sa horizontalOvershoot, boundsBehavior, boundsMovement */ qreal QQuickFlickable::verticalOvershoot() const { @@ -2722,6 +2729,68 @@ qreal QQuickFlickable::verticalOvershoot() const return d->vData.overshoot; } +/*! + \qmlproperty enumeration QtQuick::Flickable::boundsMovement + \since 5.10 + + This property holds whether the flickable will give a feeling that the edges of the + view are soft, rather than a hard physical boundary. + + The \c boundsMovement can be one of: + + \list + \li Flickable.StopAtBounds - this allows implementing custom edge effects where the + contents do not follow drags or flicks beyond the bounds of the flickable. The values + of \l horizontalOvershoot and \l verticalOvershoot can be utilized to implement custom + edge effects. + \li Flickable.FollowBoundsBehavior (default) - whether the contents follow drags or + flicks beyond the bounds of the flickable is determined by \l boundsBehavior. + \endlist + + The following example keeps the contents within bounds and instead applies a flip + effect when flicked over horizontal bounds: + \code + Flickable { + id: flickable + boundsMovement: Flickable.StopAtBounds + boundsBehavior: Flickable.DragAndOvershootBounds + transform: Rotation { + axis { x: 0; y: 1; z: 0 } + origin.x: flickable.width / 2 + origin.y: flickable.height / 2 + angle: Math.min(30, Math.max(-30, flickable.horizontalOvershoot)) + } + } + \endcode + + The following example keeps the contents within bounds and instead applies an opacity + effect when dragged over vertical bounds: + \code + Flickable { + boundsMovement: Flickable.StopAtBounds + boundsBehavior: Flickable.DragOverBounds + opacity: Math.max(0.5, 1.0 - Math.abs(verticalOvershoot) / height) + } + \endcode + + \sa boundsBehavior, verticalOvershoot, horizontalOvershoot +*/ +QQuickFlickable::BoundsMovement QQuickFlickable::boundsMovement() const +{ + Q_D(const QQuickFlickable); + return d->boundsMovement; +} + +void QQuickFlickable::setBoundsMovement(BoundsMovement movement) +{ + Q_D(QQuickFlickable); + if (d->boundsMovement == movement) + return; + + d->boundsMovement = movement; + emit boundsMovementChanged(); +} + QT_END_NAMESPACE #include "moc_qquickflickable_p.cpp" diff --git a/src/quick/items/qquickflickable_p.h b/src/quick/items/qquickflickable_p.h index 52cade1472..7558ee7df8 100644 --- a/src/quick/items/qquickflickable_p.h +++ b/src/quick/items/qquickflickable_p.h @@ -80,6 +80,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickFlickable : public QQuickItem Q_PROPERTY(qreal verticalVelocity READ verticalVelocity NOTIFY verticalVelocityChanged) Q_PROPERTY(BoundsBehavior boundsBehavior READ boundsBehavior WRITE setBoundsBehavior NOTIFY boundsBehaviorChanged) + Q_PROPERTY(BoundsMovement boundsMovement READ boundsMovement WRITE setBoundsMovement NOTIFY boundsMovementChanged REVISION 10) Q_PROPERTY(QQuickTransition *rebound READ rebound WRITE setRebound NOTIFY reboundChanged) Q_PROPERTY(qreal maximumFlickVelocity READ maximumFlickVelocity WRITE setMaximumFlickVelocity NOTIFY maximumFlickVelocityChanged) Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) @@ -132,6 +133,15 @@ public: BoundsBehavior boundsBehavior() const; void setBoundsBehavior(BoundsBehavior); + enum BoundsMovement { + // StopAtBounds = 0x0, + FollowBoundsBehavior = 0x1 + }; + Q_ENUM(BoundsMovement) + + BoundsMovement boundsMovement() const; + void setBoundsMovement(BoundsMovement movement); + QQuickTransition *rebound() const; void setRebound(QQuickTransition *transition); @@ -237,6 +247,7 @@ Q_SIGNALS: void flickableDirectionChanged(); void interactiveChanged(); void boundsBehaviorChanged(); + Q_REVISION(10) void boundsMovementChanged(); void reboundChanged(); void maximumFlickVelocityChanged(); void flickDecelerationChanged(); diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h index 1ceff22dfc..8609a15fcd 100644 --- a/src/quick/items/qquickflickable_p_p.h +++ b/src/quick/items/qquickflickable_p_p.h @@ -247,6 +247,7 @@ public: QQuickFlickableVisibleArea *visibleArea; QQuickFlickable::FlickableDirection flickableDirection; QQuickFlickable::BoundsBehavior boundsBehavior; + QQuickFlickable::BoundsMovement boundsMovement; QQuickTransition *rebound; void viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, diff --git a/src/quick/items/qquickgenericshadereffect.cpp b/src/quick/items/qquickgenericshadereffect.cpp index b366071962..305ef7e778 100644 --- a/src/quick/items/qquickgenericshadereffect.cpp +++ b/src/quick/items/qquickgenericshadereffect.cpp @@ -546,7 +546,10 @@ void QQuickGenericShaderEffect::updateShaderVars(Shader shaderType) // Have a QSignalMapper that emits mapped() with an index+type on each property change notify signal. auto &sm(m_signalMappers[shaderType][i]); if (!sm.mapper) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED sm.mapper = new QSignalMapper; +QT_WARNING_POP sm.mapper->setMapping(m_item, i | (shaderType << 16)); } sm.active = true; diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp index dacb43a421..7e13e5e0e1 100644 --- a/src/quick/items/qquickimage.cpp +++ b/src/quick/items/qquickimage.cpp @@ -515,37 +515,41 @@ void QQuickImage::updatePaintedGeometry() setImplicitSize(0, 0); return; } - qreal w = widthValid() ? width() : d->pix.width(); - qreal widthScale = w / qreal(d->pix.width()); - qreal h = heightValid() ? height() : d->pix.height(); - qreal heightScale = h / qreal(d->pix.height()); + const qreal pixWidth = d->pix.width() / d->devicePixelRatio; + const qreal pixHeight = d->pix.height() / d->devicePixelRatio; + const qreal w = widthValid() ? width() : pixWidth; + const qreal widthScale = w / pixWidth; + const qreal h = heightValid() ? height() : pixHeight; + const qreal heightScale = h / pixHeight; if (widthScale <= heightScale) { d->paintedWidth = w; - d->paintedHeight = widthScale * qreal(d->pix.height()); + d->paintedHeight = widthScale * pixHeight; } else if (heightScale < widthScale) { - d->paintedWidth = heightScale * qreal(d->pix.width()); + d->paintedWidth = heightScale * pixWidth; d->paintedHeight = h; } - qreal iHeight = (widthValid() && !heightValid()) ? d->paintedHeight : d->pix.height(); - qreal iWidth = (heightValid() && !widthValid()) ? d->paintedWidth : d->pix.width(); + const qreal iHeight = (widthValid() && !heightValid()) ? d->paintedHeight : pixHeight; + const qreal iWidth = (heightValid() && !widthValid()) ? d->paintedWidth : pixWidth; setImplicitSize(iWidth, iHeight); } else if (d->fillMode == PreserveAspectCrop) { if (!d->pix.width() || !d->pix.height()) return; - qreal widthScale = width() / qreal(d->pix.width()); - qreal heightScale = height() / qreal(d->pix.height()); + const qreal pixWidth = d->pix.width() / d->devicePixelRatio; + const qreal pixHeight = d->pix.height() / d->devicePixelRatio; + qreal widthScale = width() / pixWidth; + qreal heightScale = height() / pixHeight; if (widthScale < heightScale) { widthScale = heightScale; } else if (heightScale < widthScale) { heightScale = widthScale; } - d->paintedHeight = heightScale * qreal(d->pix.height()); - d->paintedWidth = widthScale * qreal(d->pix.width()); + d->paintedHeight = heightScale * pixHeight; + d->paintedWidth = widthScale * pixWidth; } else if (d->fillMode == Pad) { - d->paintedWidth = d->pix.width(); - d->paintedHeight = d->pix.height(); + d->paintedWidth = d->pix.width() / d->devicePixelRatio; + d->paintedHeight = d->pix.height() / d->devicePixelRatio; } else { d->paintedWidth = width(); d->paintedHeight = height(); @@ -556,7 +560,8 @@ void QQuickImage::updatePaintedGeometry() void QQuickImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { QQuickImageBase::geometryChanged(newGeometry, oldGeometry); - updatePaintedGeometry(); + if (newGeometry.size() != oldGeometry.size()) + updatePaintedGeometry(); } QRectF QQuickImage::boundingRect() const diff --git a/src/quick/items/qquickimage_p_p.h b/src/quick/items/qquickimage_p_p.h index 522dbca803..afc33def0f 100644 --- a/src/quick/items/qquickimage_p_p.h +++ b/src/quick/items/qquickimage_p_p.h @@ -58,7 +58,7 @@ QT_BEGIN_NAMESPACE class QQuickImageTextureProvider; -class QQuickImagePrivate : public QQuickImageBasePrivate +class Q_QUICK_PRIVATE_EXPORT QQuickImagePrivate : public QQuickImageBasePrivate { Q_DECLARE_PUBLIC(QQuickImage) diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index e8200bb97f..74ee859f77 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -49,6 +49,30 @@ QT_BEGIN_NAMESPACE +// This function gives derived classes the chance set the devicePixelRatio +// if they're not happy with our implementation of it. +bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio) +{ + // QQuickImageProvider and SVG can generate a high resolution image when + // sourceSize is set (this function is only called if it's set). + // If sourceSize is not set then the provider default size will be used, as usual. + bool setDevicePixelRatio = false; + if (url.scheme() == QLatin1String("image")) { + setDevicePixelRatio = true; + } else { + QString stringUrl = url.path(QUrl::PrettyDecoded); + if (stringUrl.endsWith(QLatin1String("svg")) || + stringUrl.endsWith(QLatin1String("svgz"))) { + setDevicePixelRatio = true; + } + } + + if (setDevicePixelRatio) + devicePixelRatio = targetDevicePixelRatio; + + return setDevicePixelRatio; +} + QQuickImageBase::QQuickImageBase(QQuickItem *parent) : QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent) { @@ -221,26 +245,11 @@ void QQuickImageBase::load() QUrl loadUrl = d->url; - // QQuickImageProvider and SVG can generate a high resolution image when - // sourceSize is set. If sourceSize is not set then the provider default size - // will be used, as usual. - bool setDevicePixelRatio = false; - if (d->sourcesize.isValid()) { - if (loadUrl.scheme() == QLatin1String("image")) { - setDevicePixelRatio = true; - } else { - QString stringUrl = loadUrl.path(QUrl::PrettyDecoded); - if (stringUrl.endsWith(QLatin1String("svg")) || - stringUrl.endsWith(QLatin1String("svgz"))) { - setDevicePixelRatio = true; - } - } - - if (setDevicePixelRatio) - d->devicePixelRatio = targetDevicePixelRatio; - } + bool updatedDevicePixelRatio = false; + if (d->sourcesize.isValid()) + updatedDevicePixelRatio = d->updateDevicePixelRatio(targetDevicePixelRatio); - if (!setDevicePixelRatio) { + if (!updatedDevicePixelRatio) { // (possible) local file: loadUrl and d->devicePixelRatio will be modified if // an "@2x" file is found. resolve2xLocalFile(d->url, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio); @@ -319,18 +328,18 @@ void QQuickImageBase::requestProgress(qint64 received, qint64 total) void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value) { - if (change == ItemSceneChange && value.window) - connect(value.window, &QQuickWindow::screenChanged, this, &QQuickImageBase::handleScreenChanged); + Q_D(QQuickImageBase); + // If the screen DPI changed, reload image. + if (change == ItemDevicePixelRatioHasChanged && value.realValue != d->devicePixelRatio) { + // ### how can we get here with !qmlEngine(this)? that implies + // itemChange() on an item pending deletion, which seems strange. + if (qmlEngine(this) && isComponentComplete() && d->url.isValid()) { + load(); + } + } QQuickItem::itemChange(change, value); } -void QQuickImageBase::handleScreenChanged(QScreen* screen) -{ - // Screen DPI might have changed, reload images on screen change. - if (qmlEngine(this) && screen && isComponentComplete()) - load(); -} - void QQuickImageBase::componentComplete() { Q_D(QQuickImageBase); diff --git a/src/quick/items/qquickimagebase_p.h b/src/quick/items/qquickimagebase_p.h index 532f6ce683..54b1f789c9 100644 --- a/src/quick/items/qquickimagebase_p.h +++ b/src/quick/items/qquickimagebase_p.h @@ -123,7 +123,6 @@ protected: private Q_SLOTS: virtual void requestFinished(); void requestProgress(qint64,qint64); - void handleScreenChanged(QScreen *screen); private: Q_DISABLE_COPY(QQuickImageBase) diff --git a/src/quick/items/qquickimagebase_p_p.h b/src/quick/items/qquickimagebase_p_p.h index d9b609c7fe..1b771166a2 100644 --- a/src/quick/items/qquickimagebase_p_p.h +++ b/src/quick/items/qquickimagebase_p_p.h @@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE class QNetworkReply; -class QQuickImageBasePrivate : public QQuickImplicitSizeItemPrivate +class Q_QUICK_PRIVATE_EXPORT QQuickImageBasePrivate : public QQuickImplicitSizeItemPrivate { Q_DECLARE_PUBLIC(QQuickImageBase) @@ -75,6 +75,8 @@ public: { } + virtual bool updateDevicePixelRatio(qreal targetDevicePixelRatio); + QQuickPixmap pix; QQuickImageBase::Status status; QUrl url; diff --git a/src/quick/items/qquickimplicitsizeitem.cpp b/src/quick/items/qquickimplicitsizeitem.cpp index 1996fb9489..2569b2a224 100644 --- a/src/quick/items/qquickimplicitsizeitem.cpp +++ b/src/quick/items/qquickimplicitsizeitem.cpp @@ -45,29 +45,11 @@ QT_BEGIN_NAMESPACE /*! \internal - The purpose of QQuickImplicitSizeItem is not immediately clear, as both - the implicit size properties and signals exist on QQuickItem. However, - for some items - where the implicit size has an underlying meaning (such as - Image, where the implicit size represents the real size of the image) - having implicit size writable is an undesirable thing. - - QQuickImplicitSizeItem redefines the properties as being readonly. - Unfortunately, this also means they need to redefine the change signals. - See QTBUG-30258 for more information. + QQuickImplicitSizeItem redefines the implicitWidth and implicitHeight + properties as readonly, as some items (e.g. Image, where the implicit size + represents the real size of the image) should not be able to have their + implicit size modified. */ -void QQuickImplicitSizeItemPrivate::implicitWidthChanged() -{ - Q_Q(QQuickImplicitSizeItem); - QQuickItemPrivate::implicitWidthChanged(); - emit q->implicitWidthChanged2(); -} - -void QQuickImplicitSizeItemPrivate::implicitHeightChanged() -{ - Q_Q(QQuickImplicitSizeItem); - QQuickItemPrivate::implicitHeightChanged(); - emit q->implicitHeightChanged2(); -} QQuickImplicitSizeItem::QQuickImplicitSizeItem(QQuickImplicitSizeItemPrivate &dd, QQuickItem *parent) : QQuickItem(dd, parent) diff --git a/src/quick/items/qquickimplicitsizeitem_p.h b/src/quick/items/qquickimplicitsizeitem_p.h index 75b04449f8..8ae8f9f447 100644 --- a/src/quick/items/qquickimplicitsizeitem_p.h +++ b/src/quick/items/qquickimplicitsizeitem_p.h @@ -60,16 +60,12 @@ class QQuickImplicitSizeItemPrivate; class Q_QUICK_PRIVATE_EXPORT QQuickImplicitSizeItem : public QQuickItem { Q_OBJECT - Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged2) - Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged2) + Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged) + Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged) protected: QQuickImplicitSizeItem(QQuickImplicitSizeItemPrivate &dd, QQuickItem *parent); -Q_SIGNALS: - Q_REVISION(1) void implicitWidthChanged2(); - Q_REVISION(1) void implicitHeightChanged2(); - private: Q_DISABLE_COPY(QQuickImplicitSizeItem) Q_DECLARE_PRIVATE(QQuickImplicitSizeItem) diff --git a/src/quick/items/qquickimplicitsizeitem_p_p.h b/src/quick/items/qquickimplicitsizeitem_p_p.h index 2c42fa62f2..0495cf87e1 100644 --- a/src/quick/items/qquickimplicitsizeitem_p_p.h +++ b/src/quick/items/qquickimplicitsizeitem_p_p.h @@ -65,9 +65,6 @@ public: QQuickImplicitSizeItemPrivate() { } - - void implicitWidthChanged() Q_DECL_OVERRIDE; - void implicitHeightChanged() Q_DECL_OVERRIDE; }; QT_END_NAMESPACE diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 3571feb2cc..ccc4e90abf 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -67,6 +67,7 @@ #include <QtQuick/private/qquickstate_p.h> #include <private/qquickitem_p.h> #include <QtQuick/private/qquickaccessibleattached_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <private/qv4engine_p.h> #include <private/qv4object_p.h> @@ -164,7 +165,7 @@ void QQuickTransform::update() } QQuickContents::QQuickContents(QQuickItem *item) -: m_item(item), m_x(0), m_y(0), m_width(0), m_height(0) +: m_item(item) { } @@ -179,8 +180,8 @@ QQuickContents::~QQuickContents() bool QQuickContents::calcHeight(QQuickItem *changed) { - qreal oldy = m_y; - qreal oldheight = m_height; + qreal oldy = m_contents.y(); + qreal oldheight = m_contents.height(); if (changed) { qreal top = oldy; @@ -190,8 +191,8 @@ bool QQuickContents::calcHeight(QQuickItem *changed) bottom = y + changed->height(); if (y < top) top = y; - m_y = top; - m_height = bottom - top; + m_contents.setY(top); + m_contents.setHeight(bottom - top); } else { qreal top = std::numeric_limits<qreal>::max(); qreal bottom = -std::numeric_limits<qreal>::max(); @@ -205,17 +206,17 @@ bool QQuickContents::calcHeight(QQuickItem *changed) top = y; } if (!children.isEmpty()) - m_y = top; - m_height = qMax(bottom - top, qreal(0.0)); + m_contents.setY(top); + m_contents.setHeight(qMax(bottom - top, qreal(0.0))); } - return (m_height != oldheight || m_y != oldy); + return (m_contents.height() != oldheight || m_contents.y() != oldy); } bool QQuickContents::calcWidth(QQuickItem *changed) { - qreal oldx = m_x; - qreal oldwidth = m_width; + qreal oldx = m_contents.x(); + qreal oldwidth = m_contents.width(); if (changed) { qreal left = oldx; @@ -225,8 +226,8 @@ bool QQuickContents::calcWidth(QQuickItem *changed) right = x + changed->width(); if (x < left) left = x; - m_x = left; - m_width = right - left; + m_contents.setX(left); + m_contents.setWidth(right - left); } else { qreal left = std::numeric_limits<qreal>::max(); qreal right = -std::numeric_limits<qreal>::max(); @@ -240,11 +241,11 @@ bool QQuickContents::calcWidth(QQuickItem *changed) left = x; } if (!children.isEmpty()) - m_x = left; - m_width = qMax(right - left, qreal(0.0)); + m_contents.setX(left); + m_contents.setWidth(qMax(right - left, qreal(0.0))); } - return (m_width != oldwidth || m_x != oldx); + return (m_contents.width() != oldwidth || m_contents.x() != oldx); } void QQuickContents::complete() @@ -2139,6 +2140,9 @@ void QQuickItemPrivate::updateSubFocusItem(QQuickItem *scope, bool focus) \value ItemAntialiasingHasChanged The antialiasing has changed. The current (boolean) value can be found in QQuickItem::antialiasing. + + \value ItemEnabledHasChanged The item's enabled state has changed. + ItemChangeData::boolValue contains the new enabled state. (since Qt 5.10) */ /*! @@ -3187,6 +3191,11 @@ QQuickItemPrivate::QQuickItemPrivate() , antialiasingValid(false) , isTabFence(false) , replayingPressEvent(false) +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + , touchEnabled(true) +#else + , touchEnabled(false) +#endif , dirtyAttributes(0) , nextDirtyItem(0) , prevDirtyItem(0) @@ -3238,7 +3247,14 @@ void QQuickItemPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o) } else { if (o->inherits("QGraphicsItem")) qWarning("Cannot add a QtQuick 1.0 item (%s) into a QtQuick 2.0 scene!", o->metaObject()->className()); - else { + else if (QQuickPointerHandler *pointerHandler = qmlobject_cast<QQuickPointerHandler *>(o)) { + Q_ASSERT(pointerHandler->parentItem() == that); + // Accept all buttons, and leave filtering to pointerEvent() and/or user JS, + // because there can be multiple handlers... + that->setAcceptedMouseButtons(Qt::AllButtons); + QQuickItemPrivate *p = QQuickItemPrivate::get(that); + p->extra.value().pointerHandlers.append(pointerHandler); + } else { QQuickWindow *thisWindow = qmlobject_cast<QQuickWindow *>(o); QQuickItem *item = that; QQuickWindow *itemWindow = that->window(); @@ -5014,22 +5030,31 @@ void QQuickItemPrivate::transformChanged() #endif } +bool QQuickItemPrivate::filterKeyEvent(QKeyEvent *e, bool post) +{ + if (!extra.isAllocated() || !extra->keyHandler) + return false; + + if (post) + e->accept(); + + if (e->type() == QEvent::KeyPress) + extra->keyHandler->keyPressed(e, post); + else + extra->keyHandler->keyReleased(e, post); + + return e->isAccepted(); +} + void QQuickItemPrivate::deliverKeyEvent(QKeyEvent *e) { Q_Q(QQuickItem); Q_ASSERT(e->isAccepted()); - if (extra.isAllocated() && extra->keyHandler) { - if (e->type() == QEvent::KeyPress) - extra->keyHandler->keyPressed(e, false); - else - extra->keyHandler->keyReleased(e, false); - - if (e->isAccepted()) - return; - else - e->accept(); - } + if (filterKeyEvent(e, false)) + return; + else + e->accept(); if (e->type() == QEvent::KeyPress) q->keyPressEvent(e); @@ -5039,16 +5064,7 @@ void QQuickItemPrivate::deliverKeyEvent(QKeyEvent *e) if (e->isAccepted()) return; - if (extra.isAllocated() && extra->keyHandler) { - e->accept(); - - if (e->type() == QEvent::KeyPress) - extra->keyHandler->keyPressed(e, true); - else - extra->keyHandler->keyReleased(e, true); - } - - if (e->isAccepted() || !q->window()) + if (filterKeyEvent(e, true) || !q->window()) return; //only care about KeyPress now @@ -5103,6 +5119,28 @@ void QQuickItemPrivate::deliverShortcutOverrideEvent(QKeyEvent *event) } /*! + \internal + Deliver the \a event to all PointerHandlers which are in the pre-determined + eventDeliveryTargets() vector. If \a avoidExclusiveGrabber is true, it skips + delivery to any handler which is the exclusive grabber of any point within this event + (because delivery to exclusive grabbers is handled separately). +*/ +bool QQuickItemPrivate::handlePointerEvent(QQuickPointerEvent *event, bool avoidExclusiveGrabber) +{ + bool delivered = false; + QVector<QQuickPointerHandler *> &eventDeliveryTargets = event->device()->eventDeliveryTargets(); + if (extra.isAllocated()) { + for (QQuickPointerHandler *handler : extra->pointerHandlers) { + if ((!avoidExclusiveGrabber || !event->hasExclusiveGrabber(handler)) && !eventDeliveryTargets.contains(handler)) { + handler->handlePointerEvent(event); + delivered = true; + } + } + } + return delivered; +} + +/*! Called when \a change occurs for this item. \a value contains extra information relating to the change, when @@ -5928,6 +5966,7 @@ void QQuickItemPrivate::setEffectiveEnableRecur(QQuickItem *scope, bool newEffec scope, q, Qt::OtherFocusReason, QQuickWindowPrivate::DontChangeFocusProperty | QQuickWindowPrivate::DontChangeSubFocusItem); } + itemChange(QQuickItem::ItemEnabledHasChanged, effectiveEnable); emit q->enabledChanged(); } @@ -6100,6 +6139,18 @@ void QQuickItemPrivate::itemChange(QQuickItem::ItemChange change, const QQuickIt } break; } + case QQuickItem::ItemEnabledHasChanged: { + q->itemChange(change, data); + if (!changeListeners.isEmpty()) { + const auto listeners = changeListeners; // NOTE: intentional copy (QTBUG-54732) + for (const QQuickItemPrivate::ChangeListener &change : listeners) { + if (change.types & QQuickItemPrivate::Enabled) { + change.listener->itemEnabledChanged(q); + } + } + } + break; + } case QQuickItem::ItemParentHasChanged: { q->itemChange(change, data); if (!changeListeners.isEmpty()) { @@ -6764,8 +6815,27 @@ bool QQuickItem::heightValid() const } /*! - \internal - */ + \since 5.10 + + Returns the size of the item. + + \sa setSize, width, height + */ + +QSizeF QQuickItem::size() const +{ + Q_D(const QQuickItem); + return QSizeF(d->width, d->height); +} + + +/*! + \since 5.10 + + Sets the size of the item to \a size. + + \sa size, setWidth, setHeight + */ void QQuickItem::setSize(const QSizeF &size) { Q_D(QQuickItem); @@ -7155,6 +7225,36 @@ void QQuickItem::setAcceptHoverEvents(bool enabled) d->setHasHoverInChild(enabled); } +/*! + Returns whether touch events are accepted by this item. + + The default value is false. + + If this is false, then the item will not receive any touch events through + the touchEvent() function. + + \since 5.10 +*/ +bool QQuickItem::acceptTouchEvents() const +{ + Q_D(const QQuickItem); + return d->touchEnabled; +} + +/*! + If \a enabled is true, this sets the item to accept touch events; + otherwise, touch events are not accepted by this item. + + \since 5.10 + + \sa acceptTouchEvents() +*/ +void QQuickItem::setAcceptTouchEvents(bool enabled) +{ + Q_D(QQuickItem); + d->touchEnabled = enabled; +} + void QQuickItemPrivate::setHasCursorInChild(bool hasCursor) { #if QT_CONFIG(cursor) @@ -7873,6 +7973,11 @@ QQuickItemLayer *QQuickItemPrivate::layer() const #endif } +bool QQuickItemPrivate::hasPointerHandlers() const +{ + return extra.isAllocated() && !extra->pointerHandlers.isEmpty(); +} + #if QT_CONFIG(quick_shadereffect) QQuickItemLayer::QQuickItemLayer(QQuickItem *item) : m_item(item) @@ -7887,6 +7992,7 @@ QQuickItemLayer::QQuickItemLayer(QQuickItem *item) , m_effect(0) , m_effectSource(0) , m_textureMirroring(QQuickShaderEffectSource::MirrorVertically) + , m_samples(0) { } @@ -7961,6 +8067,7 @@ void QQuickItemLayer::activate() m_effectSource->setWrapMode(m_wrapMode); m_effectSource->setFormat(m_format); m_effectSource->setTextureMirroring(m_textureMirroring); + m_effectSource->setSamples(m_samples); if (m_effectComponent) activateEffect(); @@ -8254,6 +8361,44 @@ void QQuickItemLayer::setTextureMirroring(QQuickShaderEffectSource::TextureMirro } /*! + \qmlproperty enumeration QtQuick::Item::layer.samples + \since 5.10 + + This property allows requesting multisampled rendering in the layer. + + By default multisampling is enabled whenever multisampling is + enabled for the entire window, assuming the scenegraph renderer in + use and the underlying graphics API supports this. + + By setting the value to 2, 4, etc. multisampled rendering can be requested + for a part of the scene without enabling multisampling for the entire + scene. This way multisampling is applied only to a given subtree, which can + lead to significant performance gains since multisampling is not applied to + other parts of the scene. + + \note Enabling multisampling can be potentially expensive regardless of the + layer's size, as it incurs a hardware and driver dependent performance and + memory cost. + + \note This property is only functional when support for multisample + renderbuffers and framebuffer blits is available. Otherwise the value is + silently ignored. + */ + +void QQuickItemLayer::setSamples(int count) +{ + if (m_samples == count) + return; + + m_samples = count; + + if (m_effectSource) + m_effectSource->setSamples(m_samples); + + emit samplesChanged(count); +} + +/*! \qmlproperty string QtQuick::Item::layer.samplerName Holds the name of the effect's source texture property. @@ -8402,19 +8547,19 @@ struct QQuickItemWrapper : public QObjectWrapper { struct QQuickItemWrapper : public QV4::QObjectWrapper { V4_OBJECT2(QQuickItemWrapper, QV4::QObjectWrapper) - static void markObjects(QV4::Heap::Base *that, QV4::ExecutionEngine *e); + static void markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack); }; DEFINE_OBJECT_VTABLE(QQuickItemWrapper); -void QQuickItemWrapper::markObjects(QV4::Heap::Base *that, QV4::ExecutionEngine *e) +void QQuickItemWrapper::markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack) { QObjectWrapper::Data *This = static_cast<QObjectWrapper::Data *>(that); if (QQuickItem *item = static_cast<QQuickItem*>(This->object())) { for (QQuickItem *child : qAsConst(QQuickItemPrivate::get(item)->childItems)) - QV4::QObjectWrapper::markWrapper(child, e); + QV4::QObjectWrapper::markWrapper(child, markStack); } - QV4::QObjectWrapper::markObjects(that, e); + QV4::QObjectWrapper::markObjects(that, markStack); } quint64 QQuickItemPrivate::_q_createJSWrapper(QV4::ExecutionEngine *engine) diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index c9494d91bd..25641f16f9 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -173,7 +173,8 @@ public: ItemActiveFocusHasChanged, // value.boolValue ItemRotationHasChanged, // value.realValue ItemAntialiasingHasChanged, // value.boolValue - ItemDevicePixelRatioHasChanged // value.realValue + ItemDevicePixelRatioHasChanged, // value.realValue + ItemEnabledHasChanged // value.boolValue }; union ItemChangeData { @@ -237,6 +238,7 @@ public: void setImplicitHeight(qreal); qreal implicitHeight() const; + QSizeF size() const; void setSize(const QSizeF &size); TransformOrigin transformOrigin() const; @@ -291,6 +293,8 @@ public: void setAcceptedMouseButtons(Qt::MouseButtons buttons); bool acceptHoverEvents() const; void setAcceptHoverEvents(bool enabled); + bool acceptTouchEvents() const; + void setAcceptTouchEvents(bool accept); #if QT_CONFIG(cursor) QCursor cursor() const; @@ -447,6 +451,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_resourceObjectDeleted(QObject *)) Q_PRIVATE_SLOT(d_func(), quint64 _q_createJSWrapper(QV4::ExecutionEngine *)) + friend class QQuickEventPoint; friend class QQuickWindow; friend class QQuickWindowPrivate; friend class QSGRenderer; diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index c0c9bd46bd..2f0c316602 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -87,6 +87,7 @@ class QQuickItemKeyFilter; class QQuickLayoutMirroringAttached; class QQuickEnterKeyAttached; class QQuickScreenAttached; +class QQuickPointerHandler; class QQuickContents : public QQuickItemChangeListener { @@ -94,7 +95,7 @@ public: QQuickContents(QQuickItem *item); ~QQuickContents(); - QRectF rectF() const { return QRectF(m_x, m_y, m_width, m_height); } + QRectF rectF() const { return m_contents; } inline void calcGeometry(QQuickItem *changed = 0); void complete(); @@ -112,10 +113,7 @@ private: void updateRect(); QQuickItem *m_item; - qreal m_x; - qreal m_y; - qreal m_width; - qreal m_height; + QRectF m_contents; }; void QQuickContents::calcGeometry(QQuickItem *changed) @@ -152,6 +150,8 @@ class QQuickItemLayer : public QObject, public QQuickItemChangeListener Q_PROPERTY(QByteArray samplerName READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QQmlComponent *effect READ effect WRITE setEffect NOTIFY effectChanged) Q_PROPERTY(QQuickShaderEffectSource::TextureMirroring textureMirroring READ textureMirroring WRITE setTextureMirroring NOTIFY textureMirroringChanged) + Q_PROPERTY(int samples READ samples WRITE setSamples NOTIFY samplesChanged) + public: QQuickItemLayer(QQuickItem *item); ~QQuickItemLayer(); @@ -189,6 +189,9 @@ public: QQuickShaderEffectSource::TextureMirroring textureMirroring() const { return m_textureMirroring; } void setTextureMirroring(QQuickShaderEffectSource::TextureMirroring mirroring); + int samples() const { return m_samples; } + void setSamples(int count); + QQuickShaderEffectSource *effectSource() const { return m_effectSource; } void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &) Q_DECL_OVERRIDE; @@ -213,6 +216,7 @@ Q_SIGNALS: void formatChanged(QQuickShaderEffectSource::Format format); void sourceRectChanged(const QRectF &sourceRect); void textureMirroringChanged(QQuickShaderEffectSource::TextureMirroring mirroring); + void samplesChanged(int count); private: friend class QQuickTransformAnimatorJob; @@ -237,6 +241,7 @@ private: QQuickItem *m_effect; QQuickShaderEffectSource *m_effectSource; QQuickShaderEffectSource::TextureMirroring m_textureMirroring; + int m_samples; }; #endif @@ -274,6 +279,8 @@ public: QQuickItemLayer *layer() const; + bool hasPointerHandlers() const; + // data property static void data_append(QQmlListProperty<QObject> *, QObject *); static int data_count(QQmlListProperty<QObject> *); @@ -317,7 +324,8 @@ public: Children = 0x40, Rotation = 0x80, ImplicitWidth = 0x100, - ImplicitHeight = 0x200 + ImplicitHeight = 0x200, + Enabled = 0x400, }; Q_DECLARE_FLAGS(ChangeTypes, ChangeType) @@ -344,6 +352,7 @@ public: QQuickLayoutMirroringAttached* layoutDirectionAttached; QQuickEnterKeyAttached *enterKeyAttached; QQuickItemKeyFilter *keyHandler; + QVector<QQuickPointerHandler *> pointerHandlers; #if QT_CONFIG(quick_shadereffect) mutable QQuickItemLayer *layer; #endif @@ -433,6 +442,7 @@ public: // focus chain and prevents tabbing outside. bool isTabFence:1; bool replayingPressEvent:1; + bool touchEnabled:1; enum DirtyType { TransformOrigin = 0x00000001, @@ -556,11 +566,14 @@ public: virtual void transformChanged(); void deliverKeyEvent(QKeyEvent *); + bool filterKeyEvent(QKeyEvent *, bool post); #if QT_CONFIG(im) void deliverInputMethodEvent(QInputMethodEvent *); #endif void deliverShortcutOverrideEvent(QKeyEvent *); + virtual bool handlePointerEvent(QQuickPointerEvent *, bool avoidExclusiveGrabber = false); + bool isTransparentForPositioner() const; void setTransparentForPositioner(bool trans); diff --git a/src/quick/items/qquickitemanimation.cpp b/src/quick/items/qquickitemanimation.cpp index 714a1a9012..d4d346def9 100644 --- a/src/quick/items/qquickitemanimation.cpp +++ b/src/quick/items/qquickitemanimation.cpp @@ -327,7 +327,7 @@ QAbstractAnimationJob* QQuickParentAnimation::transition(QQuickStateActions &act } if (scale != 0) - rotation = qAtan2(transform.m12()/scale, transform.m11()/scale) * 180/M_PI; + rotation = qRadiansToDegrees(qAtan2(transform.m12() / scale, transform.m11() / scale)); else { qmlWarning(this) << QQuickParentAnimation::tr("Unable to preserve appearance under scale of 0"); ok = false; diff --git a/src/quick/items/qquickitemchangelistener_p.h b/src/quick/items/qquickitemchangelistener_p.h index 83c69a9330..cb0af75c4c 100644 --- a/src/quick/items/qquickitemchangelistener_p.h +++ b/src/quick/items/qquickitemchangelistener_p.h @@ -125,6 +125,7 @@ public: virtual void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF & /* oldGeometry */) {} virtual void itemSiblingOrderChanged(QQuickItem *) {} virtual void itemVisibilityChanged(QQuickItem *) {} + virtual void itemEnabledChanged(QQuickItem *) {} virtual void itemOpacityChanged(QQuickItem *) {} virtual void itemDestroyed(QQuickItem *) {} virtual void itemChildAdded(QQuickItem *, QQuickItem * /* child */ ) {} diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 5f6d44b54d..1406e5b547 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -376,6 +376,12 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterType<QQuickFlickable, 9>(uri, 2, 9, "Flickable"); qmlRegisterType<QQuickMouseArea, 9>(uri, 2, 9, "MouseArea"); + +#if QT_CONFIG(quick_path) + qmlRegisterType<QQuickPathArc, 2>(uri, 2, 9, "PathArc"); + qmlRegisterType<QQuickPathMove>(uri, 2, 9, "PathMove"); +#endif + qmlRegisterType<QQuickText, 9>(uri, 2, 9, "Text"); qmlRegisterType<QQuickTextInput, 9>(uri, 2, 9, "TextInput"); qmlRegisterType<QQuickTouchPoint>(uri, 2, 9, "TouchPoint"); @@ -385,6 +391,14 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterUncreatableType<QQuickBasePositioner, 9>(uri, 2, 9, "Positioner", QStringLiteral("Positioner is an abstract type that is only available as an attached property.")); #endif + +#if QT_CONFIG(quick_shadereffect) + qmlRegisterType<QQuickShaderEffectSource, 2>(uri, 2, 9, "ShaderEffectSource"); +#endif + + qmlRegisterType<QQuickFlickable, 10>(uri, 2, 10, "Flickable"); + qmlRegisterType<QQuickTextEdit, 10>(uri, 2, 10, "TextEdit"); + qmlRegisterType<QQuickText, 10>(uri, 2, 10, "Text"); } static void initResources() diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index d8bad7d793..96f34ef276 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -85,6 +85,7 @@ void QQuickMouseAreaPrivate::init() { Q_Q(QQuickMouseArea); q->setAcceptedMouseButtons(Qt::LeftButton); + q->setAcceptTouchEvents(false); // rely on mouse events synthesized from touch q->setFiltersChildMouseEvents(true); if (qmlVisualTouchDebugging()) { q->setFlag(QQuickItem::ItemHasContents); diff --git a/src/quick/items/qquickmultipointtoucharea.cpp b/src/quick/items/qquickmultipointtoucharea.cpp index d4c447a384..54136b1bbf 100644 --- a/src/quick/items/qquickmultipointtoucharea.cpp +++ b/src/quick/items/qquickmultipointtoucharea.cpp @@ -418,6 +418,7 @@ QQuickMultiPointTouchArea::QQuickMultiPointTouchArea(QQuickItem *parent) if (qmlVisualTouchDebugging()) { setFlag(QQuickItem::ItemHasContents); } + setAcceptTouchEvents(true); #ifdef Q_OS_OSX setAcceptHoverEvents(true); // needed to enable touch events on mouse hover. #endif diff --git a/src/quick/items/qquickopenglshadereffect.cpp b/src/quick/items/qquickopenglshadereffect.cpp index 4fcfe04b55..c3e0ba05bd 100644 --- a/src/quick/items/qquickopenglshadereffect.cpp +++ b/src/quick/items/qquickopenglshadereffect.cpp @@ -230,7 +230,7 @@ void QQuickOpenGLShaderEffectCommon::disconnectPropertySignals(QQuickItem *item, auto mapper = signalMappers[shaderType].at(i); void *a = mapper; QObjectPrivate::disconnect(item, mapper->signalIndex(), &a); - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value)); if (source) { if (item->window()) @@ -272,7 +272,7 @@ void QQuickOpenGLShaderEffectCommon::connectPropertySignals(QQuickItem *item, qWarning("QQuickOpenGLShaderEffect: '%s' does not have a matching property!", d.name.constData()); } - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value)); if (source) { if (item->window()) @@ -335,6 +335,7 @@ void QQuickOpenGLShaderEffectCommon::lookThroughShaderCode(QQuickItem *item, Q_ASSERT(decl == UniformQualifier); const int sampLen = sizeof("sampler2D") - 1; + const int sampExtLen = sizeof("samplerExternalOES") - 1; const int opLen = sizeof("qt_Opacity") - 1; const int matLen = sizeof("qt_Matrix") - 1; const int srLen = sizeof("qt_SubRect_") - 1; @@ -357,8 +358,12 @@ void QQuickOpenGLShaderEffectCommon::lookThroughShaderCode(QQuickItem *item, mapper = new QtPrivate::MappedSlotObject([this, mappedId](){ this->mappedPropertyChanged(mappedId); }); - bool sampler = typeLength == sampLen && qstrncmp("sampler2D", s + typeIndex, sampLen) == 0; - d.specialType = sampler ? UniformData::Sampler : UniformData::None; + if (typeLength == sampLen && qstrncmp("sampler2D", s + typeIndex, sampLen) == 0) + d.specialType = UniformData::Sampler; + else if (typeLength == sampExtLen && qstrncmp("samplerExternalOES", s + typeIndex, sampExtLen) == 0) + d.specialType = UniformData::SamplerExternal; + else + d.specialType = UniformData::None; d.setValueFromProperty(item, itemMetaObject); } uniformData[shaderType].append(d); @@ -451,7 +456,8 @@ void QQuickOpenGLShaderEffectCommon::updateMaterial(QQuickOpenGLShaderEffectNode int textureProviderCount = 0; for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { for (int i = 0; i < uniformData[shaderType].size(); ++i) { - if (uniformData[shaderType].at(i).specialType == UniformData::Sampler) + if (uniformData[shaderType].at(i).specialType == UniformData::Sampler || + uniformData[shaderType].at(i).specialType == UniformData::SamplerExternal) ++textureProviderCount; } material->uniforms[shaderType] = uniformData[shaderType]; @@ -474,7 +480,7 @@ void QQuickOpenGLShaderEffectCommon::updateMaterial(QQuickOpenGLShaderEffectNode for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { for (int i = 0; i < uniformData[shaderType].size(); ++i) { const UniformData &d = uniformData[shaderType].at(i); - if (d.specialType != UniformData::Sampler) + if (d.specialType != UniformData::Sampler && d.specialType != UniformData::SamplerExternal) continue; QSGTextureProvider *oldProvider = material->textureProviders.at(index); QSGTextureProvider *newProvider = 0; @@ -513,7 +519,7 @@ void QQuickOpenGLShaderEffectCommon::updateWindow(QQuickWindow *window) for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { for (int i = 0; i < uniformData[shaderType].size(); ++i) { const UniformData &d = uniformData[shaderType].at(i); - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value)); if (source) QQuickItemPrivate::get(source)->refWindow(window); @@ -524,7 +530,7 @@ void QQuickOpenGLShaderEffectCommon::updateWindow(QQuickWindow *window) for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { for (int i = 0; i < uniformData[shaderType].size(); ++i) { const UniformData &d = uniformData[shaderType].at(i); - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value)); if (source) QQuickItemPrivate::get(source)->derefWindow(); @@ -539,7 +545,7 @@ void QQuickOpenGLShaderEffectCommon::sourceDestroyed(QObject *object) for (int shaderType = 0; shaderType < Key::ShaderTypeCount; ++shaderType) { for (int i = 0; i < uniformData[shaderType].size(); ++i) { UniformData &d = uniformData[shaderType][i]; - if (d.specialType == UniformData::Sampler && d.value.canConvert<QObject *>()) { + if ((d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) && d.value.canConvert<QObject *>()) { if (qvariant_cast<QObject *>(d.value) == object) d.value = QVariant(); } @@ -554,7 +560,7 @@ static bool qquick_uniqueInUniformData(QQuickItem *source, const QVector<QQuickO if (s == typeToSkip && i == indexToSkip) continue; const QQuickOpenGLShaderEffectMaterial::UniformData &d = uniformData[s][i]; - if (d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::Sampler && qvariant_cast<QObject *>(d.value) == source) + if ((d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::Sampler || d.specialType == QQuickOpenGLShaderEffectMaterial::UniformData::SamplerExternal) && qvariant_cast<QObject *>(d.value) == source) return false; } } @@ -568,7 +574,7 @@ void QQuickOpenGLShaderEffectCommon::propertyChanged(QQuickItem *item, Key::ShaderType shaderType = Key::ShaderType(mappedId >> 16); int index = mappedId & 0xffff; UniformData &d = uniformData[shaderType][index]; - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(d.value)); if (source) { if (item->window()) diff --git a/src/quick/items/qquickopenglshadereffectnode.cpp b/src/quick/items/qquickopenglshadereffectnode.cpp index e1ea98641d..5dbfee73cb 100644 --- a/src/quick/items/qquickopenglshadereffectnode.cpp +++ b/src/quick/items/qquickopenglshadereffectnode.cpp @@ -47,6 +47,10 @@ #include <QtCore/qmutex.h> #include <QtGui/qopenglfunctions.h> +#ifndef GL_TEXTURE_EXTERNAL_OES +#define GL_TEXTURE_EXTERNAL_OES 0x8D65 +#endif + QT_BEGIN_NAMESPACE static bool hasAtlasTexture(const QVector<QSGTextureProvider *> &textureProviders) @@ -124,7 +128,7 @@ void QQuickCustomMaterialShader::updateState(const RenderState &state, QSGMateri for (int i = 0; i < material->uniforms[shaderType].size(); ++i) { const UniformData &d = material->uniforms[shaderType].at(i); QByteArray name = d.name; - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { program()->setUniformValue(d.name.constData(), textureProviderIndex++); // We don't need to store the sampler uniform locations, since their values // only need to be set once. Look for the "qt_SubRect_" uniforms instead. @@ -143,7 +147,7 @@ void QQuickCustomMaterialShader::updateState(const RenderState &state, QSGMateri for (int i = 0; i < material->uniforms[shaderType].size(); ++i) { const UniformData &d = material->uniforms[shaderType].at(i); int loc = m_uniformLocs[shaderType].at(i); - if (d.specialType == UniformData::Sampler) { + if (d.specialType == UniformData::Sampler || d.specialType == UniformData::SamplerExternal) { int idx = textureProviderIndex++; functions->glActiveTexture(GL_TEXTURE0 + idx); if (QSGTextureProvider *provider = material->textureProviders.at(idx)) { @@ -164,7 +168,10 @@ void QQuickCustomMaterialShader::updateState(const RenderState &state, QSGMateri continue; } } - functions->glBindTexture(GL_TEXTURE_2D, 0); + if (d.specialType == UniformData::Sampler) + functions->glBindTexture(GL_TEXTURE_2D, 0); + else if (d.specialType == UniformData::SamplerExternal) + functions->glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); } else if (d.specialType == UniformData::Opacity) { program()->setUniformValue(loc, state.opacity()); } else if (d.specialType == UniformData::Matrix) { @@ -396,7 +403,7 @@ bool QQuickOpenGLShaderEffectMaterial::UniformData::operator == (const UniformDa if (name != other.name) return false; - if (specialType == UniformData::Sampler) { + if (specialType == UniformData::Sampler || specialType == UniformData::SamplerExternal) { // We can't check the source objects as these live in the GUI thread, // so return true here and rely on the textureProvider check for // equality of these.. diff --git a/src/quick/items/qquickopenglshadereffectnode_p.h b/src/quick/items/qquickopenglshadereffectnode_p.h index aea28e6612..784294d9eb 100644 --- a/src/quick/items/qquickopenglshadereffectnode_p.h +++ b/src/quick/items/qquickopenglshadereffectnode_p.h @@ -90,7 +90,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickOpenGLShaderEffectMaterial : public QSGMateri public: struct UniformData { - enum SpecialType { None, Sampler, SubRect, Opacity, Matrix }; + enum SpecialType { None, Sampler, SamplerExternal, SubRect, Opacity, Matrix }; QByteArray name; QVariant value; diff --git a/src/quick/items/qquickpathview_p_p.h b/src/quick/items/qquickpathview_p_p.h index d58c986d1a..274086ea7c 100644 --- a/src/quick/items/qquickpathview_p_p.h +++ b/src/quick/items/qquickpathview_p_p.h @@ -153,7 +153,6 @@ public: bool moving : 1; bool flicking : 1; bool dragging : 1; - bool requestedOnPath : 1; bool inRequest : 1; bool delegateValidated : 1; bool inRefill : 1; diff --git a/src/quick/items/qquickpincharea.cpp b/src/quick/items/qquickpincharea.cpp index 3f6ce7b8ba..476acd3a3e 100644 --- a/src/quick/items/qquickpincharea.cpp +++ b/src/quick/items/qquickpincharea.cpp @@ -288,6 +288,7 @@ QQuickPinchArea::QQuickPinchArea(QQuickItem *parent) { Q_D(QQuickPinchArea); d->init(); + setAcceptTouchEvents(true); #ifdef Q_OS_OSX setAcceptHoverEvents(true); // needed to enable touch events on mouse hover. #endif diff --git a/src/quick/items/qquickpositioners.cpp b/src/quick/items/qquickpositioners.cpp index 06b18d714b..e752e2538f 100644 --- a/src/quick/items/qquickpositioners.cpp +++ b/src/quick/items/qquickpositioners.cpp @@ -44,9 +44,6 @@ #include <QtQml/qqmlinfo.h> #include <QtCore/qcoreapplication.h> -#include <QtQuick/private/qquickstate_p.h> -#include <QtQuick/private/qquickstategroup_p.h> -#include <private/qquickstatechangescript_p.h> #include <QtQuick/private/qquicktransition_p.h> QT_BEGIN_NAMESPACE @@ -1137,7 +1134,7 @@ public: : QQuickBasePositionerPrivate() {} - void effectiveLayoutDirectionChange() + void effectiveLayoutDirectionChange() override { Q_Q(QQuickRow); // For RTL layout the positioning changes when the width changes. @@ -1436,7 +1433,7 @@ public: : QQuickBasePositionerPrivate() {} - void effectiveLayoutDirectionChange() + void effectiveLayoutDirectionChange() override { Q_Q(QQuickGrid); // For RTL layout the positioning changes when the width changes. @@ -2023,7 +2020,7 @@ public: : QQuickBasePositionerPrivate(), flow(QQuickFlow::LeftToRight) {} - void effectiveLayoutDirectionChange() + void effectiveLayoutDirectionChange() override { Q_Q(QQuickFlow); // Don't postpone, as it might be the only trigger for visible changes. diff --git a/src/quick/items/qquickpositioners_p.h b/src/quick/items/qquickpositioners_p.h index ae6e795794..9ae7029d69 100644 --- a/src/quick/items/qquickpositioners_p.h +++ b/src/quick/items/qquickpositioners_p.h @@ -58,7 +58,6 @@ QT_REQUIRE_CONFIG(quick_positioners); #include "qquickimplicitsizeitem_p.h" #include "qquickitemviewtransition_p.h" -#include <QtQuick/private/qquickstate_p.h> #include <private/qpodvector_p.h> #include <QtCore/qobject.h> diff --git a/src/quick/items/qquickpositioners_p_p.h b/src/quick/items/qquickpositioners_p_p.h index 6dd84e6098..0be4c56df6 100644 --- a/src/quick/items/qquickpositioners_p_p.h +++ b/src/quick/items/qquickpositioners_p_p.h @@ -58,9 +58,6 @@ QT_REQUIRE_CONFIG(quick_positioners); #include "qquickpositioners_p.h" #include "qquickimplicitsizeitem_p_p.h" -#include <QtQuick/private/qquickstate_p.h> -#include <private/qquicktransitionmanager_p_p.h> -#include <private/qquickstatechangescript_p.h> #include <private/qlazilyallocated_p.h> #include <QtCore/qobject.h> diff --git a/src/quick/items/qquickrectangle.cpp b/src/quick/items/qquickrectangle.cpp index 8ac982f74c..9308553a79 100644 --- a/src/quick/items/qquickrectangle.cpp +++ b/src/quick/items/qquickrectangle.cpp @@ -90,6 +90,7 @@ void QQuickPen::setWidth(qreal w) m_width = w; m_valid = m_color.alpha() && (qRound(m_width) >= 1 || (!m_aligned && m_width > 0)); + static_cast<QQuickItem*>(parent())->update(); emit penChanged(); } @@ -102,6 +103,7 @@ void QQuickPen::setColor(const QColor &c) { m_color = c; m_valid = m_color.alpha() && (qRound(m_width) >= 1 || (!m_aligned && m_width > 0)); + static_cast<QQuickItem*>(parent())->update(); emit penChanged(); } @@ -116,6 +118,7 @@ void QQuickPen::setPixelAligned(bool aligned) return; m_aligned = aligned; m_valid = m_color.alpha() && (qRound(m_width) >= 1 || (!m_aligned && m_width > 0)); + static_cast<QQuickItem*>(parent())->update(); emit penChanged(); } @@ -322,6 +325,9 @@ QQuickRectangle::QQuickRectangle(QQuickItem *parent) : QQuickItem(*(new QQuickRectanglePrivate), parent) { setFlag(ItemHasContents); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + setAcceptTouchEvents(false); +#endif } void QQuickRectangle::doUpdate() @@ -356,7 +362,11 @@ void QQuickRectangle::doUpdate() QQuickPen *QQuickRectangle::border() { Q_D(QQuickRectangle); - return d->getPen(); + if (!d->pen) { + d->pen = new QQuickPen; + QQml_setParent_noEvent(d->pen, this); + } + return d->pen; } /*! diff --git a/src/quick/items/qquickrectangle_p_p.h b/src/quick/items/qquickrectangle_p_p.h index 50d5817951..3c1aaf7661 100644 --- a/src/quick/items/qquickrectangle_p_p.h +++ b/src/quick/items/qquickrectangle_p_p.h @@ -70,7 +70,6 @@ public: ~QQuickRectanglePrivate() { - delete pen; } QColor color; @@ -78,20 +77,6 @@ public: QQuickPen *pen; qreal radius; static int doUpdateSlotIdx; - - QQuickPen *getPen() { - if (!pen) { - Q_Q(QQuickRectangle); - pen = new QQuickPen; - static int penChangedSignalIdx = -1; - if (penChangedSignalIdx < 0) - penChangedSignalIdx = QMetaMethod::fromSignal(&QQuickPen::penChanged).methodIndex(); - if (doUpdateSlotIdx < 0) - doUpdateSlotIdx = QQuickRectangle::staticMetaObject.indexOfSlot("doUpdate()"); - QMetaObject::connect(pen, penChangedSignalIdx, q, doUpdateSlotIdx); - } - return pen; - } }; QT_END_NAMESPACE diff --git a/src/quick/items/qquickrendercontrol.cpp b/src/quick/items/qquickrendercontrol.cpp index 7e995936af..58b76fa862 100644 --- a/src/quick/items/qquickrendercontrol.cpp +++ b/src/quick/items/qquickrendercontrol.cpp @@ -393,6 +393,8 @@ QImage QQuickRenderControl::grab() #endif } else if (d->window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window); + cd->polishItems(); + cd->syncSceneGraph(); QSGSoftwareRenderer *softwareRenderer = static_cast<QSGSoftwareRenderer *>(cd->renderer); if (softwareRenderer) { const qreal dpr = d->window->effectiveDevicePixelRatio(); @@ -402,8 +404,6 @@ QImage QQuickRenderControl::grab() QPaintDevice *prevDev = softwareRenderer->currentPaintDevice(); softwareRenderer->setCurrentPaintDevice(&grabContent); softwareRenderer->markDirty(); - cd->polishItems(); - cd->syncSceneGraph(); d->rc->endSync(); render(); softwareRenderer->setCurrentPaintDevice(prevDev); diff --git a/src/quick/items/qquickscreen.cpp b/src/quick/items/qquickscreen.cpp index 9b54b7fba9..6a3eab957e 100644 --- a/src/quick/items/qquickscreen.cpp +++ b/src/quick/items/qquickscreen.cpp @@ -100,6 +100,27 @@ QT_BEGIN_NAMESPACE The y coordinate of the screen within the virtual desktop. */ /*! + \qmlattachedproperty string Screen::manufacturer + \readonly + \since 5.10 + + The manufacturer of the screen. +*/ +/*! + \qmlattachedproperty string Screen::model + \readonly + \since 5.10 + + The model of the screen. +*/ +/*! + \qmlattachedproperty string Screen::serialNumber + \readonly + \since 5.10 + + The serial number of the screen. +*/ +/*! \qmlattachedproperty int Screen::width \readonly @@ -234,6 +255,27 @@ QString QQuickScreenInfo::name() const return m_screen->name(); } +QString QQuickScreenInfo::manufacturer() const +{ + if (!m_screen) + return QString(); + return m_screen->manufacturer(); +} + +QString QQuickScreenInfo::model() const +{ + if (!m_screen) + return QString(); + return m_screen->model(); +} + +QString QQuickScreenInfo::serialNumber() const +{ + if (!m_screen) + return QString(); + return m_screen->serialNumber(); +} + int QQuickScreenInfo::width() const { if (!m_screen) @@ -335,6 +377,12 @@ void QQuickScreenInfo::setWrappedScreen(QScreen *screen) } if (!oldScreen || screen->name() != oldScreen->name()) emit nameChanged(); + if (!oldScreen || screen->manufacturer() != oldScreen->manufacturer()) + emit manufacturerChanged(); + if (!oldScreen || screen->model() != oldScreen->model()) + emit modelChanged(); + if (!oldScreen || screen->serialNumber() != oldScreen->serialNumber()) + emit serialNumberChanged(); if (!oldScreen || screen->orientation() != oldScreen->orientation()) emit orientationChanged(); if (!oldScreen || screen->primaryOrientation() != oldScreen->primaryOrientation()) diff --git a/src/quick/items/qquickscreen_p.h b/src/quick/items/qquickscreen_p.h index 99e1466631..e9db07d14c 100644 --- a/src/quick/items/qquickscreen_p.h +++ b/src/quick/items/qquickscreen_p.h @@ -68,6 +68,9 @@ class Q_AUTOTEST_EXPORT QQuickScreenInfo : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString manufacturer READ manufacturer NOTIFY manufacturerChanged REVISION 10) + Q_PROPERTY(QString model READ model NOTIFY modelChanged REVISION 10) + Q_PROPERTY(QString serialNumber READ serialNumber NOTIFY serialNumberChanged REVISION 10) Q_PROPERTY(int width READ width NOTIFY widthChanged) Q_PROPERTY(int height READ height NOTIFY heightChanged) Q_PROPERTY(int desktopAvailableWidth READ desktopAvailableWidth NOTIFY desktopGeometryChanged) @@ -87,6 +90,9 @@ public: QQuickScreenInfo(QObject *parent = nullptr, QScreen *wrappedScreen = nullptr); QString name() const; + QString manufacturer() const; + QString model() const; + QString serialNumber() const; int width() const; int height() const; int desktopAvailableWidth() const; @@ -104,6 +110,9 @@ public: Q_SIGNALS: void nameChanged(); + Q_REVISION(10) void manufacturerChanged(); + Q_REVISION(10) void modelChanged(); + Q_REVISION(10) void serialNumberChanged(); void widthChanged(); void heightChanged(); void desktopGeometryChanged(); diff --git a/src/quick/items/qquickshadereffectsource.cpp b/src/quick/items/qquickshadereffectsource.cpp index 6b1b16618a..f61bad1179 100644 --- a/src/quick/items/qquickshadereffectsource.cpp +++ b/src/quick/items/qquickshadereffectsource.cpp @@ -189,6 +189,7 @@ QQuickShaderEffectSource::QQuickShaderEffectSource(QQuickItem *parent) , m_sourceItem(0) , m_textureSize(0, 0) , m_format(RGBA) + , m_samples(0) , m_live(true) , m_hideSource(false) , m_mipmap(false) @@ -582,6 +583,44 @@ void QQuickShaderEffectSource::setTextureMirroring(TextureMirroring mirroring) } /*! + \qmlproperty int QtQuick::ShaderEffectSource::samples + \since 5.10 + + This property allows requesting multisampled rendering. + + By default multisampling is enabled whenever multisampling is enabled for + the entire window, assuming the scenegraph renderer in use and the + underlying graphics API supports this. + + By setting the value to 2, 4, etc. multisampled rendering can be requested + for a part of the scene without enabling multisampling for the entire + scene. This way multisampling is applied only to a given subtree, which can + lead to significant performance gains since multisampling is not applied to + other parts of the scene. + + \note Enabling multisampling can be potentially expensive regardless of the + layer's size, as it incurs a hardware and driver dependent performance and + memory cost. + + \note This property is only functional when support for multisample + renderbuffers and framebuffer blits is available. Otherwise the value is + silently ignored. + */ +int QQuickShaderEffectSource::samples() const +{ + return m_samples; +} + +void QQuickShaderEffectSource::setSamples(int count) +{ + if (count == m_samples) + return; + m_samples = count; + update(); + emit samplesChanged(); +} + +/*! \qmlmethod QtQuick::ShaderEffectSource::scheduleUpdate() Schedules a re-rendering of the texture for the next frame. @@ -683,6 +722,7 @@ QSGNode *QQuickShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaint m_texture->setHasMipmaps(m_mipmap); m_texture->setMirrorHorizontal(m_textureMirroring & MirrorHorizontally); m_texture->setMirrorVertical(m_textureMirroring & MirrorVertically); + m_texture->setSamples(m_samples); if (m_grab) m_texture->scheduleUpdate(); diff --git a/src/quick/items/qquickshadereffectsource_p.h b/src/quick/items/qquickshadereffectsource_p.h index 5e7e354feb..d9f9079a3d 100644 --- a/src/quick/items/qquickshadereffectsource_p.h +++ b/src/quick/items/qquickshadereffectsource_p.h @@ -88,6 +88,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickShaderEffectSource : public QQuickItem, publi Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged) Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged) Q_PROPERTY(TextureMirroring textureMirroring READ textureMirroring WRITE setTextureMirroring NOTIFY textureMirroringChanged REVISION 1) + Q_PROPERTY(int samples READ samples WRITE setSamples NOTIFY samplesChanged REVISION 2) public: enum WrapMode { @@ -150,6 +151,9 @@ public: Q_INVOKABLE void scheduleUpdate(); + int samples() const; + void setSamples(int count); + Q_SIGNALS: void wrapModeChanged(); void sourceItemChanged(); @@ -161,6 +165,7 @@ Q_SIGNALS: void mipmapChanged(); void recursiveChanged(); void textureMirroringChanged(); + void samplesChanged(); void scheduledUpdateCompleted(); @@ -185,6 +190,7 @@ private: QRectF m_sourceRect; QSize m_textureSize; Format m_format; + int m_samples; uint m_live : 1; uint m_hideSource : 1; uint m_mipmap : 1; diff --git a/src/quick/items/qquicksprite.cpp b/src/quick/items/qquicksprite.cpp index aae657b749..8013c57938 100644 --- a/src/quick/items/qquicksprite.cpp +++ b/src/quick/items/qquicksprite.cpp @@ -40,6 +40,7 @@ #include "qquicksprite_p.h" #include <qqml.h> #include <QDebug> +#include <QRandomGenerator> QT_BEGIN_NAMESPACE @@ -236,12 +237,12 @@ int QQuickSprite::variedDuration() const //Deals with precedence when multiple d if (m_frameRate != unsetDuration) { qreal fpms = (m_frameRate - + (m_frameRateVariation * ((qreal)qrand()/RAND_MAX) * 2) + + (m_frameRateVariation * QRandomGenerator::getReal() * 2) - m_frameRateVariation) / 1000.0; return qMax(qreal(0.0) , m_frames / fpms); } else if (m_frameDuration != unsetDuration) { int mspf = m_frameDuration - + (m_frameDurationVariation * ((qreal)qrand()/RAND_MAX) * 2) + + (m_frameDurationVariation * QRandomGenerator::getReal() * 2) - m_frameDurationVariation; return qMax(0, m_frames * mspf); } else if (duration() >= 0) { diff --git a/src/quick/items/qquickspriteengine.cpp b/src/quick/items/qquickspriteengine.cpp index 92b60a8e3a..9deb223957 100644 --- a/src/quick/items/qquickspriteengine.cpp +++ b/src/quick/items/qquickspriteengine.cpp @@ -43,6 +43,7 @@ #include <qqml.h> #include <QDebug> #include <QPainter> +#include <QRandomGenerator> #include <QSet> #include <QtGui/qopengl.h> #include <QOpenGLFunctions> @@ -543,7 +544,7 @@ void QQuickStochasticEngine::restart(int index) if (m_addAdvance) m_startTimes[index] += m_advanceTime.elapsed(); if (randomStart) - m_startTimes[index] -= qrand() % m_duration.at(index); + m_startTimes[index] -= QRandomGenerator::bounded(m_duration.at(index)); int time = m_duration.at(index) + m_startTimes.at(index); for (int i=0; i<m_stateUpdates.count(); i++) m_stateUpdates[i].second.removeAll(index); @@ -557,13 +558,13 @@ void QQuickSpriteEngine::restart(int index) //Reimplemented to recognize and han if (m_loaded && m_sprites.at(m_things.at(index))->frameSync()) {//Manually advanced m_startTimes[index] = 0; if (randomStart && m_sprites.at(m_things.at(index))->m_generatedCount) - m_startTimes[index] += qrand() % m_sprites.at(m_things.at(index))->m_generatedCount; + m_startTimes[index] += QRandomGenerator::bounded(m_sprites.at(m_things.at(index))->m_generatedCount); } else { m_startTimes[index] = m_timeOffset; if (m_addAdvance) m_startTimes[index] += m_advanceTime.elapsed(); if (randomStart) - m_startTimes[index] -= qrand() % m_duration.at(index); + m_startTimes[index] -= QRandomGenerator::bounded(m_duration.at(index)); int time = spriteDuration(index) + m_startTimes.at(index); if (randomStart) { int curTime = m_timeOffset + (m_addAdvance ? m_advanceTime.elapsed() : 0); @@ -629,7 +630,7 @@ int QQuickStochasticEngine::nextState(int curState, int curThing) int nextIdx = -1; int goalPath = goalSeek(curState, curThing); if (goalPath == -1){//Random - qreal r =(qreal) qrand() / (qreal) RAND_MAX; + qreal r = QRandomGenerator::getReal(); qreal total = 0.0; for (QVariantMap::const_iterator iter=m_states.at(curState)->m_to.constBegin(); iter!=m_states.at(curState)->m_to.constEnd(); ++iter) @@ -719,7 +720,7 @@ int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist) if (options.count()==1) return *(options.begin()); int option = -1; - qreal r =(qreal) qrand() / (qreal) RAND_MAX; + qreal r = QRandomGenerator::getReal(); qreal total = 0; for (QSet<int>::const_iterator iter=options.constBegin(); iter!=options.constEnd(); ++iter) diff --git a/src/quick/items/qquickspriteengine_p.h b/src/quick/items/qquickspriteengine_p.h index 90ee68b2f6..4e5458a938 100644 --- a/src/quick/items/qquickspriteengine_p.h +++ b/src/quick/items/qquickspriteengine_p.h @@ -63,6 +63,7 @@ QT_REQUIRE_CONFIG(quick_sprite); #include <QQmlListProperty> #include <QImage> #include <QPair> +#include <QRandomGenerator> #include <private/qquickpixmapcache_p.h> #include <private/qtquickglobal_p.h> @@ -112,7 +113,7 @@ public: virtual int variedDuration() const { return qMax(qreal(0.0) , m_duration - + (m_durationVariation * ((qreal)qrand()/RAND_MAX) * 2) + + (m_durationVariation * QRandomGenerator::bounded(2.0)) - m_durationVariation); } diff --git a/src/quick/items/qquickstateoperations.cpp b/src/quick/items/qquickstateoperations.cpp index b40a9e2843..a4ce13a199 100644 --- a/src/quick/items/qquickstateoperations.cpp +++ b/src/quick/items/qquickstateoperations.cpp @@ -101,7 +101,7 @@ void QQuickParentChangePrivate::doChange(QQuickItem *targetParent, QQuickItem *s } if (scale != 0) - rotation = qAtan2(transform.m12()/scale, transform.m11()/scale) * 180/M_PI; + rotation = qRadiansToDegrees(qAtan2(transform.m12() / scale, transform.m11() / scale)); else { qmlWarning(q) << QQuickParentChange::tr("Unable to preserve appearance under scale of 0"); ok = false; diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index 6d73af80fe..5f58f0cdde 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -80,11 +80,7 @@ QQuickTextPrivate::QQuickTextPrivate() , elideMode(QQuickText::ElideNone), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop) , format(QQuickText::AutoText), wrapMode(QQuickText::NoWrap) , style(QQuickText::Normal) -#if defined(QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) - , renderType(QQuickText::QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) -#else - , renderType(QQuickText::QtRendering) -#endif + , renderType(QQuickTextUtil::textRenderType<QQuickText>()) , updateType(UpdatePaintNode) , maximumLineCountValid(false), updateOnComponentComplete(true), richText(false) , styledText(false), widthExceeded(false), heightExceeded(false), internalWidthUpdate(false) @@ -269,9 +265,6 @@ void QQuickTextPrivate::updateLayout() formatModifiesFontSize = fontSizeModified; multilengthEos = -1; } else { - layout.clearFormats(); - if (elideLayout) - elideLayout->clearFormats(); QString tmp = text; multilengthEos = tmp.indexOf(QLatin1Char('\x9c')); if (multilengthEos != -1) @@ -359,8 +352,8 @@ void QQuickTextPrivate::updateSize() } if (!requireImplicitSize) { - emit q->implicitWidthChanged(); - emit q->implicitHeightChanged(); + implicitWidthChanged(); + implicitHeightChanged(); // if the implicitWidth is used, then updateSize() has already been called (recursively) if (requireImplicitSize) return; @@ -384,6 +377,7 @@ void QQuickTextPrivate::updateSize() updateBaseline(fm.ascent(), q->height() - fontHeight - vPadding); q->setImplicitSize(hPadding, fontHeight + vPadding); layedOutTextRect = QRectF(0, 0, 0, fontHeight); + advance = QSizeF(); emit q->contentSizeChanged(); updateType = UpdatePaintNode; q->update(); @@ -457,8 +451,26 @@ void QQuickTextPrivate::updateSize() if (iWidth == -1) q->setImplicitHeight(size.height() + vPadding); + + QTextBlock firstBlock = extra->doc->firstBlock(); + while (firstBlock.layout()->lineCount() == 0) + firstBlock = firstBlock.next(); + + QTextBlock lastBlock = extra->doc->lastBlock(); + while (lastBlock.layout()->lineCount() == 0) + lastBlock = lastBlock.previous(); + + if (firstBlock.lineCount() > 0 && lastBlock.lineCount() > 0) { + QTextLine firstLine = firstBlock.layout()->lineAt(0); + QTextLine lastLine = lastBlock.layout()->lineAt(lastBlock.layout()->lineCount() - 1); + advance = QSizeF(lastLine.horizontalAdvance(), + (lastLine.y() + lastBlock.layout()->position().y()) - (firstLine.y() + firstBlock.layout()->position().y())); + } else { + advance = QSizeF(); + } } + if (layedOutTextRect.size() != previousSize) emit q->contentSizeChanged(); updateType = UpdatePaintNode; @@ -613,6 +625,13 @@ QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QT } } +void QQuickTextPrivate::clearFormats() +{ + layout.clearFormats(); + if (elideLayout) + elideLayout->clearFormats(); +} + /*! Lays out the QQuickTextPrivate::layout QTextLayout in the constraints of the QQuickText. @@ -968,6 +987,16 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) br.moveTop(0); + // Find the advance of the text layout + if (layout.lineCount() > 0) { + QTextLine firstLine = layout.lineAt(0); + QTextLine lastLine = layout.lineAt(layout.lineCount() - 1); + advance = QSizeF(lastLine.horizontalAdvance(), + lastLine.y() - firstLine.y()); + } else { + advance = QSizeF(); + } + if (!horizontalFit && !verticalFit) break; @@ -1026,12 +1055,15 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) if (eos != multilengthEos) truncated = true; + assignedFont = QFontInfo(font).family(); + if (elide) { if (!elideLayout) { elideLayout = new QTextLayout; elideLayout->setCacheEnabled(true); } - if (styledText) { + QTextEngine *engine = layout.engine(); + if (engine && engine->hasFormats()) { QVector<QTextLayout::FormatRange> formats; switch (elideMode) { case QQuickText::ElideRight: @@ -1470,6 +1502,36 @@ QQuickText::~QQuickText() Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } \endqml */ + +/*! + \qmlproperty bool QtQuick::Text::font.kerning + \since 5.10 + + Enables or disables the kerning OpenType feature when shaping the text. This may improve performance + when creating or changing the text, at the expense of some cosmetic features. The default value + is true. + + \qml + Text { text: "OATS FLAVOUR WAY"; font.kerning: false } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick::Text::font.preferShaping + \since 5.10 + + Sometimes, a font will apply complex rules to a set of characters in order to + display them correctly. In some writing systems, such as Brahmic scripts, this is + required in order for the text to be legible, but in e.g. Latin script, it is merely + a cosmetic feature. Setting the \c preferShaping property to false will disable all + such features when they are not required, which will improve performance in most cases. + + The default value is true. + + \qml + Text { text: "Some text"; font.preferShaping: false } + \endqml +*/ QFont QQuickText::font() const { Q_D(const QQuickText); @@ -1570,6 +1632,7 @@ void QQuickText::setText(const QString &n) d->extra->doc->setText(n); d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); } else { + d->clearFormats(); d->rightToLeftText = d->text.isRightToLeft(); } d->determineHorizontalAlignment(); @@ -2060,6 +2123,7 @@ void QQuickText::setTextFormat(TextFormat format) d->extra->doc->setText(d->text); d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); } else { + d->clearFormats(); d->rightToLeftText = d->text.isRightToLeft(); d->textHasChanged = true; } @@ -2365,6 +2429,12 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data void QQuickText::updatePolish() { Q_D(QQuickText); + // If the fonts used for rendering are different from the ones used in the GUI thread, + // it means we will get warnings and corrupted text. If this case is detected, we need + // to update the text layout before creating the scenegraph nodes. + if (!d->assignedFont.isEmpty() && QFontInfo(d->font).family() != d->assignedFont) + d->polishSize = true; + if (d->polishSize) { d->updateSize(); d->polishSize = false; @@ -2770,7 +2840,7 @@ void QQuickText::hoverLeaveEvent(QHoverEvent *event) Supported render types are: \list - \li Text.QtRendering - the default + \li Text.QtRendering \li Text.NativeRendering \endlist @@ -2778,6 +2848,8 @@ void QQuickText::hoverLeaveEvent(QHoverEvent *event) not require advanced features such as transformation of the text. Using such features in combination with the NativeRendering render type will lend poor and sometimes pixelated results. + + The default rendering type is determined by \l QQuickWindow::textRenderType(). */ QQuickText::RenderType QQuickText::renderType() const { @@ -3055,6 +3127,24 @@ QJSValue QQuickText::fontInfo() const return value; } +/*! + \qmlproperty size QtQuick::Text::advance + \since 5.10 + + The distance, in pixels, from the baseline origin of the first + character of the text item, to the baseline origin of the first + character in a text item occurring directly after this one + in a text flow. + + Note that the advance can be negative if the text flows from + the right to the left. +*/ +QSizeF QQuickText::advance() const +{ + Q_D(const QQuickText); + return d->advance; +} + QT_END_NAMESPACE #include "moc_qquicktext_p.cpp" diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index b190738cfb..a56bcdb87b 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -99,6 +99,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged REVISION 6) Q_PROPERTY(QJSValue fontInfo READ fontInfo NOTIFY fontInfoChanged REVISION 9) + Q_PROPERTY(QSizeF advance READ advance NOTIFY contentSizeChanged REVISION 10) public: QQuickText(QQuickItem *parent=0); @@ -251,6 +252,7 @@ public: void resetBottomPadding(); QJSValue fontInfo() const; + QSizeF advance() const; Q_SIGNALS: void textChanged(const QString &text); diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index 6456750359..87f5162384 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -85,10 +85,12 @@ public: int lineHeightOffset() const; QString elidedText(qreal lineWidth, const QTextLine &line, QTextLine *nextLine = 0) const; void elideFormats(int start, int length, int offset, QVector<QTextLayout::FormatRange> *elidedFormats); + void clearFormats(); void processHoverEvent(QHoverEvent *event); QRectF layedOutTextRect; + QSizeF advance; struct ExtraData { ExtraData(); @@ -152,6 +154,8 @@ public: QQuickText::RenderType renderType; UpdateType updateType; + QString assignedFont; + bool maximumLineCountValid:1; bool updateOnComponentComplete:1; bool richText:1; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 3714af2bc3..8f3a8998f5 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -45,7 +45,6 @@ #include "qquickwindow.h" #include "qquicktextnode_p.h" #include "qquicktextnodeengine_p.h" -#include "qquicktextutil_p.h" #include <QtCore/qmath.h> #include <QtGui/qguiapplication.h> @@ -356,6 +355,36 @@ QString QQuickTextEdit::text() const */ /*! + \qmlproperty bool QtQuick::TextEdit::font.kerning + \since 5.10 + + Enables or disables the kerning OpenType feature when shaping the text. This may improve performance + when creating or changing the text, at the expense of some cosmetic features. The default value + is true. + + \qml + TextEdit { text: "OATS FLAVOUR WAY"; kerning: font.false } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick::TextEdit::font.preferShaping + \since 5.10 + + Sometimes, a font will apply complex rules to a set of characters in order to + display them correctly. In some writing systems, such as Brahmic scripts, this is + required in order for the text to be legible, but in e.g. Latin script, it is merely + a cosmetic feature. Setting the \c preferShaping property to false will disable all + such features when they are not required, which will improve performance in most cases. + + The default value is true. + + \qml + TextEdit { text: "Some text"; font.preferShaping: false } + \endqml +*/ + +/*! \qmlproperty string QtQuick::TextEdit::text The text to display. If the text format is AutoText the text edit will @@ -482,7 +511,7 @@ void QQuickTextEdit::setTextFormat(TextFormat format) Supported render types are: \list - \li Text.QtRendering - the default + \li Text.QtRendering \li Text.NativeRendering \endlist @@ -490,6 +519,8 @@ void QQuickTextEdit::setTextFormat(TextFormat format) not require advanced features such as transformation of the text. Using such features in combination with the NativeRendering render type will lend poor and sometimes pixelated results. + + The default rendering type is determined by \l QQuickWindow::textRenderType(). */ QQuickTextEdit::RenderType QQuickTextEdit::renderType() const { @@ -3038,6 +3069,32 @@ void QQuickTextEdit::resetBottomPadding() } /*! + \qmlproperty real QtQuick::TextEdit::tabStopDistance + \since 5.10 + + The default distance, in device units, between tab stops. + + \sa QTextOption::setTabStop() +*/ +int QQuickTextEdit::tabStopDistance() const +{ + Q_D(const QQuickTextEdit); + return d->document->defaultTextOption().tabStopDistance(); +} + +void QQuickTextEdit::setTabStopDistance(qreal distance) +{ + Q_D(QQuickTextEdit); + QTextOption textOptions = d->document->defaultTextOption(); + if (textOptions.tabStopDistance() == distance) + return; + + textOptions.setTabStopDistance(distance); + d->document->setDefaultTextOption(textOptions); + emit tabStopDistanceChanged(distance); +} + +/*! \qmlmethod QtQuick::TextEdit::clear() \since 5.7 diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index c8d3515be1..23033edb88 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -111,6 +111,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding RESET resetRightPadding NOTIFY rightPaddingChanged REVISION 6) Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged REVISION 6) Q_PROPERTY(QString preeditText READ preeditText NOTIFY preeditTextChanged REVISION 7) + Q_PROPERTY(qreal tabStopDistance READ tabStopDistance WRITE setTabStopDistance NOTIFY tabStopDistanceChanged REVISION 10) public: QQuickTextEdit(QQuickItem *parent=0); @@ -296,6 +297,9 @@ public: void setBottomPadding(qreal padding); void resetBottomPadding(); + int tabStopDistance() const; + void setTabStopDistance(qreal distance); + Q_SIGNALS: void textChanged(); Q_REVISION(7) void preeditTextChanged(); @@ -340,6 +344,7 @@ Q_SIGNALS: Q_REVISION(6) void leftPaddingChanged(); Q_REVISION(6) void rightPaddingChanged(); Q_REVISION(6) void bottomPaddingChanged(); + Q_REVISION(10) void tabStopDistanceChanged(qreal distance); public Q_SLOTS: void selectAll(); diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 03bce00cb0..455fffbcbc 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -53,6 +53,7 @@ #include "qquicktextedit_p.h" #include "qquickimplicitsizeitem_p_p.h" +#include "qquicktextutil_p.h" #include <QtQml/qqml.h> #include <QtCore/qlist.h> @@ -112,11 +113,7 @@ public: , quickDocument(0), lastSelectionStart(0), lastSelectionEnd(0), lineCount(0) , hAlign(QQuickTextEdit::AlignLeft), vAlign(QQuickTextEdit::AlignTop) , format(QQuickTextEdit::PlainText), wrapMode(QQuickTextEdit::NoWrap) -#if defined(QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) - , renderType(QQuickTextEdit::QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) -#else - , renderType(QQuickTextEdit::QtRendering) -#endif + , renderType(QQuickTextUtil::textRenderType<QQuickTextEdit>()) , contentDirection(Qt::LayoutDirectionAuto) , mouseSelectionMode(QQuickTextEdit::SelectCharacters) #if QT_CONFIG(im) diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 28b089da49..d516b6f30c 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -40,7 +40,6 @@ #include "qquicktextinput_p.h" #include "qquicktextinput_p_p.h" #include "qquickwindow.h" -#include "qquicktextutil_p.h" #include <private/qqmlglobal_p.h> #include <private/qv4scopedvalue_p.h> @@ -157,7 +156,7 @@ void QQuickTextInput::setText(const QString &s) Supported render types are: \list - \li Text.QtRendering - the default + \li Text.QtRendering \li Text.NativeRendering \endlist @@ -165,6 +164,8 @@ void QQuickTextInput::setText(const QString &s) not require advanced features such as transformation of the text. Using such features in combination with the NativeRendering render type will lend poor and sometimes pixelated results. + + The default rendering type is determined by \l QQuickWindow::textRenderType(). */ QQuickTextInput::RenderType QQuickTextInput::renderType() const { @@ -378,6 +379,36 @@ QString QQuickTextInputPrivate::realText() const TextInput { text: "Hello"; renderType: TextInput.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } \endqml */ + +/*! + \qmlproperty bool QtQuick::TextInput::font.kerning + \since 5.10 + + Enables or disables the kerning OpenType feature when shaping the text. This may improve performance + when creating or changing the text, at the expense of some cosmetic features. The default value + is true. + + \qml + TextInput { text: "OATS FLAVOUR WAY"; font.kerning: false } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick::TextInput::font.preferShaping + \since 5.10 + + Sometimes, a font will apply complex rules to a set of characters in order to + display them correctly. In some writing systems, such as Brahmic scripts, this is + required in order for the text to be legible, but in e.g. Latin script, it is merely + a cosmetic feature. Setting the \c preferShaping property to false will disable all + such features when they are not required, which will improve performance in most cases. + + The default value is true. + + \qml + TextInput { text: "Some text"; font.preferShaping: false } + \endqml +*/ QFont QQuickTextInput::font() const { Q_D(const QQuickTextInput); diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index 0bf5779a53..c3218197a4 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -43,6 +43,7 @@ #include "qquicktextinput_p.h" #include "qquicktext_p.h" #include "qquickimplicitsizeitem_p_p.h" +#include "qquicktextutil_p.h" #include <QtQml/qqml.h> #include <QtCore/qelapsedtimer.h> @@ -122,11 +123,7 @@ public: , vAlign(QQuickTextInput::AlignTop) , wrapMode(QQuickTextInput::NoWrap) , m_echoMode(QQuickTextInput::Normal) -#if defined(QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) - , renderType(QQuickTextInput::QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) -#else - , renderType(QQuickTextInput::QtRendering) -#endif + , renderType(QQuickTextUtil::textRenderType<QQuickTextInput>()) , updateType(UpdatePaintNode) , mouseSelectionMode(QQuickTextInput::SelectCharacters) , m_layoutDirection(Qt::LayoutDirectionAuto) diff --git a/src/quick/items/qquicktextutil_p.h b/src/quick/items/qquicktextutil_p.h index 01055c95ec..bad5e738a8 100644 --- a/src/quick/items/qquicktextutil_p.h +++ b/src/quick/items/qquicktextutil_p.h @@ -54,6 +54,7 @@ #include <QtQml/qqml.h> #include <QtQml/qqmlincubator.h> #include <QtQuick/qquickitem.h> +#include <QtQuick/qquickwindow.h> QT_BEGIN_NAMESPACE @@ -64,6 +65,7 @@ public: template <typename Private> static void setCursorDelegate(Private *d, QQmlComponent *delegate); template <typename Private> static void createCursor(Private *d); + template <typename T> static typename T::RenderType textRenderType(); static qreal alignedX(qreal textWidth, qreal itemWidth, int alignment); static qreal alignedY(qreal textHeight, qreal itemHeight, int alignment); @@ -124,6 +126,20 @@ void QQuickTextUtil::createCursor(Private *d) parent->update(); } +template <typename T> +typename T::RenderType QQuickTextUtil::textRenderType() +{ + switch (QQuickWindow::textRenderType()) { + case QQuickWindow::QtTextRendering: + return T::QtRendering; + case QQuickWindow::NativeTextRendering: + return T::NativeRendering; + } + + Q_UNREACHABLE(); + return T::QtRendering; +} + QT_END_NAMESPACE #endif diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index b58caa061a..65d69a3bcf 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -46,6 +46,7 @@ #include "qquickevents_p_p.h" #include <private/qquickdrag_p.h> +#include <private/qquickpointerhandler_p.h> #include <QtQuick/private/qsgrenderer_p.h> #include <QtQuick/private/qsgtexture_p.h> @@ -92,6 +93,12 @@ extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_ bool QQuickWindowPrivate::defaultAlphaBuffer = false; +#if defined(QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) +QQuickWindow::TextRenderType QQuickWindowPrivate::textRenderType = QQuickWindow::QT_QUICK_DEFAULT_TEXT_RENDER_TYPE; +#else +QQuickWindow::TextRenderType QQuickWindowPrivate::textRenderType = QQuickWindow::QtTextRendering; +#endif + void QQuickWindowPrivate::updateFocusItemTransform() { #if QT_CONFIG(im) @@ -499,6 +506,8 @@ QQuickWindowPrivate::QQuickWindowPrivate() , persistentSceneGraph(true) , lastWheelEventAccepted(false) , componentCompleted(true) + , allowChildEventFiltering(true) + , allowDoubleClick(true) , lastFocusReason(Qt::OtherFocusReason) , renderTarget(0) , renderTargetId(0) @@ -652,7 +661,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve if (!item->contains(pos)) break; - qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << p.id() << "->" << item; + qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << hex << p.id() << "->" << item; QScopedPointer<QMouseEvent> mousePress(touchToMouseEvent(QEvent::MouseButtonPress, p, event.data(), item, false)); // Send a single press and see if that's accepted @@ -664,7 +673,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve if (!q->mouseGrabberItem()) item->grabMouse(); auto pointerEventPoint = pointerEvent->pointById(p.id()); - pointerEventPoint->setGrabber(item); + pointerEventPoint->setGrabberItem(item); if (checkIfDoubleClicked(event->timestamp())) { QScopedPointer<QMouseEvent> mouseDoubleClick(touchToMouseEvent(QEvent::MouseButtonDblClick, p, event.data(), item, false)); @@ -688,7 +697,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEve QCoreApplication::sendEvent(item, me.data()); event->setAccepted(me->isAccepted()); if (me->isAccepted()) { - qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << p.id() << "->" << mouseGrabberItem; + qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << hex << p.id() << "->" << mouseGrabberItem; } return event->isAccepted(); } else { @@ -744,39 +753,42 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber) if (q->mouseGrabberItem() == grabber) return; - qCDebug(DBG_MOUSE_TARGET) << "grabber" << q->mouseGrabberItem() << "->" << grabber; QQuickItem *oldGrabber = q->mouseGrabberItem(); - bool fromTouch = false; + qCDebug(DBG_MOUSE_TARGET) << "grabber" << oldGrabber << "->" << grabber; if (grabber && touchMouseId != -1 && touchMouseDevice) { // update the touch item for mouse touch id to the new grabber qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << hex << touchMouseId << "->" << q->mouseGrabberItem(); auto point = pointerEventInstance(touchMouseDevice)->pointById(touchMouseId); - if (point) - point->setGrabber(grabber); - fromTouch = true; + if (point) { + auto originalEvent = pointerEventInstance(point->pointerEvent()->device()); + for (int i = 0; i < originalEvent->pointCount(); ++i) + originalEvent->point(i)->cancelExclusiveGrab(); + point->setGrabberItem(grabber); + for (auto handler : point->passiveGrabbers()) + point->cancelPassiveGrab(handler); + } } else { QQuickPointerEvent *event = pointerEventInstance(QQuickPointerDevice::genericMouseDevice()); Q_ASSERT(event->pointCount() == 1); - event->point(0)->setGrabber(grabber); + auto point = event->point(0); + point->setGrabberItem(grabber); + for (auto handler : point->passiveGrabbers()) + point->cancelPassiveGrab(handler); } + if (oldGrabber) { QEvent e(QEvent::UngrabMouse); - QSet<QQuickItem *> hasFiltered; - if (!sendFilteredMouseEvent(oldGrabber->parentItem(), oldGrabber, &e, &hasFiltered)) { + hasFiltered.clear(); + if (!sendFilteredMouseEvent(&e, oldGrabber, oldGrabber->parentItem())) oldGrabber->mouseUngrabEvent(); - if (fromTouch) - oldGrabber->touchUngrabEvent(); - } } } -void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int> &ids) +void QQuickWindowPrivate::grabTouchPoints(QObject *grabber, const QVector<int> &ids) { - QSet<QQuickItem*> ungrab; for (int i = 0; i < ids.count(); ++i) { - // FIXME: deprecate this function, we need a device int id = ids.at(i); if (Q_UNLIKELY(id < 0)) { qWarning("ignoring grab of touchpoint %d", id); @@ -784,11 +796,11 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int } if (id == touchMouseId) { auto point = pointerEventInstance(touchMouseDevice)->pointById(id); - auto touchMouseGrabber = point->grabber(); + auto touchMouseGrabber = point->grabberItem(); if (touchMouseGrabber) { - point->setGrabber(nullptr); + point->setExclusiveGrabber(nullptr); touchMouseGrabber->mouseUngrabEvent(); - ungrab.insert(touchMouseGrabber); + touchMouseGrabber->touchUngrabEvent(); touchMouseDevice = nullptr; touchMouseId = -1; } @@ -800,19 +812,23 @@ void QQuickWindowPrivate::grabTouchPoints(QQuickItem *grabber, const QVector<int auto point = pointerEventInstance(device)->pointById(id); if (!point) continue; - QQuickItem *oldGrabber = point->grabber(); + QObject *oldGrabber = point->exclusiveGrabber(); if (oldGrabber == grabber) continue; - - point->setGrabber(grabber); - if (oldGrabber) - ungrab.insert(oldGrabber); + point->setExclusiveGrabber(grabber); } } - for (QQuickItem *oldGrabber : qAsConst(ungrab)) - oldGrabber->touchUngrabEvent(); } +/*! + Ungrabs all touchpoint grabs and/or the mouse grab from the given item \a grabber. + This should not be called when processing a release event - that's redundant. + It is called in other cases, when the points may not be released, but the item + nevertheless must lose its grab due to becoming disabled, invisible, etc. + QQuickEventPoint::setGrabberItem() calls touchUngrabEvent() when all points are released, + but if not all points are released, it cannot be sure whether to call touchUngrabEvent() + or not; so we have to do it here. +*/ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch) { Q_Q(QQuickWindow); @@ -821,17 +837,19 @@ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool to setMouseGrabber(nullptr); } if (Q_LIKELY(touch)) { + bool ungrab = false; const auto touchDevices = QQuickPointerDevice::touchDevices(); for (auto device : touchDevices) { auto pointerEvent = pointerEventInstance(device); for (int i = 0; i < pointerEvent->pointCount(); ++i) { - if (pointerEvent->point(i)->grabber() == grabber) { - pointerEvent->point(i)->setGrabber(nullptr); - // FIXME send ungrab event only once - grabber->touchUngrabEvent(); + if (pointerEvent->point(i)->exclusiveGrabber() == grabber) { + pointerEvent->point(i)->setGrabberItem(nullptr); + ungrab = true; } } } + if (ungrab) + grabber->touchUngrabEvent(); } } @@ -1494,12 +1512,12 @@ QQuickItem *QQuickWindow::mouseGrabberItem() const if (d->touchMouseId != -1 && d->touchMouseDevice) { QQuickPointerEvent *event = d->pointerEventInstance(d->touchMouseDevice); auto point = event->pointById(d->touchMouseId); - return point ? point->grabber() : nullptr; + return point ? point->grabberItem() : nullptr; } QQuickPointerEvent *event = d->pointerEventInstance(QQuickPointerDevice::genericMouseDevice()); Q_ASSERT(event->pointCount()); - return event->point(0)->grabber(); + return event->point(0)->grabberItem(); } @@ -1626,10 +1644,15 @@ void QQuickWindow::keyReleaseEvent(QKeyEvent *e) void QQuickWindowPrivate::deliverKeyEvent(QKeyEvent *e) { - Q_Q(QQuickWindow); - - if (activeFocusItem) - q->sendEvent(activeFocusItem, e); + if (activeFocusItem) { + QQuickItem *item = activeFocusItem; + e->accept(); + QCoreApplication::sendEvent(item, e); + while (!e->isAccepted() && (item = item->parentItem())) { + e->accept(); + QCoreApplication::sendEvent(item, e); + } + } } QMouseEvent *QQuickWindowPrivate::cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos) @@ -1648,36 +1671,74 @@ QMouseEvent *QQuickWindowPrivate::cloneMouseEvent(QMouseEvent *event, QPointF *t void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEvent) { - Q_Q(QQuickWindow); auto point = pointerEvent->point(0); - lastMousePosition = point->scenePos(); - QQuickItem *grabber = point->grabber(); - if (grabber) { - // if the update consists of changing button state, then don't accept it - // unless the button is one in which the item is interested - if (pointerEvent->button() != Qt::NoButton - && grabber->acceptedMouseButtons() - && !(grabber->acceptedMouseButtons() & pointerEvent->button())) { - pointerEvent->setAccepted(false); - return; - } + lastMousePosition = point->scenePosition(); + const bool mouseIsReleased = (point->state() == QQuickEventPoint::Released && pointerEvent->buttons() == Qt::NoButton); + + if (point->exclusiveGrabber()) { + if (auto grabber = point->grabberItem()) { + if (sendFilteredPointerEvent(pointerEvent, grabber)) + return; + // if the grabber is an Item: + // if the update consists of changing button state, don't accept it unless + // the button is one in which the grabber is interested + Qt::MouseButtons acceptedButtons = grabber->acceptedMouseButtons(); + if (pointerEvent->button() != Qt::NoButton && acceptedButtons + && !(acceptedButtons & pointerEvent->button())) { + pointerEvent->setAccepted(false); + return; + } - // send update - QPointF localPos = grabber->mapFromScene(lastMousePosition); - auto me = pointerEvent->asMouseEvent(localPos); - me->accept(); - q->sendEvent(grabber, me); - point->setAccepted(me->isAccepted()); + // send update + QPointF localPos = grabber->mapFromScene(lastMousePosition); + auto me = pointerEvent->asMouseEvent(localPos); + me->accept(); + QCoreApplication::sendEvent(grabber, me); + point->setAccepted(me->isAccepted()); - // release event, make sure to ungrab if there still is a grabber - if (me->type() == QEvent::MouseButtonRelease && !me->buttons() && q->mouseGrabberItem()) - q->mouseGrabberItem()->ungrabMouse(); + // release event: ungrab if no buttons are pressed anymore + if (mouseIsReleased) + setMouseGrabber(nullptr); + } else { + // if the grabber is not an Item, it must be a PointerHandler + auto handler = point->grabberPointerHandler(); + pointerEvent->localize(handler->parentItem()); + if (!sendFilteredPointerEvent(pointerEvent, handler->parentItem())) + handler->handlePointerEvent(pointerEvent); + if (mouseIsReleased) + point->setGrabberPointerHandler(nullptr, true); + } } else { - // send initial press bool delivered = false; if (pointerEvent->isPressEvent()) { - QSet<QQuickItem*> hasFiltered; - delivered = deliverPressEvent(pointerEvent, &hasFiltered); + // send initial press + delivered = deliverPressOrReleaseEvent(pointerEvent); + } else if (pointerEvent->device()->type() == QQuickPointerDevice::Mouse) { + // if this is an update or release from an actual mouse, + // and the point wasn't grabbed, deliver only to PointerHandlers: + // passive grabbers first, then the rest + const QVector<QQuickPointerHandler *> &eventDeliveryTargets = pointerEvent->device()->eventDeliveryTargets(); + for (auto handler : point->passiveGrabbers()) { + // a null pointer in passiveGrabbers is unlikely, unless the grabbing handler was deleted dynamically + if (Q_LIKELY(handler) && !eventDeliveryTargets.contains(handler)) { + if (!sendFilteredPointerEvent(pointerEvent, handler->parentItem())) + handler->handlePointerEvent(pointerEvent); + } + } + // If some points weren't grabbed, deliver to non-grabber PointerHandlers in reverse paint order + if (!pointerEvent->allPointsGrabbed() && pointerEvent->buttons()) { + QVector<QQuickItem *> targetItems = pointerTargets(contentItem, point->scenePosition(), false, false); + for (QQuickItem *item : targetItems) { + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + pointerEvent->localize(item); + if (!sendFilteredPointerEvent(pointerEvent, item)) { + if (itemPrivate->handlePointerEvent(pointerEvent, true)) // avoid re-delivering to grabbers + delivered = true; + } + if (point->exclusiveGrabber()) + break; + } + } } if (!delivered) @@ -1698,10 +1759,9 @@ bool QQuickWindowPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item, hoverEvent.setTimestamp(timestamp); hoverEvent.setAccepted(accepted); - QSet<QQuickItem *> hasFiltered; - if (sendFilteredMouseEvent(item->parentItem(), item, &hoverEvent, &hasFiltered)) { + hasFiltered.clear(); + if (sendFilteredMouseEvent(&hoverEvent, item, item->parentItem())) return true; - } QCoreApplication::sendEvent(item, &hoverEvent); @@ -1857,10 +1917,20 @@ bool QQuickWindowPrivate::deliverNativeGestureEvent(QQuickItem *item, QNativeGes return true; } - QPointF p = item->mapFromScene(event->localPos()); + // Try the Item's pointer handlers first + QQuickPointerEvent *pointerEvent = pointerEventInstance(event); + pointerEvent->localize(item); + if (itemPrivate->handlePointerEvent(pointerEvent, false)) { + if (pointerEvent->allPointsAccepted()) { + event->accept(); + return true; + } + } + // If still not accepted, try direct delivery to the item + QPointF p = item->mapFromScene(event->localPos()); if (item->contains(p)) { - QNativeGestureEvent copy(event->gestureType(), p, event->windowPos(), event->screenPos(), + QNativeGestureEvent copy(event->gestureType(), event->device(), p, event->windowPos(), event->screenPos(), event->value(), 0L, 0L); // TODO can't copy things I can't access event->accept(); item->event(©); @@ -1880,19 +1950,16 @@ bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) Q_Q(QQuickWindow); // A TouchCancel event will typically not contain any points. - // Deliver it to all items that have active touches. + // Deliver it to all items and handlers that have active touches. QQuickPointerEvent *pointerEvent = pointerEventInstance(QQuickPointerDevice::touchDevice(event->device())); - QVector<QQuickItem *> grabbers = pointerEvent->grabbers(); - - for (QQuickItem *grabber: qAsConst(grabbers)) { - q->sendEvent(grabber, event); - } + for (int i = 0; i < pointerEvent->pointCount(); ++i) + pointerEvent->point(i)->cancelExclusiveGrabImpl(event); touchMouseId = -1; touchMouseDevice = nullptr; if (q->mouseGrabberItem()) q->mouseGrabberItem()->ungrabMouse(); - // The next touch event can only be a TouchBegin so clean up. + // The next touch event can only be a TouchBegin, so clean up. pointerEvent->clearGrabbers(); return true; } @@ -2029,8 +2096,6 @@ void QQuickWindow::mouseReleaseEvent(QMouseEvent *event) void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) { - Q_Q(QQuickWindow); - if (event->source() == Qt::MouseEventSynthesizedBySystem) { event->accept(); return; @@ -2051,7 +2116,8 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) case QEvent::MouseButtonDblClick: Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick, event->button(), event->buttons()); - deliverPointerEvent(pointerEventInstance(event)); + if (allowDoubleClick) + deliverPointerEvent(pointerEventInstance(event)); break; case QEvent::MouseMove: Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove, @@ -2063,7 +2129,7 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) updateCursor(event->windowPos()); #endif - if (!q->mouseGrabberItem()) { + if (!pointerEventInstance(QQuickPointerDevice::genericMouseDevice())->point(0)->exclusiveGrabber()) { QPointF last = lastMousePosition.isNull() ? event->windowPos() : lastMousePosition; lastMousePosition = event->windowPos(); @@ -2074,7 +2140,6 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) accepted = clearHover(event->timestamp()); } event->setAccepted(accepted); - return; } deliverPointerEvent(pointerEventInstance(event)); break; @@ -2111,23 +2176,36 @@ void QQuickWindowPrivate::flushFrameSynchronousEvents() } } -QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device) const +QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const { - // the list of devices should be very small so a linear search should be ok - for (QQuickPointerEvent *e: pointerEventInstances) { + // Search for a matching reusable event object. + for (QQuickPointerEvent *e : pointerEventInstances) { + // If device can generate native gestures (e.g. a trackpad), there might be two QQuickPointerEvents: + // QQuickPointerNativeGestureEvent and QQuickPointerTouchEvent. Use eventType to disambiguate. + if (eventType == QEvent::NativeGesture && !qobject_cast<QQuickPointerNativeGestureEvent*>(e)) + continue; + // Otherwise we assume there's only one event type per device. + // More disambiguation tests might need to be added above if that changes later. if (e->device() == device) return e; } + // Not found: we have to create a suitable event instance. QQuickPointerEvent *ev = nullptr; QQuickWindow *q = const_cast<QQuickWindow*>(q_func()); switch (device->type()) { case QQuickPointerDevice::Mouse: + // QWindowSystemInterface::handleMouseEvent() does not take a device parameter: + // we assume all mouse events come from one mouse (the "core pointer"). + // So when the event is a mouse event, device == QQuickPointerDevice::genericMouseDevice() ev = new QQuickPointerMouseEvent(q, device); break; case QQuickPointerDevice::TouchPad: case QQuickPointerDevice::TouchScreen: - ev = new QQuickPointerTouchEvent(q, device); + if (eventType == QEvent::NativeGesture) + ev = new QQuickPointerNativeGestureEvent(q, device); + else // assume QEvent::Type is one of TouchBegin/Update/End + ev = new QQuickPointerTouchEvent(q, device); break; default: // TODO tablet event types @@ -2147,29 +2225,29 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevic QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QEvent *event) const { QQuickPointerDevice *dev = nullptr; - QQuickPointerEvent *ev = nullptr; switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: dev = QQuickPointerDevice::genericMouseDevice(); - ev = pointerEventInstance(dev); break; case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: case QEvent::TouchCancel: dev = QQuickPointerDevice::touchDevice(static_cast<QTouchEvent *>(event)->device()); - ev = pointerEventInstance(dev); break; // TODO tablet event types + case QEvent::NativeGesture: + dev = QQuickPointerDevice::touchDevice(static_cast<QNativeGestureEvent *>(event)->device()); + break; default: break; } - Q_ASSERT(ev); - return ev->reset(event); + Q_ASSERT(dev); + return pointerEventInstance(dev, event->type())->reset(event); } void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event) @@ -2179,8 +2257,12 @@ void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event) // the usecase a bit evil, but we at least don't want to lose events. ++pointerEventRecursionGuard; + skipDelivery.clear(); if (event->asPointerMouseEvent()) { deliverMouseEvent(event->asPointerMouseEvent()); + // failsafe: never allow any kind of grab to persist after release + if (event->isReleaseEvent() && event->buttons() == Qt::NoButton) + event->clearGrabbers(); } else if (event->asPointerTouchEvent()) { deliverTouchEvent(event->asPointerTouchEvent()); } else { @@ -2194,7 +2276,10 @@ void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event) // check if item or any of its child items contain the point // FIXME: should this be iterative instead of recursive? -QVector<QQuickItem *> QQuickWindowPrivate::pointerTargets(QQuickItem *item, const QPointF &scenePos, bool checkMouseButtons) const +// If checkMouseButtons is true, it means we are finding targets for a mouse event, so no item for which acceptedMouseButtons() is NoButton will be added. +// If checkAcceptsTouch is true, it means we are finding targets for a touch event, so either acceptTouchEvents() must return true OR +// it must accept a synth. mouse event, thus if acceptTouchEvents() returns false but acceptedMouseButtons() is true, gets added; if not, it doesn't. +QVector<QQuickItem *> QQuickWindowPrivate::pointerTargets(QQuickItem *item, const QPointF &scenePos, bool checkMouseButtons, bool checkAcceptsTouch) const { QVector<QQuickItem *> targets; auto itemPrivate = QQuickItemPrivate::get(item); @@ -2212,13 +2297,18 @@ QVector<QQuickItem *> QQuickWindowPrivate::pointerTargets(QQuickItem *item, cons auto childPrivate = QQuickItemPrivate::get(child); if (!child->isVisible() || !child->isEnabled() || childPrivate->culled) continue; - targets << pointerTargets(child, scenePos, false); + targets << pointerTargets(child, scenePos, checkMouseButtons, checkAcceptsTouch); } - if (item->contains(itemPos) && (!checkMouseButtons || itemPrivate->acceptedMouseButtons())) { - // add this item last - children take precedence - targets << item; + bool relevant = item->contains(itemPos); + if (!(itemPrivate->hasPointerHandlers())) { + if (relevant && checkMouseButtons && item->acceptedMouseButtons() == Qt::NoButton) + relevant = false; + if (relevant && checkAcceptsTouch && !(item->acceptTouchEvents() || item->acceptedMouseButtons())) + relevant = false; } + if (relevant) + targets << item; // add this item last: children take precedence return targets; } @@ -2248,11 +2338,12 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) { qCDebug(DBG_TOUCH) << " - delivering" << event->asTouchEvent(); - QSet<QQuickItem *> hasFiltered; if (event->isPressEvent()) - deliverPressEvent(event, &hasFiltered); - if (!event->allPointsAccepted()) - deliverUpdatedTouchPoints(event, &hasFiltered); + deliverPressOrReleaseEvent(event); + if (!event->allUpdatedPointsAccepted()) + deliverUpdatedTouchPoints(event); + if (event->isReleaseEvent()) + deliverPressOrReleaseEvent(event, true); // Remove released points from itemForTouchPointId bool allReleased = true; @@ -2262,7 +2353,7 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) if (point->state() == QQuickEventPoint::Released) { int id = point->pointId(); qCDebug(DBG_TOUCH_TARGET) << "TP" << hex << id << "released"; - point->setGrabber(nullptr); + point->setGrabberItem(nullptr); if (id == touchMouseId) { touchMouseId = -1; touchMouseDevice = nullptr; @@ -2272,29 +2363,93 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) } } - if (allReleased && !event->grabbers().isEmpty()) { - qWarning() << "No release received for some grabbers" << event->grabbers(); + if (allReleased) { + if (Q_UNLIKELY(!event->exclusiveGrabbers().isEmpty())) + qWarning() << "No release received for some grabbers" << event->exclusiveGrabbers(); event->clearGrabbers(); } } // Deliver touch points to existing grabbers -bool QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event, QSet<QQuickItem *> *hasFiltered) -{ - const auto grabbers = event->grabbers(); - for (auto grabber : grabbers) - deliverMatchingPointsToItem(grabber, event, hasFiltered); +void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event) +{ + const auto grabbers = event->exclusiveGrabbers(); + for (auto grabber : grabbers) { + // The grabber is guaranteed to be either an item or a handler. + QQuickItem *receiver = qmlobject_cast<QQuickItem *>(grabber); + if (!receiver) { + // The grabber is not an item? It's a handler then. Let it have the event first. + QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber); + receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem(); + if (sendFilteredPointerEvent(event, receiver)) + return; + event->localize(receiver); + handler->handlePointerEvent(event); + if (event->allPointsAccepted()) + return; + } + // If the grabber is an item or the grabbing handler didn't handle it, + // then deliver the event to the item (which may have multiple handlers). + deliverMatchingPointsToItem(receiver, event); + } - return false; + // If some points weren't grabbed, deliver only to non-grabber PointerHandlers + if (!event->allPointsGrabbed()) { + int pointCount = event->pointCount(); + + // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once) + const QVector<QQuickPointerHandler *> &eventDeliveryTargets = event->device()->eventDeliveryTargets(); + for (int i = 0; i < pointCount; ++i) { + QQuickEventPoint *point = event->point(i); + for (auto handler : point->passiveGrabbers()) { + if (Q_LIKELY(handler) && !eventDeliveryTargets.contains(handler)) { + if (sendFilteredPointerEvent(event, handler->parentItem())) + return; + handler->handlePointerEvent(event); + } + } + } + + // If some points weren't grabbed, deliver to non-grabber PointerHandlers in reverse paint order + if (!event->allPointsGrabbed()) { + QVector<QQuickItem *> targetItems; + for (int i = 0; i < pointCount; ++i) { + QQuickEventPoint *point = event->point(i); + if (point->state() == QQuickEventPoint::Pressed) + continue; // presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints + QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point->scenePosition(), false, false); + if (targetItems.count()) { + targetItems = mergePointerTargets(targetItems, targetItemsForPoint); + } else { + targetItems = targetItemsForPoint; + } + } + + for (QQuickItem *item: targetItems) { + if (grabbers.contains(item)) + continue; + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + event->localize(item); + itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers + if (event->allPointsGrabbed()) + break; + } + } + } } -// Deliver newly pressed touch points -bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event, QSet<QQuickItem *> *hasFiltered) +// Deliver an event containing newly pressed or released touch points +bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event, bool handlersOnly) { - const QVector<QPointF> points = event->unacceptedPressedPointScenePositions(); + int pointCount = event->pointCount(); QVector<QQuickItem *> targetItems; - for (QPointF point: points) { - QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point, false); + bool isTouchEvent = (event->asPointerTouchEvent() != nullptr); + for (int i = 0; i < pointCount; ++i) { + auto point = event->point(i); + point->setAccepted(false); // because otherwise touchEventForItem will ignore it + if (point->grabberPointerHandler() && point->state() == QQuickEventPoint::Released) + point->setGrabberPointerHandler(nullptr, true); + QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point->scenePosition(), !isTouchEvent, isTouchEvent); if (targetItems.count()) { targetItems = mergePointerTargets(targetItems, targetItemsForPoint); } else { @@ -2302,8 +2457,21 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event, QSet<QQui } } - for (QQuickItem *item: targetItems) { - deliverMatchingPointsToItem(item, event, hasFiltered); + for (QQuickItem *item : targetItems) { + if (!handlersOnly && sendFilteredPointerEvent(event, item)) { + if (event->isAccepted()) { + for (int i = 0; i < event->pointCount(); ++i) + event->point(i)->setAccepted(); + return true; + } + skipDelivery.append(item); + } + + // Do not deliverMatchingPointsTo any item for which the filtering parent already intercepted the event, + // nor to any item which already had a chance to filter. + if (skipDelivery.contains(item)) + continue; + deliverMatchingPointsToItem(item, event, handlersOnly); if (event->allPointsAccepted()) break; } @@ -2311,25 +2479,43 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event, QSet<QQui return event->allPointsAccepted(); } -bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, QSet<QQuickItem *> *hasFiltered) +void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, bool handlersOnly) { Q_Q(QQuickWindow); + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + pointerEvent->localize(item); + + // Let the Item's handlers (if any) have the event first. + // However, double click should never be delivered to handlers. + if (!pointerEvent->isDoubleClickEvent()) { + itemPrivate->handlePointerEvent(pointerEvent); + allowDoubleClick = !(pointerEvent->asPointerMouseEvent() && pointerEvent->isPressEvent() && pointerEvent->allPointsAccepted()); + } + if (handlersOnly) + return; + if (pointerEvent->allPointsAccepted() && !pointerEvent->isReleaseEvent()) + return; + + // If all points are released and the item is not the grabber, it doesn't get the event. + // But if at least one point is still pressed, we might be in a potential gesture-takeover scenario. + if (pointerEvent->isReleaseEvent() && !pointerEvent->isUpdateEvent() + && !pointerEvent->exclusiveGrabbers().contains(item)) + return; // TODO: unite this mouse point delivery with the synthetic mouse event below - if (auto event = pointerEvent->asPointerMouseEvent()) { - if (item->acceptedMouseButtons() & event->button()) { + auto event = pointerEvent->asPointerMouseEvent(); + if (event && item->acceptedMouseButtons() & event->button()) { auto point = event->point(0); if (point->isAccepted()) - return false; - + return; // The only reason to already have a mouse grabber here is // synthetic events - flickable sends one when setPressDelay is used. auto oldMouseGrabber = q->mouseGrabberItem(); - QPointF localPos = item->mapFromScene(point->scenePos()); + QPointF localPos = item->mapFromScene(point->scenePosition()); Q_ASSERT(item->contains(localPos)); // transform is checked already QMouseEvent *me = event->asMouseEvent(localPos); me->accept(); - q->sendEvent(item, me); + QCoreApplication::sendEvent(item, me); if (me->isAccepted()) { auto mouseGrabber = q->mouseGrabberItem(); if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) { @@ -2339,33 +2525,23 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo } point->setAccepted(true); } - return me->isAccepted(); - } - return false; + return; } - QQuickPointerTouchEvent *event = pointerEvent->asPointerTouchEvent(); - if (!event) - return false; + QQuickPointerTouchEvent *ptEvent = pointerEvent->asPointerTouchEvent(); + if (!ptEvent) + return; - QScopedPointer<QTouchEvent> touchEvent(event->touchEventForItem(item)); + QScopedPointer<QTouchEvent> touchEvent(ptEvent->touchEventForItem(item)); if (!touchEvent) - return false; + return; - qCDebug(DBG_TOUCH) << " - considering delivering " << touchEvent.data() << " to " << item; + qCDebug(DBG_TOUCH) << "considering delivering " << touchEvent.data() << " to " << item; bool eventAccepted = false; - // First check whether the parent wants to be a filter, - // and if the parent accepts the event we are done. - if (sendFilteredTouchEvent(item->parentItem(), item, event, hasFiltered)) { - // If the touch was accepted (regardless by whom or in what form), - // update acceptedNewPoints - qCDebug(DBG_TOUCH) << " - can't. intercepted " << touchEvent.data() << " to " << item->parentItem() << " instead of " << item; - for (auto point: qAsConst(touchEvent->touchPoints())) { - event->pointById(point.id())->setAccepted(); - } - return true; - } + // If any parent filters the event, we're done. + if (sendFilteredPointerEvent(pointerEvent, item)) + return; // Deliver the touch event to the given item qCDebug(DBG_TOUCH) << " - actually delivering " << touchEvent.data() << " to " << item; @@ -2373,36 +2549,34 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo eventAccepted = touchEvent->isAccepted(); // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it. - QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); if (!eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton)) { // send mouse event - if (deliverTouchAsMouse(item, event)) + if (deliverTouchAsMouse(item, ptEvent)) eventAccepted = true; } if (eventAccepted) { // If the touch was accepted (regardless by whom or in what form), // update accepted new points. + bool isPressOrRelease = pointerEvent->isPressEvent() || pointerEvent->isReleaseEvent(); for (auto point: qAsConst(touchEvent->touchPoints())) { - auto pointerEventPoint = event->pointById(point.id()); + auto pointerEventPoint = ptEvent->pointById(point.id()); pointerEventPoint->setAccepted(); - if (point.state() == Qt::TouchPointPressed) - pointerEventPoint->setGrabber(item); + if (isPressOrRelease) + pointerEventPoint->setGrabberItem(item); } } else { // But if the event was not accepted then we know this item // will not be interested in further updates for those touchpoint IDs either. for (auto point: qAsConst(touchEvent->touchPoints())) { if (point.state() == Qt::TouchPointPressed) { - if (event->pointById(point.id())->grabber() == item) { - qCDebug(DBG_TOUCH_TARGET) << "TP" << point.id() << "disassociated"; - event->pointById(point.id())->setGrabber(nullptr); + if (ptEvent->pointById(point.id())->exclusiveGrabber() == item) { + qCDebug(DBG_TOUCH_TARGET) << "TP" << hex << point.id() << "disassociated"; + ptEvent->pointById(point.id())->setGrabberItem(nullptr); } } } } - - return eventAccepted; } #if QT_CONFIG(draganddrop) @@ -2577,115 +2751,162 @@ QQuickItem *QQuickWindowPrivate::findCursorItem(QQuickItem *item, const QPointF } #endif -bool QQuickWindowPrivate::sendFilteredTouchEvent(QQuickItem *target, QQuickItem *item, QQuickPointerTouchEvent *event, QSet<QQuickItem *> *hasFiltered) +bool QQuickWindowPrivate::sendFilteredPointerEvent(QQuickPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { - Q_Q(QQuickWindow); + hasFiltered.clear(); + return sendFilteredPointerEventImpl(event, receiver, filteringParent ? filteringParent : receiver->parentItem()); +} - if (!target) +bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) +{ + if (!allowChildEventFiltering) + return false; + if (!filteringParent) return false; - bool filtered = false; - - QQuickItemPrivate *targetPrivate = QQuickItemPrivate::get(target); - if (targetPrivate->filtersChildMouseEvents && !hasFiltered->contains(target)) { - hasFiltered->insert(target); - QScopedPointer<QTouchEvent> targetEvent(event->touchEventForItem(target, true)); - if (targetEvent) { - if (target->childMouseEventFilter(item, targetEvent.data())) { - qCDebug(DBG_TOUCH) << " - first chance intercepted on childMouseEventFilter by " << target; - QVector<int> touchIds; - const int touchPointCount = targetEvent->touchPoints().size(); - touchIds.reserve(touchPointCount); - for (int i = 0; i < touchPointCount; ++i) - touchIds.append(targetEvent->touchPoints().at(i).id()); - target->grabTouchPoints(touchIds); - if (q->mouseGrabberItem()) { - q->mouseGrabberItem()->ungrabMouse(); - touchMouseId = -1; - touchMouseDevice = nullptr; + if (filteringParent->filtersChildMouseEvents() && !hasFiltered.contains(filteringParent)) { + hasFiltered.append(filteringParent); + if (QQuickPointerMouseEvent *pme = event->asPointerMouseEvent()) { + if (receiver->acceptedMouseButtons()) { + QPointF localPos = receiver->mapFromScene(pme->point(0)->scenePosition()); + QMouseEvent *me = pme->asMouseEvent(localPos); + const bool wasAccepted = me->isAccepted(); + me->setAccepted(true); + auto oldMouseGrabber = pme->point(0)->grabberItem(); + if (filteringParent->childMouseEventFilter(receiver, me)) { + qCDebug(DBG_MOUSE) << "mouse event intercepted by childMouseEventFilter of " << filteringParent; + skipDelivery.append(filteringParent); + filtered = true; + if (me->isAccepted() && pme->isPressEvent()) { + auto mouseGrabber = pme->point(0)->grabberItem(); + if (mouseGrabber && mouseGrabber != receiver && mouseGrabber != oldMouseGrabber) { + receiver->mouseUngrabEvent(); + } else { + pme->point(0)->setGrabberItem(receiver); + } + } + } else { + // Restore accepted state if the event was not filtered. + me->setAccepted(wasAccepted); } - filtered = true; } + } else if (QQuickPointerTouchEvent *pte = event->asPointerTouchEvent()) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + bool acceptsTouchEvents = receiver->acceptTouchEvents(); +#else + // In versions prior to Qt 6, we can't trust item->acceptTouchEvents() here, because it defaults to true. + bool acceptsTouchEvents = false; +#endif + if (acceptsTouchEvents || receiver->acceptedMouseButtons()) { + // get a touch event customized for delivery to filteringParent + QScopedPointer<QTouchEvent> filteringParentTouchEvent(pte->touchEventForItem(receiver, true)); + if (filteringParentTouchEvent) { + QVarLengthArray<QPair<QQuickPointerHandler *, QQuickEventPoint *>, 32> passiveGrabsToCancel; + if (filteringParent->childMouseEventFilter(receiver, filteringParentTouchEvent.data())) { + qCDebug(DBG_TOUCH) << "touch event intercepted by childMouseEventFilter of " << filteringParent; + skipDelivery.append(filteringParent); + for (auto point: qAsConst(filteringParentTouchEvent->touchPoints())) { + auto pointerEventPoint = pte->pointById(point.id()); + for (auto handler : pointerEventPoint->passiveGrabbers()) { + QPair<QQuickPointerHandler *, QQuickEventPoint *> grab(handler, pointerEventPoint); + if (!passiveGrabsToCancel.contains(grab)) + passiveGrabsToCancel.append(grab); + } + QQuickEventPoint *pt = event->pointById(point.id()); + pt->setAccepted(); + pt->setGrabberItem(filteringParent); + } + return true; + } else { + // filteringParent didn't filter the touch event. Give it a chance to filter a synthetic mouse event. + for (int i = 0; i < filteringParentTouchEvent->touchPoints().size(); ++i) { + const QTouchEvent::TouchPoint &tp = filteringParentTouchEvent->touchPoints().at(i); + + QEvent::Type t; + switch (tp.state()) { + case Qt::TouchPointPressed: + t = QEvent::MouseButtonPress; + break; + case Qt::TouchPointReleased: + t = QEvent::MouseButtonRelease; + break; + case Qt::TouchPointStationary: + continue; + default: + t = QEvent::MouseMove; + break; + } - for (int i = 0; i < targetEvent->touchPoints().size(); ++i) { - const QTouchEvent::TouchPoint &tp = targetEvent->touchPoints().at(i); - - QEvent::Type t; - switch (tp.state()) { - case Qt::TouchPointPressed: - t = QEvent::MouseButtonPress; - break; - case Qt::TouchPointReleased: - t = QEvent::MouseButtonRelease; - break; - case Qt::TouchPointStationary: - continue; - default: - t = QEvent::MouseMove; - break; - } - - bool touchMouseUnset = (touchMouseId == -1); - // Only deliver mouse event if it is the touchMouseId or it could become the touchMouseId - if (touchMouseUnset || touchMouseId == tp.id()) { - // targetEvent is already transformed wrt local position, velocity, etc. - - // FIXME: remove asTouchEvent!!! - QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(t, tp, event->asTouchEvent(), item, false)); - // If a filtering item calls QQuickWindow::mouseGrabberItem(), it should - // report the touchpoint's grabber. Whenever we send a synthetic mouse event, - // touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed. - touchMouseId = tp.id(); - touchMouseDevice = event->device(); - if (target->childMouseEventFilter(item, mouseEvent.data())) { - qCDebug(DBG_TOUCH) << " - second chance intercepted on childMouseEventFilter by " << target; - if (t != QEvent::MouseButtonRelease) { - qCDebug(DBG_TOUCH_TARGET) << "TP" << tp.id() << "->" << target; - if (touchMouseUnset) { - // the point was grabbed as a pure touch point before, now it will be treated as mouse - // but the old receiver still needs to be informed - if (auto oldGrabber = pointerEventInstance(touchMouseDevice)->pointById(tp.id())->grabber()) - oldGrabber->touchUngrabEvent(); + bool touchMouseUnset = (touchMouseId == -1); + // Only deliver mouse event if it is the touchMouseId or it could become the touchMouseId + if (touchMouseUnset || touchMouseId == tp.id()) { + // convert filteringParentTouchEvent (which is already transformed wrt local position, velocity, etc.) + // into a synthetic mouse event, and let childMouseEventFilter() have another chance with that + QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(t, tp, filteringParentTouchEvent.data(), receiver, false)); + // If a filtering item calls QQuickWindow::mouseGrabberItem(), it should + // report the touchpoint's grabber. Whenever we send a synthetic mouse event, + // touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed. + touchMouseId = tp.id(); + touchMouseDevice = event->device(); + if (filteringParent->childMouseEventFilter(receiver, mouseEvent.data())) { + qCDebug(DBG_TOUCH) << "touch event intercepted as synth mouse event by childMouseEventFilter of " << filteringParent; + skipDelivery.append(filteringParent); + if (t != QEvent::MouseButtonRelease) { + qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << hex << tp.id() << "->" << filteringParent; + pointerEventInstance(touchMouseDevice)->pointById(tp.id())->setGrabberItem(filteringParent); + touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set + if (mouseEvent->isAccepted()) + filteringParent->grabMouse(); + auto pointerEventPoint = pte->pointById(tp.id()); + for (auto handler : pointerEventPoint->passiveGrabbers()) { + QPair<QQuickPointerHandler *, QQuickEventPoint *> grab(handler, pointerEventPoint); + if (!passiveGrabsToCancel.contains(grab)) + passiveGrabsToCancel.append(grab); + } + } + filtered = true; + } + if (touchMouseUnset) { + // Now that we're done sending a synth mouse event, and it wasn't grabbed, + // the touchpoint is no longer acting as a synthetic mouse. Restore previous state. + touchMouseId = -1; + touchMouseDevice = nullptr; + } + // Only one touchpoint can be treated as a synthetic mouse, so after childMouseEventFilter + // has been called once, we're done with this loop over the touchpoints. + break; } - touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set - target->grabMouse(); } - filtered = true; - } - if (touchMouseUnset) { - // Now that we're done sending a synth mouse event, and it wasn't grabbed, - // the touchpoint is no longer acting as a synthetic mouse. Restore previous state. - touchMouseId = -1; - touchMouseDevice = nullptr; } - // Only one event can be filtered as a mouse event. - break; + for (auto grab : passiveGrabsToCancel) + grab.second->cancelPassiveGrab(grab.first); } } } } - - return sendFilteredTouchEvent(target->parentItem(), item, event, hasFiltered) || filtered; + return sendFilteredPointerEventImpl(event, receiver, filteringParent->parentItem()) || filtered; } -bool QQuickWindowPrivate::sendFilteredMouseEvent(QQuickItem *target, QQuickItem *item, QEvent *event, QSet<QQuickItem *> *hasFiltered) +bool QQuickWindowPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { - if (!target) + if (!filteringParent) return false; - QQuickItemPrivate *targetPrivate = QQuickItemPrivate::get(target); - if (targetPrivate->replayingPressEvent) + QQuickItemPrivate *filteringParentPrivate = QQuickItemPrivate::get(filteringParent); + if (filteringParentPrivate->replayingPressEvent) return false; bool filtered = false; - if (targetPrivate->filtersChildMouseEvents && !hasFiltered->contains(target)) { - hasFiltered->insert(target); - if (target->childMouseEventFilter(item, event)) + if (filteringParentPrivate->filtersChildMouseEvents && !hasFiltered.contains(filteringParent)) { + hasFiltered.append(filteringParent); + if (filteringParent->childMouseEventFilter(receiver, event)) { filtered = true; - qCDebug(DBG_MOUSE_TARGET) << target << "childMouseEventFilter ->" << filtered; + skipDelivery.append(filteringParent); + } + qCDebug(DBG_MOUSE_TARGET) << "for" << receiver << filteringParent << "childMouseEventFilter ->" << filtered; } - return sendFilteredMouseEvent(target->parentItem(), item, event, hasFiltered) || filtered; + return sendFilteredMouseEvent(event, receiver, filteringParent->parentItem()) || filtered; } bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold) @@ -2797,6 +3018,8 @@ void QQuickWindowPrivate::contextCreationFailureMessage(const QSurfaceFormat &fo #endif // !Q_OS_WIN32 } +#if QT_DEPRECATED_SINCE(5, 8) + /*! Propagates an event \a e to a QQuickItem \a item on the window. @@ -2833,8 +3056,8 @@ bool QQuickWindow::sendEvent(QQuickItem *item, QEvent *e) case QEvent::MouseButtonDblClick: case QEvent::MouseMove: { // XXX todo - should sendEvent be doing this? how does it relate to forwarded events? - QSet<QQuickItem *> hasFiltered; - if (!d->sendFilteredMouseEvent(item->parentItem(), item, e, &hasFiltered)) { + d->hasFiltered.clear(); + if (!d->sendFilteredMouseEvent(e, item, item->parentItem())) { // accept because qml items by default accept and have to explicitly opt out of accepting e->accept(); QCoreApplication::sendEvent(item, e); @@ -2849,6 +3072,8 @@ bool QQuickWindow::sendEvent(QQuickItem *item, QEvent *e) return false; } +#endif + void QQuickWindowPrivate::cleanupNodes() { for (int ii = 0; ii < cleanupNodeList.count(); ++ii) @@ -3450,6 +3675,11 @@ void QQuickWindow::setRenderTarget(QOpenGLFramebufferObject *fbo) The specified FBO must be created in the context of the window or one that shares with it. + \note \a fboId can also be set to 0. In this case rendering will target the + default framebuffer of whichever surface is current when the scenegraph + renders. \a size must still be valid, specifying the dimensions of the + surface. + \note This function only has an effect when using the default OpenGL scene graph adaptation. @@ -3631,6 +3861,23 @@ QQmlIncubationController *QQuickWindow::incubationController() const */ /*! + \enum QQuickWindow::TextRenderType + \since 5.10 + + This enum describes the default render type of text-like elements in Qt + Quick (\l Text, \l TextInput, etc.). + + Select NativeTextRendering if you prefer text to look native on the target + platform and do not require advanced features such as transformation of the + text. Using such features in combination with the NativeTextRendering + render type will lend poor and sometimes pixelated results. + + \value QtTextRendering Use Qt's own rasterization algorithm. + + \value NativeTextRendering Use the operating system's native rasterizer for text. +*/ + +/*! \fn void QQuickWindow::beforeSynchronizing() This signal is emitted before the scene graph is synchronized with the QML state. @@ -4600,7 +4847,7 @@ void QQuickWindow::setSceneGraphBackend(const QString &backend) } /*! - Returns the requested Qt Quick scenegraph \a backend. + Returns the requested Qt Quick scenegraph backend. \note The return value of this function may still be outdated by subsequent calls to setSceneGraphBackend() until the first QQuickWindow in the @@ -4652,6 +4899,34 @@ QSGNinePatchNode *QQuickWindow::createNinePatchNode() const return isSceneGraphInitialized() ? d->context->sceneGraphContext()->createNinePatchNode() : nullptr; } +/*! + \since 5.10 + + Returns the render type of text-like elements in Qt Quick. + The default is QQuickWindow::QtTextRendering. + + \sa setTextRenderType() +*/ +QQuickWindow::TextRenderType QQuickWindow::textRenderType() +{ + return QQuickWindowPrivate::textRenderType; +} + +/*! + \since 5.10 + + Sets the default render type of text-like elements in Qt Quick to \a renderType. + + \note setting the render type will only affect elements created afterwards; + the render type of existing elements will not be modified. + + \sa textRenderType() +*/ +void QQuickWindow::setTextRenderType(QQuickWindow::TextRenderType renderType) +{ + QQuickWindowPrivate::textRenderType = renderType; +} + #include "moc_qquickwindow.cpp" QT_END_NAMESPACE diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h index 9c3e7277bc..022c4738f2 100644 --- a/src/quick/items/qquickwindow.h +++ b/src/quick/items/qquickwindow.h @@ -100,6 +100,12 @@ public: }; Q_ENUM(SceneGraphError) + enum TextRenderType { + QtTextRendering, + NativeTextRendering + }; + Q_ENUM(TextRenderType) + explicit QQuickWindow(QWindow *parent = Q_NULLPTR); explicit QQuickWindow(QQuickRenderControl *renderControl); @@ -112,7 +118,9 @@ public: QQuickItem *mouseGrabberItem() const; - bool sendEvent(QQuickItem *, QEvent *); +#if QT_DEPRECATED_SINCE(5, 8) + QT_DEPRECATED bool sendEvent(QQuickItem *, QEvent *); +#endif QImage grabWindow(); #if QT_CONFIG(opengl) @@ -168,6 +176,9 @@ public: QSGImageNode *createImageNode() const; QSGNinePatchNode *createNinePatchNode() const; + static TextRenderType textRenderType(); + static void setTextRenderType(TextRenderType renderType); + Q_SIGNALS: void frameSwapped(); Q_REVISION(2) void openglContextCreated(QOpenGLContext *context); diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index b3ff5a2b35..ae49d5e304 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -143,11 +143,13 @@ public: bool deliverTouchAsMouse(QQuickItem *item, QQuickPointerEvent *pointerEvent); void translateTouchEvent(QTouchEvent *touchEvent); void setMouseGrabber(QQuickItem *grabber); - void grabTouchPoints(QQuickItem *grabber, const QVector<int> &ids); + void grabTouchPoints(QObject *grabber, const QVector<int> &ids); void removeGrabber(QQuickItem *grabber, bool mouse = true, bool touch = true); static QMouseEvent *cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos = 0); void deliverMouseEvent(QQuickPointerMouseEvent *pointerEvent); - bool sendFilteredMouseEvent(QQuickItem *, QQuickItem *, QEvent *, QSet<QQuickItem *> *); + bool sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); + bool sendFilteredPointerEvent(QQuickPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent = nullptr); + bool sendFilteredPointerEventImpl(QQuickPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); #if QT_CONFIG(wheelevent) bool deliverWheelEvent(QQuickItem *, QWheelEvent *); #endif @@ -164,19 +166,18 @@ public: // the device-specific event instances which are reused during event delivery mutable QVector<QQuickPointerEvent *> pointerEventInstances; - QQuickPointerEvent *pointerEventInstance(QQuickPointerDevice *device) const; + QQuickPointerEvent *pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType = QEvent::None) const; // delivery of pointer events: QQuickPointerEvent *pointerEventInstance(QEvent *ev) const; void deliverPointerEvent(QQuickPointerEvent *); void deliverTouchEvent(QQuickPointerTouchEvent *); bool deliverTouchCancelEvent(QTouchEvent *); - bool deliverPressEvent(QQuickPointerEvent *, QSet<QQuickItem *> *); - bool deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event, QSet<QQuickItem *> *hasFiltered); - bool deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, QSet<QQuickItem*> *filtered); - bool sendFilteredTouchEvent(QQuickItem *target, QQuickItem *item, QQuickPointerTouchEvent *event, QSet<QQuickItem*> *filtered); + bool deliverPressOrReleaseEvent(QQuickPointerEvent *, bool handlersOnly = false); + void deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event); + void deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, bool handlersOnly = false); - QVector<QQuickItem *> pointerTargets(QQuickItem *, const QPointF &, bool checkMouseButtons) const; + QVector<QQuickItem *> pointerTargets(QQuickItem *, const QPointF &, bool checkMouseButtons, bool checkAcceptsTouch) const; QVector<QQuickItem *> mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const; // hover delivery @@ -226,6 +227,8 @@ public: QList<QSGNode *> cleanupNodeList; QVector<QQuickItem *> itemsToPolish; + QVector<QQuickItem *> hasFiltered; // during event delivery to a single receiver, the filtering parents for which childMouseEventFilter was already called + QVector<QQuickItem *> skipDelivery; // during delivery of one event to all receivers, Items to which we know delivery is no longer necessary qreal devicePixelRatio; QMetaObject::Connection physicalDpiChangedConnection; @@ -263,6 +266,9 @@ public: uint lastWheelEventAccepted : 1; bool componentCompleted : 1; + bool allowChildEventFiltering : 1; + bool allowDoubleClick : 1; + Qt::FocusReason lastFocusReason; QOpenGLFramebufferObject *renderTarget; @@ -274,6 +280,7 @@ public: mutable QQuickWindowIncubationController *incubationController; static bool defaultAlphaBuffer; + static QQuickWindow::TextRenderType textRenderType; static bool dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold = -1); @@ -290,6 +297,14 @@ public: return overThreshold; } + static bool dragOverThreshold(const QQuickEventPoint *point) + { + QPointF delta = point->scenePosition() - point->scenePressPosition(); + return (QQuickWindowPrivate::dragOverThreshold(delta.x(), Qt::XAxis, point) || + QQuickWindowPrivate::dragOverThreshold(delta.y(), Qt::YAxis, point)); + } + + // data property static void data_append(QQmlListProperty<QObject> *, QObject *); static int data_count(QQmlListProperty<QObject> *); diff --git a/src/quick/items/qquickwindowmodule.cpp b/src/quick/items/qquickwindowmodule.cpp index c1cc02568c..45e3f0004d 100644 --- a/src/quick/items/qquickwindowmodule.cpp +++ b/src/quick/items/qquickwindowmodule.cpp @@ -200,6 +200,7 @@ void QQuickWindowModule::defineModule() qmlRegisterUncreatableType<QQuickScreen>(uri, 2, 0, "Screen", QStringLiteral("Screen can only be used via the attached property.")); qmlRegisterUncreatableType<QQuickScreen,1>(uri, 2, 3, "Screen", QStringLiteral("Screen can only be used via the attached property.")); qmlRegisterUncreatableType<QQuickScreenInfo,2>(uri, 2, 3, "ScreenInfo", QStringLiteral("ScreenInfo can only be used via the attached property.")); + qmlRegisterUncreatableType<QQuickScreenInfo,10>(uri, 2, 10, "ScreenInfo", QStringLiteral("ScreenInfo can only be used via the attached property.")); } QT_END_NAMESPACE |