From de0a5f28bc565c971989e68a19c8621a2309ed91 Mon Sep 17 00:00:00 2001 From: Andrew den Exter <andrew.den-exter@nokia.com> Date: Tue, 17 Jan 2012 10:42:26 +1000 Subject: [PATCH] Add a baseUrl property to Text and TextEdit. Specifies the base URL which embedded links in rich text are resolved against. By default this is the URL of the item. Task-number: QTBUG-23655 Change-Id: Ib51b8503a18d9ac4e1801c77b77b3595d8f4912a Reviewed-by: Martin Jones <martin.jones@nokia.com> --- src/quick/items/qquicktext.cpp | 55 ++++++++++++++++++- src/quick/items/qquicktext_p.h | 6 ++ src/quick/items/qquicktext_p_p.h | 4 ++ src/quick/items/qquicktextedit.cpp | 39 +++++++++++++ src/quick/items/qquicktextedit_p.h | 2 + src/quick/items/qquicktextedit_p_p.h | 1 + .../data/embeddedImagesLocalRelative.qml | 7 +++ .../data/embeddedImagesRemoteRelative.qml | 7 +++ .../qtquick2/qquicktext/tst_qquicktext.cpp | 29 ++++++++++ .../data/embeddedImagesLocalRelative.qml | 7 +++ .../data/embeddedImagesRemoteRelative.qml | 7 +++ .../qquicktextedit/tst_qquicktextedit.cpp | 29 ++++++++++ 12 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 tests/auto/qtquick2/qquicktext/data/embeddedImagesLocalRelative.qml create mode 100644 tests/auto/qtquick2/qquicktext/data/embeddedImagesRemoteRelative.qml create mode 100644 tests/auto/qtquick2/qquicktextedit/data/embeddedImagesLocalRelative.qml create mode 100644 tests/auto/qtquick2/qquicktextedit/data/embeddedImagesRemoteRelative.qml diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index fb9022072a..c5999435eb 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -119,7 +119,7 @@ QQuickTextDocumentWithImageResources::~QQuickTextDocumentWithImageResources() QVariant QQuickTextDocumentWithImageResources::loadResource(int type, const QUrl &name) { QDeclarativeContext *context = qmlContext(parent()); - QUrl url = context->resolvedUrl(name); + QUrl url = m_baseUrl.resolved(name); if (type == QTextDocument::ImageResource) { QDeclarativePixmap *p = loadPixmap(context, url); @@ -160,7 +160,7 @@ QSizeF QQuickTextDocumentWithImageResources::intrinsicSize( QSizeF size(width, height); if (!hasWidth || !hasHeight) { QDeclarativeContext *context = qmlContext(parent()); - QUrl url = context->resolvedUrl(QUrl(imageFormat.name())); + QUrl url = m_baseUrl.resolved(QUrl(imageFormat.name())); QDeclarativePixmap *p = loadPixmap(context, url); if (!p->isReady()) { @@ -198,12 +198,21 @@ void QQuickTextDocumentWithImageResources::drawObject( QImage QQuickTextDocumentWithImageResources::image(const QTextImageFormat &format) { QDeclarativeContext *context = qmlContext(parent()); - QUrl url = context->resolvedUrl(QUrl(format.name())); + QUrl url = m_baseUrl.resolved(QUrl(format.name())); QDeclarativePixmap *p = loadPixmap(context, url); return p->image(); } +void QQuickTextDocumentWithImageResources::setBaseUrl(const QUrl &url, bool clear) +{ + m_baseUrl = url; + if (clear) { + clearResources(); + markContentsDirty(0, characterCount()); + } +} + QDeclarativePixmap *QQuickTextDocumentWithImageResources::loadPixmap( QDeclarativeContext *context, const QUrl &url) { @@ -901,6 +910,7 @@ void QQuickTextPrivate::ensureDoc() Q_Q(QQuickText); doc = new QQuickTextDocumentWithImageResources(q); doc->setDocumentMargin(0); + doc->setBaseUrl(q->baseUrl()); FAST_CONNECT(doc, SIGNAL(imagesLoaded()), q, SLOT(q_imagesLoaded())); } } @@ -1717,6 +1727,45 @@ void QQuickText::setElideMode(QQuickText::TextElideMode mode) emit elideModeChanged(d->elideMode); } +/*! + \qmlproperty url QtQuick2::Text::baseUrl + + This property specifies a base URL which is used to resolve relative URLs + within the text. + + By default is the url of the Text element. +*/ + +QUrl QQuickText::baseUrl() const +{ + Q_D(const QQuickText); + if (d->baseUrl.isEmpty()) { + if (QDeclarativeContext *context = qmlContext(this)) + const_cast<QQuickTextPrivate *>(d)->baseUrl = context->baseUrl(); + } + return d->baseUrl; +} + +void QQuickText::setBaseUrl(const QUrl &url) +{ + Q_D(QQuickText); + if (baseUrl() != url) { + d->baseUrl = url; + + if (d->doc) + d->doc->setBaseUrl(url); + emit baseUrlChanged(); + } +} + +void QQuickText::resetBaseUrl() +{ + if (QDeclarativeContext *context = qmlContext(this)) + setBaseUrl(context->baseUrl()); + else + setBaseUrl(QUrl()); +} + /*! \internal */ QRectF QQuickText::boundingRect() const { diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index ad3895358c..002169d603 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -85,6 +85,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) Q_PROPERTY(qreal lineHeight READ lineHeight WRITE setLineHeight NOTIFY lineHeightChanged) Q_PROPERTY(LineHeightMode lineHeightMode READ lineHeightMode WRITE setLineHeightMode NOTIFY lineHeightModeChanged) + Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl RESET resetBaseUrl NOTIFY baseUrlChanged) public: QQuickText(QQuickItem *parent=0); @@ -164,6 +165,10 @@ public: LineHeightMode lineHeightMode() const; void setLineHeightMode(LineHeightMode); + QUrl baseUrl() const; + void setBaseUrl(const QUrl &url); + void resetBaseUrl(); + virtual void componentComplete(); int resourcesLoading() const; // mainly for testing @@ -194,6 +199,7 @@ Q_SIGNALS: void lineHeightModeChanged(LineHeightMode mode); void effectiveHorizontalAlignmentChanged(); void lineLaidOut(QQuickTextLine *line); + void baseUrlChanged(); protected: void mousePressEvent(QMouseEvent *event); diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index cf95fc6153..b461dc5d18 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -84,6 +84,7 @@ public: bool isLineLaidOutConnected(); QString text; + QUrl baseUrl; QFont font; QFont sourceFont; QColor color; @@ -188,6 +189,8 @@ public: QImage image(const QTextImageFormat &format); + void setBaseUrl(const QUrl &url, bool clear = true); + Q_SIGNALS: void imagesLoaded(); @@ -201,6 +204,7 @@ private slots: private: QHash<QUrl, QDeclarativePixmap *> m_resources; + QUrl m_baseUrl; int outstanding; static QSet<QUrl> errors; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 0ecaf4cf05..7ca2b50022 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -685,6 +685,44 @@ qreal QQuickTextEdit::paintedHeight() const return d->paintedSize.height(); } +/*! + \qmlproperty url QtQuick2::TextEdit::baseUrl + + This property specifies a base URL which is used to resolve relative URLs + within the text. + + By default is the url of the TextEdit element. +*/ + +QUrl QQuickTextEdit::baseUrl() const +{ + Q_D(const QQuickTextEdit); + if (d->baseUrl.isEmpty()) { + if (QDeclarativeContext *context = qmlContext(this)) + const_cast<QQuickTextEditPrivate *>(d)->baseUrl = context->baseUrl(); + } + return d->baseUrl; +} + +void QQuickTextEdit::setBaseUrl(const QUrl &url) +{ + Q_D(QQuickTextEdit); + if (baseUrl() != url) { + d->baseUrl = url; + + d->document->setBaseUrl(url, d->richText); + emit baseUrlChanged(); + } +} + +void QQuickTextEdit::resetBaseUrl() +{ + if (QDeclarativeContext *context = qmlContext(this)) + setBaseUrl(context->baseUrl()); + else + setBaseUrl(QUrl()); +} + /*! \qmlmethod rectangle QtQuick2::TextEdit::positionToRectangle(position) @@ -1116,6 +1154,7 @@ void QQuickTextEdit::componentComplete() Q_D(QQuickTextEdit); QQuickImplicitSizeItem::componentComplete(); + d->document->setBaseUrl(baseUrl(), d->richText); if (d->richText) d->useImageFallback = qmlEnableImageCache(); diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index 8cdd984735..9a591c9c5f 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -93,6 +93,7 @@ class Q_AUTOTEST_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem Q_PROPERTY(bool canUndo READ canUndo NOTIFY canUndoChanged) Q_PROPERTY(bool canRedo READ canRedo NOTIFY canRedoChanged) Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged) + Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl RESET resetBaseUrl NOTIFY baseUrlChanged) public: QQuickTextEdit(QQuickItem *parent=0); @@ -261,6 +262,7 @@ Q_SIGNALS: void canRedoChanged(); void inputMethodComposingChanged(); void effectiveHorizontalAlignmentChanged(); + void baseUrlChanged(); public Q_SLOTS: void selectAll(); diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 801bca0ff9..3a7291b60e 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -95,6 +95,7 @@ public: qreal getImplicitWidth() const; QString text; + QUrl baseUrl; QFont font; QFont sourceFont; QColor color; diff --git a/tests/auto/qtquick2/qquicktext/data/embeddedImagesLocalRelative.qml b/tests/auto/qtquick2/qquicktext/data/embeddedImagesLocalRelative.qml new file mode 100644 index 0000000000..8de7364d08 --- /dev/null +++ b/tests/auto/qtquick2/qquicktext/data/embeddedImagesLocalRelative.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Text { + textFormat: Text.RichText + text: "<img src='exists.png'>" + baseUrl: "http/" +} diff --git a/tests/auto/qtquick2/qquicktext/data/embeddedImagesRemoteRelative.qml b/tests/auto/qtquick2/qquicktext/data/embeddedImagesRemoteRelative.qml new file mode 100644 index 0000000000..cee19740f9 --- /dev/null +++ b/tests/auto/qtquick2/qquicktext/data/embeddedImagesRemoteRelative.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Text { + textFormat: Text.RichText + text: "<img src='exists.png'>" + baseUrl: "http://127.0.0.1:14453/text.html" +} diff --git a/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp b/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp index fa9549afc7..60dab3380a 100644 --- a/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp @@ -75,6 +75,7 @@ private slots: void alignments_data(); void alignments(); + void baseUrl(); void embeddedImages_data(); void embeddedImages(); @@ -1282,6 +1283,32 @@ void tst_qquicktext::clickLink() } } +void tst_qquicktext::baseUrl() +{ + QUrl localUrl("file:///tests/text.qml"); + QUrl remoteUrl("http://qt.nokia.com/test.qml"); + + QDeclarativeComponent textComponent(&engine); + textComponent.setData("import QtQuick 2.0\n Text {}", localUrl); + QQuickText *textObject = qobject_cast<QQuickText *>(textComponent.create()); + + QCOMPARE(textObject->baseUrl(), localUrl); + + QSignalSpy spy(textObject, SIGNAL(baseUrlChanged())); + + textObject->setBaseUrl(localUrl); + QCOMPARE(textObject->baseUrl(), localUrl); + QCOMPARE(spy.count(), 0); + + textObject->setBaseUrl(remoteUrl); + QCOMPARE(textObject->baseUrl(), remoteUrl); + QCOMPARE(spy.count(), 1); + + textObject->resetBaseUrl(); + QCOMPARE(textObject->baseUrl(), localUrl); + QCOMPARE(spy.count(), 2); +} + void tst_qquicktext::embeddedImages_data() { QTest::addColumn<QUrl>("qmlfile"); @@ -1289,9 +1316,11 @@ void tst_qquicktext::embeddedImages_data() QTest::newRow("local") << testFileUrl("embeddedImagesLocal.qml") << ""; QTest::newRow("local-error") << testFileUrl("embeddedImagesLocalError.qml") << testFileUrl("embeddedImagesLocalError.qml").toString()+":3:1: QML Text: Cannot open: " + testFileUrl("http/notexists.png").toString(); + QTest::newRow("local") << testFileUrl("embeddedImagesLocalRelative.qml") << ""; QTest::newRow("remote") << testFileUrl("embeddedImagesRemote.qml") << ""; QTest::newRow("remote-error") << testFileUrl("embeddedImagesRemoteError.qml") << testFileUrl("embeddedImagesRemoteError.qml").toString()+":3:1: QML Text: Error downloading http://127.0.0.1:14453/notexists.png - server replied: Not found"; + QTest::newRow("remote") << testFileUrl("embeddedImagesRemoteRelative.qml") << ""; } void tst_qquicktext::embeddedImages() diff --git a/tests/auto/qtquick2/qquicktextedit/data/embeddedImagesLocalRelative.qml b/tests/auto/qtquick2/qquicktextedit/data/embeddedImagesLocalRelative.qml new file mode 100644 index 0000000000..200ded196d --- /dev/null +++ b/tests/auto/qtquick2/qquicktextedit/data/embeddedImagesLocalRelative.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +TextEdit { + textFormat: TextEdit.RichText + text: "<img src='exists.png'>" + baseUrl: "http/" +} diff --git a/tests/auto/qtquick2/qquicktextedit/data/embeddedImagesRemoteRelative.qml b/tests/auto/qtquick2/qquicktextedit/data/embeddedImagesRemoteRelative.qml new file mode 100644 index 0000000000..ee39e089ea --- /dev/null +++ b/tests/auto/qtquick2/qquicktextedit/data/embeddedImagesRemoteRelative.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +TextEdit { + textFormat: TextEdit.RichText + text: "<img src='exists.png'>" + baseUrl: "http://127.0.0.1:42332/text.html" +} diff --git a/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp index 272ec19924..4a5bb01d1c 100644 --- a/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp @@ -175,6 +175,7 @@ private slots: void undo_keypressevents_data(); void undo_keypressevents(); + void baseUrl(); void embeddedImages(); void embeddedImages_data(); @@ -3658,6 +3659,32 @@ void tst_qquicktextedit::undo_keypressevents() QVERIFY(textEdit->text().isEmpty()); } +void tst_qquicktextedit::baseUrl() +{ + QUrl localUrl("file:///tests/text.qml"); + QUrl remoteUrl("http://qt.nokia.com/test.qml"); + + QDeclarativeComponent textComponent(&engine); + textComponent.setData("import QtQuick 2.0\n TextEdit {}", localUrl); + QQuickTextEdit *textObject = qobject_cast<QQuickTextEdit *>(textComponent.create()); + + QCOMPARE(textObject->baseUrl(), localUrl); + + QSignalSpy spy(textObject, SIGNAL(baseUrlChanged())); + + textObject->setBaseUrl(localUrl); + QCOMPARE(textObject->baseUrl(), localUrl); + QCOMPARE(spy.count(), 0); + + textObject->setBaseUrl(remoteUrl); + QCOMPARE(textObject->baseUrl(), remoteUrl); + QCOMPARE(spy.count(), 1); + + textObject->resetBaseUrl(); + QCOMPARE(textObject->baseUrl(), localUrl); + QCOMPARE(spy.count(), 2); +} + void tst_qquicktextedit::embeddedImages_data() { QTest::addColumn<QUrl>("qmlfile"); @@ -3665,9 +3692,11 @@ void tst_qquicktextedit::embeddedImages_data() QTest::newRow("local") << testFileUrl("embeddedImagesLocal.qml") << ""; QTest::newRow("local-error") << testFileUrl("embeddedImagesLocalError.qml") << testFileUrl("embeddedImagesLocalError.qml").toString()+":3:1: QML TextEdit: Cannot open: " + testFileUrl("http/notexists.png").toString(); + QTest::newRow("local") << testFileUrl("embeddedImagesLocalRelative.qml") << ""; QTest::newRow("remote") << testFileUrl("embeddedImagesRemote.qml") << ""; QTest::newRow("remote-error") << testFileUrl("embeddedImagesRemoteError.qml") << testFileUrl("embeddedImagesRemoteError.qml").toString()+":3:1: QML TextEdit: Error downloading http://127.0.0.1:42332/notexists.png - server replied: Not found"; + QTest::newRow("remote") << testFileUrl("embeddedImagesRemoteRelative.qml") << ""; } void tst_qquicktextedit::embeddedImages() -- GitLab