aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick
diff options
context:
space:
mode:
authorYann Bodson <yann.bodson@nokia.com>2011-11-28 11:26:40 +1000
committerQt by Nokia <qt-info@nokia.com>2012-02-02 11:11:53 +0100
commit8872c0716fb33e33311a78e693b61d3dd6b656c1 (patch)
tree01b970d0cc196cb99b971f9905d3a002a1a97190 /src/quick
parent76ed62fb836ea3e3e5236f8ed567f7ac64dd63fc (diff)
Support inline images with <img> tag in StyledText
Task-number: QTBUG-21793 Change-Id: Ie7b9f293c6c9a949c1899152c38b61251b0069d3 Reviewed-by: Yann Bodson <yann.bodson@nokia.com>
Diffstat (limited to 'src/quick')
-rw-r--r--src/quick/items/qquicktext.cpp151
-rw-r--r--src/quick/items/qquicktext_p.h3
-rw-r--r--src/quick/items/qquicktext_p_p.h7
-rw-r--r--src/quick/util/qdeclarativestyledtext.cpp98
-rw-r--r--src/quick/util/qdeclarativestyledtext_p.h46
5 files changed, 276 insertions, 29 deletions
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp
index 16fbc85b77..e7673478bd 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
&gt; &lt; &amp;
@@ -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 0725f53c66..ddc9d38c75 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 40c50378f0..e7b0478e21 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 d34601d09f..39ea6b1a22 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 f3e9fef457..1c9086e7d1 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;