diff --git a/examples/declarative/text/imgtag/TextWithImage.qml b/examples/declarative/text/imgtag/TextWithImage.qml new file mode 100644 index 0000000000000000000000000000000000000000..bc09aa2a521f05ba1b0492571a76ab2e6335f94b --- /dev/null +++ b/examples/declarative/text/imgtag/TextWithImage.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Text { + width: parent.width + font.pointSize: 14 + wrapMode: Text.WordWrap + textFormat: Text.StyledText + horizontalAlignment: main.hAlign + + Rectangle { + border.color: "#efefef" + color: "transparent" + anchors.fill: parent + } +} diff --git a/examples/declarative/text/imgtag/images/face-sad.png b/examples/declarative/text/imgtag/images/face-sad.png new file mode 100644 index 0000000000000000000000000000000000000000..24188b7985f9e680a434394dd47c131bae0a977d Binary files /dev/null and b/examples/declarative/text/imgtag/images/face-sad.png differ diff --git a/examples/declarative/text/imgtag/images/face-smile-big.png b/examples/declarative/text/imgtag/images/face-smile-big.png new file mode 100644 index 0000000000000000000000000000000000000000..d05e0bf21e7e0b439f731895cd04768a9063a645 Binary files /dev/null and b/examples/declarative/text/imgtag/images/face-smile-big.png differ diff --git a/examples/declarative/text/imgtag/images/face-smile.png b/examples/declarative/text/imgtag/images/face-smile.png new file mode 100644 index 0000000000000000000000000000000000000000..e5e1d1353c06eba8ce4c83a9991dfdf835a5dfd7 Binary files /dev/null and b/examples/declarative/text/imgtag/images/face-smile.png differ diff --git a/examples/declarative/text/imgtag/images/heart200.png b/examples/declarative/text/imgtag/images/heart200.png new file mode 100644 index 0000000000000000000000000000000000000000..cedd3ea608e3dab1a62dc29aa3e455b62b536309 Binary files /dev/null and b/examples/declarative/text/imgtag/images/heart200.png differ diff --git a/examples/declarative/text/imgtag/images/qtlogo.png b/examples/declarative/text/imgtag/images/qtlogo.png new file mode 100644 index 0000000000000000000000000000000000000000..996719a4c606fc81ceb38a154e622ab5a22c7f0c Binary files /dev/null and b/examples/declarative/text/imgtag/images/qtlogo.png differ diff --git a/examples/declarative/text/imgtag/images/starfish_2.png b/examples/declarative/text/imgtag/images/starfish_2.png new file mode 100644 index 0000000000000000000000000000000000000000..132c20ffd0234a590411c6ba1936249335664ac8 Binary files /dev/null and b/examples/declarative/text/imgtag/images/starfish_2.png differ diff --git a/examples/declarative/text/imgtag/imgtag.qml b/examples/declarative/text/imgtag/imgtag.qml new file mode 100644 index 0000000000000000000000000000000000000000..7f0d1dcfa34045063a8cd475a695f61a0685b78c --- /dev/null +++ b/examples/declarative/text/imgtag/imgtag.qml @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id: main + width: 640; height: 800 + focus: true + + property var hAlign: Text.AlignLeft + + Flickable { + anchors.fill: parent + contentWidth: parent.width + contentHeight: col.height + 20 + + Column { + id: col + x: 10; y: 10 + spacing: 20 + width: parent.width - 20 + + TextWithImage { + text: "This is a <b>happy</b> face<img src=\"images/face-smile.png\">" + } + TextWithImage { + text: "This is a <b>very<img src=\"images/face-smile-big.png\" align=\"middle\"/>happy</b> face aligned in the middle." + } + TextWithImage { + elide: Text.ElideRight + maximumLineCount: 2 + text: "This is a sad face aligned to the top. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc rutrum dui pretium ipsum malesuada venenatis. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis<img src=\"images/face-sad.png\" align=\"top\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc rutrum dui pretium ipsum malesuada venenatis. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis." + } + TextWithImage { + text: "This is a tiny<img src=\"images/face-smile.png\" width=\"15\" height=\"15\">happy face." + } + TextWithImage { + text: "This is a starfish<img src=\"images/starfish_2.png\" width=\"50\" height=\"50\" align=\"top\">aligned to the top and another one<img src=\"images/heart200.png\" width=\"50\" height=\"50\">aligned to the bottom." + } + TextWithImage { + text: "Qt logos<img src=\"images/qtlogo.png\" width=\"55\" height=\"60\" align=\"middle\"><img src=\"images/qtlogo.png\" width=\"37\" height=\"40\" align=\"middle\"><img src=\"images/qtlogo.png\" width=\"18\" height=\"20\" align=\"middle\">aligned in the middle with different sizes." + } + TextWithImage { + text: "Some hearts<img src=\"images/heart200.png\" width=\"20\" height=\"20\" align=\"bottom\"><img src=\"images/heart200.png\" width=\"30\" height=\"30\" align=\"bottom\"> <img src=\"images/heart200.png\" width=\"40\" height=\"40\"><img src=\"images/heart200.png\" width=\"50\" height=\"50\" align=\"bottom\">with different sizes." + } + TextWithImage { + text: "Resized image<img width=\"80\" height=\"76\" align=\"middle\" src=\"http://files.app4mobile.com/wp-content/uploads/2011/08/nokia-n9-price-specification-features-us-europe-india.jpg\">from the internet." + } + TextWithImage { + text: "Image<img align=\"middle\" src=\"http://qt.gitorious.org/images/sites/qt/logo.png\">from the internet." + } + TextWithImage { + height: 120 + verticalAlignment: Text.AlignVCenter + text: "This is a <b>happy</b> face<img src=\"images/face-smile.png\"> with an explicit height." + } + } + } + + Keys.onUpPressed: main.hAlign = Text.AlignHCenter + Keys.onLeftPressed: main.hAlign = Text.AlignLeft + Keys.onRightPressed: main.hAlign = Text.AlignRight +} diff --git a/examples/declarative/text/styledtext-layout.qml b/examples/declarative/text/styledtext-layout.qml index c76dd44f400435a1262df0cbe815484429c3822a..291d75f109c6689a00db5dda9f4bc4c96e9f020a 100644 --- a/examples/declarative/text/styledtext-layout.qml +++ b/examples/declarative/text/styledtext-layout.qml @@ -3,37 +3,36 @@ ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** +** This file is part of the examples of the Qt Toolkit. ** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: ** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. ** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index 16fbc85b77b159e0046211a8e110ba6fb84227ea..e7673478bdc38b5f1da35712fddd65e4ad1f0948 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -58,12 +58,13 @@ #include <QtGui/qguiapplication.h> #include <QtGui/qinputpanel.h> -#include <private/qdeclarativestyledtext_p.h> #include <QtQuick/private/qdeclarativepixmapcache_p.h> #include <qmath.h> #include <limits.h> +DEFINE_BOOL_CONFIG_OPTION(qmlTextDebug, QML_TEXT_DEBUG) + QT_BEGIN_NAMESPACE extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled; @@ -85,12 +86,12 @@ QQuickTextPrivate::QQuickTextPrivate() disableDistanceField(false), internalWidthUpdate(false), requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false), layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), textHasChanged(true), - naturalWidth(0), doc(0), elipsisLayout(0), textLine(0), nodeType(NodeIsNull), updateType(UpdatePaintNode) + needToUpdateLayout(false), naturalWidth(0), doc(0), elipsisLayout(0), textLine(0), nodeType(NodeIsNull), + updateType(UpdatePaintNode), nbActiveDownloads(0) #if defined(Q_OS_MAC) , layoutThread(0), paintingThread(0) #endif - { cacheAllTextAsImage = enableImageCache(); disableDistanceField = qmlDisableDistanceField(); @@ -266,6 +267,8 @@ QQuickTextPrivate::~QQuickTextPrivate() delete elipsisLayout; delete textLine; textLine = 0; delete imageCache; + qDeleteAll(imgTags); + imgTags.clear(); } qreal QQuickTextPrivate::getImplicitWidth() const @@ -295,6 +298,11 @@ void QQuickTextPrivate::updateLayout() } updateOnComponentComplete = false; layoutTextElided = false; + + if (!visibleImgTags.isEmpty()) + visibleImgTags.clear(); + needToUpdateLayout = false; + // Setup instance of QTextLayout for all cases other than richtext if (!richText) { if (elipsisLayout) { @@ -329,7 +337,7 @@ void QQuickTextPrivate::updateLayout() } else { singleline = false; if (textHasChanged) { - QDeclarativeStyledText::parse(text, layout); + QDeclarativeStyledText::parse(text, layout, imgTags, qmlContext(q), !maximumLineCountValid); textHasChanged = false; } } @@ -346,6 +354,41 @@ void QQuickTextPrivate::updateLayout() } updateSize(); + + if (needToUpdateLayout) { + needToUpdateLayout = false; + textHasChanged = true; + updateLayout(); + } +} + +void QQuickText::imageDownloadFinished() +{ + Q_D(QQuickText); + + (d->nbActiveDownloads)--; + + // when all the remote images have been downloaded, + // if one of the sizes was not specified at parsing time + // we use the implicit size from pixmapcache and re-layout. + + if (d->nbActiveDownloads == 0) { + bool needToUpdateLayout = false; + foreach (QDeclarativeStyledTextImgTag *img, d->visibleImgTags) { + if (!img->size.isValid()) { + img->size = img->pix->implicitSize(); + needToUpdateLayout = true; + } + } + + if (needToUpdateLayout) { + d->textHasChanged = true; + d->updateLayout(); + } else { + d->updateType = QQuickTextPrivate::UpdatePaintNode; + update(); + } + } } void QQuickTextPrivate::updateSize() @@ -659,6 +702,7 @@ QRect QQuickTextPrivate::setupTextLayout() lineWidth = INT_MAX; int linesLeft = maximumLineCount; int visibleTextLength = 0; + forever { QTextLine line = layout.createLine(); if (!line.isValid()) @@ -667,13 +711,10 @@ QRect QQuickTextPrivate::setupTextLayout() visibleCount++; qreal preLayoutHeight = height; - if (customLayout) { + if (customLayout) setupCustomLineGeometry(line, height); - } else if (lineWidth) { - line.setLineWidth(lineWidth); - line.setPosition(QPointF(line.position().x(), height)); - height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight; - } + else if (lineWidth) + setLineGeometry(line, lineWidth, height); bool elide = false; if (multilineElideEnabled && q->heightValid() && height > q->height()) { @@ -682,7 +723,7 @@ QRect QQuickTextPrivate::setupTextLayout() if (visibleCount > 1) { --visibleCount; height = preLayoutHeight; - line.setLineWidth(0.0); + setLineGeometry(line, 0.0, height); line.setPosition(QPointF(FLT_MAX,FLT_MAX)); line = layout.lineAt(visibleCount-1); } @@ -693,13 +734,14 @@ QRect QQuickTextPrivate::setupTextLayout() if (elide || (maximumLineCountValid && --linesLeft == 0)) { if (visibleTextLength < text.length()) { truncate = true; + height = preLayoutHeight; if (multilineElideEnabled) { qreal elideWidth = fm.width(elideChar); // Need to correct for alignment if (customLayout) setupCustomLineGeometry(line, height, elideWidth); else - line.setLineWidth(lineWidth - elideWidth); + setLineGeometry(line, lineWidth - elideWidth, height); if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) { line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y())); elidePos.setX(line.naturalTextRect().left() - elideWidth); @@ -722,6 +764,7 @@ QRect QQuickTextPrivate::setupTextLayout() br = br.united(line.naturalTextRect()); } layout.endLayout(); + br.moveTop(0); //Update truncated if (truncated != truncate) { @@ -740,10 +783,71 @@ QRect QQuickTextPrivate::setupTextLayout() lineCount = visibleCount; emit q->lineCountChanged(); } - return QRect(qRound(br.x()), qRound(br.y()), qCeil(br.width()), qCeil(br.height())); } +void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height) +{ + Q_Q(QQuickText); + line.setLineWidth(lineWidth); + + if (imgTags.isEmpty()) { + line.setPosition(QPointF(line.position().x(), height)); + height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight; + return; + } + + qreal textTop = 0; + qreal textHeight = line.height(); + qreal totalLineHeight = textHeight; + + QList<QDeclarativeStyledTextImgTag *> imagesInLine; + + foreach (QDeclarativeStyledTextImgTag *image, imgTags) { + if (image->position >= line.textStart() && + image->position < line.textStart() + line.textLength()) { + + if (!image->pix) { + QUrl url = qmlContext(q)->resolvedUrl(image->url); + image->pix = new QDeclarativePixmap(qmlEngine(q), url, image->size); + if (image->pix->isLoading()) { + image->pix->connectFinished(q, SLOT(imageDownloadFinished())); + nbActiveDownloads++; + } else if (image->pix->isReady()) { + if (!image->size.isValid()) { + image->size = image->pix->implicitSize(); + // if the size of the image was not explicitly set, we need to + // call updateLayout() once again. + needToUpdateLayout = true; + } + } else if (image->pix->isError()) { + qmlInfo(q) << image->pix->error(); + } + } + + qreal ih = qreal(image->size.height()); + if (image->align == QDeclarativeStyledTextImgTag::Top) + image->pos.setY(0); + else if (image->align == QDeclarativeStyledTextImgTag::Middle) + image->pos.setY((textHeight / 2.0) - (ih / 2.0)); + else + image->pos.setY(textHeight - ih); + imagesInLine << image; + textTop = qMax(textTop, qAbs(image->pos.y())); + } + } + + foreach (QDeclarativeStyledTextImgTag *image, imagesInLine) { + totalLineHeight = qMax(totalLineHeight, textTop + image->pos.y() + image->size.height()); + image->pos.setX(line.cursorToX(image->position)); + image->pos.setY(image->pos.y() + height + textTop); + visibleImgTags << image; + } + + line.setPosition(QPointF(line.position().x(), height + textTop)); + height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : totalLineHeight * lineHeight; +} + /*! Returns a painted version of the QQuickTextPrivate::layout QTextLayout. If \a drawStyle is true, the style color overrides all colors in the document. @@ -1251,6 +1355,8 @@ void QQuickText::setText(const QString &n) d->determineHorizontalAlignment(); } d->textHasChanged = true; + qDeleteAll(d->imgTags); + d->imgTags.clear(); d->updateLayout(); emit textChanged(d->text); } @@ -1629,6 +1735,7 @@ void QQuickText::resetMaximumLineCount() <font color="color_name" size="1-7"></font> <h1> to <h6> - headers <a href=""> - anchor + <img src="" align="top,middle,bottom" width="" height=""> - inline images <ol type="">, <ul type=""> and <li> - ordered and unordered lists <pre></pre> - preformatted > < & @@ -1947,6 +2054,24 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data node->addTextLayout(QPoint(0, bounds.y()), d->elipsisLayout, d->color, d->style, d->styleColor); } + foreach (QDeclarativeStyledTextImgTag *img, d->visibleImgTags) { + if (qmlTextDebug()) { + QSGRectangleNode *rectangle = d->sceneGraphContext()->createRectangleNode(); + rectangle->setRect(QRectF(img->pos.x(), img->pos.y() + bounds.y(),img->size.width(), img->size.height())); + rectangle->setColor(QColor("red")); + rectangle->update(); + node->appendChildNode(rectangle); + } + QDeclarativePixmap *pix = img->pix; + if (pix && pix->isReady()) { + QSGTexture *texture = d->sceneGraphContext()->textureForFactory(pix->textureFactory()); + QSGImageNode *imgnode = d->sceneGraphContext()->createImageNode(); + imgnode->setTexture(texture); + imgnode->setTargetRect(QRectF(img->pos.x(), img->pos.y() + bounds.y(), pix->width(), pix->height())); + node->appendChildNode(imgnode); + imgnode->update(); + } + } return node; } } diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index 0725f53c66d4e187f23afe5d07803d45a91f05e1..ddc9d38c7575735fd0a9e56605dad2020995dd4c 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -44,9 +44,7 @@ #define QQUICKTEXT_P_H #include "qquickimplicitsizeitem_p.h" - #include <private/qtquickglobal_p.h> - #include <QtGui/qtextoption.h> QT_BEGIN_HEADER @@ -212,6 +210,7 @@ protected: private Q_SLOTS: void q_imagesLoaded(); void triggerPreprocess(); + void imageDownloadFinished(); private: Q_DISABLE_COPY(QQuickText) diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index 40c50378f013b1f1f3effd6ce46977105a22f3cc..e7b0478e21671291feda3a5ce07bbd2c1a5c8f7b 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -60,6 +60,7 @@ #include <QtDeclarative/qdeclarative.h> #include <QtGui/qabstracttextdocumentlayout.h> #include <QtGui/qtextlayout.h> +#include <private/qdeclarativestyledtext_p.h> QT_BEGIN_NAMESPACE @@ -82,6 +83,7 @@ public: void mirrorChange(); QTextDocument *textDocument(); bool isLineLaidOutConnected(); + void setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height); QString text; QUrl baseUrl; @@ -127,6 +129,7 @@ public: bool richTextAsImage:1; bool textureImageCacheDirty:1; bool textHasChanged:1; + bool needToUpdateLayout:1; QRect layedOutTextRect; QSize paintedSize; @@ -168,6 +171,10 @@ public: }; UpdateType updateType; + QList<QDeclarativeStyledTextImgTag*> imgTags; + QList<QDeclarativeStyledTextImgTag*> visibleImgTags; + int nbActiveDownloads; + #if defined(Q_OS_MAC) QList<QRectF> linesRects; QThread *layoutThread; diff --git a/src/quick/util/qdeclarativestyledtext.cpp b/src/quick/util/qdeclarativestyledtext.cpp index d34601d09f5ab31c366f4b2f46fd222f4b3404f1..39ea6b1a22e1fa6714d0521460ad8b3beca2538f 100644 --- a/src/quick/util/qdeclarativestyledtext.cpp +++ b/src/quick/util/qdeclarativestyledtext.cpp @@ -46,6 +46,7 @@ #include <QDebug> #include <qmath.h> #include "qdeclarativestyledtext_p.h" +#include <QDeclarativeContext> /* QDeclarativeStyledText supports few tags: @@ -61,6 +62,7 @@ <a href=""> - anchor <ol type="">, <ul type=""> and <li> - ordered and unordered lists <pre></pre> - preformated + <img src=""> - images The opening and closing tags must be correctly nested. */ @@ -79,9 +81,12 @@ public: ListFormat format; }; - QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l) - : text(t), layout(l), baseFont(layout.font()), hasNewLine(false) - , preFormat(false), prependSpace(false), hasSpace(true) + QDeclarativeStyledTextPrivate(const QString &t, QTextLayout &l, + QList<QDeclarativeStyledTextImgTag*> &imgTags, + QDeclarativeContext *context, + bool preloadImages) + : text(t), layout(l), imgTags(&imgTags), baseFont(layout.font()), hasNewLine(false), nbImages(0), updateImagePositions(false) + , preFormat(false), prependSpace(false), hasSpace(true), preloadImages(preloadImages), context(context) { } @@ -94,6 +99,7 @@ public: bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn); bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn); bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); + void parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut); QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn); QStringRef parseValue(const QChar *&ch, const QString &textIn); @@ -108,12 +114,17 @@ public: QString text; QTextLayout &layout; + QList<QDeclarativeStyledTextImgTag*> *imgTags; QFont baseFont; QStack<List> listStack; bool hasNewLine; + int nbImages; + bool updateImagePositions; bool preFormat; bool prependSpace; bool hasSpace; + bool preloadImages; + QDeclarativeContext *context; static const QChar lessThan; static const QChar greaterThan; @@ -143,8 +154,10 @@ const QChar QDeclarativeStyledTextPrivate::square(0x25a1); const QChar QDeclarativeStyledTextPrivate::lineFeed(QLatin1Char('\n')); const QChar QDeclarativeStyledTextPrivate::space(QLatin1Char(' ')); -QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout) -: d(new QDeclarativeStyledTextPrivate(string, layout)) +QDeclarativeStyledText::QDeclarativeStyledText(const QString &string, QTextLayout &layout, + QList<QDeclarativeStyledTextImgTag*> &imgTags, QDeclarativeContext *context, + bool preloadImages) + : d(new QDeclarativeStyledTextPrivate(string, layout, imgTags, context, preloadImages)) { } @@ -153,11 +166,13 @@ QDeclarativeStyledText::~QDeclarativeStyledText() delete d; } -void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout) +void QDeclarativeStyledText::parse(const QString &string, QTextLayout &layout, + QList<QDeclarativeStyledTextImgTag*> &imgTags, QDeclarativeContext *context, + bool preloadImages) { if (string.isEmpty()) return; - QDeclarativeStyledText styledText(string, layout); + QDeclarativeStyledText styledText(string, layout, imgTags, context, preloadImages); styledText.d->parse(); } @@ -169,6 +184,8 @@ void QDeclarativeStyledTextPrivate::parse() QString drawText; drawText.reserve(text.count()); + updateImagePositions = !imgTags->isEmpty(); + int textStart = 0; int textLength = 0; int rangeStart = 0; @@ -401,6 +418,10 @@ bool QDeclarativeStyledTextPrivate::parseTag(const QChar *&ch, const QString &te if (tag == QLatin1String("a")) { return parseAnchorAttributes(ch, textIn, format); } + if (tag == QLatin1String("img")) { + parseImageAttributes(ch, textIn, textOut); + return false; + } if (*ch == greaterThan || ch->isNull()) continue; } else if (*ch != slash) { @@ -606,6 +627,69 @@ bool QDeclarativeStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, cons return valid; } +void QDeclarativeStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut) +{ + qreal imgWidth = 0.0; + + if (!updateImagePositions) { + QDeclarativeStyledTextImgTag *image = new QDeclarativeStyledTextImgTag; + image->position = textOut.length() + 1; + + QPair<QStringRef,QStringRef> attr; + do { + attr = parseAttribute(ch, textIn); + if (attr.first == QLatin1String("src")) { + image->url = QUrl(attr.second.toString()); + } else if (attr.first == QLatin1String("width")) { + image->size.setWidth(attr.second.toString().toInt()); + } else if (attr.first == QLatin1String("height")) { + image->size.setHeight(attr.second.toString().toInt()); + } else if (attr.first == QLatin1String("align")) { + if (attr.second.toString() == QLatin1String("top")) { + image->align = QDeclarativeStyledTextImgTag::Top; + } else if (attr.second.toString() == QLatin1String("middle")) { + image->align = QDeclarativeStyledTextImgTag::Middle; + } + } + } while (!ch->isNull() && !attr.first.isEmpty()); + + if (preloadImages && !image->size.isValid()) { + // if we don't know its size but the image is a local image, + // we load it in the pixmap cache and save its implicit size + // to avoid a relayout later on. + QUrl url = context->resolvedUrl(image->url); + if (url.isLocalFile()) { + QDeclarativePixmap *pix = new QDeclarativePixmap(context->engine(), url, image->size); + if (pix && pix->isReady()) { + image->size = pix->implicitSize(); + image->pix = pix; + } + } + } + + imgWidth = image->size.width(); + imgTags->append(image); + + } else { + // if we already have a list of img tags for this text + // we only want to update the positions of these tags. + QDeclarativeStyledTextImgTag *image = imgTags->value(nbImages); + image->position = textOut.length() + 1; + imgWidth = image->size.width(); + QPair<QStringRef,QStringRef> attr; + do { + attr = parseAttribute(ch, textIn); + } while (!ch->isNull() && !attr.first.isEmpty()); + nbImages++; + } + + QFontMetricsF fm(layout.font()); + QString padding(qFloor(imgWidth / fm.width(QChar::Nbsp)), QChar::Nbsp); + textOut += QChar(' '); + textOut += padding; + textOut += QChar(' '); +} + QPair<QStringRef,QStringRef> QDeclarativeStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn) { skipSpace(ch); diff --git a/src/quick/util/qdeclarativestyledtext_p.h b/src/quick/util/qdeclarativestyledtext_p.h index f3e9fef45770a5c58cab908cf9e0fdf21728db9e..1c9086e7d1f31fffc72c53ae18ed61e0b4d222eb 100644 --- a/src/quick/util/qdeclarativestyledtext_p.h +++ b/src/quick/util/qdeclarativestyledtext_p.h @@ -42,23 +42,55 @@ #ifndef QDECLARATIVESTYLEDTEXT_H #define QDECLARATIVESTYLEDTEXT_H -#include <QSizeF> +#include <QSize> +#include <QPointF> +#include <QList> +#include <QUrl> +#include <QtQuick/private/qdeclarativepixmapcache_p.h> QT_BEGIN_NAMESPACE -class QPainter; -class QPointF; -class QString; +class QDeclarativeStyledTextImgTag; class QDeclarativeStyledTextPrivate; -class QTextLayout; +class QString; +class QDeclarativeContext; + +class Q_AUTOTEST_EXPORT QDeclarativeStyledTextImgTag +{ +public: + QDeclarativeStyledTextImgTag() + : position(0), align(QDeclarativeStyledTextImgTag::Bottom), pix(0) + { } + + ~QDeclarativeStyledTextImgTag() { delete pix; } + + enum Align { + Bottom, + Middle, + Top + }; + + QUrl url; + QPointF pos; + QSize size; + int position; + Align align; + QDeclarativePixmap *pix; +}; class Q_AUTOTEST_EXPORT QDeclarativeStyledText { public: - static void parse(const QString &string, QTextLayout &layout); + static void parse(const QString &string, QTextLayout &layout, + QList<QDeclarativeStyledTextImgTag*> &imgTags, + QDeclarativeContext *context, + bool preloadImages); private: - QDeclarativeStyledText(const QString &string, QTextLayout &layout); + QDeclarativeStyledText(const QString &string, QTextLayout &layout, + QList<QDeclarativeStyledTextImgTag*> &imgTags, + QDeclarativeContext *context, + bool preloadImages); ~QDeclarativeStyledText(); QDeclarativeStyledTextPrivate *d; diff --git a/tests/auto/qtquick2/qdeclarativestyledtext/tst_qdeclarativestyledtext.cpp b/tests/auto/qtquick2/qdeclarativestyledtext/tst_qdeclarativestyledtext.cpp index 46acbb2db30472bb0b22e2b268193b1f0577a0fb..b4e0ba1b7a9f4772e0046abab4a152418a44fe39 100644 --- a/tests/auto/qtquick2/qdeclarativestyledtext/tst_qdeclarativestyledtext.cpp +++ b/tests/auto/qtquick2/qdeclarativestyledtext/tst_qdeclarativestyledtext.cpp @@ -41,7 +41,8 @@ #include <qtest.h> #include <QtTest/QtTest> #include <QtGui/QTextLayout> -#include <private/qdeclarativestyledtext_p.h> +#include <QtCore/QList> +#include <QtQuick/private/qdeclarativestyledtext_p.h> class tst_qdeclarativestyledtext : public QObject { @@ -148,6 +149,7 @@ void tst_qdeclarativestyledtext::textOutput_data() QTest::newRow("space before bold") << "this is <b>bold</b>" << "this is bold" << (FormatList() << Format(Format::Bold, 8, 4)); QTest::newRow("space leading bold") << "this is<b> bold</b>" << "this is bold" << (FormatList() << Format(Format::Bold, 7, 5)); QTest::newRow("space trailing bold") << "this is <b>bold </b>" << "this is bold " << (FormatList() << Format(Format::Bold, 8, 5)); + QTest::newRow("img") << "a<img src=\"blah.png\"/>b" << "a b" << FormatList(); } void tst_qdeclarativestyledtext::textOutput() @@ -157,7 +159,8 @@ void tst_qdeclarativestyledtext::textOutput() QFETCH(FormatList, formats); QTextLayout layout; - QDeclarativeStyledText::parse(input, layout); + QList<QDeclarativeStyledTextImgTag*> imgTags; + QDeclarativeStyledText::parse(input, layout, imgTags, 0, false); QCOMPARE(layout.text(), output); diff --git a/tests/auto/qtquick2/qquicktext/data/images/face-sad.png b/tests/auto/qtquick2/qquicktext/data/images/face-sad.png new file mode 100644 index 0000000000000000000000000000000000000000..24188b7985f9e680a434394dd47c131bae0a977d Binary files /dev/null and b/tests/auto/qtquick2/qquicktext/data/images/face-sad.png differ diff --git a/tests/auto/qtquick2/qquicktext/data/images/heart200.png b/tests/auto/qtquick2/qquicktext/data/images/heart200.png new file mode 100644 index 0000000000000000000000000000000000000000..cedd3ea608e3dab1a62dc29aa3e455b62b536309 Binary files /dev/null and b/tests/auto/qtquick2/qquicktext/data/images/heart200.png differ diff --git a/tests/auto/qtquick2/qquicktext/data/images/starfish_2.png b/tests/auto/qtquick2/qquicktext/data/images/starfish_2.png new file mode 100644 index 0000000000000000000000000000000000000000..132c20ffd0234a590411c6ba1936249335664ac8 Binary files /dev/null and b/tests/auto/qtquick2/qquicktext/data/images/starfish_2.png differ diff --git a/tests/auto/qtquick2/qquicktext/data/imgTagsElide.qml b/tests/auto/qtquick2/qquicktext/data/imgTagsElide.qml new file mode 100644 index 0000000000000000000000000000000000000000..fbd64cc5bfe3a09ce5b9c2266600a1df724a3a09 --- /dev/null +++ b/tests/auto/qtquick2/qquicktext/data/imgTagsElide.qml @@ -0,0 +1,24 @@ +import QtQuick 2.0 + +Item { + width: 300 + height: 200 + + Text { + id: myText + objectName: "myText" + elide: Text.ElideRight + maximumLineCount: 2 + width: 200 + wrapMode: Text.WordWrap + text: "This is a sad face aligned to the top. Lorem ipsum dolor sit amet. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis<img src=\"images/face-sad.png\" width=\"30\" height=\"30\" align=\"top\">Lorem ipsum dolor sit amet. Nulla sed turpis risus. Integer sit amet odio quis mauris varius venenatis. Lorem ipsum dolor sit amet. Nulla sed turpis risus.Lorem ipsum dolor sit amet. Nulla sed turpis risus. Lorem ipsum dolor sit amet. Nulla sed turpis risus.Lorem ipsum dolor sit amet. Nulla sed turpis risus." + } + + MouseArea { + anchors.fill: parent + onClicked: myText.width = 400 + + } +} + + diff --git a/tests/auto/qtquick2/qquicktext/data/imgTagsUpdates.qml b/tests/auto/qtquick2/qquicktext/data/imgTagsUpdates.qml new file mode 100644 index 0000000000000000000000000000000000000000..baf5113e521cbc7f465ae57271d5918bf10f2ea9 --- /dev/null +++ b/tests/auto/qtquick2/qquicktext/data/imgTagsUpdates.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 + +Rectangle { + id: main + width: 300; height: 400 + + Text { + id: myText + objectName: "myText" + text: "" + } +} diff --git a/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp b/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp index 706276c52fec86f6071693ee891b0bf6d23c5f2d..cac65196eb2ba9eea7ed73abc63d69fb98c96a03 100644 --- a/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp @@ -107,6 +107,12 @@ private slots: void lineLaidOut(); + void imgTagsAlign_data(); + void imgTagsAlign(); + void imgTagsMultipleImages(); + void imgTagsElide(); + void imgTagsUpdates(); + void imgTagsError(); private: QStringList standard; @@ -1478,6 +1484,120 @@ void tst_qquicktext::lineLaidOut() delete canvas; } +void tst_qquicktext::imgTagsAlign_data() +{ + QTest::addColumn<QString>("src"); + QTest::addColumn<int>("imgHeight"); + QTest::addColumn<QString>("align"); + QTest::newRow("heart-bottom") << "data/images/heart200.png" << 181 << "bottom"; + QTest::newRow("heart-middle") << "data/images/heart200.png" << 181 << "middle"; + QTest::newRow("heart-top") << "data/images/heart200.png" << 181 << "top"; + QTest::newRow("starfish-bottom") << "data/images/starfish_2.png" << 217 << "bottom"; + QTest::newRow("starfish-middle") << "data/images/starfish_2.png" << 217 << "middle"; + QTest::newRow("starfish-top") << "data/images/starfish_2.png" << 217 << "top"; +} + +void tst_qquicktext::imgTagsAlign() +{ + QFETCH(QString, src); + QFETCH(int, imgHeight); + QFETCH(QString, align); + QString componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src + "\\\" align=\\\"" + align + "\\\"> of image.\" }"; + QDeclarativeComponent textComponent(&engine); + textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); + QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create()); + + QVERIFY(textObject != 0); + QVERIFY(textObject->height() == imgHeight); + + QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); + QVERIFY(textPrivate != 0); + + QRectF br = textPrivate->layout.boundingRect(); + if (align == "bottom") + QVERIFY(br.y() == imgHeight - br.height()); + else if (align == "middle") + QVERIFY(br.y() == imgHeight / 2.0 - br.height() / 2.0); + else if (align == "top") + QVERIFY(br.y() == 0); + + delete textObject; +} + +void tst_qquicktext::imgTagsMultipleImages() +{ + QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.png\\\" width=\\\"60\\\" height=\\\"60\\\" > and another one<img src=\\\"data/images/heart200.png\\\" width=\\\"85\\\" height=\\\"85\\\">.\" }"; + + QDeclarativeComponent textComponent(&engine); + textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); + QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create()); + + QVERIFY(textObject != 0); + QVERIFY(textObject->height() == 85); + + QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); + QVERIFY(textPrivate != 0); + QVERIFY(textPrivate->visibleImgTags.count() == 2); + + delete textObject; +} + +void tst_qquicktext::imgTagsElide() +{ + QQuickView *canvas = createView(testFile("imgTagsElide.qml")); + QQuickText *myText = canvas->rootObject()->findChild<QQuickText*>("myText"); + QVERIFY(myText != 0); + + QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); + QVERIFY(textPrivate != 0); + QVERIFY(textPrivate->visibleImgTags.count() == 0); + myText->setMaximumLineCount(20); + QTRY_VERIFY(textPrivate->visibleImgTags.count() == 1); + + delete myText; + delete canvas; +} + +void tst_qquicktext::imgTagsUpdates() +{ + QQuickView *canvas = createView(testFile("imgTagsUpdates.qml")); + QQuickText *myText = canvas->rootObject()->findChild<QQuickText*>("myText"); + QVERIFY(myText != 0); + + QSignalSpy spy(myText, SIGNAL(paintedSizeChanged())); + + QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); + QVERIFY(textPrivate != 0); + + myText->setText("This is a heart<img src=\"images/heart200.png\">."); + QVERIFY(textPrivate->visibleImgTags.count() == 1); + QVERIFY(spy.count() == 1); + + myText->setMaximumLineCount(2); + myText->setText("This is another heart<img src=\"images/heart200.png\">."); + QTRY_VERIFY(textPrivate->visibleImgTags.count() == 1); + + // if maximumLineCount is set and the img tag doesn't have an explicit size + // we relayout twice. + QVERIFY(spy.count() == 3); + + delete myText; + delete canvas; +} + +void tst_qquicktext::imgTagsError() +{ + QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.pn\\\" width=\\\"60\\\" height=\\\"60\\\">.\" }"; + + QDeclarativeComponent textComponent(&engine); + QTest::ignoreMessage(QtWarningMsg, "file::2:1: QML Text: Cannot open: file:data/images/starfish_2.pn"); + textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); + QQuickText *textObject = qobject_cast<QQuickText*>(textComponent.create()); + + QVERIFY(textObject != 0); + delete textObject; +} + QTEST_MAIN(tst_qquicktext) #include "tst_qquicktext.moc"