aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2022-11-09 10:15:13 +0100
committerShawn Rutledge <shawn.rutledge@qt.io>2023-12-04 12:16:06 -0700
commit7fb39a7accba014063e32ac41a58b77905bbd95b (patch)
treea7ed0e25e83af7f1dde97577ee684db2ef703e70 /src/quick/items
parent90abce175f9440de0978eb8ae36a0afe11e4c058 (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 <oliver.eftevaag@qt.io> Reviewed-by: Axel Spoerl <axel.spoerl@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/quick/items')
-rw-r--r--src/quick/items/qquicktext.cpp106
-rw-r--r--src/quick/items/qquicktext_p.h2
-rw-r--r--src/quick/items/qquicktext_p_p.h6
-rw-r--r--src/quick/items/qquicktextdocument.cpp103
-rw-r--r--src/quick/items/qquicktextdocument_p.h68
-rw-r--r--src/quick/items/qquicktextedit.cpp104
-rw-r--r--src/quick/items/qquicktextedit_p.h5
-rw-r--r--src/quick/items/qquicktextedit_p_p.h6
-rw-r--r--src/quick/items/qquicktextnodeengine.cpp12
9 files changed, 251 insertions, 161 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()) {