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