diff options
Diffstat (limited to 'src/quick/items')
31 files changed, 1306 insertions, 238 deletions
diff --git a/src/quick/items/context2d/qquickcanvasitem.cpp b/src/quick/items/context2d/qquickcanvasitem.cpp index 6ae5914970..d78bd040c2 100644 --- a/src/quick/items/context2d/qquickcanvasitem.cpp +++ b/src/quick/items/context2d/qquickcanvasitem.cpp @@ -1206,7 +1206,7 @@ QRect QQuickCanvasItem::tiledRect(const QRectF &window, const QSize &tileSize) This signal is emitted when the \a region needs to be rendered. If a context is active it can be referenced from the context property. - This signal can be triggered by markdirty(), requestPaint() or by changing + This signal can be triggered by markDirty(), requestPaint() or by changing the current canvas window. */ diff --git a/src/quick/items/qquickaccessibleattached.cpp b/src/quick/items/qquickaccessibleattached.cpp index 865fb8bf11..bd24d1b1de 100644 --- a/src/quick/items/qquickaccessibleattached.cpp +++ b/src/quick/items/qquickaccessibleattached.cpp @@ -120,6 +120,15 @@ QT_BEGIN_NAMESPACE \endtable */ +/*! + \qmlproperty string QtQuick::Accessible::id + + This property sets an identifier for the object. + It can be used to provide stable identifiers to UI tests. + By default, the identifier is set to the ID of the QML object. + If the ID is not set the default of \l QAccessible::Identifier is used. +*/ + /*! \qmlproperty bool QtQuick::Accessible::focusable \brief This property holds whether this item is focusable. @@ -301,15 +310,19 @@ QQuickAccessibleAttached::QQuickAccessibleAttached(QObject *parent) : QObject(parent), m_role(QAccessible::NoRole) { Q_ASSERT(parent); - if (!item()) { - qmlWarning(parent) << "Accessible must be attached to an Item"; - return; - } - // Enable accessibility for items with accessible content. This also - // enables accessibility for the ancestors of souch items. - item()->d_func()->setAccessible(); - QAccessibleEvent ev(item(), QAccessible::ObjectCreated); + // enables accessibility for the ancestors of such items. + auto item = qobject_cast<QQuickItem *>(parent); + if (item) { + item->d_func()->setAccessible(); + } else { + const QLatin1StringView className(QQmlData::ensurePropertyCache(parent)->firstCppMetaObject()->className()); + if (className != QLatin1StringView("QQuickAction")) { + qmlWarning(parent) << "Accessible must be attached to an Item or an Action"; + return; + } + } + QAccessibleEvent ev(parent, QAccessible::ObjectCreated); QAccessible::updateAccessibility(&ev); if (const QMetaObject *pmo = parent->metaObject()) { @@ -423,13 +436,15 @@ QQuickAccessibleAttached *QQuickAccessibleAttached::qmlAttachedProperties(QObjec bool QQuickAccessibleAttached::ignored() const { - return item() ? !item()->d_func()->isAccessible : false; + auto item = qobject_cast<QQuickItem *>(parent()); + return item ? !item->d_func()->isAccessible : false; } void QQuickAccessibleAttached::setIgnored(bool ignored) { - if (this->ignored() != ignored && item()) { - item()->d_func()->isAccessible = !ignored; + auto item = qobject_cast<QQuickItem *>(parent()); + if (item && this->ignored() != ignored) { + item->d_func()->isAccessible = !ignored; emit ignoredChanged(); } } @@ -457,8 +472,13 @@ bool QQuickAccessibleAttached::doAction(const QString &actionName) sig = &sigPreviousPage; else if (actionName == QAccessibleActionInterface::nextPageAction()) sig = &sigNextPage; - if (sig && isSignalConnected(*sig)) - return sig->invoke(this); + if (sig && isSignalConnected(*sig)) { + bool ret = false; + if (m_proxying) + ret = sig->invoke(m_proxying); + ret |= sig->invoke(this); + return ret; + } return false; } @@ -497,6 +517,69 @@ QString QQuickAccessibleAttached::stripHtml(const QString &html) #endif } +void QQuickAccessibleAttached::setProxying(QQuickAccessibleAttached *proxying) +{ + if (proxying == m_proxying) + return; + + const QMetaObject &mo = staticMetaObject; + if (m_proxying) { + // We disconnect all signals from the proxy into this object + auto mo = m_proxying->metaObject(); + auto propertyCache = QQmlData::ensurePropertyCache(m_proxying); + for (int signalIndex = propertyCache->signalOffset(); + signalIndex < propertyCache->signalCount(); ++signalIndex) { + const QMetaMethod m = mo->method(propertyCache->signal(signalIndex)->coreIndex()); + Q_ASSERT(m.methodType() == QMetaMethod::Signal); + if (m.methodType() != QMetaMethod::Signal) + continue; + + disconnect(m_proxying, m, this, m); + } + } + + m_proxying = proxying; + + if (m_proxying) { + // We connect all signals from the proxy into this object + auto propertyCache = QQmlData::ensurePropertyCache(m_proxying); + auto mo = m_proxying->metaObject(); + for (int signalIndex = propertyCache->signalOffset(); + signalIndex < propertyCache->signalCount(); ++signalIndex) { + const QMetaMethod m = mo->method(propertyCache->signal(signalIndex)->coreIndex()); + Q_ASSERT(m.methodType() == QMetaMethod::Signal); + connect(proxying, m, this, m); + } + } + + // We check all properties + for (int prop = mo.propertyOffset(); prop < mo.propertyCount(); ++prop) { + const QMetaProperty p = mo.property(prop); + if (!p.hasNotifySignal()) { + continue; + } + + const QMetaMethod signal = p.notifySignal(); + if (signal.parameterCount() == 0) + signal.invoke(this); + else + signal.invoke(this, Q_ARG(bool, p.read(this).toBool())); + } +} + +/*! + * \since 6.8 + * Issues an announcement event with a \a message with priority \a priority. + * + * \sa QAccessibleAnnouncementEvent + */ +void QQuickAccessibleAttached::announce(const QString &message, QAccessible::AnnouncementPriority priority) +{ + QAccessibleAnnouncementEvent event(parent(), message); + event.setPriority(priority); + QAccessible::updateAccessibility(&event); +} + QT_END_NAMESPACE #include "moc_qquickaccessibleattached_p.cpp" diff --git a/src/quick/items/qquickaccessibleattached_p.h b/src/quick/items/qquickaccessibleattached_p.h index 92d1307a9a..3ec8fcab98 100644 --- a/src/quick/items/qquickaccessibleattached_p.h +++ b/src/quick/items/qquickaccessibleattached_p.h @@ -30,9 +30,11 @@ QT_BEGIN_NAMESPACE #define STATE_PROPERTY(P) \ Q_PROPERTY(bool P READ P WRITE set_ ## P NOTIFY P ## Changed FINAL) \ - bool P() const { return m_state.P ; } \ + bool P() const { return m_proxying && !m_stateExplicitlySet.P ? m_proxying->P() : m_state.P ; } \ void set_ ## P(bool arg) \ { \ + if (m_proxying) \ + m_proxying->set_##P(arg);\ m_stateExplicitlySet.P = true; \ if (m_state.P == arg) \ return; \ @@ -51,6 +53,7 @@ class Q_QUICK_EXPORT QQuickAccessibleAttached : public QObject Q_PROPERTY(QAccessible::Role role READ role WRITE setRole NOTIFY roleChanged FINAL) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL) Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged FINAL) + Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged REVISION(6, 8) FINAL) Q_PROPERTY(bool ignored READ ignored WRITE setIgnored NOTIFY ignoredChanged FINAL) QML_NAMED_ELEMENT(Accessible) @@ -84,6 +87,8 @@ public: QString name() const { if (m_state.passwordEdit) return QString(); + if (m_proxying) + return m_proxying->name(); return m_name; } @@ -99,9 +104,15 @@ public: } void setNameImplicitly(const QString &name); - QString description() const { return m_description; } + QString description() const { + return !m_descriptionExplicitlySet && m_proxying ? m_proxying->description() : m_description; + } void setDescription(const QString &description) { + if (!m_descriptionExplicitlySet && m_proxying) { + disconnect(m_proxying, &QQuickAccessibleAttached::descriptionChanged, this, &QQuickAccessibleAttached::descriptionChanged); + } + m_descriptionExplicitlySet = true; if (m_description != description) { m_description = description; Q_EMIT descriptionChanged(); @@ -110,6 +121,17 @@ public: } } + QString id() const { return m_id; } + void setId(const QString &id) + { + if (m_id != id) { + m_id = id; + Q_EMIT idChanged(); + QAccessibleEvent ev(parent(), QAccessible::IdentifierChanged); + QAccessible::updateAccessibility(&ev); + } + } + // Factory function static QQuickAccessibleAttached *qmlAttachedProperties(QObject *obj); @@ -143,6 +165,13 @@ public: if (att && (role == QAccessible::NoRole || att->role() == role)) { break; } + if (auto action = object->property("action").value<QObject *>(); action) { + QQuickAccessibleAttached *att = QQuickAccessibleAttached::attachedProperties(action); + if (att && (role == QAccessible::NoRole || att->role() == role)) { + object = action; + break; + } + } object = object->parent(); } return object; @@ -154,6 +183,9 @@ public: void availableActions(QStringList *actions) const; Q_REVISION(6, 2) Q_INVOKABLE static QString stripHtml(const QString &html); + void setProxying(QQuickAccessibleAttached *proxying); + + Q_REVISION(6, 8) Q_INVOKABLE void announce(const QString &message, QAccessible::AnnouncementPriority priority = QAccessible::AnnouncementPriority::Polite); public Q_SLOTS: void valueChanged() { @@ -171,6 +203,7 @@ Q_SIGNALS: void roleChanged(); void nameChanged(); void descriptionChanged(); + void idChanged(); void ignoredChanged(); void pressAction(); void toggleAction(); @@ -184,14 +217,15 @@ Q_SIGNALS: void nextPageAction(); private: - QQuickItem *item() const { return qobject_cast<QQuickItem*>(parent()); } - QAccessible::Role m_role; QAccessible::State m_state; QAccessible::State m_stateExplicitlySet; QString m_name; bool m_nameExplicitlySet = false; QString m_description; + bool m_descriptionExplicitlySet = false; + QQuickAccessibleAttached* m_proxying = nullptr; + QString m_id; static QMetaMethod sigPress; static QMetaMethod sigToggle; diff --git a/src/quick/items/qquickborderimage.cpp b/src/quick/items/qquickborderimage.cpp index 40daf518d3..004f0491bd 100644 --- a/src/quick/items/qquickborderimage.cpp +++ b/src/quick/items/qquickborderimage.cpp @@ -400,11 +400,16 @@ void QQuickBorderImage::requestFinished() { Q_D(QQuickBorderImage); - const QSize impsize = d->pix.implicitSize(); + if (d->pendingPix != d->currentPix) { + std::swap(d->pendingPix, d->currentPix); + d->pendingPix->clear(this); // Clear the old image + } + + const QSize impsize = d->currentPix->implicitSize(); setImplicitSize(impsize.width() / d->devicePixelRatio, impsize.height() / d->devicePixelRatio); - if (d->pix.isError()) { - qmlWarning(this) << d->pix.error(); + if (d->currentPix->isError()) { + qmlWarning(this) << d->currentPix->error(); d->setStatus(Error); d->setProgress(0); } else { @@ -416,8 +421,8 @@ void QQuickBorderImage::requestFinished() d->oldSourceSize = sourceSize(); emit sourceSizeChanged(); } - if (d->frameCount != d->pix.frameCount()) { - d->frameCount = d->pix.frameCount(); + if (d->frameCount != d->currentPix->frameCount()) { + d->frameCount = d->currentPix->frameCount(); emit frameCountChanged(); } @@ -507,7 +512,7 @@ QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat { Q_D(QQuickBorderImage); - QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->pix.textureFactory(), window()); + QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window()); if (!texture || width() <= 0 || height() <= 0) { delete oldNode; @@ -532,7 +537,7 @@ QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat QRectF innerSourceRect; QRectF subSourceRect; d->calculateRects(d->border, - QSize(d->pix.width(), d->pix.height()), QSizeF(width(), height()), + QSize(d->currentPix->width(), d->currentPix->height()), QSizeF(width(), height()), d->horizontalTileMode, d->verticalTileMode, d->devicePixelRatio, &targetRect, &innerTargetRect, &innerSourceRect, &subSourceRect); @@ -577,6 +582,13 @@ void QQuickBorderImage::pixmapChange() frameCount is the number of frames in the image. Most images have only one frame. */ +/*! + \qmlproperty bool QtQuick::BorderImage::retainWhileLoading + \since 6.8 + + \include qquickimage.cpp qml-image-retainwhileloading + */ + QT_END_NAMESPACE #include "moc_qquickborderimage_p.cpp" diff --git a/src/quick/items/qquickdrag.cpp b/src/quick/items/qquickdrag.cpp index dbe443b2b3..47064ad433 100644 --- a/src/quick/items/qquickdrag.cpp +++ b/src/quick/items/qquickdrag.cpp @@ -362,7 +362,7 @@ void QQuickDragAttached::setImageSource(const QUrl &url) } /*! - \qmlattachedproperty QUrl QtQuick::Drag::imageSourceSize + \qmlattachedproperty size QtQuick::Drag::imageSourceSize \since 6.8 This property holds the size of the image that will be used to represent diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp index 25ef44ecb0..e67d80b2d6 100644 --- a/src/quick/items/qquickgridview.cpp +++ b/src/quick/items/qquickgridview.cpp @@ -1382,7 +1382,7 @@ void QQuickGridView::setHighlightFollowsCurrentItem(bool autoHighlight) /*! \qmlproperty int QtQuick::GridView::count - This property holds the number of items in the view. + This property holds the number of items in the model. */ /*! diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp index 3aaa59819c..3efe082332 100644 --- a/src/quick/items/qquickimage.cpp +++ b/src/quick/items/qquickimage.cpp @@ -218,7 +218,7 @@ QQuickImage::~QQuickImage() void QQuickImagePrivate::setImage(const QImage &image) { Q_Q(QQuickImage); - pix.setImage(image); + currentPix->setImage(image); q->pixmapChange(); q->update(); } @@ -226,7 +226,7 @@ void QQuickImagePrivate::setImage(const QImage &image) void QQuickImagePrivate::setPixmap(const QQuickPixmap &pixmap) { Q_Q(QQuickImage); - pix.setPixmap(pixmap); + currentPix->setPixmap(pixmap); q->pixmapChange(); q->update(); } @@ -605,12 +605,12 @@ void QQuickImage::updatePaintedGeometry() Q_D(QQuickImage); if (d->fillMode == PreserveAspectFit) { - if (!d->pix.width() || !d->pix.height()) { + if (!d->currentPix->width() || !d->currentPix->height()) { setImplicitSize(0, 0); return; } - const qreal pixWidth = d->pix.width() / d->devicePixelRatio; - const qreal pixHeight = d->pix.height() / d->devicePixelRatio; + const qreal pixWidth = d->currentPix->width() / d->devicePixelRatio; + const qreal pixHeight = d->currentPix->height() / d->devicePixelRatio; const qreal w = widthValid() ? width() : pixWidth; const qreal widthScale = w / pixWidth; const qreal h = heightValid() ? height() : pixHeight; @@ -627,10 +627,10 @@ void QQuickImage::updatePaintedGeometry() setImplicitSize(iWidth, iHeight); } else if (d->fillMode == PreserveAspectCrop) { - if (!d->pix.width() || !d->pix.height()) + if (!d->currentPix->width() || !d->currentPix->height()) return; - const qreal pixWidth = d->pix.width() / d->devicePixelRatio; - const qreal pixHeight = d->pix.height() / d->devicePixelRatio; + const qreal pixWidth = d->currentPix->width() / d->devicePixelRatio; + const qreal pixHeight = d->currentPix->height() / d->devicePixelRatio; qreal widthScale = width() / pixWidth; qreal heightScale = height() / pixHeight; if (widthScale < heightScale) { @@ -642,8 +642,8 @@ void QQuickImage::updatePaintedGeometry() d->paintedHeight = heightScale * pixHeight; d->paintedWidth = widthScale * pixWidth; } else if (d->fillMode == Pad) { - d->paintedWidth = d->pix.width() / d->devicePixelRatio; - d->paintedHeight = d->pix.height() / d->devicePixelRatio; + d->paintedWidth = d->currentPix->width() / d->devicePixelRatio; + d->paintedHeight = d->currentPix->height() / d->devicePixelRatio; } else { d->paintedWidth = width(); d->paintedHeight = height(); @@ -685,7 +685,7 @@ QSGTextureProvider *QQuickImage::textureProvider() const dd->provider = new QQuickImageTextureProvider; dd->provider->m_smooth = d->smooth; dd->provider->m_mipmap = d->mipmap; - dd->provider->updateTexture(d->sceneGraphRenderContext()->textureForFactory(d->pix.textureFactory(), window())); + dd->provider->updateTexture(d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window())); } return d->provider; @@ -711,7 +711,7 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { Q_D(QQuickImage); - QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->pix.textureFactory(), window()); + QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window()); // Copy over the current texture state into the texture provider... if (d->provider) { @@ -736,8 +736,8 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) QSGTexture::WrapMode hWrap = QSGTexture::ClampToEdge; QSGTexture::WrapMode vWrap = QSGTexture::ClampToEdge; - qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->pix.width() / d->devicePixelRatio; - qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->pix.height() / d->devicePixelRatio; + qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->currentPix->width() / d->devicePixelRatio; + qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->currentPix->height() / d->devicePixelRatio; int xOffset = 0; if (d->hAlign == QQuickImage::AlignHCenter) @@ -754,36 +754,36 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) switch (d->fillMode) { case Stretch: targetRect = QRectF(0, 0, width(), height()); - sourceRect = d->pix.rect(); + sourceRect = d->currentPix->rect(); break; case PreserveAspectFit: targetRect = QRectF(xOffset, yOffset, d->paintedWidth, d->paintedHeight); - sourceRect = d->pix.rect(); + sourceRect = d->currentPix->rect(); break; case PreserveAspectCrop: { targetRect = QRectF(0, 0, width(), height()); - qreal wscale = width() / qreal(d->pix.width()); - qreal hscale = height() / qreal(d->pix.height()); + qreal wscale = width() / qreal(d->currentPix->width()); + qreal hscale = height() / qreal(d->currentPix->height()); if (wscale > hscale) { - int src = (hscale / wscale) * qreal(d->pix.height()); + int src = (hscale / wscale) * qreal(d->currentPix->height()); int y = 0; if (d->vAlign == QQuickImage::AlignVCenter) - y = qCeil((d->pix.height() - src) / 2.); + y = qCeil((d->currentPix->height() - src) / 2.); else if (d->vAlign == QQuickImage::AlignBottom) - y = qCeil(d->pix.height() - src); - sourceRect = QRectF(0, y, d->pix.width(), src); + y = qCeil(d->currentPix->height() - src); + sourceRect = QRectF(0, y, d->currentPix->width(), src); } else { - int src = (wscale / hscale) * qreal(d->pix.width()); + int src = (wscale / hscale) * qreal(d->currentPix->width()); int x = 0; if (d->hAlign == QQuickImage::AlignHCenter) - x = qCeil((d->pix.width() - src) / 2.); + x = qCeil((d->currentPix->width() - src) / 2.); else if (d->hAlign == QQuickImage::AlignRight) - x = qCeil(d->pix.width() - src); - sourceRect = QRectF(x, 0, src, d->pix.height()); + x = qCeil(d->currentPix->width() - src); + sourceRect = QRectF(x, 0, src, d->currentPix->height()); } } break; @@ -797,13 +797,13 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) case TileHorizontally: targetRect = QRectF(0, 0, width(), height()); - sourceRect = QRectF(-xOffset, 0, width(), d->pix.height()); + sourceRect = QRectF(-xOffset, 0, width(), d->currentPix->height()); hWrap = QSGTexture::Repeat; break; case TileVertically: targetRect = QRectF(0, 0, width(), height()); - sourceRect = QRectF(0, -yOffset, d->pix.width(), height()); + sourceRect = QRectF(0, -yOffset, d->currentPix->width(), height()); vWrap = QSGTexture::Repeat; break; @@ -817,8 +817,8 @@ QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) break; } - qreal nsWidth = (hWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->pix.width() / d->devicePixelRatio : d->pix.width(); - qreal nsHeight = (vWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->pix.height() / d->devicePixelRatio : d->pix.height(); + qreal nsWidth = (hWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->currentPix->width() / d->devicePixelRatio : d->currentPix->width(); + qreal nsHeight = (vWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->currentPix->height() / d->devicePixelRatio : d->currentPix->height(); QRectF nsrect(sourceRect.x() / nsWidth, sourceRect.y() / nsHeight, sourceRect.width() / nsWidth, @@ -967,6 +967,25 @@ void QQuickImage::setMipmap(bool use) frameCount is the number of frames in the image. Most images have only one frame. */ +/*! + \qmlproperty bool QtQuick::Image::retainWhileLoading + \since 6.8 + +//! [qml-image-retainwhileloading] + This property defines the behavior when the \l source property is changed and loading happens + asynchronously. This is the case when the \l asynchronous property is set to \c true, or if the + image is not on the local file system. + + If \c retainWhileLoading is \c false (the default), the old image is discarded immediately, and + the component is cleared while the new image is being loaded. If set to \c true, the old image + is retained and remains visible until the new one is ready. + + Enabling this property can avoid flickering in cases where loading the new image takes a long + time. It comes at the cost of some extra memory use for double buffering while the new image is + being loaded. +//! [qml-image-retainwhileloading] + */ + QT_END_NAMESPACE #include "moc_qquickimage_p_p.cpp" diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index ab827379fb..b00baf7f7d 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -15,16 +15,7 @@ QT_BEGIN_NAMESPACE -bool isScalableImageFormat(const QUrl &url) -{ - if (url.scheme() == QLatin1String("image")) - return true; - - const QString stringUrl = url.path(QUrl::PrettyDecoded); - return stringUrl.endsWith(QLatin1String("svg")) - || stringUrl.endsWith(QLatin1String("svgz")) - || stringUrl.endsWith(QLatin1String("pdf")); -} +using namespace Qt::Literals::StringLiterals; // This function gives derived classes the chance set the devicePixelRatio // if they're not happy with our implementation of it. @@ -33,7 +24,7 @@ bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio // QQuickImageProvider and SVG and PDF 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. - const bool setDevicePixelRatio = isScalableImageFormat(url); + const bool setDevicePixelRatio = QQuickPixmap::isScalableImageFormat(url); if (setDevicePixelRatio) devicePixelRatio = targetDevicePixelRatio; @@ -144,7 +135,7 @@ QSize QQuickImageBase::sourceSize() const int width = d->sourcesize.width(); int height = d->sourcesize.height(); - return QSize(width != -1 ? width : d->pix.width(), height != -1 ? height : d->pix.height()); + return QSize(width != -1 ? width : d->currentPix->width(), height != -1 ? height : d->currentPix->height()); } void QQuickImageBase::resetSourceSize() @@ -197,7 +188,7 @@ void QQuickImageBase::setCache(bool cache) QImage QQuickImageBase::image() const { Q_D(const QQuickImageBase); - return d->pix.image(); + return d->currentPix->image(); } void QQuickImageBase::setMirror(bool mirror) @@ -243,7 +234,7 @@ bool QQuickImageBase::mirrorVertically() const void QQuickImageBase::setCurrentFrame(int frame) { Q_D(QQuickImageBase); - if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->pix.frameCount())) + if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->currentPix->frameCount())) return; d->currentFrame = frame; @@ -273,7 +264,8 @@ int QQuickImageBase::frameCount() const void QQuickImageBase::loadEmptyUrl() { Q_D(QQuickImageBase); - d->pix.clear(this); + d->currentPix->clear(this); + d->pendingPix->clear(this); d->setProgress(0); d->status = Null; // do not emit statusChanged until after setImplicitSize setImplicitSize(0, 0); // also called in QQuickImageBase::pixmapChange, but not QQuickImage/QQuickBorderImage overrides @@ -299,7 +291,7 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions) options |= QQuickPixmap::Asynchronous; if (d->cache) options |= QQuickPixmap::Cache; - d->pix.clear(this); + d->pendingPix->clear(this); QUrl loadUrl = url; const QQmlContext *context = qmlContext(this); if (context) @@ -310,7 +302,7 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions) d->devicePixelRatio = 1.0; bool updatedDevicePixelRatio = false; if (d->sourcesize.isValid() - || (isScalableImageFormat(d->url) && d->url.scheme() != QLatin1String("image"))) { + || (QQuickPixmap::isScalableImageFormat(d->url) && d->url.scheme() != "image"_L1)) { updatedDevicePixelRatio = d->updateDevicePixelRatio(targetDevicePixelRatio); } @@ -324,16 +316,16 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions) d->status = Null; // reset status, no emit - d->pix.load(qmlEngine(this), - loadUrl, - d->sourceClipRect.toRect(), - (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(), - options, - (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(), - d->currentFrame, d->frameCount, - d->devicePixelRatio); + d->pendingPix->load(qmlEngine(this), + loadUrl, + d->sourceClipRect.toRect(), + (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(), + options, + (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(), + d->currentFrame, d->frameCount, + d->devicePixelRatio); - if (d->pix.isLoading()) { + if (d->pendingPix->isLoading()) { d->setProgress(0); d->setStatus(Loading); @@ -346,9 +338,10 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions) QQuickImageBase::staticMetaObject.indexOfSlot("requestFinished()"); } - d->pix.connectFinished(this, thisRequestFinished); - d->pix.connectDownloadProgress(this, thisRequestProgress); - update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint + d->pendingPix->connectFinished(this, thisRequestFinished); + d->pendingPix->connectDownloadProgress(this, thisRequestProgress); + if (!d->retainWhileLoading) + update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint } else { requestFinished(); } @@ -369,10 +362,15 @@ void QQuickImageBase::load() void QQuickImageBase::requestFinished() { Q_D(QQuickImageBase); + if (d->pendingPix != d->currentPix + && d->pendingPix->status() != QQuickPixmap::Null + && d->pendingPix->status() != QQuickPixmap::Loading) { + std::swap(d->pendingPix, d->currentPix); + d->pendingPix->clear(this); // Clear the old image + } - if (d->pix.isError()) { - qmlWarning(this) << d->pix.error(); - d->pix.clear(this); + if (d->currentPix->isError()) { + qmlWarning(this) << d->currentPix->error(); d->status = Error; d->setProgress(0); } else { @@ -391,12 +389,12 @@ void QQuickImageBase::requestFinished() d->oldAutoTransform = autoTransform(); emitAutoTransformBaseChanged(); } - if (d->frameCount != d->pix.frameCount()) { - d->frameCount = d->pix.frameCount(); + if (d->frameCount != d->currentPix->frameCount()) { + d->frameCount = d->currentPix->frameCount(); emit frameCountChanged(); } - if (d->colorSpace != d->pix.colorSpace()) { - d->colorSpace = d->pix.colorSpace(); + if (d->colorSpace != d->currentPix->colorSpace()) { + d->colorSpace = d->currentPix->colorSpace(); emit colorSpaceChanged(); } @@ -439,7 +437,7 @@ void QQuickImageBase::componentComplete() void QQuickImageBase::pixmapChange() { Q_D(QQuickImageBase); - setImplicitSize(d->pix.width() / d->devicePixelRatio, d->pix.height() / d->devicePixelRatio); + setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio); } void QQuickImageBase::resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio) @@ -479,7 +477,7 @@ bool QQuickImageBase::autoTransform() const { Q_D(const QQuickImageBase); if (d->providerOptions.autoTransform() == QQuickImageProviderOptions::UsePluginDefaultTransform) - return d->pix.autoTransform() == QQuickImageProviderOptions::ApplyTransform; + return d->currentPix->autoTransform() == QQuickImageProviderOptions::ApplyTransform; return d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform; } @@ -509,6 +507,30 @@ void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace) emit colorSpaceChanged(); } +bool QQuickImageBase::retainWhileLoading() const +{ + Q_D(const QQuickImageBase); + return d->retainWhileLoading; +} + +void QQuickImageBase::setRetainWhileLoading(bool retainWhileLoading) +{ + Q_D(QQuickImageBase); + if (d->retainWhileLoading == retainWhileLoading) + return; + + d->retainWhileLoading = retainWhileLoading; + if (d->retainWhileLoading) { + if (d->currentPix == &d->pix1) + d->pendingPix = &d->pix2; + else + d->pendingPix = &d->pix1; + } else { + d->pendingPix->clear(); + d->pendingPix = d->currentPix; + } +} + QT_END_NAMESPACE #include "moc_qquickimagebase_p.cpp" diff --git a/src/quick/items/qquickimagebase_p.h b/src/quick/items/qquickimagebase_p.h index 08f9abbc63..5a33db43b8 100644 --- a/src/quick/items/qquickimagebase_p.h +++ b/src/quick/items/qquickimagebase_p.h @@ -33,6 +33,7 @@ class Q_QUICK_EXPORT QQuickImageBase : public QQuickImplicitSizeItem Q_PROPERTY(bool cache READ cache WRITE setCache NOTIFY cacheChanged) Q_PROPERTY(bool mirror READ mirror WRITE setMirror NOTIFY mirrorChanged) Q_PROPERTY(bool mirrorVertically READ mirrorVertically WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged REVISION(6, 2)) + Q_PROPERTY(bool retainWhileLoading READ retainWhileLoading WRITE setRetainWhileLoading NOTIFY retainWhileLoadingChanged REVISION(6, 8)) Q_PROPERTY(int currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY currentFrameChanged REVISION(2, 14)) Q_PROPERTY(int frameCount READ frameCount NOTIFY frameCountChanged REVISION(2, 14)) Q_PROPERTY(QColorSpace colorSpace READ colorSpace WRITE setColorSpace NOTIFY colorSpaceChanged REVISION(2, 15)) @@ -94,6 +95,9 @@ public: QColorSpace colorSpace() const; virtual void setColorSpace(const QColorSpace &colorSpace); + bool retainWhileLoading() const; + void setRetainWhileLoading(bool retain); + static void resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio); // Use a virtual rather than a signal->signal to avoid the huge @@ -113,6 +117,7 @@ Q_SIGNALS: Q_REVISION(2, 15) void sourceClipRectChanged(); Q_REVISION(2, 15) void colorSpaceChanged(); Q_REVISION(6, 2) void mirrorVerticallyChanged(); + Q_REVISION(6, 8) void retainWhileLoadingChanged(); protected: void loadEmptyUrl(); diff --git a/src/quick/items/qquickimagebase_p_p.h b/src/quick/items/qquickimagebase_p_p.h index 3cbb9facb0..d53712b779 100644 --- a/src/quick/items/qquickimagebase_p_p.h +++ b/src/quick/items/qquickimagebase_p_p.h @@ -33,8 +33,11 @@ public: cache(true), mirrorHorizontally(false), mirrorVertically(false), - oldAutoTransform(false) + oldAutoTransform(false), + retainWhileLoading(false) { + pendingPix = &pix1; + currentPix = &pix1; } virtual bool updateDevicePixelRatio(qreal targetDevicePixelRatio); @@ -43,7 +46,10 @@ public: void setProgress(qreal value); QUrl url; - QQuickPixmap pix; + QQuickPixmap *pendingPix = nullptr; + QQuickPixmap *currentPix = nullptr; + QQuickPixmap pix1; + QQuickPixmap pix2; QSize sourcesize; QSize oldSourceSize; QRectF sourceClipRect; @@ -61,6 +67,7 @@ public: bool mirrorHorizontally: 1; bool mirrorVertically : 1; bool oldAutoTransform : 1; + bool retainWhileLoading : 1; }; QT_END_NAMESPACE diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 4c26708168..e7b2a31f04 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -2470,12 +2470,32 @@ bool QQuickItemPrivate::canAcceptTabFocus(QQuickItem *item) */ bool QQuickItemPrivate::focusNextPrev(QQuickItem *item, bool forward) { - QQuickItem *next = QQuickItemPrivate::nextPrevItemInTabFocusChain(item, forward); + QQuickWindow *window = item->window(); + const bool wrap = !window || window->isTopLevel(); + + QQuickItem *next = QQuickItemPrivate::nextPrevItemInTabFocusChain(item, forward, wrap); if (next == item) return false; - next->forceActiveFocus(forward ? Qt::TabFocusReason : Qt::BacktabFocusReason); + const auto reason = forward ? Qt::TabFocusReason : Qt::BacktabFocusReason; + + if (!wrap && !next) { + // Focus chain wrapped and we are not top-level window + // Give focus to parent window + Q_ASSERT(window); + Q_ASSERT(window->parent()); + + + qt_window_private(window->parent())->setFocusToTarget( + forward ? QWindowPrivate::FocusTarget::Next + : QWindowPrivate::FocusTarget::Prev, + reason); + window->parent()->requestActivate(); + return true; + } + + next->forceActiveFocus(reason); return true; } @@ -2524,7 +2544,7 @@ QQuickItem *QQuickItemPrivate::prevTabChildItem(const QQuickItem *item, int star return nullptr; } -QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, bool forward) +QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, bool forward, bool wrap) { Q_ASSERT(item); qCDebug(lcFocus) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: item:" << item << ", forward:" << forward; @@ -2618,6 +2638,14 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo } current = parent; } else if (hasChildren) { + if (!wrap && (forward || firstFromItem != from)) { + qCDebug(lcFocus) << "QQuickItemPrivate::nextPrevItemInTabFocusChain:" + << "Focus chain about to wrap."; + // If focus chain wraps, we should give the parent window + // a chance to get focus, so we should stop here + return nullptr; + } + // Wrap around after checking all items forward if (forward) { current = firstChild; @@ -9289,12 +9317,12 @@ void QQuickItemPrivate::localizedTouchEvent(const QTouchEvent *event, bool isFil bool hasAnotherGrabber = pointGrabber && pointGrabber != q; // if there's no exclusive grabber, look for passive grabbers during filtering if (isFiltering && !pointGrabber) { - auto pg = event->passiveGrabbers(p); + const auto pg = event->passiveGrabbers(p); if (!pg.isEmpty()) { // It seems unlikely to have multiple passive grabbers of one eventpoint with different grandparents. // So hopefully if we start from one passive grabber and go up the parent chain from there, // we will find any filtering parent items that exist. - auto handler = qmlobject_cast<QQuickPointerHandler *>(pg.first()); + auto handler = qmlobject_cast<QQuickPointerHandler *>(pg.constFirst()); if (handler) pointGrabber = handler->parentItem(); } diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index c2e014b72d..bb238904ca 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -575,7 +575,7 @@ public: static bool focusNextPrev(QQuickItem *item, bool forward); static QQuickItem *nextTabChildItem(const QQuickItem *item, int start); static QQuickItem *prevTabChildItem(const QQuickItem *item, int start); - static QQuickItem *nextPrevItemInTabFocusChain(QQuickItem *item, bool forward); + static QQuickItem *nextPrevItemInTabFocusChain(QQuickItem *item, bool forward, bool wrap = true); static bool canAcceptTabFocus(QQuickItem *item); diff --git a/src/quick/items/qquickitemanimation.cpp b/src/quick/items/qquickitemanimation.cpp index 8bccc7f4cb..11b2642748 100644 --- a/src/quick/items/qquickitemanimation.cpp +++ b/src/quick/items/qquickitemanimation.cpp @@ -993,9 +993,8 @@ QQuickPathAnimationAnimator::QQuickPathAnimationAnimator(QQuickPathAnimationPriv QQuickPathAnimationAnimator::~QQuickPathAnimationAnimator() { if (animationTemplate && pathUpdater()) { - QHash<QQuickItem*, QQuickPathAnimationAnimator* >::iterator it = - animationTemplate->activeAnimations.find(pathUpdater()->target); - if (it != animationTemplate->activeAnimations.end() && it.value() == this) + auto it = animationTemplate->activeAnimations.constFind(pathUpdater()->target); + if (it != animationTemplate->activeAnimations.cend() && it.value() == this) animationTemplate->activeAnimations.erase(it); } } diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 3fde95e213..6b185b4e7c 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -2411,7 +2411,7 @@ QQuickListView::~QQuickListView() /*! \qmlproperty int QtQuick::ListView::count - This property holds the number of items in the view. + This property holds the number of items in the model. */ /*! diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index 9c83183eb7..a6edfc4754 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -1284,6 +1284,7 @@ bool QQuickMouseArea::setPressed(Qt::MouseButton button, bool p, Qt::MouseEventS return me.isAccepted(); } + Q_UNUSED(source) return false; } diff --git a/src/quick/items/qquickpalettecolorprovider.cpp b/src/quick/items/qquickpalettecolorprovider.cpp index af6f0ef7a5..f01fa22bdd 100644 --- a/src/quick/items/qquickpalettecolorprovider.cpp +++ b/src/quick/items/qquickpalettecolorprovider.cpp @@ -162,6 +162,7 @@ bool QQuickPaletteColorProvider::doInheritPalette(const QPalette &palette) { auto inheritedMask = m_requestedPalette.isAllocated() ? m_requestedPalette->resolveMask() | palette.resolveMask() : palette.resolveMask(); + // If a palette was set on this item, it should always win over the palette to be inherited from. QPalette parentPalette = m_requestedPalette.isAllocated() ? m_requestedPalette->resolve(palette) : palette; parentPalette.setResolveMask(inheritedMask); diff --git a/src/quick/items/qquickselectable_p.h b/src/quick/items/qquickselectable_p.h index dbee64c49e..65434be490 100644 --- a/src/quick/items/qquickselectable_p.h +++ b/src/quick/items/qquickselectable_p.h @@ -37,7 +37,7 @@ public: virtual void normalizeSelection() = 0; virtual QRectF selectionRectangle() const = 0; - virtual QSizeF scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step) = 0; + virtual QSizeF scrollTowardsPoint(const QPointF &pos, const QSizeF &step) = 0; virtual void setCallback(std::function<void(CallBackFlag)> func) = 0; }; diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 6203f6f3c1..c7bd050327 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -6,11 +6,13 @@ #include <QtCore/qtimer.h> #include <QtCore/qdir.h> +#include <QtCore/qmimedata.h> #include <QtQmlModels/private/qqmldelegatemodel_p.h> #include <QtQmlModels/private/qqmldelegatemodel_p_p.h> #include <QtQml/private/qqmlincubator_p.h> #include <QtQmlModels/private/qqmlchangeset_p.h> #include <QtQml/qqmlinfo.h> +#include <QtQuick/qquickitemgrabresult.h> #include <QtQuick/private/qquickflickable_p_p.h> #include <QtQuick/private/qquickitemviewfxitem_p_p.h> @@ -467,6 +469,9 @@ \li required property bool current - \c true if the delegate is \l {Keyboard navigation}{current.} \li required property bool selected - \c true if the delegate is \l {Selecting items}{selected.} \li required property bool editing - \c true if the delegate is being \l {Editing cells}{edited.} + \li required property bool containsDrag - \c true if a column or row is currently being dragged + over this delegate. This property is only supported for HorizontalHeaderView and + VerticalHeaderView. (since Qt 6.8) \endlist The following example shows how to use these properties: @@ -944,6 +949,48 @@ */ /*! + \qmlmethod QtQuick::TableView::moveColumn(int source, int destination) + \since 6.8 + + Moves a column from the \a source to the \a destination position. + + \note If a syncView is set, the sync view will control the internal index mapping for + column reordering. Therefore, in that case, a call to this function will be forwarded to + the sync view instead. +*/ + +/*! + \qmlmethod QtQuick::TableView::clearColumnReordering() + \since 6.8 + + Resets any previously applied column reordering. + + \note If a syncView is set, a call to this function will be forwarded to + corresponding view item and reset the column ordering. +*/ + +/*! + \qmlmethod QtQuick::TableView::moveRow(int source, int destination) + \since 6.8 + + Moves a row from the \a source to the \a destination position. + + \note If a syncView is set, the sync view will control the internal index mapping for + row reordering. Therefore, in that case, a call to this function will be forwarded to + the sync view instead. +*/ + +/*! + \qmlmethod QtQuick::TableView::clearRowReordering() + \since 6.8 + + Resets any previously applied row reordering. + + \note If a syncView is set, a call to this function will be forwarded to + the corresponding view item and reset the row ordering. +*/ + +/*! \qmlmethod Item QtQuick::TableView::itemAtCell(point cell) Returns the delegate item at \a cell if loaded, otherwise \c null. @@ -1339,6 +1386,24 @@ */ /*! + \qmlsignal QtQuick::TableView::columnMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) + \since 6.8 + + This signal is emitted when a column is moved. The column's logical index is specified by + \a logicalIndex, the old index by \a oldVisualIndex, and the new index position by + \a newVisualIndex. +*/ + +/*! + \qmlsignal QtQuick::TableView::rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) + \since 6.8 + + This signal is emitted when a row is moved. The row's logical index is specified by + \a logicalIndex, the old index by \a oldVisualIndex, and the new index position by + \a newVisualIndex. +*/ + +/*! \qmlattachedproperty TableView QtQuick::TableView::view This attached property holds the view that manages the delegate instance. @@ -1459,6 +1524,7 @@ static const char* kRequiredProperties = "_qt_tableview_requiredpropertymask"; static const char* kRequiredProperty_selected = "selected"; static const char* kRequiredProperty_current = "current"; static const char* kRequiredProperty_editing = "editing"; +static const char* kRequiredProperty_containsDrag = "containsDrag"; QDebug operator<<(QDebug dbg, QQuickTableViewPrivate::RebuildState state) { @@ -1849,28 +1915,43 @@ void QQuickTableViewPrivate::updateSelection(const QRect &oldSelection, const QR { const QModelIndex startIndex = qaim->index(newRect.y(), newRect.x()); const QModelIndex endIndex = qaim->index(newRect.y() + newRect.height(), newRect.x() + newRect.width()); - select = QItemSelection(startIndex, endIndex); + for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { + const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); + select.append(QItemSelection(logicalModelIndex, logicalModelIndex)); + } } // Unselect cells in the new minus old rects if (oldRect.x() < newRect.x()) { const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x()); const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), newRect.x() - 1); - deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select); + for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { + const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); + deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); + } } else if (oldRect.x() + oldRect.width() > newRect.x() + newRect.width()) { const QModelIndex startIndex = qaim->index(oldRect.y(), newRect.x() + newRect.width() + 1); const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width()); - deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select); + for (auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { + const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); + deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); + } } if (oldRect.y() < newRect.y()) { const QModelIndex startIndex = qaim->index(oldRect.y(), oldRect.x()); const QModelIndex endIndex = qaim->index(newRect.y() - 1, oldRect.x() + oldRect.width()); - deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select); + for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { + const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); + deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); + } } else if (oldRect.y() + oldRect.height() > newRect.y() + newRect.height()) { const QModelIndex startIndex = qaim->index(newRect.y() + newRect.height() + 1, oldRect.x()); const QModelIndex endIndex = qaim->index(oldRect.y() + oldRect.height(), oldRect.x() + oldRect.width()); - deselect.merge(QItemSelection(startIndex, endIndex), QItemSelectionModel::Select); + for (const auto &modelIndex : QItemSelection(startIndex, endIndex).indexes()) { + const QModelIndex &logicalModelIndex = qaim->index(logicalRowIndex(modelIndex.row()), logicalColumnIndex(modelIndex.column())); + deselect.merge(QItemSelection(logicalModelIndex, logicalModelIndex), QItemSelectionModel::Select); + } } if (selectionFlag == QItemSelectionModel::Select) { @@ -1976,7 +2057,7 @@ QRect QQuickTableViewPrivate::selection() const return QRect(selectionStartCell.x(), selectionStartCell.y(), w, h); } -QSizeF QQuickTableViewPrivate::scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step) +QSizeF QQuickTableViewPrivate::scrollTowardsPoint(const QPointF &pos, const QSizeF &step) { Q_Q(QQuickTableView); @@ -2085,14 +2166,14 @@ QPoint QQuickTableViewPrivate::cellAtModelIndex(int modelIndex) const } } -int QQuickTableViewPrivate::modelIndexToCellIndex(const QModelIndex &modelIndex) const +int QQuickTableViewPrivate::modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex) const { // Convert QModelIndex to cell index. A cell index is just an // integer representation of a cell instead of using a QPoint. const QPoint cell = q_func()->cellAtIndex(modelIndex); if (!cellIsValid(cell)) return -1; - return modelIndexAtCell(cell); + return modelIndexAtCell(visualIndex ? cell : QPoint(modelIndex.column(), modelIndex.row())); } int QQuickTableViewPrivate::edgeToArrayIndex(Qt::Edge edge) const @@ -2647,7 +2728,9 @@ FxTableItem *QQuickTableViewPrivate::createFxTableItem(const QPoint &cell, QQmlI Q_Q(QQuickTableView); bool ownItem = false; - int modelIndex = modelIndexAtCell(cell); + + int modelIndex = modelIndexAtCell(isTransposed ? QPoint(logicalRowIndex(cell.x()), logicalColumnIndex(cell.y())) : + QPoint(logicalColumnIndex(cell.x()), logicalRowIndex(cell.y()))); QObject* object = model->object(modelIndex, incubationMode); if (!object) { @@ -3000,7 +3083,7 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) const const int noExplicitColumnWidth = -1; - if (cachedColumnWidth.startIndex == column) + if (cachedColumnWidth.startIndex == logicalColumnIndex(column)) return cachedColumnWidth.size; if (syncHorizontally) @@ -3019,7 +3102,7 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) const qreal columnWidth = noExplicitColumnWidth; if (columnWidthProvider.isCallable()) { - auto const columnAsArgument = QJSValueList() << QJSValue(column); + auto const columnAsArgument = QJSValueList() << QJSValue(logicalColumnIndex(column)); columnWidth = columnWidthProvider.call(columnAsArgument).toNumber(); if (qIsNaN(columnWidth) || columnWidth < 0) columnWidth = noExplicitColumnWidth; @@ -3031,7 +3114,7 @@ qreal QQuickTableViewPrivate::getColumnWidth(int column) const columnWidth = noExplicitColumnWidth; } - cachedColumnWidth.startIndex = column; + cachedColumnWidth.startIndex = logicalColumnIndex(column); cachedColumnWidth.size = columnWidth; return columnWidth; } @@ -3046,7 +3129,7 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) const const int noExplicitRowHeight = -1; - if (cachedRowHeight.startIndex == row) + if (cachedRowHeight.startIndex == logicalRowIndex(row)) return cachedRowHeight.size; if (syncVertically) @@ -3065,7 +3148,7 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) const qreal rowHeight = noExplicitRowHeight; if (rowHeightProvider.isCallable()) { - auto const rowAsArgument = QJSValueList() << QJSValue(row); + auto const rowAsArgument = QJSValueList() << QJSValue(logicalRowIndex(row)); rowHeight = rowHeightProvider.call(rowAsArgument).toNumber(); if (qIsNaN(rowHeight) || rowHeight < 0) rowHeight = noExplicitRowHeight; @@ -3077,7 +3160,7 @@ qreal QQuickTableViewPrivate::getRowHeight(int row) const rowHeight = noExplicitRowHeight; } - cachedRowHeight.startIndex = row; + cachedRowHeight.startIndex = logicalRowIndex(row); cachedRowHeight.size = rowHeight; return rowHeight; } @@ -4288,11 +4371,13 @@ void QQuickTableViewPrivate::initItemCallback(int modelIndex, QObject *object) item->setZ(1); const QPoint cell = cellAtModelIndex(modelIndex); - const bool current = currentInSelectionModel(cell); - const bool selected = selectedInSelectionModel(cell); + const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); + const bool current = currentInSelectionModel(visualCell); + const bool selected = selectedInSelectionModel(visualCell); setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, true); setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, true); setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), modelIndex, item, true); + setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, item, true); if (auto attached = getAttachedObject(object)) attached->setView(q); @@ -4309,11 +4394,13 @@ void QQuickTableViewPrivate::itemPooledCallback(int modelIndex, QObject *object) void QQuickTableViewPrivate::itemReusedCallback(int modelIndex, QObject *object) { const QPoint cell = cellAtModelIndex(modelIndex); - const bool current = currentInSelectionModel(cell); - const bool selected = selectedInSelectionModel(cell); + const QPoint visualCell = QPoint(visualColumnIndex(cell.x()), visualRowIndex(cell.y())); + const bool current = currentInSelectionModel(visualCell); + const bool selected = selectedInSelectionModel(visualCell); setRequiredProperty(kRequiredProperty_current, QVariant::fromValue(current), modelIndex, object, false); setRequiredProperty(kRequiredProperty_selected, QVariant::fromValue(selected), modelIndex, object, false); // Note: the edit item will never be reused, so no reason to set kRequiredProperty_editing + setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(false), modelIndex, object, false); if (auto item = qobject_cast<QQuickItem*>(object)) QQuickItemPrivate::get(item)->setCulled(false); @@ -4896,6 +4983,7 @@ void QQuickTableViewPrivate::init() hoverHandler = new QQuickTableViewHoverHandler(q); resizeHandler = new QQuickTableViewResizeHandler(q); + hoverHandler->setEnabled(resizableRows || resizableColumns); resizeHandler->setEnabled(resizableRows || resizableColumns); @@ -5668,6 +5756,10 @@ void QQuickTableView::setSyncView(QQuickTableView *view) if (d->assignedSyncView == view) return; + // Clear existing index mapping information maintained + // in the current view + d->clearIndexMapping(); + d->assignedSyncView = view; d->scheduleRebuildTable(QQuickTableViewPrivate::RebuildOption::ViewportOnly); @@ -6012,6 +6104,169 @@ void QQuickTableView::positionViewAtCell(int column, int row, PositionMode mode, } #endif +void QQuickTableView::moveColumn(int source, int destination) +{ + Q_D(QQuickTableView); + d->moveSection(source, destination, Qt::Horizontal); +} + +void QQuickTableView::moveRow(int source, int destination) +{ + Q_D(QQuickTableView); + d->moveSection(source, destination, Qt::Vertical); +} + +void QQuickTableViewPrivate::moveSection(int source, int destination, Qt::Orientations orientation) +{ + Q_Q(QQuickTableView); + + if (source < 0 || destination < 0 || + (orientation == Qt::Horizontal && + (source >= tableSize.width() || destination >= tableSize.width())) || + (orientation == Qt::Vertical && + (source >= tableSize.height() || destination >= tableSize.height()))) + return; + + if (source == destination) + return; + + if (m_sectionState != SectionState::Moving) { + m_sectionState = SectionState::Moving; + if (syncView) + syncView->d_func()->moveSection(source, destination, orientation); + else { + // Initialize the visual and logical index mapping + initializeIndexMapping(); + + // Set current index mapping according to moving rows or columns + SectionData *visualIndex = nullptr; + SectionData *logicalIndex = nullptr; + + if (orientation == Qt::Horizontal) { + visualIndex = visualIndices[0].data(); + logicalIndex = logicalIndices[0].data(); + } else if (orientation == Qt::Vertical) { + visualIndex = visualIndices[1].data(); + logicalIndex = logicalIndices[1].data(); + } + + const int logical = logicalIndex[source].index; + int visual = source; + + if (destination > source) { + while (visual < destination) { + SectionData &visualData = visualIndex[logicalIndex[visual + 1].index]; + SectionData &logicalData = logicalIndex[visual]; + visualData.prevIndex = visualData.index; + visualData.index = visual; + logicalData.prevIndex = logicalData.index; + logicalData.index = logicalIndex[visual + 1].index; + ++visual; + } + } else { + while (visual > destination) { + SectionData &visualData = visualIndex[logicalIndex[visual - 1].index]; + SectionData &logicalData = logicalIndex[visual]; + visualData.prevIndex = visualData.index; + visualData.index = visual; + logicalData.prevIndex = logicalData.index; + logicalData.index = logicalIndex[visual - 1].index; + --visual; + } + } + + visualIndex[logical].prevIndex = visualIndex[logical].index; + visualIndex[logical].index = destination; + logicalIndex[destination].prevIndex = logicalIndex[destination].index; + logicalIndex[destination].index = logical; + + // Trigger section move for horizontal and vertical child views + // Used in a case where moveSection() triggered for table view + for (auto syncChild : std::as_const(syncChildren)) { + auto syncChild_d = syncChild->d_func(); + if (syncChild_d->m_sectionState != SectionState::Moving && + ((syncChild_d->syncHorizontally && orientation == Qt::Horizontal) || + (syncChild_d->syncVertically && orientation == Qt::Vertical))) + syncChild_d->moveSection(source, destination, orientation); + } + } + + // Rebuild the view to reflect the section order + scheduleRebuildTable(RebuildOption::ViewportOnly); + m_sectionState = SectionState::Idle; + + // Emit section moved signal for the sections moved in the view + const int startIndex = (source > destination) ? destination : source; + const int endIndex = (source > destination) ? source : destination; + const int mapIndex = static_cast<int>(orientation) - 1; + for (int index = startIndex; index <= endIndex; index++) { + const SectionData *logicalDataIndices = (syncView ? syncView->d_func()->logicalIndices[mapIndex].constData() : logicalIndices[mapIndex].constData()); + const SectionData *visualDataIndices = syncView ? syncView->d_func()->visualIndices[mapIndex].constData() : visualIndices[mapIndex].constData(); + const int prevLogicalIndex = logicalDataIndices[index].prevIndex; + if (orientation == Qt::Horizontal) + emit q->columnMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index); + else + emit q->rowMoved(prevLogicalIndex, visualDataIndices[prevLogicalIndex].prevIndex, visualDataIndices[prevLogicalIndex].index); + } + } +} + +void QQuickTableView::clearColumnReordering() +{ + Q_D(QQuickTableView); + d->clearSection(Qt::Horizontal); +} + +void QQuickTableView::clearRowReordering() +{ + Q_D(QQuickTableView); + d->clearSection(Qt::Vertical); +} + +void QQuickTableViewPrivate::clearSection(Qt::Orientations orientation) +{ + Q_Q(QQuickTableView); + + const int mapIndex = static_cast<int>(orientation) - 1; + const QList<SectionData> oldLogicalIndices = syncView ? syncView->d_func()->logicalIndices[mapIndex] : logicalIndices[mapIndex]; + const QList<SectionData> oldVisualIndices = syncView ? syncView->d_func()->visualIndices[mapIndex] : visualIndices[mapIndex];; + + if (syncView) + syncView->d_func()->clearSection(orientation); + else { + // Clear the index mapping and rebuild the table + logicalIndices[mapIndex].clear(); + visualIndices[mapIndex].clear(); + scheduleRebuildTable(RebuildOption::ViewportOnly); + } + + // Emit section moved signal for the sections moved in the view + for (int index = 0; index < oldLogicalIndices.size(); index++) { + const SectionData *logicalDataIndices = oldLogicalIndices.constData(); + const SectionData *visualDataIndices = oldVisualIndices.constData(); + if (logicalDataIndices[index].index != index) { + const int currentIndex = logicalDataIndices[index].index; + if (orientation == Qt::Horizontal) + emit q->columnMoved(currentIndex, visualDataIndices[currentIndex].index, index); + else + emit q->rowMoved(currentIndex, visualDataIndices[currentIndex].index, index); + } + } +} + +void QQuickTableViewPrivate::setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay) +{ + if (!modelIndex.isValid()) + return; + + const int cellIndex = modelIndexToCellIndex(modelIndex); + if (!loadedItems.contains(cellIndex)) + return; + const QPoint cell = cellAtModelIndex(cellIndex); + QQuickItem *item = loadedTableItem(cell)->item; + setRequiredProperty(kRequiredProperty_containsDrag, QVariant::fromValue(overlay), cellIndex, item, false); +} + QQuickItem *QQuickTableView::itemAtCell(const QPoint &cell) const { Q_D(const QQuickTableView); @@ -6190,9 +6445,9 @@ void QQuickTableView::setColumnWidth(int column, qreal size) return; if (size < 0) - d->explicitColumnWidths.remove(column); + d->explicitColumnWidths.remove(d->logicalColumnIndex(column)); else - d->explicitColumnWidths.insert(column, size); + d->explicitColumnWidths.insert(d->logicalColumnIndex(column), size); if (d->loadedItems.isEmpty()) return; @@ -6225,7 +6480,7 @@ qreal QQuickTableView::explicitColumnWidth(int column) const if (d->syncHorizontally) return d->syncView->explicitColumnWidth(column); - const auto it = d->explicitColumnWidths.constFind(column); + const auto it = d->explicitColumnWidths.constFind(d->logicalColumnIndex(column)); if (it != d->explicitColumnWidths.constEnd()) return *it; return -1; @@ -6248,9 +6503,9 @@ void QQuickTableView::setRowHeight(int row, qreal size) return; if (size < 0) - d->explicitRowHeights.remove(row); + d->explicitRowHeights.remove(d->logicalRowIndex(row)); else - d->explicitRowHeights.insert(row, size); + d->explicitRowHeights.insert(d->logicalRowIndex(row), size); if (d->loadedItems.isEmpty()) return; @@ -6283,7 +6538,7 @@ qreal QQuickTableView::explicitRowHeight(int row) const if (d->syncVertically) return d->syncView->explicitRowHeight(row); - const auto it = d->explicitRowHeights.constFind(row); + const auto it = d->explicitRowHeights.constFind(d->logicalRowIndex(row)); if (it != d->explicitRowHeights.constEnd()) return *it; return -1; @@ -6299,14 +6554,15 @@ QModelIndex QQuickTableView::modelIndex(const QPoint &cell) const if (!qaim) return {}; - return qaim->index(cell.y(), cell.x()); + return qaim->index(d->logicalRowIndex(cell.y()), d->logicalColumnIndex(cell.x())); } QPoint QQuickTableView::cellAtIndex(const QModelIndex &index) const { if (!index.isValid() || index.parent().isValid()) return {-1, -1}; - return {index.column(), index.row()}; + Q_D(const QQuickTableView); + return {d->visualColumnIndex(index.column()), d->visualRowIndex(index.row())}; } #if QT_DEPRECATED_SINCE(6, 4) @@ -6371,7 +6627,8 @@ void QQuickTableView::edit(const QModelIndex &index) // is currently dependent of the QQmlTableInstanceModel that was used to create the object // in order to initialize required properties, so we need to set the editItem variable // early on, so that we can use it in setRequiredProperty. - d->editIndex = modelIndex(d->cellAtModelIndex(serializedModelIndex)); + const QPoint cell = d->cellAtModelIndex(serializedModelIndex); + d->editIndex = modelIndex({d->visualColumnIndex(cell.x()), d->visualRowIndex(cell.y())}); d->editItem = qmlobject_cast<QQuickItem*>(object); if (!d->editItem) return; @@ -6400,7 +6657,7 @@ void QQuickTableView::edit(const QModelIndex &index) d->editModel->setModel(d->tableModel->model()); d->editModel->setDelegate(attached->editDelegate()); - const int cellIndex = d->modelIndexToCellIndex(index); + const int cellIndex = d->modelIndexToCellIndex(index, false); QObject* object = d->editModel->object(cellIndex, QQmlIncubator::Synchronous); if (!object) { d->editIndex = QModelIndex(); @@ -6451,7 +6708,7 @@ void QQuickTableView::closeEditor() d->editItem = nullptr; cellItem->setZ(1); - const int cellIndex = d->modelIndexToCellIndex(d->editIndex); + const int cellIndex = d->modelIndexToCellIndex(d->editIndex, false); d->setRequiredProperty(kRequiredProperty_editing, QVariant::fromValue(false), cellIndex, cellItem, false); // Remove the extra reference we sat on the cell item from edit() d->model->release(cellItem, QQmlInstanceModel::NotReusable); @@ -6669,7 +6926,6 @@ void QQuickTableView::setResizableRows(bool enabled) } // ---------------------------------------------- - QQuickTableViewHoverHandler::QQuickTableViewHoverHandler(QQuickTableView *view) : QQuickHoverHandler(view->contentItem()) { @@ -6720,13 +6976,35 @@ void QQuickTableViewHoverHandler::handleEventPoint(QPointerEvent *event, QEventP // ---------------------------------------------- -QQuickTableViewResizeHandler::QQuickTableViewResizeHandler(QQuickTableView *view) +QQuickTableViewPointerHandler::QQuickTableViewPointerHandler(QQuickTableView *view) : QQuickSinglePointHandler(view->contentItem()) { - setMargin(5); // Set a grab permission that stops the flickable, as well as // any drag handler inside the delegate, from stealing the drag. setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything); +} + +bool QQuickTableViewPointerHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) +{ + if (!QQuickSinglePointHandler::wantsEventPoint(event, point)) + return false; + + // If we have a mouse wheel event then we do not want to do anything related to resizing. + if (event->type() == QEvent::Type::Wheel) + return false; + + // When the user is flicking, we disable resizing, so that + // he doesn't start to resize by accident. + const auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + return !tableView->isMoving(); +} + +// ---------------------------------------------- + +QQuickTableViewResizeHandler::QQuickTableViewResizeHandler(QQuickTableView *view) + : QQuickTableViewPointerHandler(view) +{ + setMargin(5); setObjectName("tableViewResizeHandler"); } @@ -6754,23 +7032,14 @@ void QQuickTableViewResizeHandler::onGrabChanged(QQuickPointerHandler *grabber } } -bool QQuickTableViewResizeHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) -{ - if (!QQuickSinglePointHandler::wantsEventPoint(event, point)) - return false; - - // If we have a mouse wheel event then we do not want to do anything related to resizing. - if (event->type() == QEvent::Type::Wheel) - return false; - - // When the user is flicking, we disable resizing, so that - // he doesn't start to resize by accident. - auto tableView = static_cast<QQuickTableView *>(parentItem()->parent()); - return !tableView->isMoving(); -} - void QQuickTableViewResizeHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point) { + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView); + const auto *activeHandler = tableViewPrivate->activePointerHandler(); + if (activeHandler && !qobject_cast<const QQuickTableViewResizeHandler *>(activeHandler)) + return; + // Resolve which state we're in first... updateState(point); // ...and act on it next @@ -6835,6 +7104,7 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint // pointer handlers to do flicking, so setting an exclusive grab (together // with grab permissions) doens't work ATM. tableView->setFiltersChildMouseEvents(false); + tableViewPrivate->setActivePointerHandler(this); break; case DraggingStarted: setExclusiveGrab(event, point, true); @@ -6856,6 +7126,7 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint break; } case DraggingFinished: { tableView->setFiltersChildMouseEvents(true); + tableViewPrivate->setActivePointerHandler(nullptr); #if QT_CONFIG(cursor) tableViewPrivate->updateCursor(); #endif @@ -6864,6 +7135,310 @@ void QQuickTableViewResizeHandler::updateDrag(QPointerEvent *event, QEventPoint } // ---------------------------------------------- +QQuickTableViewSectionDragHandler::QQuickTableViewSectionDragHandler(QQuickTableView *view) + : QQuickTableViewPointerHandler(view) +{ + setObjectName("tableViewDragHandler"); +} + +QQuickTableViewSectionDragHandler::~QQuickTableViewSectionDragHandler() +{ + resetDragData(); +} + +void QQuickTableViewSectionDragHandler::resetDragData() +{ + if (m_state != Listening) { + m_state = Listening; + resetSectionOverlay(); + m_source = -1; + m_destination = -1; + if (m_grabResult.data()) + m_grabResult.data()->disconnect(); + if (!m_drag.isNull()) { + m_drag->disconnect(); + delete m_drag; + } + if (!m_dropArea.isNull()) { + m_dropArea->disconnect(); + delete m_dropArea; + } + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + tableView->setFiltersChildMouseEvents(true); + } +} + +void QQuickTableViewSectionDragHandler::resetSectionOverlay() +{ + if (m_destination != -1) { + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView); + const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : m_destination; + const int column = (m_sectionOrientation == Qt::Horizontal) ? m_destination : 0; + tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), false); + m_destination = -1; + } +} + +void QQuickTableViewSectionDragHandler::grabSection() +{ + // Generate the transparent section image in pixmap + QPixmap pixmap(m_grabResult->image().size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.setOpacity(0.6); + painter.drawImage(0, 0, m_grabResult->image()); + painter.end(); + + // Specify the pixmap and mime data to be as drag object + auto *mimeData = new QMimeData(); + mimeData->setImageData(pixmap); + m_drag->setMimeData(mimeData); + m_drag->setPixmap(pixmap); +} + +void QQuickTableViewSectionDragHandler::handleDrop(QQuickDragEvent *event) +{ + Q_UNUSED(event); + + if (m_state == Dragging) { + event->setAccepted(true); + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView); + tableViewPrivate->moveSection(m_source, m_destination, m_sectionOrientation); + m_state = DraggingFinished; + resetSectionOverlay(); + if (m_scrollTimer.isActive()) + m_scrollTimer.stop(); + } +} + +void QQuickTableViewSectionDragHandler::handleDrag(QQuickDragEvent *event) +{ + Q_UNUSED(event); + + if (m_state == Dragging) { + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + const QPoint dragItemPosition(tableView->contentX() + event->x(), tableView->contentY() + event->y()); + const auto *sourceItem = qobject_cast<QQuickItem *>(m_drag->source()); + const QPoint targetCell = tableView->cellAtPosition(dragItemPosition, true); + + auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView); + const int newDestination = (m_sectionOrientation == Qt::Horizontal) ? targetCell.x() : targetCell.y(); + if (newDestination != m_destination) { + // Reset the overlay property in the existing model delegate item + resetSectionOverlay(); + // Set the overlay property in the new model delegate item + const int row = (m_sectionOrientation == Qt::Horizontal) ? 0 : newDestination; + const int column = (m_sectionOrientation == Qt::Horizontal) ? newDestination : 0; + tableViewPrivate->setContainsDragOnDelegateItem(tableView->index(row, column), true); + m_destination = newDestination; + } + + // Scroll header view while section item moves out of the table boundary + const QPoint dragItemStartPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() - sourceItem->width() / 2, dragItemPosition.y()) : + QPoint(dragItemPosition.x(), dragItemPosition.y() - sourceItem->height() / 2); + const QPoint dragItemEndPos = (m_sectionOrientation == Qt::Horizontal) ? QPoint(dragItemPosition.x() + sourceItem->width() / 2, dragItemPosition.y()) : + QPoint(dragItemPosition.x(), dragItemPosition.y() + sourceItem->height() / 2); + const bool useStartPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemStartPos.x() <= tableView->contentX()) : (dragItemStartPos.y() <= tableView->contentY()); + const bool useEndPos = (m_sectionOrientation == Qt::Horizontal) ? (dragItemEndPos.x() >= tableView->width()) : (dragItemEndPos.y() >= tableView->height()); + if (useStartPos || useEndPos) { + if (!m_scrollTimer.isActive()) { + m_dragPoint = (m_sectionOrientation == Qt::Horizontal) ? QPoint(useStartPos ? dragItemStartPos.x() : dragItemEndPos.x(), 0) : + QPoint(0, useStartPos ? dragItemStartPos.y() : dragItemEndPos.y()); + m_scrollTimer.start(1); + } + } else { + if (m_scrollTimer.isActive()) + m_scrollTimer.stop(); + } + } +} + +void QQuickTableViewSectionDragHandler::handleDragDropAction(Qt::DropAction action) +{ + // Reset the overlay property in the model delegate item when drag or drop + // happens outside specified drop area (i.e. during ignore action) + if (action == Qt::IgnoreAction) { + resetSectionOverlay(); + if (m_scrollTimer.isActive()) + m_scrollTimer.stop(); + } +} + +void QQuickTableViewSectionDragHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point) +{ + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + auto *tableViewPrivate = QQuickTableViewPrivate::get(tableView); + const auto *activeHandler = tableViewPrivate->activePointerHandler(); + if (activeHandler && !qobject_cast<const QQuickTableViewSectionDragHandler *>(activeHandler)) + return; + + if (m_state == DraggingFinished) + resetDragData(); + + if (point.state() == QEventPoint::Pressed) { + // Reset the information in the drag handler + resetDragData(); + // Activate the passive grab to get further move updates + setPassiveGrab(event, point, true); + // Disable flicking while dragging. TableView uses filtering instead of + // pointer handlers to do flicking, so setting an exclusive grab (together + // with grab permissions) doens't work ATM. + auto *tableView = static_cast<QQuickTableView *>(parentItem()->parent()); + tableView->setFiltersChildMouseEvents(false); + m_state = Tracking; + } else if (point.state() == QEventPoint::Released) { + // Reset the information in the drag handler + resetDragData(); + } else if (point.state() == QEventPoint::Updated) { + // Check to see that the movement can be considered as dragging + const qreal distX = point.position().x() - point.pressPosition().x(); + const qreal distY = point.position().y() - point.pressPosition().y(); + const qreal dragDist = qSqrt(distX * distX + distY * distY); + if (dragDist > qApp->styleHints()->startDragDistance()) { + switch (m_state) { + case Tracking: { + // Grab the image for dragging header + const QPoint cell = tableView->cellAtPosition(point.position(), true); + auto *item = tableView->itemAtCell(cell); + if (m_drag.isNull()) { + m_drag = new QDrag(item); + connect(m_drag.data(), &QDrag::actionChanged, this, + &QQuickTableViewSectionDragHandler::handleDragDropAction); + } + // Connect the timer for scroling + QObject::connect(&m_scrollTimer, &QTimer::timeout, [&]{ + const QSizeF dist = tableViewPrivate->scrollTowardsPoint(m_dragPoint, m_step); + m_dragPoint.rx() += dist.width() > 0 ? m_step.width() : -m_step.width(); + m_dragPoint.ry() += dist.height() > 0 ? m_step.height() : -m_step.height(); + m_step = QSizeF(qAbs(dist.width() * 0.010), qAbs(dist.height() * 0.010)); + }); + // Set the drop area + if (m_dropArea.isNull()) { + m_dropArea = new QQuickDropArea(tableView); + m_dropArea->setSize(tableView->size()); + connect(m_dropArea, &QQuickDropArea::positionChanged, this, + &QQuickTableViewSectionDragHandler::handleDrag); + connect(m_dropArea, &QQuickDropArea::dropped, this, + &QQuickTableViewSectionDragHandler::handleDrop); + } + // Grab the image of the section + m_grabResult = item->grabToImage(); + connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, + &QQuickTableViewSectionDragHandler::grabSection); + // Update source depending on the type of orientation + m_source = (m_sectionOrientation == Qt::Horizontal) ? cell.x() : cell.y(); + m_state = DraggingStarted; + // Set drag handler as active and it further handles section pointer events + tableViewPrivate->setActivePointerHandler(this); + } + break; + + case DraggingStarted: { + if (m_drag && m_drag->mimeData()) { + if (auto *item = qobject_cast<QQuickItem *>(m_drag->source())) { + m_state = Dragging; + const QPointF itemPos = item->mapFromItem(tableView->contentItem(), point.position()); + Q_UNUSED(itemPos); + m_drag->setHotSpot(m_sectionOrientation == Qt::Horizontal ? QPoint(item->width()/2, itemPos.y()) : QPoint(itemPos.x(), item->height()/2)); + m_drag->exec(); + // Reset the active handler + tableViewPrivate->setActivePointerHandler(nullptr); + } + } + } + break; + + default: + break; + } + } + } +} + +// ---------------------------------------------- +void QQuickTableViewPrivate::initSectionDragHandler(Qt::Orientation orientation) +{ + if (!sectionDragHandler) { + Q_Q(QQuickTableView); + sectionDragHandler = new QQuickTableViewSectionDragHandler(q); + sectionDragHandler->setSectionOrientation(orientation); + } +} + +void QQuickTableViewPrivate::destroySectionDragHandler() +{ + if (sectionDragHandler) + delete sectionDragHandler; +} + +void QQuickTableViewPrivate::initializeIndexMapping() +{ + auto initIndices = [](auto& visualIndex, auto& logicalIndex, int size) { + visualIndex.resize(size); + logicalIndex.resize(size); + for (int index = 0; index < size; ++index) + visualIndex[index].index = logicalIndex[index].index = index; + }; + + if (!tableSize.isEmpty()) { + if (visualIndices[0].size() != tableSize.width() + || logicalIndices[0].size() != tableSize.width()) + initIndices(visualIndices[0], logicalIndices[0], tableSize.width()); + + if (visualIndices[1].size() != tableSize.height() + || logicalIndices[1].size() != tableSize.height()) + initIndices(visualIndices[1], logicalIndices[1], tableSize.height()); + } +} + +void QQuickTableViewPrivate::clearIndexMapping() +{ + logicalIndices[0].clear(); + visualIndices[0].clear(); + + logicalIndices[1].clear(); + visualIndices[1].clear(); +} + +int QQuickTableViewPrivate::logicalRowIndex(const int visualIndex) const +{ + if (syncView) + return syncView->d_func()->logicalRowIndex(visualIndex); + if (logicalIndices[1].isEmpty() || visualIndex < 0) + return visualIndex; + return logicalIndices[1].constData()[visualIndex].index; +} + +int QQuickTableViewPrivate::logicalColumnIndex(const int visualIndex) const +{ + if (syncView) + return syncView->d_func()->logicalColumnIndex(visualIndex); + if (logicalIndices[0].isEmpty() || visualIndex < 0) + return visualIndex; + return logicalIndices[0].constData()[visualIndex].index; +} + +int QQuickTableViewPrivate::visualRowIndex(const int logicalIndex) const +{ + if (syncView) + return syncView->d_func()->visualRowIndex(logicalIndex); + if (visualIndices[1].isEmpty() || logicalIndex < 0) + return logicalIndex; + return visualIndices[1].constData()[logicalIndex].index; +} + +int QQuickTableViewPrivate::visualColumnIndex(const int logicalIndex) const +{ + if (syncView) + return syncView->d_func()->visualColumnIndex(logicalIndex); + if (visualIndices[0].isEmpty() || logicalIndex < 0) + return logicalIndex; + return visualIndices[0].constData()[logicalIndex].index; +} + +// ---------------------------------------------- QQuickTableViewTapHandler::QQuickTableViewTapHandler(QQuickTableView *view) : QQuickTapHandler(view->contentItem()) diff --git a/src/quick/items/qquicktableview_p.h b/src/quick/items/qquicktableview_p.h index cdde6de3d6..10cc53274d 100644 --- a/src/quick/items/qquicktableview_p.h +++ b/src/quick/items/qquicktableview_p.h @@ -233,6 +233,11 @@ public: Q_INVOKABLE void positionViewAtCell(int column, int row, PositionMode mode, const QPointF &offset = QPointF(), const QRectF &subRect = QRectF()); #endif + Q_REVISION(6, 8) Q_INVOKABLE void moveColumn(int source, int destination); + Q_REVISION(6, 8) Q_INVOKABLE void moveRow(int source, int destination); + Q_REVISION(6, 8) Q_INVOKABLE void clearColumnReordering(); + Q_REVISION(6, 8) Q_INVOKABLE void clearRowReordering(); + static QQuickTableViewAttached *qmlAttachedProperties(QObject *); Q_SIGNALS: @@ -264,6 +269,9 @@ Q_SIGNALS: Q_REVISION(6, 5) void editTriggersChanged(); Q_REVISION(6, 5) void layoutChanged(); Q_REVISION(6, 6) void selectionModeChanged(); + Q_REVISION(6, 8) void rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + Q_REVISION(6, 8) void columnMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + protected: void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; diff --git a/src/quick/items/qquicktableview_p_p.h b/src/quick/items/qquicktableview_p_p.h index 867e03485a..ea49a3309b 100644 --- a/src/quick/items/qquicktableview_p_p.h +++ b/src/quick/items/qquicktableview_p_p.h @@ -23,8 +23,8 @@ #include <QtQml/private/qqmlincubator_p.h> #include <QtQmlModels/private/qqmlchangeset_p.h> #include <QtQml/qqmlinfo.h> +#include <QtGui/qdrag.h> -#include <QtQuick/private/qminimalflatset_p.h> #include <QtQuick/private/qquickflickable_p_p.h> #include <QtQuick/private/qquickitemviewfxitem_p_p.h> #include <QtQuick/private/qquickanimation_p.h> @@ -32,6 +32,9 @@ #include <QtQuick/private/qquicksinglepointhandler_p.h> #include <QtQuick/private/qquickhoverhandler_p.h> #include <QtQuick/private/qquicktaphandler_p.h> +#include <QtQuick/private/qquickdroparea_p.h> + +#include <QtCore/private/qminimalflatset_p.h> QT_BEGIN_NAMESPACE @@ -67,15 +70,10 @@ protected: void handleEventPoint(QPointerEvent *event, QEventPoint &point) override; }; -/*! \internal - * TableView uses QQuickTableViewResizeHandler to enable the user to resize - * rows and columns. By using a custom pointer handler, we can get away with - * using a single pointer handler for the whole content item, rather than - * e.g having to split it up into multiple items with drag handlers placed - * between the cells. - */ -class QQuickTableViewResizeHandler : public QQuickSinglePointHandler +class QQuickTableViewPointerHandler : public QQuickSinglePointHandler { + Q_OBJECT + public: enum State { Listening, // the pointer is not being pressed between the cells @@ -85,12 +83,28 @@ public: DraggingFinished // dragging was finished }; - QQuickTableViewResizeHandler(QQuickTableView *view); - State state() { return m_state; } - void updateState(QEventPoint &point); - void updateDrag(QPointerEvent *event, QEventPoint &point); + QQuickTableViewPointerHandler(QQuickTableView *view); State m_state = Listening; + State state() { return m_state; } + +protected: + bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override; +}; + +/*! \internal + * TableView uses QQuickTableViewResizeHandler to enable the user to resize + * rows and columns. By using a custom pointer handler, we can get away with + * using a single pointer handler for the whole content item, rather than + * e.g having to split it up into multiple items with drag handlers placed + * between the cells. + */ +class QQuickTableViewResizeHandler : public QQuickTableViewPointerHandler +{ + Q_OBJECT + +public: + QQuickTableViewResizeHandler(QQuickTableView *view); int m_row = -1; qreal m_rowStartY = -1; @@ -100,15 +114,54 @@ public: qreal m_columnStartX = -1; qreal m_columnStartWidth = -1; + void updateState(QEventPoint &point); + void updateDrag(QPointerEvent *event, QEventPoint &point); + friend class QQuickTableViewPrivate; protected: - bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override; void handleEventPoint(QPointerEvent *event, QEventPoint &point) override; void onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, QPointerEvent *ev, QEventPoint &point) override; }; +class QQuickTableViewSectionDragHandler : public QQuickTableViewPointerHandler +{ + Q_OBJECT + +public: + QQuickTableViewSectionDragHandler(QQuickTableView *view); + ~QQuickTableViewSectionDragHandler(); + + void grabSection(); + + void handleDrag(QQuickDragEvent *event); + void handleDrop(QQuickDragEvent *event); + void handleDragDropAction(Qt::DropAction action); + + void setSectionOrientation(Qt::Orientation orientation) { m_sectionOrientation = orientation; } + + friend class QQuickTableViewPrivate; + +protected: + void handleEventPoint(QPointerEvent *event, QEventPoint &point) override; + +private: + void resetDragData(); + void resetSectionOverlay(); + + QSharedPointer<QQuickItemGrabResult> m_grabResult; + QPointer<QDrag> m_drag; + int m_source = -1; + int m_destination = -1; + QPointer<QQuickDropArea> m_dropArea; + Qt::Orientation m_sectionOrientation; + + QPointF m_dragPoint; + QSizeF m_step = QSizeF(1, 1); + QTimer m_scrollTimer; +}; + /*! \internal * QQuickTableViewTapHandler used to handle tap events explicitly for table view */ @@ -123,7 +176,6 @@ public: friend class QQuickTableViewPrivate; }; - class Q_QUICK_EXPORT QQuickTableViewPrivate : public QQuickFlickablePrivate, public QQuickSelectable { public: @@ -240,6 +292,11 @@ public: Done }; + enum class SectionState { + Idle = 0, + Moving + }; + enum class RebuildOption { None = 0, All = 0x1, @@ -393,6 +450,8 @@ public: QQuickTableViewHoverHandler *hoverHandler = nullptr; QQuickTableViewResizeHandler *resizeHandler = nullptr; + QQuickTableViewSectionDragHandler *sectionDragHandler = nullptr; + QQuickTableViewPointerHandler *activePtrHandler = nullptr; QQmlTableInstanceModel *editModel = nullptr; QQuickItem *editItem = nullptr; @@ -403,6 +462,16 @@ public: QString forcedIncubationMode = qEnvironmentVariable("QT_TABLEVIEW_INCUBATION_MODE"); #endif + struct SectionData { + int index = -1; + int prevIndex = -1; + }; + + QList<SectionData> visualIndices[Qt::Vertical]; + QList<SectionData> logicalIndices[Qt::Vertical]; + + SectionState m_sectionState = SectionState::Idle; + public: void init(); @@ -410,7 +479,7 @@ public: int modelIndexAtCell(const QPoint &cell) const; QPoint cellAtModelIndex(int modelIndex) const; - int modelIndexToCellIndex(const QModelIndex &modelIndex) const; + int modelIndexToCellIndex(const QModelIndex &modelIndex, bool visualIndex = true) const; inline bool cellIsValid(const QPoint &cell) const { return cell.x() != -1 && cell.y() != -1; } qreal sizeHintForColumn(int column) const; @@ -589,7 +658,7 @@ public: void clearSelection() override; void normalizeSelection() override; QRectF selectionRectangle() const override; - QSizeF scrollTowardsSelectionPoint(const QPointF &pos, const QSizeF &step) override; + QSizeF scrollTowardsPoint(const QPointF &pos, const QSizeF &step) override; void setCallback(std::function<void(CallBackFlag)> func) override; void cancelSelectionTracking(); @@ -597,6 +666,22 @@ public: virtual void updateSelection(const QRect &oldSelection, const QRect &newSelection); QRect selection() const; // ---------------- + + // Section drag handler + void initSectionDragHandler(Qt::Orientation orientation); + void destroySectionDragHandler(); + inline void setActivePointerHandler(QQuickTableViewPointerHandler *handler) { activePtrHandler = handler; } + inline QQuickTableViewPointerHandler* activePointerHandler() const { return activePtrHandler; } + // Row/Column reordering + void moveSection(int source , int destination, Qt::Orientations orientation); + void initializeIndexMapping(); + void clearIndexMapping(); + void clearSection(Qt::Orientations orientation); + virtual int logicalRowIndex(const int visualIndex) const; + virtual int logicalColumnIndex(const int visualIndex) const; + virtual int visualRowIndex(const int logicalIndex) const; + virtual int visualColumnIndex(const int logicalIndex) const; + void setContainsDragOnDelegateItem(const QModelIndex &modelIndex, bool overlay); }; class FxTableItem : public QQuickItemViewFxItem diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index b30084474d..f918a0da2e 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -11,7 +11,6 @@ #include <private/qqmlglobal_p.h> #include <private/qsgadaptationlayer_p.h> #include "qsginternaltextnode_p.h" -#include "qquickimage_p_p.h" #include "qquicktextutil_p.h" #include <QtQuick/private/qsgtexture_p.h> @@ -49,7 +48,7 @@ const QChar QQuickTextPrivate::elideChar = QChar(0x2026); const int QQuickTextPrivate::largeTextSizeThreshold = QQUICKTEXT_LARGETEXT_THRESHOLD; QQuickTextPrivate::QQuickTextPrivate() - : fontInfo(font), elideLayout(nullptr), textLine(nullptr), lineWidth(0) + : fontInfo(font), lineWidth(0) , color(0xFF000000), linkColor(0xFF0000FF), styleColor(0xFF000000) , lineCount(1), multilengthEos(-1) , elideMode(QQuickText::ElideNone), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop) @@ -82,7 +81,6 @@ QQuickTextPrivate::ExtraData::ExtraData() , doc(nullptr) , minimumPixelSize(12) , minimumPointSize(12) - , nbActiveDownloads(0) , maximumLineCount(INT_MAX) , renderTypeQuality(QQuickText::DefaultRenderTypeQuality) , lineHeightValid(false) @@ -101,9 +99,6 @@ void QQuickTextPrivate::init() QQuickTextPrivate::~QQuickTextPrivate() { - delete elideLayout; - delete textLine; textLine = nullptr; - if (extra.isAllocated()) { qDeleteAll(extra->imgTags); extra->imgTags.clear(); @@ -357,29 +352,33 @@ void QQuickText::resourceRequestFinished() void QQuickText::imageDownloadFinished() { Q_D(QQuickText); + if (!d->extra.isAllocated()) + return; - (d->extra->nbActiveDownloads)--; + if (std::any_of(d->extra->imgTags.cbegin(), d->extra->imgTags.cend(), + [] (auto *image) { return image->pix && image->pix->isLoading(); })) { + // return if we still have any active download + return; + } // when all the remote images have been downloaded, // if one of the sizes was not specified at parsing time // we use the implicit size from pixmapcache and re-layout. - if (d->extra.isAllocated() && d->extra->nbActiveDownloads == 0) { - bool needToUpdateLayout = false; - for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) { - if (!img->size.isValid()) { - img->size = img->pix->implicitSize(); - needToUpdateLayout = true; - } + bool needToUpdateLayout = false; + for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) { + if (!img->size.isValid()) { + img->size = img->pix->implicitSize(); + needToUpdateLayout = true; } + } - if (needToUpdateLayout) { - d->textHasChanged = true; - d->updateLayout(); - } else { - d->updateType = QQuickTextPrivate::UpdatePaintNode; - update(); - } + if (needToUpdateLayout) { + d->textHasChanged = true; + d->updateLayout(); + } else { + d->updateType = QQuickTextPrivate::UpdatePaintNode; + update(); } } @@ -677,7 +676,7 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, Q_Q(QQuickText); if (!textLine) - textLine = new QQuickTextLine; + textLine.reset(new QQuickTextLine); textLine->setFullLayoutTextLength(fullLayoutTextLength); textLine->setLine(&line); textLine->setY(height); @@ -693,7 +692,7 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, if (lineHeight() != 1.0) textLine->setHeight((lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight()); - emit q->lineLaidOut(textLine); + emit q->lineLaidOut(textLine.get()); height += textLine->height(); } @@ -1189,7 +1188,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) if (elide) { if (!elideLayout) { - elideLayout = new QTextLayout; + elideLayout.reset(new QTextLayout); elideLayout->setCacheEnabled(true); } QTextEngine *engine = layout.engine(); @@ -1239,8 +1238,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) if (visibleCount == 1) layout.clearLayout(); } else { - delete elideLayout; - elideLayout = nullptr; + elideLayout.reset(); } QTextLine firstLine = visibleCount == 1 && elideLayout @@ -1289,12 +1287,10 @@ void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal if (!image->pix) { const QQmlContext *context = qmlContext(q); const QUrl url = context->resolvedUrl(q->baseUrl()).resolved(image->url); - image->pix = new QQuickPixmap(context->engine(), url, QRect(), image->size); + image->pix.reset(new QQuickPixmap(context->engine(), url, QRect(), image->size * devicePixelRatio())); + if (image->pix->isLoading()) { image->pix->connectFinished(q, SLOT(imageDownloadFinished())); - if (!extra.isAllocated() || !extra->nbActiveDownloads) - extra.value().nbActiveDownloads = 0; - extra->nbActiveDownloads++; } else if (image->pix->isReady()) { if (!image->size.isValid()) { image->size = image->pix->implicitSize(); @@ -1379,6 +1375,11 @@ void QQuickTextPrivate::updateDocumentText() rightToLeftText = extra->doc->toPlainText().isRightToLeft(); } +qreal QQuickTextPrivate::devicePixelRatio() const +{ + return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio()); +} + /*! \qmltype Text \instantiates QQuickText @@ -1773,9 +1774,7 @@ QQuickText::~QQuickText() By default, no variable axes are set. - \note In order to use variable axes on Windows, the application has to run with either the - FreeType or DirectWrite font databases. See the documentation for - QGuiApplication::QGuiApplication() for more information on how to select these technologies. + \note On Windows, variable axes are not supported if the optional GDI font backend is in use. \sa QFont::setVariableAxis() //! [qml-font-variable-axes] @@ -1849,6 +1848,53 @@ QQuickText::~QQuickText() \sa QFont::setFeature() //! [qml-font-features] */ + +/*! + \qmlproperty bool QtQuick::Text::font.contextFontMerging + \since 6.8 + +//! [qml-font-context-font-merging] + If the selected font does not contain a certain character, Qt automatically chooses a + similar-looking fallback font that contains the character. By default this is done on a + character-by-character basis. + + This means that in certain uncommon cases, many different fonts may be used to represent one + string of text even if it's in the same script. Setting \c contextFontMerging to true will try + finding the fallback font that matches the largest subset of the input string instead. This + will be more expensive for strings where missing glyphs occur, but may give more consistent + results. By default, \c contextFontMerging is \c{false}. + + \sa QFont::StyleStrategy +//! [qml-font-context-font-merging] +*/ + +/*! + \qmlproperty bool QtQuick::Text::font.preferTypoLineMetrics + \since 6.8 + +//! [qml-font-prefer-typo-line-metrics] For compatibility reasons, OpenType fonts contain two + competing sets of the vertical line metrics that provide the \l{QFontMetricsF::ascent()}{ascent}, + \l{QFontMetricsF::descent()}{descent} and \l{QFontMetricsF::leading()}{leading} of the font. These + are often referred to as the + \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win} (Windows) + metrics and the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo} + (typographical) metrics. While the specification recommends using the \c typo metrics for line + spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS} flag is set in + the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection} + field of the font. For backwards-compatibility reasons, this is also the case for Qt applications. + This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to indicate that the \c{typo} + metrics are valid, nor for fonts where the \c{win} metrics and \c{typo} metrics match up. However, + for certain fonts the \c{win} metrics may be larger than the preferable line spacing and the + \c{USE_TYPO_METRICS} flag may be unset by mistake. For such fonts, setting + \c{font.preferTypoLineMetrics} may give superior results. + + By default, \c preferTypoLineMetrics is \c{false}. + + \sa QFont::StyleStrategy +//! [qml-font-prefer-typo-line-metrics] +*/ + + QFont QQuickText::font() const { Q_D(const QQuickText); @@ -1903,14 +1949,30 @@ void QQuickText::itemChange(ItemChange change, const ItemChangeData &value) break; case ItemDevicePixelRatioHasChanged: - if (d->renderType == NativeRendering) { - // Native rendering optimizes for a given pixel grid, so its results must not be scaled. - // Text layout code respects the current device pixel ratio automatically, we only need - // to rerun layout after the ratio changed. - // Changes of implicit size should be minimal; they are hard to avoid. - d->implicitWidthValid = false; - d->implicitHeightValid = false; - d->updateLayout(); + { + bool needUpdateLayout = false; + if (d->renderType == NativeRendering) { + // Native rendering optimizes for a given pixel grid, so its results must not be scaled. + // Text layout code respects the current device pixel ratio automatically, we only need + // to rerun layout after the ratio changed. + // Changes of implicit size should be minimal; they are hard to avoid. + d->implicitWidthValid = false; + d->implicitHeightValid = false; + needUpdateLayout = true; + } + + if (d->extra.isAllocated()) { + // check if we have scalable inline images with explicit size set, which should be reloaded + for (QQuickStyledTextImgTag *image : std::as_const(d->extra->visibleImgTags)) { + if (image->size.isValid() && QQuickPixmap::isScalableImageFormat(image->url)) { + image->pix.reset(); + needUpdateLayout = true; + } + } + } + + if (needUpdateLayout) + d->updateLayout(); } break; @@ -2751,13 +2813,12 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data node->addTextLayout(QPointF(dx, dy), &d->layout, -1, -1,0, unelidedLineCount); if (d->elideLayout) - node->addTextLayout(QPointF(dx, dy), d->elideLayout); + node->addTextLayout(QPointF(dx, dy), d->elideLayout.get()); if (d->extra.isAllocated()) { for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) { - QQuickPixmap *pix = img->pix; - if (pix && pix->isReady()) - node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, pix->width(), pix->height()), pix->image()); + if (img->pix && img->pix->isReady()) + node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, img->size.width(), img->size.height()), img->pix->image()); } } } @@ -3044,7 +3105,7 @@ QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const if (styledText) { QString link = anchorAt(&layout, translatedMousePos); if (link.isEmpty() && elideLayout) - link = anchorAt(elideLayout, translatedMousePos); + link = anchorAt(elideLayout.get(), translatedMousePos); return link; } else if (richText && extra.isAllocated() && extra->doc) { translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), availableWidth(), q->effectiveHAlign()); diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index 2e54ae53a1..6dba7a7d75 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -77,7 +77,6 @@ public: QString hoveredLink; int minimumPixelSize; int minimumPointSize; - int nbActiveDownloads; int maximumLineCount; int renderTypeQuality; bool lineHeightValid : 1; @@ -96,8 +95,8 @@ public: QFontInfo fontInfo; QTextLayout layout; - QTextLayout *elideLayout; - QQuickTextLine *textLine; + QScopedPointer<QTextLayout> elideLayout; + QScopedPointer<QQuickTextLine> textLine; qreal lineWidth; @@ -164,6 +163,8 @@ public: void ensureDoc(); void updateDocumentText(); + qreal devicePixelRatio() const; + QRectF setupTextLayout(qreal * const baseline); void setupCustomLineGeometry(QTextLine &line, qreal &height, int fullLayoutTextLength, int lineOffset = 0); bool isLinkActivatedConnected(); diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp index 6443b9ef70..a1fb7adcea 100644 --- a/src/quick/items/qquicktextdocument.cpp +++ b/src/quick/items/qquicktextdocument.cpp @@ -326,13 +326,15 @@ void QQuickTextDocumentPrivate::load() setStatus(QQuickTextDocument::Status::Loading, {}); QByteArray data = file.readAll(); doc->setBaseUrl(resolvedUrl.adjusted(QUrl::RemoveFilename)); +#if QT_CONFIG(textmarkdownreader) || QT_CONFIG(texthtmlparser) const bool plainText = editor->textFormat() == QQuickTextEdit::PlainText; +#endif #if QT_CONFIG(textmarkdownreader) if (!plainText && isMarkdown) { doc->setMarkdown(QString::fromUtf8(data)); } else #endif -#ifndef QT_NO_TEXTHTMLPARSER +#if QT_CONFIG(texthtmlparser) if (!plainText && isHtml) { // If a user loads an HTML file, remember the encoding. // If the user then calls save() later, the same encoding will be used. @@ -411,7 +413,7 @@ void QQuickTextDocumentPrivate::writeTo(const QUrl &fileUrl) raw = doc->toMarkdown().toUtf8(); break; #endif -#ifndef QT_NO_TEXTHTMLPARSER +#if QT_CONFIG(texthtmlparser) case Qt::RichText: if (sameUrl && encoding) { QStringEncoder enc(*encoding); @@ -587,10 +589,24 @@ QSizeF QQuickTextImageHandler::intrinsicSize( { if (format.isImageFormat()) { QTextImageFormat imageFormat = format.toImageFormat(); - const int width = qRound(imageFormat.width()); + int width = qRound(imageFormat.width()); const bool hasWidth = imageFormat.hasProperty(QTextFormat::ImageWidth) && width > 0; const int height = qRound(imageFormat.height()); const bool hasHeight = imageFormat.hasProperty(QTextFormat::ImageHeight) && height > 0; + const auto maxWidth = imageFormat.maximumWidth(); + const bool hasMaxWidth = imageFormat.hasProperty(QTextFormat::ImageMaxWidth) && maxWidth.type() != QTextLength::VariableLength; + + int effectiveMaxWidth = INT_MAX; + if (hasMaxWidth) { + if (maxWidth.type() == QTextLength::PercentageLength) { + effectiveMaxWidth = (doc->pageSize().width() - 2 * doc->documentMargin()) * maxWidth.value(100) / 100; + } else { + effectiveMaxWidth = maxWidth.rawValue(); + } + + width = qMin(effectiveMaxWidth, width); + } + QSizeF size(width, height); if (!hasWidth || !hasHeight) { QVariant res = doc->resource(QTextDocument::ImageResource, QUrl(imageFormat.name())); @@ -605,11 +621,17 @@ QSizeF QQuickTextImageHandler::intrinsicSize( return size; } QSize imgSize = image.size(); + if (imgSize.width() > effectiveMaxWidth) { + // image is bigger than effectiveMaxWidth, scale it down + imgSize.setHeight(effectiveMaxWidth * imgSize.height() / (qreal) imgSize.width()); + imgSize.setWidth(effectiveMaxWidth); + } + if (!hasWidth) { if (!hasHeight) size.setWidth(imgSize.width()); else - size.setWidth(qRound(height * (imgSize.width() / (qreal) imgSize.height()))); + size.setWidth(qMin(effectiveMaxWidth, qRound(height * (imgSize.width() / (qreal) imgSize.height())))); } if (!hasHeight) { if (!hasWidth) diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 22bbd6e05b..854c2e17fd 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -376,6 +376,20 @@ QString QQuickTextEdit::text() const */ /*! + \qmlproperty bool QtQuick::TextEdit::font.contextFontMerging + \since 6.8 + + \include qquicktext.cpp qml-font-context-font-merging +*/ + +/*! + \qmlproperty bool QtQuick::TextEdit::font.preferTypoLineMetrics + \since 6.8 + + \include qquicktext.cpp qml-font-prefer-typo-line-metrics +*/ + +/*! \qmlproperty string QtQuick::TextEdit::text The text to display. If the text format is AutoText the text edit will @@ -862,6 +876,7 @@ void QQuickTextEdit::setHAlign(HAlignment align) if (d->setHAlign(align, true) && isComponentComplete()) { d->updateDefaultTextOption(); updateSize(); + updateWholeDocument(); } } diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index ef00451788..0826011a54 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -403,6 +403,20 @@ QString QQuickTextInputPrivate::realText() const \include qquicktext.cpp qml-font-features */ + +/*! + \qmlproperty bool QtQuick::TextInput::font.contextFontMerging + \since 6.8 + + \include qquicktext.cpp qml-font-context-font-merging +*/ + +/*! + \qmlproperty bool QtQuick::TextInput::font.preferTypoLineMetrics + \since 6.8 + + \include qquicktext.cpp qml-font-prefer-typo-line-metrics +*/ QFont QQuickTextInput::font() const { Q_D(const QQuickTextInput); diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp index fd33fa86e2..c486fece40 100644 --- a/src/quick/items/qquicktextnodeengine.cpp +++ b/src/quick/items/qquicktextnodeengine.cpp @@ -660,10 +660,14 @@ void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFra if (borderStyle == QTextFrameFormat::BorderStyle_None) return; - addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(), - -frameFormat.rightMargin() - borderWidth, - -frameFormat.bottomMargin() - borderWidth), - borderWidth, borderStyle, borderBrush); + const auto collapsed = table->format().borderCollapse(); + + if (!collapsed) { + addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(), + -frameFormat.rightMargin() - borderWidth, + -frameFormat.bottomMargin() - borderWidth), + borderWidth, borderStyle, borderBrush); + } if (table != nullptr) { int rows = table->rows(); int columns = table->columns(); @@ -673,7 +677,7 @@ void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFra QTextTableCell cell = table->cellAt(row, column); QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell); - addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth, + addBorder(cellRect.adjusted(-borderWidth, -borderWidth, collapsed ? -borderWidth : 0, collapsed ? -borderWidth : 0), borderWidth, borderStyle, borderBrush); } } diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index b8c7878e04..eb969e7476 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -956,6 +956,22 @@ void QQuickWindowPrivate::cleanup(QSGNode *n) // The confirmExitPopup allows user to save or discard the document, // or to cancel the closing. \endcode + + \section1 Styling + + As with all visual types in Qt Quick, Window supports + \l {palette}{palettes}. However, as with types like \l Text, Window does + not use palettes by default. For example, to change the background color + of the window when the operating system's theme changes, the \l color must + be set: + + \snippet qml/windowPalette.qml declaration-and-color + \codeline + \snippet qml/windowPalette.qml text-item + \snippet qml/windowPalette.qml closing-brace + + Use \l {ApplicationWindow} (and \l {Label}) from \l {Qt Quick Controls} + instead of Window to get automatic styling. */ /*! @@ -1853,6 +1869,41 @@ void QQuickWindowPrivate::clearFocusObject() da->clearFocusObject(); } +void QQuickWindowPrivate::setFocusToTarget(FocusTarget target, Qt::FocusReason reason) +{ + if (!contentItem) + return; + + QQuickItem *newFocusItem = nullptr; + switch (target) { + case FocusTarget::First: + case FocusTarget::Last: { + const bool forward = (target == FocusTarget::First); + newFocusItem = QQuickItemPrivate::nextPrevItemInTabFocusChain(contentItem, forward); + if (newFocusItem) { + const auto *itemPriv = QQuickItemPrivate::get(newFocusItem); + if (itemPriv->subFocusItem && itemPriv->flags & QQuickItem::ItemIsFocusScope) + clearFocusInScope(newFocusItem, itemPriv->subFocusItem, reason); + } + break; + } + case FocusTarget::Next: + case FocusTarget::Prev: { + const auto da = deliveryAgentPrivate(); + Q_ASSERT(da); + QQuickItem *focusItem = da->focusTargetItem() ? da->focusTargetItem() : contentItem; + bool forward = (target == FocusTarget::Next); + newFocusItem = QQuickItemPrivate::nextPrevItemInTabFocusChain(focusItem, forward); + break; + } + default: + break; + } + + if (newFocusItem) + newFocusItem->forceActiveFocus(reason); +} + /*! \qmlproperty list<QtObject> Window::data \qmldefault diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 1b1b12de65..8ba4e56515 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -146,6 +146,7 @@ public: #endif void clearFocusObject() override; + void setFocusToTarget(FocusTarget, Qt::FocusReason) override; void dirtyItem(QQuickItem *); void cleanup(QSGNode *); diff --git a/src/quick/items/qquickwindowcontainer.cpp b/src/quick/items/qquickwindowcontainer.cpp index 839a30330d..55806356f6 100644 --- a/src/quick/items/qquickwindowcontainer.cpp +++ b/src/quick/items/qquickwindowcontainer.cpp @@ -21,7 +21,6 @@ using namespace Qt::StringLiterals; \inqmlmodule QtQuick \ingroup qtquick-visual \inherits Item - \instantiates QQuickItem \since 6.7 \preliminary diff --git a/src/quick/items/qquickwindowmodule.cpp b/src/quick/items/qquickwindowmodule.cpp index c4a978dec8..e539880452 100644 --- a/src/quick/items/qquickwindowmodule.cpp +++ b/src/quick/items/qquickwindowmodule.cpp @@ -35,7 +35,15 @@ QQuickWindowQmlImpl::QQuickWindowQmlImpl(QQuickWindowQmlImplPrivate &dd, QWindow : QQuickWindow(dd, parent) { connect(this, &QWindow::visibleChanged, this, &QQuickWindowQmlImpl::visibleChanged); - connect(this, &QWindow::visibilityChanged, this, &QQuickWindowQmlImpl::visibilityChanged); + connect(this, &QWindow::visibilityChanged, this, [&]{ + Q_D(QQuickWindowQmlImpl); + // Update the window's actual visibility and turn off visibilityExplicitlySet, + // so that future applyWindowVisibility() calls do not apply both window state + // and visible state, unless setVisibility() is called again by the user. + d->visibility = QWindow::visibility(); + d->visibilityExplicitlySet = false; + emit QQuickWindowQmlImpl::visibilityChanged(d->visibility); + }); connect(this, &QWindow::screenChanged, this, &QQuickWindowQmlImpl::screenChanged); // We shadow the x and y properties, so that we can re-map them in case @@ -110,6 +118,7 @@ void QQuickWindowQmlImpl::setVisibility(Visibility visibility) { Q_D(QQuickWindowQmlImpl); d->visibility = visibility; + d->visibilityExplicitlySet = true; if (d->componentComplete) applyWindowVisibility(); } @@ -190,8 +199,8 @@ void QQuickWindowQmlImpl::applyWindowVisibility() Q_ASSERT(d->componentComplete); - const bool visible = d->visibility == AutomaticVisibility - ? d->visible : d->visibility != Hidden; + const bool visible = d->visibilityExplicitlySet + ? d->visibility != Hidden : d->visible; qCDebug(lcQuickWindow) << "Applying visible" << visible << "for" << this; @@ -234,20 +243,30 @@ void QQuickWindowQmlImpl::applyWindowVisibility() } } - if (d->visibleExplicitlySet && ((d->visibility == Hidden && d->visible) || - (d->visibility > AutomaticVisibility && !d->visible))) { + if (d->visibleExplicitlySet && d->visibilityExplicitlySet && + ((d->visibility == Hidden && d->visible) || + (d->visibility > AutomaticVisibility && !d->visible))) { // FIXME: Should we bail out in this case? qmlWarning(this) << "Conflicting properties 'visible' and 'visibility'"; } if (d->visibility == AutomaticVisibility) { + // We're either showing for the first time, with the default + // visibility of AutomaticVisibility, or the user has called + // setVisibility with AutomaticVisibility at some point, so + // apply both window state and visible. if (QWindow::parent() || visualParent()) setWindowState(Qt::WindowNoState); else setWindowState(QGuiApplicationPrivate::platformIntegration()->defaultWindowState(flags())); QQuickWindow::setVisible(d->visible); - } else { + } else if (d->visibilityExplicitlySet) { + // We're not AutomaticVisibility, but the user has requested + // an explicit visibility, so apply both window state and visible. QQuickWindow::setVisibility(d->visibility); + } else { + // Our window state should be up to date, so only apply visible + QQuickWindow::setVisible(d->visible); } } diff --git a/src/quick/items/qquickwindowmodule_p_p.h b/src/quick/items/qquickwindowmodule_p_p.h index bda666e15b..227b8aa01e 100644 --- a/src/quick/items/qquickwindowmodule_p_p.h +++ b/src/quick/items/qquickwindowmodule_p_p.h @@ -30,6 +30,8 @@ public: bool visible = false; bool visibleExplicitlySet = false; QQuickWindow::Visibility visibility = QQuickWindow::AutomaticVisibility; + bool visibilityExplicitlySet = false; + QV4::PersistentValue rootItemMarker; QMetaObject::Connection itemParentWindowChangeListener; |