diff options
-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 | ||||
-rw-r--r-- | tests/auto/quick/qquicktext/tst_qquicktext.cpp | 37 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp | 16 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextedit/data/embeddedImagesRemote.qml | 2 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp | 53 |
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 ®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 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" |