diff options
author | Shawn Rutledge <[email protected]> | 2022-11-09 10:15:13 +0100 |
---|---|---|
committer | Shawn Rutledge <[email protected]> | 2023-12-04 12:16:06 -0700 |
commit | 7fb39a7accba014063e32ac41a58b77905bbd95b (patch) | |
tree | a7ed0e25e83af7f1dde97577ee684db2ef703e70 /src | |
parent | 90abce175f9440de0978eb8ae36a0afe11e4c058 (diff) |
Get rid of QQuickTextDocumentWithImageResources
Users want to be able to provide their own QTextDocument instances
to TextEdit; but we had been creating a subclass called
QQuickTextDocumentWithImageResources, which was an obstacle for that.
QTextDocumentPrivate has two QMaps to hold resources, but
QQuickTextDocumentWithImageResources existed for the purpose of caching
remote resources, which only QQuickPixmap knows how to fetch. (This
design was apparently invented as a workaround to the lack of
virtual-filesystem functionality in Qt Core. If QtCore already knew how
to fetch web resources from URLs in the background, QTextDocument could
use it to do its own fetching of remote resources.) What we want instead
of subclassing QTextDocument is to keep doing the fetching in Qt Quick
(because there's no other choice for now), but get the images into the
same QTextDocumentPrivate::cachedResources map where local-file
resources are cached.
As it turns out, since qtbase ac300a166f801a6f6c0b15278e6893720a5726f8
QTextDocument::loadResource() can use QMetaMethod::invoke() to call a
method with the signature QVariant loadResource(int,QUrl) if such a
method is found in QTD's parent object; so since QQuickTextEdit creates
its own document by default, and is the document's parent, we can keep
remote resource fetching functionality working by 1) providing the
QQuickTextEdit::loadResource() method to be invoked, and 2) moving the
document to the QML thread so that it can be invoked directly.
(QMetaMethod::invoke() doesn't work across a queued connection, because
we need to return a value: the QVariant.) QTD will already cache the
images as soon as the call to loadResource() returns a valid QVariant.
We ask for the QQuickPixmap not to be cached by passing an empty
QQuickPixmap::Option enum to its ctor, which gets passed through
to the load() function.
When we consider fetching resources from a web server, it's unfortunate
that the signature of QTextDocument::resource() sets the expectation
that resources can be loaded immediately. But as long as
QQuickTextEdit::loadResource() is waiting for fetching to be done, it
keeps returning a default-constructed QVariant, which won't be cached;
and it will be called again later, repeatedly, until it eventually
succeeds. To ensure that it is called again when fetching is done, we
call QTextDocument::resource() again, to provoke it to call our
QQuickTextEdit::loadResource() one last time. If the returned image
wasn't cached before, it will be after that. Then it's ok to delete the
QQuickPixmap, and invalidate the TextEdit so that layout will be updated
to include the image, now that we have it.
But most of the time it's relatively boring: QTextDocument knows
how to load local files on its own, and caches them.
So we no longer need QQuickTextDocumentWithImageResources. But we
still needed to replace QTextImageHandler with a custom implementation,
as explained in bab2eaf3da299c471dd898c89cf356984b077412: we need
QQuickPixmap for its QML-specific URL resolution relative to the Text
item's context. And it turns out that in case the document contains
only a missing image, the minimum size is 16x16 pixels, to reserve
space to display a "broken image" icon as browsers sometimes do...
we have never actually done that in Qt Quick AFAICT, but autotests have
been enforcing the 16x16 minimum size all along. This is done in
QQuickTextImageHandler::intrinsicSize() now.
The same approach is taken with Text (when textFormat is RichText or
MarkdownText. In case of StyledText, there is no QTextDocument instance,
and resource loading is taken care of entirely within QQuickText.)
For the autotests, friendly use of QQuickPixmapCache::m_cache requires
QT_BEGIN_NAMESPACE.
Task-number: QTBUG-35688
Change-Id: I8ad8142b3b3790254dd56d6cfe5209d641465f08
Reviewed-by: Oliver Eftevaag <[email protected]>
Reviewed-by: Axel Spoerl <[email protected]>
Reviewed-by: Fabian Kosmale <[email protected]>
Diffstat (limited to 'src')
-rw-r--r-- | src/quick/items/qquicktext.cpp | 106 | ||||
-rw-r--r-- | src/quick/items/qquicktext_p.h | 2 | ||||
-rw-r--r-- | src/quick/items/qquicktext_p_p.h | 6 | ||||
-rw-r--r-- | src/quick/items/qquicktextdocument.cpp | 103 | ||||
-rw-r--r-- | src/quick/items/qquicktextdocument_p.h | 68 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit.cpp | 104 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit_p.h | 5 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit_p_p.h | 6 | ||||
-rw-r--r-- | src/quick/items/qquicktextnodeengine.cpp | 12 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgdefaultrendercontext.cpp | 4 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgdistancefieldglyphnode.cpp | 6 | ||||
-rw-r--r-- | src/quick/util/qquickpixmap_p.h | 13 | ||||
-rw-r--r-- | src/quick/util/qquickpixmapcache.cpp | 6 | ||||
-rw-r--r-- | src/quick/util/qquickpixmapcache_p.h | 2 |
14 files changed, 272 insertions, 171 deletions
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index e65dde2158..08af9e7008 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -13,7 +13,6 @@ #include "qsginternaltextnode_p.h" #include "qquickimage_p_p.h" #include "qquicktextutil_p.h" -#include "qquicktextdocument_p.h" #include <QtQuick/private/qsgtexture_p.h> @@ -37,6 +36,9 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace) +Q_LOGGING_CATEGORY(lcText, "qt.quick.text") + +using namespace Qt::StringLiterals; const QChar QQuickTextPrivate::elideChar = QChar(0x2026); @@ -273,6 +275,85 @@ void QQuickTextPrivate::updateLayout() q->polish(); } +/*! \internal + QTextDocument::loadResource() calls this to load inline images etc. + But if it's a local file, don't do it: let QTextDocument::loadResource() + load it in the default way. QQuickPixmap is for QtQuick-specific uses. +*/ +QVariant QQuickText::loadResource(int type, const QUrl &source) +{ + Q_D(QQuickText); + const QUrl url = d->extra->doc->baseUrl().resolved(source); + if (url.isLocalFile()) { + // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that) + const QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url)); + if (!fi.exists()) + qmlWarning(this) << "Cannot open: " << url.toString(); + // let QTextDocument::loadResource() handle local file loading + return {}; + } + // see if we already started a load job + for (auto it = d->extra->pixmapsInProgress.cbegin(); it != d->extra->pixmapsInProgress.cend();) { + auto *job = *it; + if (job->url() == url) { + if (job->isError()) { + qmlWarning(this) << job->error(); + delete *it; + it = d->extra->pixmapsInProgress.erase(it); + return QImage(); + } + qCDebug(lcText) << "already downloading" << url; + // existing job: return a null variant if it's not done yet + return job->isReady() ? job->image() : QVariant(); + } + ++it; + } + qCDebug(lcText) << "loading" << source << "resolved" << url + << "type" << static_cast<QTextDocument::ResourceType>(type); + QQmlContext *context = qmlContext(this); + Q_ASSERT(context); + // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources + QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{}); + p->connectFinished(this, SLOT(resourceRequestFinished())); + d->extra->pixmapsInProgress.append(p); + // the new job is probably not done; return a null variant if the caller should poll again + return p->isReady() ? p->image() : QVariant(); +} + +/*! \internal + Handle completion of a download that QQuickText::loadResource() started. +*/ +void QQuickText::resourceRequestFinished() +{ + Q_D(QQuickText); + bool allDone = true; + for (auto it = d->extra->pixmapsInProgress.cbegin(); it != d->extra->pixmapsInProgress.cend();) { + auto *job = *it; + if (job->isError()) { + // get QTextDocument::loadResource() to call QQuickText::loadResource() again, to return the placeholder + qCDebug(lcText) << "failed to load" << job->url(); + d->extra->doc->resource(QTextDocument::ImageResource, job->url()); + } else if (job->isReady()) { + // get QTextDocument::loadResource() to call QQuickText::loadResource() again, and cache the result + auto res = d->extra->doc->resource(QTextDocument::ImageResource, job->url()); + // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done. + qCDebug(lcText) << (res.isValid() ? "done downloading" : "failed to load") << job->url(); + delete *it; + it = d->extra->pixmapsInProgress.erase(it); + } else { + allDone = false; + ++it; + } + } + if (allDone) { + Q_ASSERT(d->extra->pixmapsInProgress.isEmpty()); + d->updateLayout(); + } +} + +/*! \internal + Handle completion of StyledText image downloads (there's no QTextDocument instance in that case). +*/ void QQuickText::imageDownloadFinished() { Q_D(QQuickText); @@ -1266,13 +1347,14 @@ void QQuickTextPrivate::ensureDoc() { if (!extra.isAllocated() || !extra->doc) { Q_Q(QQuickText); - extra.value().doc = new QQuickTextDocumentWithImageResources(q); - extra->doc->setPageSize(QSizeF(0, 0)); - extra->doc->setDocumentMargin(0); + extra.value().doc = new QTextDocument(q); + auto *doc = extra->doc; + extra->imageHandler = new QQuickTextImageHandler(doc); + doc->documentLayout()->registerHandler(QTextFormat::ImageObject, extra->imageHandler); + doc->setPageSize(QSizeF(0, 0)); + doc->setDocumentMargin(0); const QQmlContext *context = qmlContext(q); - extra->doc->setBaseUrl(context ? context->resolvedUrl(q->baseUrl()) : q->baseUrl()); - qmlobject_connect(extra->doc, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), - q, QQuickText, SLOT(q_updateLayout())); + doc->setBaseUrl(context ? context->resolvedUrl(q->baseUrl()) : q->baseUrl()); } } @@ -1289,7 +1371,6 @@ void QQuickTextPrivate::updateDocumentText() #else extra->doc->setPlainText(text); #endif - extra->doc->clearResources(); rightToLeftText = extra->doc->toPlainText().isRightToLeft(); } @@ -1353,6 +1434,11 @@ QQuickText::QQuickText(QQuickTextPrivate &dd, QQuickItem *parent) QQuickText::~QQuickText() { + Q_D(QQuickText); + if (d->extra.isAllocated()) { + qDeleteAll(d->extra->pixmapsInProgress); + d->extra->pixmapsInProgress.clear(); + } } /*! @@ -2829,8 +2915,8 @@ void QQuickText::setMinimumPointSize(int size) int QQuickText::resourcesLoading() const { Q_D(const QQuickText); - if (d->richText && d->extra.isAllocated() && d->extra->doc) - return d->extra->doc->resourcesLoading(); + if (d->richText && d->extra.isAllocated()) + return d->extra->pixmapsInProgress.size(); return 0; } diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index c4c48286b2..2a15c11136 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -301,6 +301,8 @@ protected: private Q_SLOTS: void q_updateLayout(); void triggerPreprocess(); + QVariant loadResource(int type, const QUrl &source); + void resourceRequestFinished(); void imageDownloadFinished(); private: diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index 9870197c31..7e8e562ead 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -23,11 +23,11 @@ #include <QtGui/qtextlayout.h> #include <private/qquickstyledtext_p.h> #include <private/qlazilyallocated_p.h> +#include <private/qquicktextdocument_p.h> QT_BEGIN_NAMESPACE class QTextLayout; -class QQuickTextDocumentWithImageResources; class Q_QUICK_PRIVATE_EXPORT QQuickTextPrivate : public QQuickImplicitSizeItemPrivate { @@ -71,7 +71,8 @@ public: bool explicitRightPadding : 1; bool explicitBottomPadding : 1; qreal lineHeight; - QQuickTextDocumentWithImageResources *doc; + QTextDocument *doc; + QQuickTextImageHandler *imageHandler = nullptr; QString activeLink; QString hoveredLink; int minimumPixelSize; @@ -84,6 +85,7 @@ public: QQuickText::FontSizeMode fontSizeMode; QList<QQuickStyledTextImgTag*> imgTags; QList<QQuickStyledTextImgTag*> visibleImgTags; + QList<QQuickPixmap *> pixmapsInProgress; QUrl baseUrl; }; QLazilyAllocated<ExtraData> extra; diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp index ac876c2e74..37a054f33a 100644 --- a/src/quick/items/qquicktextdocument.cpp +++ b/src/quick/items/qquicktextdocument.cpp @@ -34,12 +34,6 @@ QT_BEGIN_NAMESPACE in question may stop functioning or crash. */ -class QQuickTextDocumentPrivate : public QObjectPrivate -{ -public: - QPointer<QTextDocument> document; -}; - /*! Constructs a QQuickTextDocument object with \a parent as the parent object. @@ -62,61 +56,27 @@ QTextDocument* QQuickTextDocument::textDocument() const return d->document.data(); } -QQuickTextDocumentWithImageResources::QQuickTextDocumentWithImageResources(QQuickItem *parent) -: QTextDocument(parent), outstanding(0) -{ - setUndoRedoEnabled(false); - documentLayout()->registerHandler(QTextFormat::ImageObject, this); - connect(this, &QTextDocument::baseUrlChanged, [this]() { - clearResources(); - markContentsDirty(0, characterCount()); - }); -} - -QQuickTextDocumentWithImageResources::~QQuickTextDocumentWithImageResources() +QQuickTextImageHandler::QQuickTextImageHandler(QObject *parent) + : QObject(parent) { - if (!m_resources.isEmpty()) - qDeleteAll(m_resources); -} - -QVariant QQuickTextDocumentWithImageResources::loadResource(int type, const QUrl &name) -{ - QVariant resource = QTextDocument::loadResource(type, name); - if (resource.isNull() && type == QTextDocument::ImageResource) { - QQmlContext *context = qmlContext(parent()); - QUrl url = baseUrl().resolved(name); - QQuickPixmap *p = loadPixmap(context, url); - resource = p->image(); - } - - return resource; } -void QQuickTextDocumentWithImageResources::requestFinished() -{ - outstanding--; - if (outstanding == 0) { - markContentsDirty(0, characterCount()); - emit imagesLoaded(); - } -} - -QSizeF QQuickTextDocumentWithImageResources::intrinsicSize( - QTextDocument *, int, const QTextFormat &format) +QSizeF QQuickTextImageHandler::intrinsicSize( + QTextDocument *doc, int, const QTextFormat &format) { if (format.isImageFormat()) { QTextImageFormat imageFormat = format.toImageFormat(); - const 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; - QSizeF size(width, height); if (!hasWidth || !hasHeight) { - QVariant res = resource(QTextDocument::ImageResource, QUrl(imageFormat.name())); + QVariant res = doc->resource(QTextDocument::ImageResource, QUrl(imageFormat.name())); QImage image = res.value<QImage>(); if (image.isNull()) { + // autotests expect us to reserve a 16x16 space for a "broken image" icon, + // even though we don't actually display one if (!hasWidth) size.setWidth(16); if (!hasHeight) @@ -124,7 +84,6 @@ QSizeF QQuickTextDocumentWithImageResources::intrinsicSize( return size; } QSize imgSize = image.size(); - if (!hasWidth) { if (!hasHeight) size.setWidth(imgSize.width()); @@ -143,54 +102,6 @@ QSizeF QQuickTextDocumentWithImageResources::intrinsicSize( return QSizeF(); } -void QQuickTextDocumentWithImageResources::drawObject( - QPainter *, const QRectF &, QTextDocument *, int, const QTextFormat &) -{ -} - -QImage QQuickTextDocumentWithImageResources::image(const QTextImageFormat &format) const -{ - QVariant res = resource(QTextDocument::ImageResource, QUrl(format.name())); - return res.value<QImage>(); -} - -QQuickPixmap *QQuickTextDocumentWithImageResources::loadPixmap( - QQmlContext *context, const QUrl &url) -{ - - QHash<QUrl, QQuickPixmap *>::Iterator iter = m_resources.find(url); - - if (iter == m_resources.end()) { - QQuickPixmap *p = new QQuickPixmap(context->engine(), url); - iter = m_resources.insert(url, p); - - if (p->isLoading()) { - p->connectFinished(this, SLOT(requestFinished())); - outstanding++; - } - } - - QQuickPixmap *p = *iter; - if (p->isError()) { - if (!errors.contains(url)) { - errors.insert(url); - qmlWarning(parent()) << p->error(); - } - } - return p; -} - -void QQuickTextDocumentWithImageResources::clearResources() -{ - for (QQuickPixmap *pixmap : std::as_const(m_resources)) - pixmap->clear(this); - qDeleteAll(m_resources); - m_resources.clear(); - outstanding = 0; -} - -QSet<QUrl> QQuickTextDocumentWithImageResources::errors; - QT_END_NAMESPACE #include "moc_qquicktextdocument.cpp" diff --git a/src/quick/items/qquicktextdocument_p.h b/src/quick/items/qquicktextdocument_p.h index 257103b626..5f4b1b7623 100644 --- a/src/quick/items/qquicktextdocument_p.h +++ b/src/quick/items/qquicktextdocument_p.h @@ -15,54 +15,52 @@ // We mean it. // -#include <QtCore/qhash.h> -#include <QtCore/qvariant.h> -#include <QtGui/qimage.h> +#include "qquicktextdocument.h" + #include <QtGui/qtextdocument.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qtextformat.h> +#include <QtCore/qrect.h> #include <QtGui/qabstracttextdocumentlayout.h> -#include <QtGui/qtextlayout.h> -#include <QtCore/private/qglobal_p.h> +#include <QtGui/qtextdocumentfragment.h> +#include <QtGui/qclipboard.h> +#include <QtGui/private/qinputcontrol_p.h> +#include <QtCore/qmimedatabase.h> +#include <QtCore/private/qobject_p_p.h> QT_BEGIN_NAMESPACE -class QQuickItem; class QQuickPixmap; -class QQmlContext; -class Q_AUTOTEST_EXPORT QQuickTextDocumentWithImageResources : public QTextDocument, public QTextObjectInterface +/*! \internal + QTextImageHandler would attempt to resolve relative paths, and load the + image itself if the document returns an invalid image from loadResource(). + We replace it with this version instead, because Qt Quick's text resources + are resolved against the Text item's context, and because we override + intrinsicSize(). drawObject() is empty because we don't need to use this + handler to paint images: they get put into scene graph nodes instead. +*/ +class QQuickTextImageHandler : public QObject, public QTextObjectInterface { Q_OBJECT Q_INTERFACES(QTextObjectInterface) public: - QQuickTextDocumentWithImageResources(QQuickItem *parent); - virtual ~QQuickTextDocumentWithImageResources(); - - int resourcesLoading() const { return outstanding; } - + QQuickTextImageHandler(QObject *parent = nullptr); + ~QQuickTextImageHandler() override = default; QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) override; - void drawObject(QPainter *p, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) override; - - QImage image(const QTextImageFormat &format) const; - -public Q_SLOTS: - void clearResources(); - -Q_SIGNALS: - void imagesLoaded(); - -protected: - QVariant loadResource(int type, const QUrl &name) override; - - QQuickPixmap *loadPixmap(QQmlContext *context, const QUrl &name); - -private Q_SLOTS: - void requestFinished(); + void drawObject(QPainter *, const QRectF &, QTextDocument *, int, const QTextFormat &) override { } +}; -private: - QHash<QUrl, QQuickPixmap *> m_resources; +class QQuickTextDocumentPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickTextDocument) +public: + static QQuickTextDocumentPrivate *get(QQuickTextDocument *doc) { + return doc->d_func(); + } - int outstanding; - static QSet<QUrl> errors; + QPointer<QTextDocument> document; }; namespace QtPrivate { @@ -78,4 +76,4 @@ public: QT_END_NAMESPACE -#endif // QQUICKDOCUMENT_P_H +#endif // QQUICKTEXTDOCUMENT_P_H diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index e139b825f0..4e143530ba 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -22,6 +22,7 @@ #include <private/qqmlproperty_p.h> #include <private/qtextengine_p.h> #include <private/qsgadaptationlayer_p.h> +#include <QtQuick/private/qquickpixmapcache_p.h> #if QT_CONFIG(accessibility) #include <private/qquickaccessibleattached_p.h> @@ -36,6 +37,8 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcVP) Q_LOGGING_CATEGORY(lcTextEdit, "qt.quick.textedit") +using namespace Qt::StringLiterals; + /*! \qmltype TextEdit \instantiates QQuickTextEdit @@ -145,6 +148,12 @@ QQuickTextEdit::QQuickTextEdit(QQuickItem *parent) d->init(); } +QQuickTextEdit::~QQuickTextEdit() +{ + Q_D(QQuickTextEdit); + qDeleteAll(d->pixmapsInProgress); +} + QQuickTextEdit::QQuickTextEdit(QQuickTextEditPrivate &dd, QQuickItem *parent) : QQuickImplicitSizeItem(dd, parent) { @@ -386,7 +395,6 @@ void QQuickTextEdit::setText(const QString &text) if (QQuickTextEdit::text() == text) return; - d->document->clearResources(); d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text)); d->markdownText = d->format == MarkdownText; if (!isComponentComplete()) { @@ -1575,6 +1583,12 @@ void QQuickTextEdit::componentComplete() #endif } +int QQuickTextEdit::resourcesLoading() const +{ + Q_D(const QQuickTextEdit); + return d->pixmapsInProgress.size(); +} + /*! \qmlproperty bool QtQuick::TextEdit::selectByKeyboard \since 5.1 @@ -2098,6 +2112,83 @@ void QQuickTextEdit::triggerPreprocess() update(); } +/*! \internal + QTextDocument::loadResource() calls this to load inline images etc. + But if it's a local file, don't do it: let QTextDocument::loadResource() + load it in the default way. QQuickPixmap is for QtQuick-specific uses. +*/ +QVariant QQuickTextEdit::loadResource(int type, const QUrl &source) +{ + Q_D(QQuickTextEdit); + const QUrl url = d->document->baseUrl().resolved(source); + if (url.isLocalFile()) { + // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that) + QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url)); + if (!fi.exists()) + qmlWarning(this) << "Cannot open: " << url.toString(); + // let QTextDocument::loadResource() handle local file loading + return {}; + } + // see if we already started a load job + for (auto it = d->pixmapsInProgress.cbegin(); it != d->pixmapsInProgress.cend(); ++it) { + const auto *job = *it; + if (job->url() == url) { + if (job->isError()) { + qmlWarning(this) << job->error(); + delete *it; + it = d->pixmapsInProgress.erase(it); + return QImage(); + } + qCDebug(lcTextEdit) << "already downloading" << url; + // existing job: return a null variant if it's not done yet + return job->isReady() ? job->image() : QVariant(); + } + } + qCDebug(lcTextEdit) << "loading" << source << "resolved" << url + << "type" << static_cast<QTextDocument::ResourceType>(type); + QQmlContext *context = qmlContext(this); + Q_ASSERT(context); + // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources + QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{}); + p->connectFinished(this, SLOT(resourceRequestFinished())); + d->pixmapsInProgress.append(p); + // the new job is probably not done; return a null variant if the caller should poll again + return p->isReady() ? p->image() : QVariant(); +} + +/*! \internal + Handle completion of a download that QQuickTextEdit::loadResource() started. +*/ +void QQuickTextEdit::resourceRequestFinished() +{ + Q_D(QQuickTextEdit); + bool allDone = true; + for (auto it = d->pixmapsInProgress.cbegin(); it != d->pixmapsInProgress.cend();) { + auto *job = *it; + if (job->isError()) { + // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, to return the placeholder + qCDebug(lcTextEdit) << "failed to load" << job->url(); + d->document->resource(QTextDocument::ImageResource, job->url()); + } else if (job->isReady()) { + // get QTextDocument::loadResource() to call QQuickTextEdit::loadResource() again, and cache the result + auto res = d->document->resource(QTextDocument::ImageResource, job->url()); + // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done. + qCDebug(lcTextEdit) << (res.isValid() ? "done downloading" : "failed to load") << job->url() << job->rect(); + delete *it; + it = d->pixmapsInProgress.erase(it); + } else { + allDone = false; + ++it; + } + } + if (allDone) { + Q_ASSERT(d->pixmapsInProgress.isEmpty()); + invalidate(); + updateSize(); + q_invalidate(); + } +} + typedef QQuickTextEditPrivate::Node TextNode; using TextNodeIterator = QQuickTextEditPrivate::TextNodeIterator; @@ -2494,7 +2585,9 @@ void QQuickTextEditPrivate::init() q->setAcceptHoverEvents(true); - document = new QQuickTextDocumentWithImageResources(q); + document = new QTextDocument(q); + auto *imageHandler = new QQuickTextImageHandler(document); + document->documentLayout()->registerHandler(QTextFormat::ImageObject, imageHandler); control = new QQuickTextControl(document, q); control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable); @@ -2515,10 +2608,9 @@ void QQuickTextEditPrivate::init() #if QT_CONFIG(clipboard) qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged())); #endif - qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged())); - qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged())); - qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize())); - QObject::connect(document, &QQuickTextDocumentWithImageResources::contentsChange, q, &QQuickTextEdit::q_contentsChange); + qmlobject_connect(document, QTextDocument, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged())); + qmlobject_connect(document, QTextDocument, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged())); + QObject::connect(document, &QTextDocument::contentsChange, q, &QQuickTextEdit::q_contentsChange); QObject::connect(document->documentLayout(), &QAbstractTextDocumentLayout::updateBlock, q, &QQuickTextEdit::invalidateBlock); QObject::connect(control, &QQuickTextControl::linkHovered, q, &QQuickTextEdit::q_linkHovered); QObject::connect(control, &QQuickTextControl::markerHovered, q, &QQuickTextEdit::q_markerHovered); diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index 547c69572e..b62ee3c419 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -85,6 +85,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, pub public: QQuickTextEdit(QQuickItem *parent=nullptr); + ~QQuickTextEdit() override; enum HAlignment { AlignLeft = Qt::AlignLeft, @@ -209,6 +210,8 @@ public: void componentComplete() override; + int resourcesLoading() const; // mainly for testing + /* FROM EDIT */ void setReadOnly(bool); bool isReadOnly() const; @@ -354,6 +357,8 @@ private Q_SLOTS: void q_updateAlignment(); void updateSize(); void triggerPreprocess(); + QVariant loadResource(int type, const QUrl &source); + void resourceRequestFinished(); private: void markDirtyNodesForRange(int start, int end, int charDelta); diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 7891a744ec..934ef0e460 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -22,6 +22,7 @@ #include <QtQml/qqml.h> #include <QtCore/qlist.h> #include <private/qlazilyallocated_p.h> +#include <private/qquicktextdocument_p.h> #if QT_CONFIG(accessibility) #include <QtGui/qaccessible.h> @@ -31,7 +32,7 @@ QT_BEGIN_NAMESPACE class QTextLayout; -class QQuickTextDocumentWithImageResources; +class QQuickPixmap; class QQuickTextControl; class QSGInternalTextNode; class QQuickTextNodeEngine; @@ -163,10 +164,11 @@ public: QQmlComponent* cursorComponent = nullptr; QQuickItem* cursorItem = nullptr; - QQuickTextDocumentWithImageResources *document = nullptr; + QTextDocument *document = nullptr; QQuickTextControl *control = nullptr; QQuickTextDocument *quickDocument = nullptr; QList<Node> textNodeMap; + QList<QQuickPixmap *> pixmapsInProgress; int lastSelectionStart = 0; int lastSelectionEnd = 0; diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp index be6e01df1f..9f43bec2a0 100644 --- a/src/quick/items/qquicktextnodeengine.cpp +++ b/src/quick/items/qquicktextnodeengine.cpp @@ -13,7 +13,6 @@ #include <QtGui/qtextlist.h> #include <private/qquicktext_p.h> -#include <private/qquicktextdocument_p.h> #include <private/qtextdocumentlayout_p.h> #include <private/qtextimagehandler_p.h> #include <private/qrawfont_p.h> @@ -429,15 +428,8 @@ void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF if (format.objectType() == QTextFormat::ImageObject) { QTextImageFormat imageFormat = format.toImageFormat(); - if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) { - image = imageDoc->image(imageFormat); - - if (image.isNull()) - return; - } else { - QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler); - image = imageHandler->image(textDocument, imageFormat); - } + QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler); + image = imageHandler->image(textDocument, imageFormat); } if (image.isNull()) { diff --git a/src/quick/scenegraph/qsgdefaultrendercontext.cpp b/src/quick/scenegraph/qsgdefaultrendercontext.cpp index dc3262f2da..a81366b401 100644 --- a/src/quick/scenegraph/qsgdefaultrendercontext.cpp +++ b/src/quick/scenegraph/qsgdefaultrendercontext.cpp @@ -230,7 +230,7 @@ QSGTexture *QSGDefaultRenderContext::compressedTextureForFactory(const QSGCompre QString QSGDefaultRenderContext::fontKey(const QRawFont &font, int renderTypeQuality) { QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine; - if (!fe->faceId().filename.isEmpty()) { + if (fe && !fe->faceId().filename.isEmpty()) { QByteArray keyName = fe->faceId().filename + ' ' + QByteArray::number(fe->faceId().index) + (font.style() != QFont::StyleNormal ? QByteArray(" I") : QByteArray()) @@ -277,7 +277,7 @@ QSGDistanceFieldGlyphCache *QSGDefaultRenderContext::distanceFieldGlyphCache(con { QString key = fontKey(font, renderTypeQuality); QSGDistanceFieldGlyphCache *cache = m_glyphCaches.value(key, 0); - if (!cache) { + if (!cache && font.isValid()) { cache = new QSGRhiDistanceFieldGlyphCache(this, font, renderTypeQuality); m_glyphCaches.insert(key, cache); } diff --git a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp index e9f5fbb8ca..d5adb52a9f 100644 --- a/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp +++ b/src/quick/scenegraph/qsgdistancefieldglyphnode.cpp @@ -94,7 +94,8 @@ void QSGDistanceFieldGlyphNode::setGlyphs(const QPointF &position, const QGlyphR } m_glyph_cache->registerGlyphNode(this); } - m_glyph_cache->populate(glyphs.glyphIndexes()); + if (m_glyph_cache) + m_glyph_cache->populate(glyphs.glyphIndexes()); const QVector<quint32> glyphIndexes = m_glyphs.glyphIndexes(); for (int i = 0; i < glyphIndexes.size(); ++i) @@ -154,7 +155,8 @@ void QSGDistanceFieldGlyphNode::invalidateGlyphs(const QVector<quint32> &glyphs) void QSGDistanceFieldGlyphNode::updateGeometry() { - Q_ASSERT(m_glyph_cache); + if (!m_glyph_cache) + return; // Remove previously created sub glyph nodes // We assume all the children are sub glyph nodes diff --git a/src/quick/util/qquickpixmap_p.h b/src/quick/util/qquickpixmap_p.h index 316374beb7..ce210321cb 100644 --- a/src/quick/util/qquickpixmap_p.h +++ b/src/quick/util/qquickpixmap_p.h @@ -101,12 +101,6 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPixmap { Q_DECLARE_TR_FUNCTIONS(QQuickPixmap) public: - QQuickPixmap(); - QQuickPixmap(QQmlEngine *, const QUrl &); - QQuickPixmap(QQmlEngine *, const QUrl &, const QRect ®ion, const QSize &); - QQuickPixmap(const QUrl &, const QImage &image); - ~QQuickPixmap(); - enum Status { Null, Ready, Error, Loading }; enum Option { @@ -115,6 +109,13 @@ public: }; Q_DECLARE_FLAGS(Options, Option) + QQuickPixmap(); + QQuickPixmap(QQmlEngine *, const QUrl &); + QQuickPixmap(QQmlEngine *, const QUrl &, Options options); + QQuickPixmap(QQmlEngine *, const QUrl &, const QRect ®ion, const QSize &); + QQuickPixmap(const QUrl &, const QImage &image); + ~QQuickPixmap(); + bool isNull() const; bool isReady() const; bool isError() const; diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index 8ce469d07e..8de79f2009 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -1647,6 +1647,12 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url) load(engine, url); } +QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, Options options) +: d(nullptr) +{ + load(engine, url, options); +} + QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect ®ion, const QSize &size) : d(nullptr) { diff --git a/src/quick/util/qquickpixmapcache_p.h b/src/quick/util/qquickpixmapcache_p.h index bd9ce5087b..8c0c085b50 100644 --- a/src/quick/util/qquickpixmapcache_p.h +++ b/src/quick/util/qquickpixmapcache_p.h @@ -79,6 +79,8 @@ private: friend class QQuickPixmap; friend class QQuickPixmapData; friend class tst_qquickpixmapcache; + friend class tst_qquicktext; + friend class tst_qquicktextedit; }; QT_END_NAMESPACE |