--- /dev/null
+/****************************************************************************
+**
+** 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
+ }
+}
--- /dev/null
+/****************************************************************************
+**
+** 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
+}
** 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$
**
#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;
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();
delete elipsisLayout;
delete textLine; textLine = 0;
delete imageCache;
+ qDeleteAll(imgTags);
+ imgTags.clear();
}
qreal QQuickTextPrivate::getImplicitWidth() const
}
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) {
} else {
singleline = false;
if (textHasChanged) {
- QDeclarativeStyledText::parse(text, layout);
+ QDeclarativeStyledText::parse(text, layout, imgTags, qmlContext(q), !maximumLineCountValid);
textHasChanged = false;
}
}
}
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()
lineWidth = INT_MAX;
int linesLeft = maximumLineCount;
int visibleTextLength = 0;
+
forever {
QTextLine line = layout.createLine();
if (!line.isValid())
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()) {
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);
}
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);
br = br.united(line.naturalTextRect());
}
layout.endLayout();
+ br.moveTop(0);
//Update truncated
if (truncated != truncate) {
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.
d->determineHorizontalAlignment();
}
d->textHasChanged = true;
+ qDeleteAll(d->imgTags);
+ d->imgTags.clear();
d->updateLayout();
emit textChanged(d->text);
}
<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
> < &
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;
}
}
#define QQUICKTEXT_P_H
#include "qquickimplicitsizeitem_p.h"
-
#include <private/qtquickglobal_p.h>
-
#include <QtGui/qtextoption.h>
QT_BEGIN_HEADER
private Q_SLOTS:
void q_imagesLoaded();
void triggerPreprocess();
+ void imageDownloadFinished();
private:
Q_DISABLE_COPY(QQuickText)
#include <QtDeclarative/qdeclarative.h>
#include <QtGui/qabstracttextdocumentlayout.h>
#include <QtGui/qtextlayout.h>
+#include <private/qdeclarativestyledtext_p.h>
QT_BEGIN_NAMESPACE
void mirrorChange();
QTextDocument *textDocument();
bool isLineLaidOutConnected();
+ void setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height);
QString text;
QUrl baseUrl;
bool richTextAsImage:1;
bool textureImageCacheDirty:1;
bool textHasChanged:1;
+ bool needToUpdateLayout:1;
QRect layedOutTextRect;
QSize paintedSize;
};
UpdateType updateType;
+ QList<QDeclarativeStyledTextImgTag*> imgTags;
+ QList<QDeclarativeStyledTextImgTag*> visibleImgTags;
+ int nbActiveDownloads;
+
#if defined(Q_OS_MAC)
QList<QRectF> linesRects;
QThread *layoutThread;
#include <QDebug>
#include <qmath.h>
#include "qdeclarativestyledtext_p.h"
+#include <QDeclarativeContext>
/*
QDeclarativeStyledText supports few tags:
<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.
*/
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)
{
}
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);
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;
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))
{
}
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();
}
QString drawText;
drawText.reserve(text.count());
+ updateImagePositions = !imgTags->isEmpty();
+
int textStart = 0;
int textLength = 0;
int rangeStart = 0;
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) {
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);
#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;
#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
{
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()
QFETCH(FormatList, formats);
QTextLayout layout;
- QDeclarativeStyledText::parse(input, layout);
+ QList<QDeclarativeStyledTextImgTag*> imgTags;
+ QDeclarativeStyledText::parse(input, layout, imgTags, 0, false);
QCOMPARE(layout.text(), output);
--- /dev/null
+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
+
+ }
+}
+
+
--- /dev/null
+import QtQuick 2.0
+
+Rectangle {
+ id: main
+ width: 300; height: 400
+
+ Text {
+ id: myText
+ objectName: "myText"
+ text: ""
+ }
+}
void lineLaidOut();
+ void imgTagsAlign_data();
+ void imgTagsAlign();
+ void imgTagsMultipleImages();
+ void imgTagsElide();
+ void imgTagsUpdates();
+ void imgTagsError();
private:
QStringList standard;
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"