diff options
author | Fabian Kosmale <[email protected]> | 2021-09-03 12:23:15 +0200 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2021-09-20 17:46:52 +0200 |
commit | deb238586a4420f6848fb60b3356ec6220b86b17 (patch) | |
tree | 7dd66f4416782ae07e1ebd01e04035e58dba6b73 | |
parent | 88aea5173f5bb7d900a4a3220ba44a2632262624 (diff) |
QQuickIcon: Resolve URL relative to current element
If a user passes a relative URL to icon, they probably expect the URL to
be handled relative to the current file (like in Qt 5). We restore this
behavior by doing the URL resolution in the current QML context.
Unfortunately, as a gadget, QQuickIcon does not have its own context.
Thus, we need to store (and set) a pointer to its "owner", so that we can
retrieve the context for the binding that causes the URL to be set.
We adjust the three classes which currently use QQuickIcon to set the
owner in their setIcon methods.
Task-number: QTBUG-95587
Pick-to: 6.2
Change-Id: Icd1f2ddf65ae7d09ff787a50a01f1db78c571abf
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Mitch Curtis <[email protected]>
Reviewed-by: Fabian Kosmale <[email protected]>
-rw-r--r-- | src/quickcontrols2impl/qquickiconlabel.cpp | 5 | ||||
-rw-r--r-- | src/quicktemplates2/qquickabstractbutton.cpp | 1 | ||||
-rw-r--r-- | src/quicktemplates2/qquickaction.cpp | 1 | ||||
-rw-r--r-- | src/quicktemplates2/qquickicon.cpp | 70 | ||||
-rw-r--r-- | src/quicktemplates2/qquickicon_p.h | 5 | ||||
-rw-r--r-- | tests/auto/quickcontrols2/qquickiconlabel/data/a.png | bin | 0 -> 98 bytes | |||
-rw-r--r-- | tests/auto/quickcontrols2/qquickiconlabel/data/iconSourceContext.qml | 7 | ||||
-rw-r--r-- | tests/auto/quickcontrols2/qquickiconlabel/tst_qquickiconlabel.cpp | 31 |
8 files changed, 103 insertions, 17 deletions
diff --git a/src/quickcontrols2impl/qquickiconlabel.cpp b/src/quickcontrols2impl/qquickiconlabel.cpp index d76da28f93..111c68cc98 100644 --- a/src/quickcontrols2impl/qquickiconlabel.cpp +++ b/src/quickcontrols2impl/qquickiconlabel.cpp @@ -78,7 +78,7 @@ bool QQuickIconLabelPrivate::createImage() beginClass(image); image->setObjectName(QStringLiteral("image")); image->setName(icon.name()); - image->setSource(icon.source()); + image->setSource(icon.resolvedSource()); image->setSourceSize(QSize(icon.width(), icon.height())); image->setColor(icon.color()); image->setCache(icon.cache()); @@ -112,7 +112,7 @@ void QQuickIconLabelPrivate::syncImage() return; image->setName(icon.name()); - image->setSource(icon.source()); + image->setSource(icon.resolvedSource()); image->setSourceSize(QSize(icon.width(), icon.height())); image->setColor(icon.color()); image->setCache(icon.cache()); @@ -399,6 +399,7 @@ void QQuickIconLabel::setIcon(const QQuickIcon &icon) return; d->icon = icon; + d->icon.setOwner(this); d->updateOrSyncImage(); } diff --git a/src/quicktemplates2/qquickabstractbutton.cpp b/src/quicktemplates2/qquickabstractbutton.cpp index 61b358e0d6..af8ff0ab11 100644 --- a/src/quicktemplates2/qquickabstractbutton.cpp +++ b/src/quicktemplates2/qquickabstractbutton.cpp @@ -793,6 +793,7 @@ void QQuickAbstractButton::setIcon(const QQuickIcon &icon) { Q_D(QQuickAbstractButton); d->icon = icon; + d->icon.setOwner(this); d->updateEffectiveIcon(); } diff --git a/src/quicktemplates2/qquickaction.cpp b/src/quicktemplates2/qquickaction.cpp index e3eb937007..214fc1f599 100644 --- a/src/quicktemplates2/qquickaction.cpp +++ b/src/quicktemplates2/qquickaction.cpp @@ -403,6 +403,7 @@ void QQuickAction::setIcon(const QQuickIcon &icon) return; d->icon = icon; + d->icon.setOwner(this); emit iconChanged(icon); } diff --git a/src/quicktemplates2/qquickicon.cpp b/src/quicktemplates2/qquickicon.cpp index bf0a4658ef..2b58529ec1 100644 --- a/src/quicktemplates2/qquickicon.cpp +++ b/src/quicktemplates2/qquickicon.cpp @@ -35,19 +35,17 @@ ****************************************************************************/ #include "qquickicon_p.h" +#include "qtaggedpointer.h" + +#include <private/qqmlcontextdata_p.h> +#include <private/qqmldata_p.h> QT_BEGIN_NAMESPACE class QQuickIconPrivate : public QSharedData { public: - QString name; - QUrl source; - int width = 0; - int height = 0; - QColor color = Qt::transparent; - bool cache = true; - + // This is based on QFont's resolve_mask. enum ResolveProperties { NameResolved = 0x0001, SourceResolved = 0x0002, @@ -57,9 +55,29 @@ public: CacheResolved = 0x0020, AllPropertiesResolved = 0x1ffff }; - - // This is based on QFont's resolve_mask. int resolveMask = 0; + + QString name; + QUrl source; + int width = 0; + int height = 0; + QColor color = Qt::transparent; + + // we want DoCache as the default, and thus as the zero value + // so that the tagged pointer can be zero initialized + enum CacheStatus : bool { DoCache, SkipCaching }; + static_assert (DoCache == 0); + /* We use a QTaggedPointer here to save space: + - Without it, we would need an additional boolean, which due to + alignment would increase the class size by sizeof(void *) + - The pointer part stores the "owner" of the QQuickIcon, i.e. + an object which has an icon property. We need the owner to + access its context to resolve relative url's in the way users + expect. + - The tag bits are used to track whether caching is enabled. + */ + QTaggedPointer<QObject, CacheStatus> ownerAndCache = nullptr; + }; QQuickIcon::QQuickIcon() @@ -89,7 +107,7 @@ bool QQuickIcon::operator==(const QQuickIcon &other) const && d->width == other.d->width && d->height == other.d->height && d->color == other.d->color - && d->cache == other.d->cache); + && d->ownerAndCache == other.d->ownerAndCache); } bool QQuickIcon::operator!=(const QQuickIcon &other) const @@ -146,6 +164,17 @@ void QQuickIcon::resetSource() d->resolveMask &= ~QQuickIconPrivate::SourceResolved; } +QUrl QQuickIcon::resolvedSource() const +{ + if (QObject *owner = d->ownerAndCache.data()) { + QQmlData *data = QQmlData::get(owner); + if (data && data->outerContext) + return data->outerContext->resolvedUrl(d->source); + } + + return d->source; +} + int QQuickIcon::width() const { return d->width; @@ -214,26 +243,35 @@ void QQuickIcon::resetColor() bool QQuickIcon::cache() const { - return d->cache; + return d->ownerAndCache.tag() == QQuickIconPrivate::DoCache; } void QQuickIcon::setCache(bool cache) { - if ((d->resolveMask & QQuickIconPrivate::CacheResolved) && d->cache == cache) + const auto cacheState = cache ? QQuickIconPrivate::DoCache : QQuickIconPrivate::SkipCaching; + if ((d->resolveMask & QQuickIconPrivate::CacheResolved) && d->ownerAndCache.tag() == cacheState) return; d.detach(); - d->cache = cache; + d->ownerAndCache.setTag(cacheState); d->resolveMask |= QQuickIconPrivate::CacheResolved; } void QQuickIcon::resetCache() { d.detach(); - d->cache = true; + d->ownerAndCache.setTag(QQuickIconPrivate::DoCache); d->resolveMask &= ~QQuickIconPrivate::CacheResolved; } +void QQuickIcon::setOwner(QObject *owner) +{ + if (d->ownerAndCache.data() == owner) + return; + d.detach(); + d->ownerAndCache = owner; +} + QQuickIcon QQuickIcon::resolve(const QQuickIcon &other) const { QQuickIcon resolved = *this; @@ -255,7 +293,9 @@ QQuickIcon QQuickIcon::resolve(const QQuickIcon &other) const resolved.d->color = other.d->color; if (!(d->resolveMask & QQuickIconPrivate::CacheResolved)) - resolved.d->cache = other.d->cache; + resolved.d->ownerAndCache.setTag(other.d->ownerAndCache.tag()); + + // owner does not change when resolving an icon return resolved; } diff --git a/src/quicktemplates2/qquickicon_p.h b/src/quicktemplates2/qquickicon_p.h index 0c0fe101a3..aa05055e5f 100644 --- a/src/quicktemplates2/qquickicon_p.h +++ b/src/quicktemplates2/qquickicon_p.h @@ -90,6 +90,7 @@ public: QUrl source() const; void setSource(const QUrl &source); void resetSource(); + QUrl resolvedSource() const; int width() const; void setWidth(int width); @@ -107,6 +108,10 @@ public: void setCache(bool cache); void resetCache(); + // owner is not a property - it is set internally by classes using icon + // so that we can resolve relative URL's correctly + void setOwner(QObject *owner); + QQuickIcon resolve(const QQuickIcon &other) const; private: diff --git a/tests/auto/quickcontrols2/qquickiconlabel/data/a.png b/tests/auto/quickcontrols2/qquickiconlabel/data/a.png Binary files differnew file mode 100644 index 0000000000..fe839a40e4 --- /dev/null +++ b/tests/auto/quickcontrols2/qquickiconlabel/data/a.png diff --git a/tests/auto/quickcontrols2/qquickiconlabel/data/iconSourceContext.qml b/tests/auto/quickcontrols2/qquickiconlabel/data/iconSourceContext.qml new file mode 100644 index 0000000000..0d562d1500 --- /dev/null +++ b/tests/auto/quickcontrols2/qquickiconlabel/data/iconSourceContext.qml @@ -0,0 +1,7 @@ +import QtQuick.Controls +import QtQuick + +Item { + Image { source: "a.png" } + IconLabel { icon.source: "a.png" } +} diff --git a/tests/auto/quickcontrols2/qquickiconlabel/tst_qquickiconlabel.cpp b/tests/auto/quickcontrols2/qquickiconlabel/tst_qquickiconlabel.cpp index 6119a9e571..7385e1e3ef 100644 --- a/tests/auto/quickcontrols2/qquickiconlabel/tst_qquickiconlabel.cpp +++ b/tests/auto/quickcontrols2/qquickiconlabel/tst_qquickiconlabel.cpp @@ -34,11 +34,13 @@ #include <QtQuick/qquickview.h> #include <QtQuick/qquickitemgrabresult.h> #include <QtQuick/private/qquicktext_p.h> +#include <QtQuick/private/qquickimage_p_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquickicon_p.h> #include <QtQuickControls2Impl/private/qquickiconimage_p.h> #include <QtQuickControls2Impl/private/qquickiconlabel_p.h> +#include <QtQuickControls2Impl/private/qquickiconlabel_p_p.h> using namespace QQuickVisualTestUtils; @@ -55,6 +57,7 @@ private slots: void spacingWithOneDelegate(); void emptyIconSource(); void colorChanges(); + void iconSourceContext(); }; tst_qquickiconlabel::tst_qquickiconlabel() @@ -329,6 +332,34 @@ void tst_qquickiconlabel::colorChanges() QVERIFY(grabResult->image() != enabledImageGrab); } +void tst_qquickiconlabel::iconSourceContext() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("iconSourceContext.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + QScopedPointer<QObject> o(component.create()); + QQuickItem *root = qobject_cast<QQuickItem *>(o.data()); + QVERIFY(root); + + for (QQuickItem *child : root->childItems()) { + QQuickImage *image = qobject_cast<QQuickImage *>(child); + if (!image) { + if (QQuickIconLabel *label = qobject_cast<QQuickIconLabel *>(child)) { + QQuickIconLabelPrivate *labelPrivate = static_cast<QQuickIconLabelPrivate *>( + QQuickItemPrivate::get(label)); + image = labelPrivate->image; + } + } + + QVERIFY(image); + QQuickImagePrivate *imagePrivate + = static_cast<QQuickImagePrivate *>(QQuickItemPrivate::get(image)); + QCOMPARE(imagePrivate->pix.url(), dataDirectoryUrl().resolved(QStringLiteral("a.png"))); + } + +} + QTEST_MAIN(tst_qquickiconlabel) #include "tst_qquickiconlabel.moc" |