aboutsummaryrefslogtreecommitdiffstats
path: root/src/declarative/items/qsgtext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/declarative/items/qsgtext.cpp')
-rw-r--r--src/declarative/items/qsgtext.cpp1255
1 files changed, 1255 insertions, 0 deletions
diff --git a/src/declarative/items/qsgtext.cpp b/src/declarative/items/qsgtext.cpp
new file mode 100644
index 0000000000..e7e655d591
--- /dev/null
+++ b/src/declarative/items/qsgtext.cpp
@@ -0,0 +1,1255 @@
+// Commit: cce89db1e2555cbca8fc28072e1c6dd737cec6c4
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsgtext_p.h"
+#include "qsgtext_p_p.h"
+
+#include <private/qsgdistancefieldglyphcache_p.h>
+#include <private/qsgcontext_p.h>
+#include <private/qsgadaptationlayer_p.h>
+#include "qsgtextnode_p.h"
+#include "qsgimage_p_p.h"
+#include <private/qsgtexture_p.h>
+
+#include <QtDeclarative/qdeclarativeinfo.h>
+#include <QtGui/qgraphicssceneevent.h>
+#include <QtGui/qabstracttextdocumentlayout.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextobject.h>
+#include <QtGui/qtextcursor.h>
+#include <QtGui/qapplication.h>
+
+#include <private/qdeclarativestyledtext_p.h>
+#include <private/qdeclarativepixmapcache_p.h>
+
+#include <qmath.h>
+#include <limits.h>
+
+QT_BEGIN_NAMESPACE
+
+extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled;
+
+class QSGTextDocumentWithImageResources : public QTextDocument {
+ Q_OBJECT
+
+public:
+ QSGTextDocumentWithImageResources(QSGText *parent);
+ virtual ~QSGTextDocumentWithImageResources();
+
+ void setText(const QString &);
+ int resourcesLoading() const { return outstanding; }
+
+protected:
+ QVariant loadResource(int type, const QUrl &name);
+
+private slots:
+ void requestFinished();
+
+private:
+ QHash<QUrl, QDeclarativePixmap *> m_resources;
+
+ int outstanding;
+ static QSet<QUrl> errors;
+};
+
+DEFINE_BOOL_CONFIG_OPTION(enableImageCache, QML_ENABLE_TEXT_IMAGE_CACHE);
+
+QString QSGTextPrivate::elideChar = QString(0x2026);
+
+QSGTextPrivate::QSGTextPrivate()
+: color((QRgb)0), style(QSGText::Normal), hAlign(QSGText::AlignLeft),
+ vAlign(QSGText::AlignTop), elideMode(QSGText::ElideNone),
+ format(QSGText::AutoText), wrapMode(QSGText::NoWrap), lineHeight(1),
+ lineHeightMode(QSGText::ProportionalHeight), lineCount(1), maximumLineCount(INT_MAX),
+ maximumLineCountValid(false),
+ texture(0),
+ imageCacheDirty(true), updateOnComponentComplete(true),
+ richText(false), singleline(false), cacheAllTextAsImage(true), internalWidthUpdate(false),
+ requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false),
+ layoutTextElided(false), naturalWidth(0), doc(0), layoutThread(0), nodeType(NodeIsNull)
+{
+ cacheAllTextAsImage = enableImageCache();
+}
+
+void QSGTextPrivate::init()
+{
+ Q_Q(QSGText);
+ q->setAcceptedMouseButtons(Qt::LeftButton);
+ q->setFlag(QSGItem::ItemHasContents);
+}
+
+QSGTextDocumentWithImageResources::QSGTextDocumentWithImageResources(QSGText *parent)
+: QTextDocument(parent), outstanding(0)
+{
+ setUndoRedoEnabled(false);
+}
+
+QSGTextDocumentWithImageResources::~QSGTextDocumentWithImageResources()
+{
+ if (!m_resources.isEmpty())
+ qDeleteAll(m_resources);
+}
+
+QVariant QSGTextDocumentWithImageResources::loadResource(int type, const QUrl &name)
+{
+ QDeclarativeContext *context = qmlContext(parent());
+ QUrl url = context->resolvedUrl(name);
+
+ if (type == QTextDocument::ImageResource) {
+ QHash<QUrl, QDeclarativePixmap *>::Iterator iter = m_resources.find(url);
+
+ if (iter == m_resources.end()) {
+ QDeclarativePixmap *p = new QDeclarativePixmap(context->engine(), url);
+ iter = m_resources.insert(name, p);
+
+ if (p->isLoading()) {
+ p->connectFinished(this, SLOT(requestFinished()));
+ outstanding++;
+ }
+ }
+
+ QDeclarativePixmap *p = *iter;
+ if (p->isReady()) {
+ return p->pixmap();
+ } else if (p->isError()) {
+ if (!errors.contains(url)) {
+ errors.insert(url);
+ qmlInfo(parent()) << p->error();
+ }
+ }
+ }
+
+ return QTextDocument::loadResource(type,url); // The *resolved* URL
+}
+
+void QSGTextDocumentWithImageResources::requestFinished()
+{
+ outstanding--;
+ if (outstanding == 0) {
+ QSGText *textItem = static_cast<QSGText*>(parent());
+ QString text = textItem->text();
+#ifndef QT_NO_TEXTHTMLPARSER
+ setHtml(text);
+#else
+ setPlainText(text);
+#endif
+ QSGTextPrivate *d = QSGTextPrivate::get(textItem);
+ d->updateLayout();
+ }
+}
+
+void QSGTextDocumentWithImageResources::setText(const QString &text)
+{
+ if (!m_resources.isEmpty()) {
+ qDeleteAll(m_resources);
+ m_resources.clear();
+ outstanding = 0;
+ }
+
+#ifndef QT_NO_TEXTHTMLPARSER
+ setHtml(text);
+#else
+ setPlainText(text);
+#endif
+}
+
+QSet<QUrl> QSGTextDocumentWithImageResources::errors;
+
+QSGTextPrivate::~QSGTextPrivate()
+{
+}
+
+qreal QSGTextPrivate::getImplicitWidth() const
+{
+ if (!requireImplicitWidth) {
+ // We don't calculate implicitWidth unless it is required.
+ // We need to force a size update now to ensure implicitWidth is calculated
+ QSGTextPrivate *me = const_cast<QSGTextPrivate*>(this);
+ me->requireImplicitWidth = true;
+ me->updateSize();
+ }
+ return implicitWidth;
+}
+
+void QSGTextPrivate::updateLayout()
+{
+ Q_Q(QSGText);
+ if (!q->isComponentComplete()) {
+ updateOnComponentComplete = true;
+ return;
+ }
+
+ layoutTextElided = false;
+ // Setup instance of QTextLayout for all cases other than richtext
+ if (!richText) {
+ layout.clearLayout();
+ layout.setFont(font);
+ if (format != QSGText::StyledText) {
+ QString tmp = text;
+ tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
+ singleline = !tmp.contains(QChar::LineSeparator);
+ if (singleline && !maximumLineCountValid && elideMode != QSGText::ElideNone && q->widthValid()) {
+ QFontMetrics fm(font);
+ tmp = fm.elidedText(tmp,(Qt::TextElideMode)elideMode,q->width());
+ if (tmp != text) {
+ layoutTextElided = true;
+ if (!truncated) {
+ truncated = true;
+ emit q->truncatedChanged();
+ }
+ }
+ }
+ layout.setText(tmp);
+ } else {
+ singleline = false;
+ QDeclarativeStyledText::parse(text, layout);
+ }
+ } else {
+ ensureDoc();
+ QTextBlockFormat::LineHeightTypes type;
+ type = lineHeightMode == QSGText::FixedHeight ? QTextBlockFormat::FixedHeight : QTextBlockFormat::ProportionalHeight;
+ QTextBlockFormat blockFormat;
+ blockFormat.setLineHeight((lineHeightMode == QSGText::FixedHeight ? lineHeight : lineHeight * 100), type);
+ for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) {
+ QTextCursor cursor(it);
+ cursor.setBlockFormat(blockFormat);
+ }
+ }
+
+ updateSize();
+}
+
+void QSGTextPrivate::updateSize()
+{
+ Q_Q(QSGText);
+
+ if (!q->isComponentComplete()) {
+ updateOnComponentComplete = true;
+ return;
+ }
+
+ if (!requireImplicitWidth) {
+ emit q->implicitWidthChanged();
+ // if the implicitWidth is used, then updateSize() has already been called (recursively)
+ if (requireImplicitWidth)
+ return;
+ }
+
+ invalidateImageCache();
+
+ QFontMetrics fm(font);
+ if (text.isEmpty()) {
+ q->setImplicitWidth(0);
+ q->setImplicitHeight(fm.height());
+ paintedSize = QSize(0, fm.height());
+ emit q->paintedSizeChanged();
+ q->update();
+ return;
+ }
+
+ int dy = q->height();
+ QSize size(0, 0);
+
+ //setup instance of QTextLayout for all cases other than richtext
+ if (!richText) {
+ QRect textRect = setupTextLayout();
+ layedOutTextRect = textRect;
+ size = textRect.size();
+ dy -= size.height();
+ } else {
+ singleline = false; // richtext can't elide or be optimized for single-line case
+ ensureDoc();
+ doc->setDefaultFont(font);
+ QSGText::HAlignment horizontalAlignment = q->effectiveHAlign();
+ if (rightToLeftText) {
+ if (horizontalAlignment == QSGText::AlignLeft)
+ horizontalAlignment = QSGText::AlignRight;
+ else if (horizontalAlignment == QSGText::AlignRight)
+ horizontalAlignment = QSGText::AlignLeft;
+ }
+ QTextOption option;
+ option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign));
+ option.setWrapMode(QTextOption::WrapMode(wrapMode));
+ doc->setDefaultTextOption(option);
+ if (requireImplicitWidth && q->widthValid()) {
+ doc->setTextWidth(-1);
+ naturalWidth = doc->idealWidth();
+ }
+ if (wrapMode != QSGText::NoWrap && q->widthValid())
+ doc->setTextWidth(q->width());
+ else
+ doc->setTextWidth(doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug)
+ dy -= (int)doc->size().height();
+ QSize dsize = doc->size().toSize();
+ layedOutTextRect = QRect(QPoint(0,0), dsize);
+ size = QSize(int(doc->idealWidth()),dsize.height());
+ }
+ int yoff = 0;
+
+ if (q->heightValid()) {
+ if (vAlign == QSGText::AlignBottom)
+ yoff = dy;
+ else if (vAlign == QSGText::AlignVCenter)
+ yoff = dy/2;
+ }
+ q->setBaselineOffset(fm.ascent() + yoff);
+
+ //### need to comfirm cost of always setting these for richText
+ internalWidthUpdate = true;
+ if (!q->widthValid())
+ q->setImplicitWidth(size.width());
+ else if (requireImplicitWidth)
+ q->setImplicitWidth(naturalWidth);
+ internalWidthUpdate = false;
+
+ q->setImplicitHeight(size.height());
+ if (paintedSize != size) {
+ paintedSize = size;
+ emit q->paintedSizeChanged();
+ }
+ q->update();
+}
+
+/*!
+ Lays out the QSGTextPrivate::layout QTextLayout in the constraints of the QSGText.
+
+ Returns the size of the final text. This can be used to position the text vertically (the text is
+ already absolutely positioned horizontally).
+*/
+QRect QSGTextPrivate::setupTextLayout()
+{
+ // ### text layout handling should be profiled and optimized as needed
+ // what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine);
+ Q_Q(QSGText);
+ layout.setCacheEnabled(true);
+
+ qreal lineWidth = 0;
+ int visibleCount = 0;
+
+ //set manual width
+ if (q->widthValid())
+ lineWidth = q->width();
+
+ QTextOption textOption = layout.textOption();
+ textOption.setAlignment(Qt::Alignment(q->effectiveHAlign()));
+ textOption.setWrapMode(QTextOption::WrapMode(wrapMode));
+ layout.setTextOption(textOption);
+
+ bool elideText = false;
+ bool truncate = false;
+
+ layoutThread = QThread::currentThread();
+
+ QFontMetrics fm(layout.font());
+ elidePos = QPointF();
+
+ if (requireImplicitWidth && q->widthValid()) {
+ // requires an extra layout
+ QString elidedText;
+ if (layoutTextElided) {
+ // We have provided elided text to the layout, but we must calculate unelided width.
+ elidedText = layout.text();
+ layout.setText(text);
+ }
+ layout.beginLayout();
+ forever {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+ }
+ layout.endLayout();
+ QRectF br;
+ for (int i = 0; i < layout.lineCount(); ++i) {
+ QTextLine line = layout.lineAt(i);
+ br = br.united(line.naturalTextRect());
+ }
+ naturalWidth = br.width();
+ if (layoutTextElided)
+ layout.setText(elidedText);
+ }
+
+ if (maximumLineCountValid) {
+ layout.beginLayout();
+ if (!lineWidth)
+ lineWidth = INT_MAX;
+ int linesLeft = maximumLineCount;
+ int visibleTextLength = 0;
+ while (linesLeft > 0) {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+
+ visibleCount++;
+ if (lineWidth)
+ line.setLineWidth(lineWidth);
+ visibleTextLength += line.textLength();
+
+ if (--linesLeft == 0) {
+ if (visibleTextLength < text.length()) {
+ truncate = true;
+ if (elideMode==QSGText::ElideRight && q->widthValid()) {
+ qreal elideWidth = fm.width(elideChar);
+ // Need to correct for alignment
+ line.setLineWidth(lineWidth-elideWidth);
+ 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);
+ } else {
+ elidePos.setX(line.naturalTextRect().right());
+ }
+ elideText = true;
+ }
+ }
+ }
+ }
+ layout.endLayout();
+
+ //Update truncated
+ if (truncated != truncate) {
+ truncated = truncate;
+ emit q->truncatedChanged();
+ }
+ } else {
+ layout.beginLayout();
+ forever {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+ visibleCount++;
+ if (lineWidth)
+ line.setLineWidth(lineWidth);
+ }
+ layout.endLayout();
+ }
+
+ qreal height = 0;
+ QRectF br;
+ for (int i = 0; i < layout.lineCount(); ++i) {
+ QTextLine line = layout.lineAt(i);
+ // set line spacing
+ line.setPosition(QPointF(line.position().x(), height));
+ if (elideText && i == layout.lineCount()-1) {
+ elidePos.setY(height + fm.ascent());
+ br = br.united(QRectF(elidePos, QSizeF(fm.width(elideChar), fm.ascent())));
+ }
+ br = br.united(line.naturalTextRect());
+ height += (lineHeightMode == QSGText::FixedHeight) ? lineHeight : line.height() * lineHeight;
+ }
+ br.setHeight(height);
+
+ if (!q->widthValid())
+ naturalWidth = br.width();
+
+ //Update the number of visible lines
+ if (lineCount != visibleCount) {
+ lineCount = visibleCount;
+ emit q->lineCountChanged();
+ }
+
+ return QRect(qRound(br.x()), qRound(br.y()), qCeil(br.width()), qCeil(br.height()));
+}
+
+/*!
+ Returns a painted version of the QSGTextPrivate::layout QTextLayout.
+ If \a drawStyle is true, the style color overrides all colors in the document.
+*/
+QPixmap QSGTextPrivate::textLayoutImage(bool drawStyle)
+{
+ QSize size = layedOutTextRect.size();
+
+ //paint text
+ QPixmap img(size);
+ if (!size.isEmpty()) {
+ img.fill(Qt::transparent);
+#ifdef Q_WS_MAC
+ bool oldSmooth = qt_applefontsmoothing_enabled;
+ qt_applefontsmoothing_enabled = false;
+#endif
+ QPainter p(&img);
+#ifdef Q_WS_MAC
+ qt_applefontsmoothing_enabled = oldSmooth;
+#endif
+ drawTextLayout(&p, QPointF(-layedOutTextRect.x(),0), drawStyle);
+ }
+ return img;
+}
+
+/*!
+ Paints the QSGTextPrivate::layout QTextLayout into \a painter at \a pos. If
+ \a drawStyle is true, the style color overrides all colors in the document.
+*/
+void QSGTextPrivate::drawTextLayout(QPainter *painter, const QPointF &pos, bool drawStyle)
+{
+ if (drawStyle)
+ painter->setPen(styleColor);
+ else
+ painter->setPen(color);
+ painter->setFont(font);
+ layout.draw(painter, pos);
+ if (!elidePos.isNull())
+ painter->drawText(pos + elidePos, elideChar);
+}
+
+/*!
+ Returns a painted version of the QSGTextPrivate::doc QTextDocument.
+ If \a drawStyle is true, the style color overrides all colors in the document.
+*/
+QPixmap QSGTextPrivate::textDocumentImage(bool drawStyle)
+{
+ QSize size = doc->size().toSize();
+
+ //paint text
+ QPixmap img(size);
+ img.fill(Qt::transparent);
+#ifdef Q_WS_MAC
+ bool oldSmooth = qt_applefontsmoothing_enabled;
+ qt_applefontsmoothing_enabled = false;
+#endif
+ QPainter p(&img);
+#ifdef Q_WS_MAC
+ qt_applefontsmoothing_enabled = oldSmooth;
+#endif
+
+ QAbstractTextDocumentLayout::PaintContext context;
+
+ QTextOption oldOption(doc->defaultTextOption());
+ if (drawStyle) {
+ context.palette.setColor(QPalette::Text, styleColor);
+ QTextOption colorOption(doc->defaultTextOption());
+ colorOption.setFlags(QTextOption::SuppressColors);
+ doc->setDefaultTextOption(colorOption);
+ } else {
+ context.palette.setColor(QPalette::Text, color);
+ }
+ doc->documentLayout()->draw(&p, context);
+ if (drawStyle)
+ doc->setDefaultTextOption(oldOption);
+ return img;
+}
+
+/*!
+ Mark the image cache as dirty.
+*/
+void QSGTextPrivate::invalidateImageCache()
+{
+ Q_Q(QSGText);
+
+ if(cacheAllTextAsImage || (!QSGDistanceFieldGlyphCache::distanceFieldEnabled() && style != QSGText::Normal)){//If actually using the image cache
+ if (imageCacheDirty)
+ return;
+
+ imageCacheDirty = true;
+ imageCache = QPixmap();
+ }
+ if (q->isComponentComplete())
+ q->update();
+}
+
+/*!
+ Tests if the image cache is dirty, and repaints it if it is.
+*/
+void QSGTextPrivate::checkImageCache()
+{
+ if (!imageCacheDirty)
+ return;
+
+ if (text.isEmpty()) {
+
+ imageCache = QPixmap();
+
+ } else {
+
+ QPixmap textImage;
+ QPixmap styledImage;
+
+ if (richText) {
+ textImage = textDocumentImage(false);
+ if (style != QSGText::Normal)
+ styledImage = textDocumentImage(true); //### should use styleColor
+ } else {
+ textImage = textLayoutImage(false);
+ if (style != QSGText::Normal)
+ styledImage = textLayoutImage(true); //### should use styleColor
+ }
+
+ switch (style) {
+ case QSGText::Outline:
+ imageCache = drawOutline(textImage, styledImage);
+ break;
+ case QSGText::Sunken:
+ imageCache = drawOutline(textImage, styledImage, -1);
+ break;
+ case QSGText::Raised:
+ imageCache = drawOutline(textImage, styledImage, 1);
+ break;
+ default:
+ imageCache = textImage;
+ break;
+ }
+
+ }
+
+ imageCacheDirty = false;
+}
+
+/*!
+ Ensures the QSGTextPrivate::doc variable is set to a valid text document
+*/
+void QSGTextPrivate::ensureDoc()
+{
+ if (!doc) {
+ Q_Q(QSGText);
+ doc = new QSGTextDocumentWithImageResources(q);
+ doc->setDocumentMargin(0);
+ }
+}
+
+/*!
+ Draw \a styleSource as an outline around \a source and return the new image.
+*/
+QPixmap QSGTextPrivate::drawOutline(const QPixmap &source, const QPixmap &styleSource)
+{
+ QPixmap img = QPixmap(styleSource.width() + 2, styleSource.height() + 2);
+ img.fill(Qt::transparent);
+
+ QPainter ppm(&img);
+
+ QPoint pos(0, 0);
+ pos += QPoint(-1, 0);
+ ppm.drawPixmap(pos, styleSource);
+ pos += QPoint(2, 0);
+ ppm.drawPixmap(pos, styleSource);
+ pos += QPoint(-1, -1);
+ ppm.drawPixmap(pos, styleSource);
+ pos += QPoint(0, 2);
+ ppm.drawPixmap(pos, styleSource);
+
+ pos += QPoint(0, -1);
+ ppm.drawPixmap(pos, source);
+ ppm.end();
+
+ return img;
+}
+
+/*!
+ Draw \a styleSource below \a source at \a yOffset and return the new image.
+*/
+QPixmap QSGTextPrivate::drawOutline(const QPixmap &source, const QPixmap &styleSource, int yOffset)
+{
+ QPixmap img = QPixmap(styleSource.width() + 2, styleSource.height() + 2);
+ img.fill(Qt::transparent);
+
+ QPainter ppm(&img);
+
+ ppm.drawPixmap(QPoint(0, yOffset), styleSource);
+ ppm.drawPixmap(0, 0, source);
+
+ ppm.end();
+
+ return img;
+}
+
+QSGText::QSGText(QSGItem *parent)
+: QSGImplicitSizeItem(*(new QSGTextPrivate), parent)
+{
+ Q_D(QSGText);
+ d->init();
+}
+
+QSGText::~QSGText()
+{
+}
+
+QFont QSGText::font() const
+{
+ Q_D(const QSGText);
+ return d->sourceFont;
+}
+
+void QSGText::setFont(const QFont &font)
+{
+ Q_D(QSGText);
+ if (d->sourceFont == font)
+ return;
+
+ d->sourceFont = font;
+ QFont oldFont = d->font;
+ d->font = font;
+ if (QSGDistanceFieldGlyphCache::distanceFieldEnabled())
+ d->font.setHintingPreference(QFont::PreferNoHinting);
+
+ if (d->font.pointSizeF() != -1) {
+ // 0.5pt resolution
+ qreal size = qRound(d->font.pointSizeF()*2.0);
+ d->font.setPointSizeF(size/2.0);
+ }
+
+ if (oldFont != d->font)
+ d->updateLayout();
+
+ emit fontChanged(d->sourceFont);
+}
+
+QString QSGText::text() const
+{
+ Q_D(const QSGText);
+ return d->text;
+}
+
+void QSGText::setText(const QString &n)
+{
+ Q_D(QSGText);
+ if (d->text == n)
+ return;
+
+ d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(n));
+ d->text = n;
+ if (isComponentComplete()) {
+ if (d->richText) {
+ d->ensureDoc();
+ d->doc->setText(n);
+ d->rightToLeftText = d->doc->toPlainText().isRightToLeft();
+ } else {
+ d->rightToLeftText = d->text.isRightToLeft();
+ }
+ d->determineHorizontalAlignment();
+ }
+ d->updateLayout();
+ emit textChanged(d->text);
+}
+
+QColor QSGText::color() const
+{
+ Q_D(const QSGText);
+ return d->color;
+}
+
+void QSGText::setColor(const QColor &color)
+{
+ Q_D(QSGText);
+ if (d->color == color)
+ return;
+
+ d->color = color;
+ d->invalidateImageCache();
+ emit colorChanged(d->color);
+}
+
+QSGText::TextStyle QSGText::style() const
+{
+ Q_D(const QSGText);
+ return d->style;
+}
+
+void QSGText::setStyle(QSGText::TextStyle style)
+{
+ Q_D(QSGText);
+ if (d->style == style)
+ return;
+
+ // changing to/from Normal requires the boundingRect() to change
+ if (isComponentComplete() && (d->style == Normal || style == Normal))
+ update();
+ d->style = style;
+ d->invalidateImageCache();
+ emit styleChanged(d->style);
+}
+
+QColor QSGText::styleColor() const
+{
+ Q_D(const QSGText);
+ return d->styleColor;
+}
+
+void QSGText::setStyleColor(const QColor &color)
+{
+ Q_D(QSGText);
+ if (d->styleColor == color)
+ return;
+
+ d->styleColor = color;
+ d->invalidateImageCache();
+ emit styleColorChanged(d->styleColor);
+}
+
+QSGText::HAlignment QSGText::hAlign() const
+{
+ Q_D(const QSGText);
+ return d->hAlign;
+}
+
+void QSGText::setHAlign(HAlignment align)
+{
+ Q_D(QSGText);
+ bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
+ d->hAlignImplicit = false;
+ if (d->setHAlign(align, forceAlign) && isComponentComplete())
+ d->updateLayout();
+}
+
+void QSGText::resetHAlign()
+{
+ Q_D(QSGText);
+ d->hAlignImplicit = true;
+ if (d->determineHorizontalAlignment() && isComponentComplete())
+ d->updateLayout();
+}
+
+QSGText::HAlignment QSGText::effectiveHAlign() const
+{
+ Q_D(const QSGText);
+ QSGText::HAlignment effectiveAlignment = d->hAlign;
+ if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
+ switch (d->hAlign) {
+ case QSGText::AlignLeft:
+ effectiveAlignment = QSGText::AlignRight;
+ break;
+ case QSGText::AlignRight:
+ effectiveAlignment = QSGText::AlignLeft;
+ break;
+ default:
+ break;
+ }
+ }
+ return effectiveAlignment;
+}
+
+bool QSGTextPrivate::setHAlign(QSGText::HAlignment alignment, bool forceAlign)
+{
+ Q_Q(QSGText);
+ if (hAlign != alignment || forceAlign) {
+ QSGText::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
+ hAlign = alignment;
+
+ emit q->horizontalAlignmentChanged(hAlign);
+ if (oldEffectiveHAlign != q->effectiveHAlign())
+ emit q->effectiveHorizontalAlignmentChanged();
+ return true;
+ }
+ return false;
+}
+
+bool QSGTextPrivate::determineHorizontalAlignment()
+{
+ Q_Q(QSGText);
+ if (hAlignImplicit && q->isComponentComplete()) {
+ bool alignToRight = text.isEmpty() ? QApplication::keyboardInputDirection() == Qt::RightToLeft : rightToLeftText;
+ return setHAlign(alignToRight ? QSGText::AlignRight : QSGText::AlignLeft);
+ }
+ return false;
+}
+
+void QSGTextPrivate::mirrorChange()
+{
+ Q_Q(QSGText);
+ if (q->isComponentComplete()) {
+ if (!hAlignImplicit && (hAlign == QSGText::AlignRight || hAlign == QSGText::AlignLeft)) {
+ updateLayout();
+ emit q->effectiveHorizontalAlignmentChanged();
+ }
+ }
+}
+
+QTextDocument *QSGTextPrivate::textDocument()
+{
+ return doc;
+}
+
+QSGText::VAlignment QSGText::vAlign() const
+{
+ Q_D(const QSGText);
+ return d->vAlign;
+}
+
+void QSGText::setVAlign(VAlignment align)
+{
+ Q_D(QSGText);
+ if (d->vAlign == align)
+ return;
+
+ d->vAlign = align;
+ emit verticalAlignmentChanged(align);
+}
+
+QSGText::WrapMode QSGText::wrapMode() const
+{
+ Q_D(const QSGText);
+ return d->wrapMode;
+}
+
+void QSGText::setWrapMode(WrapMode mode)
+{
+ Q_D(QSGText);
+ if (mode == d->wrapMode)
+ return;
+
+ d->wrapMode = mode;
+ d->updateLayout();
+
+ emit wrapModeChanged();
+}
+
+int QSGText::lineCount() const
+{
+ Q_D(const QSGText);
+ return d->lineCount;
+}
+
+bool QSGText::truncated() const
+{
+ Q_D(const QSGText);
+ return d->truncated;
+}
+
+int QSGText::maximumLineCount() const
+{
+ Q_D(const QSGText);
+ return d->maximumLineCount;
+}
+
+void QSGText::setMaximumLineCount(int lines)
+{
+ Q_D(QSGText);
+
+ d->maximumLineCountValid = lines==INT_MAX ? false : true;
+ if (d->maximumLineCount != lines) {
+ d->maximumLineCount = lines;
+ d->updateLayout();
+ emit maximumLineCountChanged();
+ }
+}
+
+void QSGText::resetMaximumLineCount()
+{
+ Q_D(QSGText);
+ setMaximumLineCount(INT_MAX);
+ d->elidePos = QPointF();
+ if (d->truncated != false) {
+ d->truncated = false;
+ emit truncatedChanged();
+ }
+}
+
+QSGText::TextFormat QSGText::textFormat() const
+{
+ Q_D(const QSGText);
+ return d->format;
+}
+
+void QSGText::setTextFormat(TextFormat format)
+{
+ Q_D(QSGText);
+ if (format == d->format)
+ return;
+ d->format = format;
+ bool wasRich = d->richText;
+ d->richText = format == RichText || (format == AutoText && Qt::mightBeRichText(d->text));
+
+ if (!wasRich && d->richText && isComponentComplete()) {
+ d->ensureDoc();
+ d->doc->setText(d->text);
+ }
+
+ d->updateLayout();
+
+ emit textFormatChanged(d->format);
+}
+
+QSGText::TextElideMode QSGText::elideMode() const
+{
+ Q_D(const QSGText);
+ return d->elideMode;
+}
+
+void QSGText::setElideMode(QSGText::TextElideMode mode)
+{
+ Q_D(QSGText);
+ if (mode == d->elideMode)
+ return;
+
+ d->elideMode = mode;
+ d->updateLayout();
+
+ emit elideModeChanged(d->elideMode);
+}
+
+/*! \internal */
+QRectF QSGText::boundingRect() const
+{
+ Q_D(const QSGText);
+
+ QRect rect = d->layedOutTextRect;
+ if (d->style != Normal)
+ rect.adjust(-1, 0, 1, 2);
+
+ // Could include font max left/right bearings to either side of rectangle.
+
+ int h = height();
+ switch (d->vAlign) {
+ case AlignTop:
+ break;
+ case AlignBottom:
+ rect.moveTop(h - rect.height());
+ break;
+ case AlignVCenter:
+ rect.moveTop((h - rect.height()) / 2);
+ break;
+ }
+
+ return QRectF(rect);
+}
+
+/*! \internal */
+void QSGText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ Q_D(QSGText);
+ if ((!d->internalWidthUpdate && newGeometry.width() != oldGeometry.width())
+ && (d->wrapMode != QSGText::NoWrap
+ || d->elideMode != QSGText::ElideNone
+ || d->hAlign != QSGText::AlignLeft)) {
+ if ((d->singleline || d->maximumLineCountValid) && d->elideMode != QSGText::ElideNone && widthValid()) {
+ // We need to re-elide
+ d->updateLayout();
+ } else {
+ // We just need to re-layout
+ d->updateSize();
+ }
+ }
+
+ QSGItem::geometryChanged(newGeometry, oldGeometry);
+}
+
+QSGNode *QSGText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
+{
+ Q_UNUSED(data);
+ Q_D(QSGText);
+
+ bool richTextAsImage = false;
+ if (d->richText) {
+ d->ensureDoc();
+ richTextAsImage = QSGTextNode::isComplexRichText(d->doc);
+ }
+
+ QRectF bounds = boundingRect();
+
+ // We need to make sure the layout is done in the current thread
+ if (d->layoutThread != QThread::currentThread())
+ d->updateLayout();
+
+ // XXX todo - some styled text can be done by the QSGTextNode
+ if (richTextAsImage || d->cacheAllTextAsImage || (!QSGDistanceFieldGlyphCache::distanceFieldEnabled() && d->style != Normal)) {
+ bool wasDirty = d->imageCacheDirty;
+
+ d->checkImageCache();
+
+ if (d->imageCache.isNull()) {
+ delete oldNode;
+ return 0;
+ }
+
+ QSGImageNode *node = 0;
+ if (!oldNode || d->nodeType != QSGTextPrivate::NodeIsTexture) {
+ delete oldNode;
+ node = QSGItemPrivate::get(this)->sceneGraphContext()->createImageNode();
+ d->texture = new QSGPlainTexture();
+ wasDirty = true;
+ d->nodeType = QSGTextPrivate::NodeIsTexture;
+ } else {
+ node = static_cast<QSGImageNode *>(oldNode);
+ Q_ASSERT(d->texture);
+ }
+
+ if (wasDirty) {
+ qobject_cast<QSGPlainTexture *>(d->texture)->setImage(d->imageCache.toImage());
+ node->setTexture(0);
+ node->setTexture(d->texture);
+ }
+
+ node->setTargetRect(QRectF(bounds.x(), bounds.y(), d->imageCache.width(), d->imageCache.height()));
+ node->setSourceRect(QRectF(0, 0, 1, 1));
+ node->setHorizontalWrapMode(QSGTexture::ClampToEdge);
+ node->setVerticalWrapMode(QSGTexture::ClampToEdge);
+ node->setFiltering(QSGTexture::Linear); // Nonsmooth text just ugly, so don't do that..
+ node->update();
+
+ return node;
+
+ } else {
+ QSGTextNode *node = 0;
+ if (!oldNode || d->nodeType != QSGTextPrivate::NodeIsText) {
+ delete oldNode;
+ node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext());
+ d->nodeType = QSGTextPrivate::NodeIsText;
+ } else {
+ node = static_cast<QSGTextNode *>(oldNode);
+ }
+
+ node->deleteContent();
+ node->setMatrix(QMatrix4x4());
+
+ if (d->richText) {
+
+ d->ensureDoc();
+ node->addTextDocument(bounds.topLeft(), d->doc, QColor(), d->style, d->styleColor);
+
+ } else {
+ node->addTextLayout(QPoint(0, bounds.y()), &d->layout, d->color, d->style, d->styleColor);
+ }
+
+ return node;
+ }
+}
+
+qreal QSGText::paintedWidth() const
+{
+ Q_D(const QSGText);
+ return d->paintedSize.width();
+}
+
+qreal QSGText::paintedHeight() const
+{
+ Q_D(const QSGText);
+ return d->paintedSize.height();
+}
+
+qreal QSGText::lineHeight() const
+{
+ Q_D(const QSGText);
+ return d->lineHeight;
+}
+
+void QSGText::setLineHeight(qreal lineHeight)
+{
+ Q_D(QSGText);
+
+ if ((d->lineHeight == lineHeight) || (lineHeight < 0.0))
+ return;
+
+ d->lineHeight = lineHeight;
+ d->updateLayout();
+ emit lineHeightChanged(lineHeight);
+}
+
+QSGText::LineHeightMode QSGText::lineHeightMode() const
+{
+ Q_D(const QSGText);
+ return d->lineHeightMode;
+}
+
+void QSGText::setLineHeightMode(LineHeightMode mode)
+{
+ Q_D(QSGText);
+ if (mode == d->lineHeightMode)
+ return;
+
+ d->lineHeightMode = mode;
+ d->updateLayout();
+
+ emit lineHeightModeChanged(mode);
+}
+
+/*!
+ Returns the number of resources (images) that are being loaded asynchronously.
+*/
+int QSGText::resourcesLoading() const
+{
+ Q_D(const QSGText);
+ return d->doc ? d->doc->resourcesLoading() : 0;
+}
+
+/*! \internal */
+void QSGText::componentComplete()
+{
+ Q_D(QSGText);
+ QSGItem::componentComplete();
+ if (d->updateOnComponentComplete) {
+ d->updateOnComponentComplete = false;
+ if (d->richText) {
+ d->ensureDoc();
+ d->doc->setText(d->text);
+ d->rightToLeftText = d->doc->toPlainText().isRightToLeft();
+ } else {
+ d->rightToLeftText = d->text.isRightToLeft();
+ }
+ d->determineHorizontalAlignment();
+ d->updateLayout();
+ }
+}
+
+/*! \internal */
+void QSGText::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_D(QSGText);
+
+ if (!d->richText || !d->doc || d->doc->documentLayout()->anchorAt(event->pos()).isEmpty()) {
+ event->setAccepted(false);
+ d->activeLink.clear();
+ } else {
+ d->activeLink = d->doc->documentLayout()->anchorAt(event->pos());
+ }
+
+ // ### may malfunction if two of the same links are clicked & dragged onto each other)
+
+ if (!event->isAccepted())
+ QSGItem::mousePressEvent(event);
+
+}
+
+/*! \internal */
+void QSGText::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_D(QSGText);
+
+ // ### confirm the link, and send a signal out
+ if (d->richText && d->doc && d->activeLink == d->doc->documentLayout()->anchorAt(event->pos()))
+ emit linkActivated(d->activeLink);
+ else
+ event->setAccepted(false);
+
+ if (!event->isAccepted())
+ QSGItem::mouseReleaseEvent(event);
+}
+
+QT_END_NAMESPACE
+
+#include "qsgtext.moc"