aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
-rw-r--r--src/quick/scenegraph/qsgdefaultrendercontext.cpp4
-rw-r--r--src/quick/scenegraph/qsgdistancefieldglyphnode.cpp6
-rw-r--r--src/quick/util/qquickpixmap_p.h13
-rw-r--r--src/quick/util/qquickpixmapcache.cpp6
-rw-r--r--src/quick/util/qquickpixmapcache_p.h2
-rw-r--r--tests/auto/quick/qquicktext/tst_qquicktext.cpp37
-rw-r--r--tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp16
-rw-r--r--tests/auto/quick/qquicktextedit/data/embeddedImagesRemote.qml2
-rw-r--r--tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp53
18 files changed, 322 insertions, 229 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 &region, 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 &region, 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 &region, 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
diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp
index c31b2c3861..218d1d47ac 100644
--- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp
+++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp
@@ -9,10 +9,10 @@
#include <QtQuick/private/qquicktext_p.h>
#include <QtQuick/private/qquickflickable_p.h>
#include <QtQuick/private/qquickmousearea_p.h>
+#include <QtQuick/private/qquickpixmapcache_p.h>
#include <QtQuickTest/QtQuickTest>
#include <private/qquicktext_p_p.h>
#include <private/qsginternaltextnode_p.h>
-#include <private/qquicktextdocument_p.h>
#include <private/qquickvaluetypes_p.h>
#include <QFontMetrics>
#include <qmath.h>
@@ -29,12 +29,17 @@ DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
Q_DECLARE_METATYPE(QQuickText::TextFormat)
-QT_BEGIN_NAMESPACE
-extern void qt_setQtEnableTestFont(bool value);
-QT_END_NAMESPACE
+typedef QVector<QPointF> PointVector;
+Q_DECLARE_METATYPE(PointVector);
+
+typedef qreal (*ExpectedBaseline)(QQuickText *item);
+Q_DECLARE_METATYPE(ExpectedBaseline)
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
+QT_BEGIN_NAMESPACE
+extern void qt_setQtEnableTestFont(bool value);
+
class tst_qquicktext : public QQmlDataTest
{
Q_OBJECT
@@ -1803,10 +1808,6 @@ public:
QTextLayout layout;
};
-
-typedef QVector<QPointF> PointVector;
-Q_DECLARE_METATYPE(PointVector);
-
void tst_qquicktext::linkInteraction_data()
{
QTest::addColumn<QString>("text");
@@ -2181,13 +2182,10 @@ void tst_qquicktext::embeddedImages()
if (!error.isEmpty())
QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
- QScopedPointer<QQuickView> view(new QQuickView);
- view->rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl());
- view->setSource(qmlfile);
- view->show();
- view->requestActivate();
- QVERIFY(QTest::qWaitForWindowActive(view.get()));
- QQuickText *textObject = qobject_cast<QQuickText*>(view->rootObject());
+ QQuickView view;
+ view.rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl());
+ QVERIFY(QQuickTest::showView(view, qmlfile));
+ QQuickText *textObject = qobject_cast<QQuickText*>(view.rootObject());
QVERIFY(textObject != nullptr);
QTRY_COMPARE(textObject->resourcesLoading(), 0);
@@ -2201,6 +2199,10 @@ void tst_qquicktext::embeddedImages()
QCOMPARE(textObject->width(), 16.0); // default size of QTextDocument broken image icon
QCOMPARE(textObject->height(), 16.0);
}
+
+ // QTextDocument images are cached in QTextDocumentPrivate::cachedResources,
+ // so verify that we don't redundantly cache them in QQuickPixmapCache
+ QCOMPARE(QQuickPixmapCache::instance()->m_cache.size(), 0);
}
void tst_qquicktext::lineCount()
@@ -4048,9 +4050,6 @@ void tst_qquicktext::fontFormatSizes()
}
}
-typedef qreal (*ExpectedBaseline)(QQuickText *item);
-Q_DECLARE_METATYPE(ExpectedBaseline)
-
static qreal expectedBaselineTop(QQuickText *item)
{
QFontMetricsF fm(item->font());
@@ -4823,6 +4822,8 @@ void tst_qquicktext::displaySuperscriptedTag()
QCOMPARE(color.green(), 255);
}
+QT_END_NAMESPACE
+
QTEST_MAIN(tst_qquicktext)
#include "tst_qquicktext.moc"
diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp
index 63d4f24a54..e6fb32acf6 100644
--- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp
+++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp
@@ -6,7 +6,6 @@
#include <QtQuick/QQuickTextDocument>
#include <QtQuick/QQuickItem>
#include <QtQuick/private/qquicktextedit_p.h>
-#include <QtQuick/private/qquicktextdocument_p.h>
#include <QtGui/QTextDocument>
#include <QtGui/QTextDocumentWriter>
#include <QtQml/QQmlEngine>
@@ -21,7 +20,6 @@ public:
private slots:
void textDocumentWriter();
- void textDocumentWithImage();
};
QString text = QStringLiteral("foo bar");
@@ -54,20 +52,6 @@ void tst_qquicktextdocument::textDocumentWriter()
delete o;
}
-void tst_qquicktextdocument::textDocumentWithImage()
-{
- QQuickTextDocumentWithImageResources document(nullptr);
- QImage image(1, 1, QImage::Format_Mono);
- image.fill(1);
-
- QString name = "image";
- document.addResource(QTextDocument::ImageResource, name, image);
- QTextImageFormat format;
- format.setName(name);
- QCOMPARE(image, document.image(format));
- QCOMPARE(image, document.resource(QTextDocument::ImageResource, name).value<QImage>());
-}
-
QTEST_MAIN(tst_qquicktextdocument)
#include "tst_qquicktextdocument.moc"
diff --git a/tests/auto/quick/qquicktextedit/data/embeddedImagesRemote.qml b/tests/auto/quick/qquicktextedit/data/embeddedImagesRemote.qml
index 6fc12edf35..a80e669140 100644
--- a/tests/auto/quick/qquicktextedit/data/embeddedImagesRemote.qml
+++ b/tests/auto/quick/qquicktextedit/data/embeddedImagesRemote.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.0
+import QtQuick
TextEdit {
property string serverBaseUrl;
diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
index bbb17e23d5..5ad49ffd1b 100644
--- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
+++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
@@ -18,7 +18,7 @@
#include <private/qquicktextedit_p.h>
#include <private/qquicktextedit_p_p.h>
#include <private/qquicktext_p.h>
-#include <private/qquicktextdocument_p.h>
+#include <QtQuick/private/qquickpixmapcache_p.h>
#include <QFontMetrics>
#include <QtQuick/QQuickView>
#include <QDir>
@@ -40,6 +40,21 @@ Q_DECLARE_METATYPE(QQuickTextEdit::SelectionMode)
Q_DECLARE_METATYPE(Qt::Key)
DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
+typedef QList<int> IntList;
+Q_DECLARE_METATYPE(IntList)
+
+typedef QPair<int, QChar> Key;
+typedef QList<Key> KeyList;
+Q_DECLARE_METATYPE(KeyList)
+
+Q_DECLARE_METATYPE(QQuickTextEdit::HAlignment)
+Q_DECLARE_METATYPE(QQuickTextEdit::VAlignment)
+Q_DECLARE_METATYPE(QQuickTextEdit::TextFormat)
+
+#if QT_CONFIG(shortcut)
+Q_DECLARE_METATYPE(QKeySequence::StandardKey)
+#endif
+
Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
static bool isPlatformWayland()
@@ -47,7 +62,7 @@ static bool isPlatformWayland()
return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive);
}
-typedef QPair<int, QChar> Key;
+QT_BEGIN_NAMESPACE
class tst_qquicktextedit : public QQmlDataTest
@@ -237,16 +252,6 @@ private:
QPointingDevice *touchDevice = QTest::createTouchDevice();
};
-typedef QList<int> IntList;
-Q_DECLARE_METATYPE(IntList)
-
-typedef QList<Key> KeyList;
-Q_DECLARE_METATYPE(KeyList)
-
-Q_DECLARE_METATYPE(QQuickTextEdit::HAlignment)
-Q_DECLARE_METATYPE(QQuickTextEdit::VAlignment)
-Q_DECLARE_METATYPE(QQuickTextEdit::TextFormat)
-
void tst_qquicktextedit::simulateKeys(QWindow *window, const QList<Key> &keys)
{
for (int i = 0; i < keys.size(); ++i) {
@@ -2375,8 +2380,6 @@ void tst_qquicktextedit::selectByKeyboard()
#if QT_CONFIG(shortcut)
-Q_DECLARE_METATYPE(QKeySequence::StandardKey)
-
void tst_qquicktextedit::keyboardSelection_data()
{
QTest::addColumn<QString>("text");
@@ -6053,30 +6056,32 @@ void tst_qquicktextedit::embeddedImages()
QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
QQmlComponent textComponent(&engine, qmlfile);
- QQuickTextEdit *textObject = qobject_cast<QQuickTextEdit*>(textComponent.beginCreate(engine.rootContext()));
- QVERIFY(textObject != nullptr);
+ QScopedPointer<QQuickTextEdit> textObject(qobject_cast<QQuickTextEdit*>(textComponent.beginCreate(engine.rootContext())));
+ QVERIFY(!textObject.isNull());
const int baseUrlPropertyIndex = textObject->metaObject()->indexOfProperty("serverBaseUrl");
if (baseUrlPropertyIndex != -1) {
QMetaProperty prop = textObject->metaObject()->property(baseUrlPropertyIndex);
- QVERIFY(prop.write(textObject, server.baseUrl().toString()));
+ QVERIFY(prop.write(textObject.get(), server.baseUrl().toString()));
}
textComponent.completeCreate();
- QTRY_COMPARE(QQuickTextEditPrivate::get(textObject)->document->resourcesLoading(), 0);
+ QTRY_COMPARE(textObject->resourcesLoading(), 0);
QPixmap pm(testFile("http/exists.png"));
if (error.isEmpty()) {
- QCOMPARE(textObject->width(), double(pm.width()));
- QCOMPARE(textObject->height(), double(pm.height()));
+ QCOMPARE(textObject->width(), pm.width());
+ QCOMPARE(textObject->height(), pm.height());
} else {
QVERIFY(16 != pm.width()); // check test is effective
- QCOMPARE(textObject->width(), 16.0); // default size of QTextDocument broken image icon
- QCOMPARE(textObject->height(), 16.0);
+ QCOMPARE(textObject->width(), 16); // default size of QTextDocument broken image icon
+ QCOMPARE(textObject->height(), 16);
}
- delete textObject;
+ // QTextDocument images are cached in QTextDocumentPrivate::cachedResources,
+ // so verify that we don't redundantly cache them in QQuickPixmapCache
+ QCOMPARE(QQuickPixmapCache::instance()->m_cache.size(), 0);
}
void tst_qquicktextedit::emptytags_QTBUG_22058()
@@ -6591,6 +6596,8 @@ void tst_qquicktextedit::rtlAlignmentInColumnLayout_QTBUG_112858()
}
}
+QT_END_NAMESPACE
+
QTEST_MAIN(tst_qquicktextedit)
#include "tst_qquicktextedit.moc"