diff options
Diffstat (limited to 'src/quick/items/qquicktext.cpp')
-rw-r--r-- | src/quick/items/qquicktext.cpp | 1950 |
1 files changed, 1950 insertions, 0 deletions
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp new file mode 100644 index 0000000000..d01512740d --- /dev/null +++ b/src/quick/items/qquicktext.cpp @@ -0,0 +1,1950 @@ +/**************************************************************************** +** +** 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$ +** 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicktext_p.h" +#include "qquicktext_p_p.h" + +#include <QtQuick/private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> +#include "qquicktextnode_p.h" +#include "qquickimage_p_p.h" +#include <QtQuick/private/qsgtexture_p.h> + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qevent.h> +#include <QtGui/qabstracttextdocumentlayout.h> +#include <QtGui/qpainter.h> +#include <QtGui/qtextdocument.h> +#include <QtGui/qtextobject.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qguiapplication.h> + +#include <private/qdeclarativestyledtext_p.h> +#include <QtQuick/private/qdeclarativepixmapcache_p.h> + +#include <qmath.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled; + +class QQuickTextDocumentWithImageResources : public QTextDocument { + Q_OBJECT + +public: + QQuickTextDocumentWithImageResources(QQuickText *parent); + virtual ~QQuickTextDocumentWithImageResources(); + + 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(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) +DEFINE_BOOL_CONFIG_OPTION(enableImageCache, QML_ENABLE_TEXT_IMAGE_CACHE); + +QString QQuickTextPrivate::elideChar = QString(0x2026); + +QQuickTextPrivate::QQuickTextPrivate() +: color((QRgb)0), style(QQuickText::Normal), hAlign(QQuickText::AlignLeft), + vAlign(QQuickText::AlignTop), elideMode(QQuickText::ElideNone), + format(QQuickText::AutoText), wrapMode(QQuickText::NoWrap), lineHeight(1), + lineHeightMode(QQuickText::ProportionalHeight), lineCount(1), maximumLineCount(INT_MAX), + maximumLineCountValid(false), + texture(0), + imageCacheDirty(false), updateOnComponentComplete(true), + richText(false), styledText(false), singleline(false), cacheAllTextAsImage(true), 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) + +#if defined(Q_OS_MAC) +, layoutThread(0), paintingThread(0) +#endif + +{ + cacheAllTextAsImage = enableImageCache(); +} + +void QQuickTextPrivate::init() +{ + Q_Q(QQuickText); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QQuickItem::ItemHasContents); +} + +QQuickTextDocumentWithImageResources::QQuickTextDocumentWithImageResources(QQuickText *parent) +: QTextDocument(parent), outstanding(0) +{ + setUndoRedoEnabled(false); +} + +QQuickTextDocumentWithImageResources::~QQuickTextDocumentWithImageResources() +{ + if (!m_resources.isEmpty()) + qDeleteAll(m_resources); +} + +QVariant QQuickTextDocumentWithImageResources::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(url, p); + + if (p->isLoading()) { + p->connectFinished(this, SLOT(requestFinished())); + outstanding++; + } + } + + QDeclarativePixmap *p = *iter; + if (p->isReady()) { + return p->image(); + } else if (p->isError()) { + if (!errors.contains(url)) { + errors.insert(url); + qmlInfo(parent()) << p->error(); + } + } + } + + return QTextDocument::loadResource(type,url); // The *resolved* URL +} + +void QQuickTextDocumentWithImageResources::requestFinished() +{ + outstanding--; + if (outstanding == 0) { + QQuickText *textItem = static_cast<QQuickText*>(parent()); + QString text = textItem->text(); +#ifndef QT_NO_TEXTHTMLPARSER + setHtml(text); +#else + setPlainText(text); +#endif + QQuickTextPrivate *d = QQuickTextPrivate::get(textItem); + d->updateLayout(); + } +} + +void QQuickTextDocumentWithImageResources::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> QQuickTextDocumentWithImageResources::errors; + +QQuickTextPrivate::~QQuickTextPrivate() +{ + delete elipsisLayout; + delete textLine; textLine = 0; +} + +qreal QQuickTextPrivate::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 + QQuickTextPrivate *me = const_cast<QQuickTextPrivate*>(this); + me->requireImplicitWidth = true; + me->updateSize(); + } + return implicitWidth; +} + +void QQuickTextPrivate::updateLayout() +{ + Q_Q(QQuickText); + if (!q->isComponentComplete()) { + updateOnComponentComplete = true; + return; + } + updateOnComponentComplete = false; + layoutTextElided = false; + // Setup instance of QTextLayout for all cases other than richtext + if (!richText) { + if (elipsisLayout) { + delete elipsisLayout; + elipsisLayout = 0; + } + layout.clearLayout(); + layout.setFont(font); + if (!styledText) { + QString tmp = text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + singleline = !tmp.contains(QChar::LineSeparator); + if (singleline && !maximumLineCountValid && elideMode != QQuickText::ElideNone && q->widthValid() && wrapMode == QQuickText::NoWrap) { + 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; + if (textHasChanged) { + QDeclarativeStyledText::parse(text, layout); + textHasChanged = false; + } + } + } else { + ensureDoc(); + QTextBlockFormat::LineHeightTypes type; + type = lineHeightMode == QQuickText::FixedHeight ? QTextBlockFormat::FixedHeight : QTextBlockFormat::ProportionalHeight; + QTextBlockFormat blockFormat; + blockFormat.setLineHeight((lineHeightMode == QQuickText::FixedHeight ? lineHeight : lineHeight * 100), type); + for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) { + QTextCursor cursor(it); + cursor.mergeBlockFormat(blockFormat); + } + } + + updateSize(); +} + +void QQuickTextPrivate::updateSize() +{ + Q_Q(QQuickText); + + 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->setImplicitSize(0, fm.height()); + paintedSize = QSize(0, fm.height()); + emit q->paintedSizeChanged(); + q->update(); + return; + } + + int dy = q->height(); + QSize size(0, 0); + +#if defined(Q_OS_MAC) + layoutThread = QThread::currentThread(); +#endif + + //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); + QQuickText::HAlignment horizontalAlignment = q->effectiveHAlign(); + if (rightToLeftText) { + if (horizontalAlignment == QQuickText::AlignLeft) + horizontalAlignment = QQuickText::AlignRight; + else if (horizontalAlignment == QQuickText::AlignRight) + horizontalAlignment = QQuickText::AlignLeft; + } + QTextOption option; + option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign)); + option.setWrapMode(QTextOption::WrapMode(wrapMode)); + if (!cacheAllTextAsImage && !richTextAsImage && !qmlDisableDistanceField()) + option.setUseDesignMetrics(true); + doc->setDefaultTextOption(option); + if (requireImplicitWidth && q->widthValid()) { + doc->setTextWidth(-1); + naturalWidth = doc->idealWidth(); + } + if (wrapMode != QQuickText::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 == QQuickText::AlignBottom) + yoff = dy; + else if (vAlign == QQuickText::AlignVCenter) + yoff = dy/2; + } + q->setBaselineOffset(fm.ascent() + yoff); + + //### need to comfirm cost of always setting these for richText + internalWidthUpdate = true; + qreal iWidth = -1; + if (!q->widthValid()) + iWidth = size.width(); + else if (requireImplicitWidth) + iWidth = naturalWidth; + if (iWidth > -1) + q->setImplicitSize(iWidth, size.height()); + internalWidthUpdate = false; + + if (iWidth == -1) + q->setImplicitHeight(size.height()); + if (paintedSize != size) { + paintedSize = size; + emit q->paintedSizeChanged(); + } + q->update(); +} + +QQuickTextLine::QQuickTextLine() + : QObject(), m_line(0), m_height(0) +{ +} + +void QQuickTextLine::setLine(QTextLine *line) +{ + m_line = line; +} + +int QQuickTextLine::number() const +{ + if (m_line) + return m_line->lineNumber(); + return 0; +} + +qreal QQuickTextLine::width() const +{ + if (m_line) + return m_line->width(); + return 0; +} + +void QQuickTextLine::setWidth(qreal width) +{ + if (m_line) + m_line->setLineWidth(width); +} + +qreal QQuickTextLine::height() const +{ + if (m_height) + return m_height; + if (m_line) + return m_line->height(); + return 0; +} + +void QQuickTextLine::setHeight(qreal height) +{ + if (m_line) + m_line->setPosition(QPointF(m_line->x(), m_line->y() - m_line->height() + height)); + m_height = height; +} + +qreal QQuickTextLine::x() const +{ + if (m_line) + return m_line->x(); + return 0; +} + +void QQuickTextLine::setX(qreal x) +{ + if (m_line) + m_line->setPosition(QPointF(x, m_line->y())); +} + +qreal QQuickTextLine::y() const +{ + if (m_line) + return m_line->y(); + return 0; +} + +void QQuickTextLine::setY(qreal y) +{ + if (m_line) + m_line->setPosition(QPointF(m_line->x(), y)); +} + +void QQuickText::doLayout() +{ + Q_D(QQuickText); + d->updateSize(); +} + +bool QQuickTextPrivate::isLineLaidOutConnected() +{ + static int idx = this->signalIndex("lineLaidOut(QQuickTextLine*)"); + return this->isSignalConnected(idx); +} + +void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, qreal elideWidth = 0) +{ + Q_Q(QQuickText); + +#if defined(Q_OS_MAC) + if (QThread::currentThread() != paintingThread) { +#endif + if (!line.lineNumber()) + linesRects.clear(); + + if (!textLine) + textLine = new QQuickTextLine; + textLine->setLine(&line); + textLine->setY(height); + textLine->setHeight(0); + + // use the text item's width by default if it has one and wrap is on + if (q->widthValid() && q->wrapMode() != QQuickText::NoWrap) + textLine->setWidth(q->width() - elideWidth); + else + textLine->setWidth(INT_MAX); + if (lineHeight != 1.0) + textLine->setHeight((lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight); + + emit q->lineLaidOut(textLine); + + linesRects << QRectF(textLine->x(), textLine->y(), textLine->width(), textLine->height()); + height += textLine->height(); + +#if defined(Q_OS_MAC) + } else { + if (line.lineNumber() < linesRects.count()) { + QRectF r = linesRects.at(line.lineNumber()); + line.setLineWidth(r.width()); + line.setPosition(r.topLeft()); + } + } +#endif +} + +/*! + Lays out the QQuickTextPrivate::layout QTextLayout in the constraints of the QQuickText. + + Returns the size of the final text. This can be used to position the text vertically (the text is + already absolutely positioned horizontally). +*/ +QRect QQuickTextPrivate::setupTextLayout() +{ + // ### text layout handling should be profiled and optimized as needed + // what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine); + Q_Q(QQuickText); + 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)); + if (!cacheAllTextAsImage && !richTextAsImage && !qmlDisableDistanceField()) + textOption.setUseDesignMetrics(true); + layout.setTextOption(textOption); + + 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); + } + + qreal height = 0; + QRectF br; + + bool truncate = false; + bool customLayout = isLineLaidOutConnected(); + bool elideEnabled = elideMode == QQuickText::ElideRight && q->widthValid(); + + layout.beginLayout(); + if (!lineWidth) + lineWidth = INT_MAX; + int linesLeft = maximumLineCount; + int visibleTextLength = 0; + forever { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + + visibleCount++; + + qreal preLayoutHeight = height; + 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; + } + + bool elide = false; + if (elideEnabled && q->heightValid() && height > q->height()) { + // This line does not fit in the remaining area. + elide = true; + if (visibleCount > 1) { + --visibleCount; + height = preLayoutHeight; + line.setLineWidth(0.0); + line.setPosition(QPointF(FLT_MAX,FLT_MAX)); + line = layout.lineAt(visibleCount-1); + } + } else { + visibleTextLength += line.textLength(); + } + + if (elide || (maximumLineCountValid && --linesLeft == 0)) { + if (visibleTextLength < text.length()) { + truncate = true; + if (elideEnabled) { + qreal elideWidth = fm.width(elideChar); + // Need to correct for alignment + if (customLayout) + setupCustomLineGeometry(line, height, elideWidth); + else + 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()); + } + elidePos.setY(line.position().y()); + if (!elipsisLayout) + elipsisLayout = new QTextLayout(elideChar, layout.font()); + elipsisLayout->beginLayout(); + QTextLine el = elipsisLayout->createLine(); + el.setPosition(elidePos); + elipsisLayout->endLayout(); + br = br.united(el.naturalTextRect()); + } + br = br.united(line.naturalTextRect()); + break; + } + } + br = br.united(line.naturalTextRect()); + } + layout.endLayout(); + + //Update truncated + if (truncated != truncate) { + truncated = truncate; + emit q->truncatedChanged(); + } + + if (!customLayout) + 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 QQuickTextPrivate::layout QTextLayout. + If \a drawStyle is true, the style color overrides all colors in the document. +*/ +QPixmap QQuickTextPrivate::textLayoutImage(bool drawStyle) +{ + QSize size = layedOutTextRect.size(); + + //paint text + QPixmap img(size); + if (!size.isEmpty()) { + img.fill(Qt::transparent); +/*#ifdef Q_OS_MAC // Fails on CocoaX64 + bool oldSmooth = qt_applefontsmoothing_enabled; + qt_applefontsmoothing_enabled = false; +#endif*/ + QPainter p(&img); +/*#ifdef Q_OS_MAC // Fails on CocoaX64 + qt_applefontsmoothing_enabled = oldSmooth; +#endif*/ + drawTextLayout(&p, QPointF(-layedOutTextRect.x(),0), drawStyle); + } + return img; +} + +/*! + Paints the QQuickTextPrivate::layout QTextLayout into \a painter at \a pos. If + \a drawStyle is true, the style color overrides all colors in the document. +*/ +void QQuickTextPrivate::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 QQuickTextPrivate::doc QTextDocument. + If \a drawStyle is true, the style color overrides all colors in the document. +*/ +QPixmap QQuickTextPrivate::textDocumentImage(bool drawStyle) +{ + QSize size = doc->size().toSize(); + + //paint text + QPixmap img(size); + img.fill(Qt::transparent); +/*#ifdef Q_OS_MAC // Fails on CocoaX64 + bool oldSmooth = qt_applefontsmoothing_enabled; + qt_applefontsmoothing_enabled = false; +#endif*/ + QPainter p(&img); +/*#ifdef Q_OS_MAC // Fails on CocoaX64 + 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 QQuickTextPrivate::invalidateImageCache() +{ + Q_Q(QQuickText); + + if (richTextAsImage || cacheAllTextAsImage || (qmlDisableDistanceField() && style != QQuickText::Normal)) { // If actually using the image cache + if (imageCacheDirty) + return; + + imageCacheDirty = true; + + if (q->isComponentComplete()) + QCoreApplication::postEvent(q, new QEvent(QEvent::User)); + } else if (q->isComponentComplete()) + q->update(); +} + +/*! + Tests if the image cache is dirty, and repaints it if it is. +*/ +void QQuickTextPrivate::checkImageCache() +{ + Q_Q(QQuickText); + + if (!imageCacheDirty) + return; + + if (text.isEmpty()) { + + imageCache = QPixmap(); + + } else { + + QPixmap textImage; + QPixmap styledImage; + + if (richText) { + textImage = textDocumentImage(false); + if (style != QQuickText::Normal) + styledImage = textDocumentImage(true); //### should use styleColor + } else { + textImage = textLayoutImage(false); + if (style != QQuickText::Normal) + styledImage = textLayoutImage(true); //### should use styleColor + } + + switch (style) { + case QQuickText::Outline: + imageCache = drawOutline(textImage, styledImage); + break; + case QQuickText::Sunken: + imageCache = drawOutline(textImage, styledImage, -1); + break; + case QQuickText::Raised: + imageCache = drawOutline(textImage, styledImage, 1); + break; + default: + imageCache = textImage; + break; + } + + } + + imageCacheDirty = false; + textureImageCacheDirty = true; + q->update(); +} + +/*! + Ensures the QQuickTextPrivate::doc variable is set to a valid text document +*/ +void QQuickTextPrivate::ensureDoc() +{ + if (!doc) { + Q_Q(QQuickText); + doc = new QQuickTextDocumentWithImageResources(q); + doc->setDocumentMargin(0); + } +} + +/*! + Draw \a styleSource as an outline around \a source and return the new image. +*/ +QPixmap QQuickTextPrivate::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 QQuickTextPrivate::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; +} + +/*! + \qmlclass Text QQuickText + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The Text item allows you to add formatted text to a scene. + \inherits Item + + Text items can display both plain and rich text. For example, red text with + a specific font and size can be defined like this: + + \qml + Text { + text: "Hello World!" + font.family: "Helvetica" + font.pointSize: 24 + color: "red" + } + \endqml + + Rich text is defined using HTML-style markup: + + \qml + Text { + text: "<b>Hello</b> <i>World!</i>" + } + \endqml + + \image declarative-text.png + + If height and width are not explicitly set, Text will attempt to determine how + much room is needed and set it accordingly. Unless \l wrapMode is set, it will always + prefer width to height (all text will be placed on a single line). + + The \l elide property can alternatively be used to fit a single line of + plain text to a set width. + + Note that the \l{Supported HTML Subset} is limited. Also, if the text contains + HTML img tags that load remote images, the text is reloaded. + + Text provides read-only text. For editable text, see \l TextEdit. + + \sa {declarative/text/fonts}{Fonts example} +*/ +QQuickText::QQuickText(QQuickItem *parent) +: QQuickImplicitSizeItem(*(new QQuickTextPrivate), parent) +{ + Q_D(QQuickText); + d->init(); +} + +QQuickText::~QQuickText() +{ +} + +/*! + \qmlproperty bool QtQuick2::Text::clip + This property holds whether the text is clipped. + + Note that if the text does not fit in the bounding rectangle it will be abruptly chopped. + + If you want to display potentially long text in a limited space, you probably want to use \c elide instead. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::smooth + + This property holds whether the text is smoothly scaled or transformed. + + Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +/*! + \qmlsignal QtQuick2::Text::onLineLaidOut(line) + + This handler is called for every line during the layout process. + This gives the opportunity to position and resize a line as it is being laid out. + It can for example be used to create columns or lay out text around objects. + + The properties of a line are: + \list + \o number (read-only) + \o x + \o y + \o width + \o height + \endlist + + For example, this will move the first 5 lines of a text element by 100 pixels to the right: + \code + onLineLaidOut: { + if (line.number < 5) { + line.x = line.x + 100 + line.width = line.width - 100 + } + } + \endcode +*/ + +/*! + \qmlsignal QtQuick2::Text::onLinkActivated(string link) + + This handler is called when the user clicks on a link embedded in the text. + The link must be in rich text or HTML format and the + \a link string provides access to the particular link. + + \snippet doc/src/snippets/declarative/text/onLinkActivated.qml 0 + + The example code will display the text + "The main website is at \l{http://qt.nokia.com}{Nokia Qt DF}." + + Clicking on the highlighted link will output + \tt{http://qt.nokia.com link activated} to the console. +*/ + +/*! + \qmlproperty string QtQuick2::Text::font.family + + Sets the family name of the font. + + The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". + If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. + If the family isn't available a family will be set using the font matching algorithm. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.bold + + Sets whether the font weight is bold. +*/ + +/*! + \qmlproperty enumeration QtQuick2::Text::font.weight + + Sets the font's weight. + + The weight can be one of: + \list + \o Font.Light + \o Font.Normal - the default + \o Font.DemiBold + \o Font.Bold + \o Font.Black + \endlist + + \qml + Text { text: "Hello"; font.weight: Font.DemiBold } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.italic + + Sets whether the font has an italic style. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.underline + + Sets whether the text is underlined. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.strikeout + + Sets whether the font has a strikeout style. +*/ + +/*! + \qmlproperty real QtQuick2::Text::font.pointSize + + Sets the font size in points. The point size must be greater than zero. +*/ + +/*! + \qmlproperty int QtQuick2::Text::font.pixelSize + + Sets the font size in pixels. + + Using this function makes the font device dependent. + Use \c pointSize to set the size of the font in a device independent manner. +*/ + +/*! + \qmlproperty real QtQuick2::Text::font.letterSpacing + + Sets the letter spacing for the font. + + Letter spacing changes the default spacing between individual letters in the font. + A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. +*/ + +/*! + \qmlproperty real QtQuick2::Text::font.wordSpacing + + Sets the word spacing for the font. + + Word spacing changes the default spacing between individual words. + A positive value increases the word spacing by a corresponding amount of pixels, + while a negative value decreases the inter-word spacing accordingly. +*/ + +/*! + \qmlproperty enumeration QtQuick2::Text::font.capitalization + + Sets the capitalization for the text. + + \list + \o Font.MixedCase - This is the normal text rendering option where no capitalization change is applied. + \o Font.AllUppercase - This alters the text to be rendered in all uppercase type. + \o Font.AllLowercase - This alters the text to be rendered in all lowercase type. + \o Font.SmallCaps - This alters the text to be rendered in small-caps type. + \o Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character. + \endlist + + \qml + Text { text: "Hello"; font.capitalization: Font.AllLowercase } + \endqml +*/ +QFont QQuickText::font() const +{ + Q_D(const QQuickText); + return d->sourceFont; +} + +void QQuickText::setFont(const QFont &font) +{ + Q_D(QQuickText); + if (d->sourceFont == font) + return; + + d->sourceFont = font; + QFont oldFont = d->font; + d->font = font; + + 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); +} + +/*! + \qmlproperty string QtQuick2::Text::text + + The text to display. Text supports both plain and rich text strings. + + The item will try to automatically determine whether the text should + be treated as styled text. This determination is made using Qt::mightBeRichText(). +*/ +QString QQuickText::text() const +{ + Q_D(const QQuickText); + return d->text; +} + +void QQuickText::setText(const QString &n) +{ + Q_D(QQuickText); + if (d->text == n) + return; + + d->richText = d->format == RichText; + d->styledText = d->format == StyledText || (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(); + d->richTextAsImage = enableImageCache(); + } else { + d->rightToLeftText = d->text.isRightToLeft(); + } + d->determineHorizontalAlignment(); + } + d->textHasChanged = true; + d->updateLayout(); + emit textChanged(d->text); +} + +/*! + \qmlproperty color QtQuick2::Text::color + + The text color. + + An example of green text defined using hexadecimal notation: + \qml + Text { + color: "#00FF00" + text: "green text" + } + \endqml + + An example of steel blue text defined using an SVG color name: + \qml + Text { + color: "steelblue" + text: "blue text" + } + \endqml +*/ +QColor QQuickText::color() const +{ + Q_D(const QQuickText); + return d->color; +} + +void QQuickText::setColor(const QColor &color) +{ + Q_D(QQuickText); + if (d->color == color) + return; + + d->color = color; + d->invalidateImageCache(); + emit colorChanged(d->color); +} +/*! + \qmlproperty enumeration QtQuick2::Text::style + + Set an additional text style. + + Supported text styles are: + \list + \o Text.Normal - the default + \o Text.Outline + \o Text.Raised + \o Text.Sunken + \endlist + + \qml + Row { + Text { font.pointSize: 24; text: "Normal" } + Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" } + Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" } + Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" } + } + \endqml + + \image declarative-textstyle.png +*/ +QQuickText::TextStyle QQuickText::style() const +{ + Q_D(const QQuickText); + return d->style; +} + +void QQuickText::setStyle(QQuickText::TextStyle style) +{ + Q_D(QQuickText); + 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); +} + +/*! + \qmlproperty color QtQuick2::Text::styleColor + + Defines the secondary color used by text styles. + + \c styleColor is used as the outline color for outlined text, and as the + shadow color for raised or sunken text. If no style has been set, it is not + used at all. + + \qml + Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" } + \endqml + + \sa style + */ +QColor QQuickText::styleColor() const +{ + Q_D(const QQuickText); + return d->styleColor; +} + +void QQuickText::setStyleColor(const QColor &color) +{ + Q_D(QQuickText); + if (d->styleColor == color) + return; + + d->styleColor = color; + d->invalidateImageCache(); + emit styleColorChanged(d->styleColor); +} + +/*! + \qmlproperty enumeration QtQuick2::Text::horizontalAlignment + \qmlproperty enumeration QtQuick2::Text::verticalAlignment + \qmlproperty enumeration QtQuick2::Text::effectiveHorizontalAlignment + + Sets the horizontal and vertical alignment of the text within the Text items + width and height. By default, the text is vertically aligned to the top. Horizontal + alignment follows the natural alignment of the text, for example text that is read + from left to right will be aligned to the left. + + The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight, \c Text.AlignHCenter and + \c Text.AlignJustify. The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom + and \c Text.AlignVCenter. + + Note that for a single line of text, the size of the text is the area of the text. In this common case, + all alignments are equivalent. If you want the text to be, say, centered in its parent, then you will + need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to + that of the parent. + + When using the attached property LayoutMirroring::enabled to mirror application + layouts, the horizontal alignment of text will also be mirrored. However, the property + \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment + of Text, use the read-only property \c effectiveHorizontalAlignment. +*/ +QQuickText::HAlignment QQuickText::hAlign() const +{ + Q_D(const QQuickText); + return d->hAlign; +} + +void QQuickText::setHAlign(HAlignment align) +{ + Q_D(QQuickText); + bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; + d->hAlignImplicit = false; + if (d->setHAlign(align, forceAlign) && isComponentComplete()) + d->updateLayout(); +} + +void QQuickText::resetHAlign() +{ + Q_D(QQuickText); + d->hAlignImplicit = true; + if (isComponentComplete() && d->determineHorizontalAlignment()) + d->updateLayout(); +} + +QQuickText::HAlignment QQuickText::effectiveHAlign() const +{ + Q_D(const QQuickText); + QQuickText::HAlignment effectiveAlignment = d->hAlign; + if (!d->hAlignImplicit && d->effectiveLayoutMirror) { + switch (d->hAlign) { + case QQuickText::AlignLeft: + effectiveAlignment = QQuickText::AlignRight; + break; + case QQuickText::AlignRight: + effectiveAlignment = QQuickText::AlignLeft; + break; + default: + break; + } + } + return effectiveAlignment; +} + +bool QQuickTextPrivate::setHAlign(QQuickText::HAlignment alignment, bool forceAlign) +{ + Q_Q(QQuickText); + if (hAlign != alignment || forceAlign) { + QQuickText::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); + hAlign = alignment; + + emit q->horizontalAlignmentChanged(hAlign); + if (oldEffectiveHAlign != q->effectiveHAlign()) + emit q->effectiveHorizontalAlignmentChanged(); + return true; + } + return false; +} + +bool QQuickTextPrivate::determineHorizontalAlignment() +{ + if (hAlignImplicit) { + bool alignToRight = text.isEmpty() ? QGuiApplication::keyboardInputDirection() == Qt::RightToLeft : rightToLeftText; + return setHAlign(alignToRight ? QQuickText::AlignRight : QQuickText::AlignLeft); + } + return false; +} + +void QQuickTextPrivate::mirrorChange() +{ + Q_Q(QQuickText); + if (q->isComponentComplete()) { + if (!hAlignImplicit && (hAlign == QQuickText::AlignRight || hAlign == QQuickText::AlignLeft)) { + updateLayout(); + emit q->effectiveHorizontalAlignmentChanged(); + } + } +} + +QTextDocument *QQuickTextPrivate::textDocument() +{ + return doc; +} + +QQuickText::VAlignment QQuickText::vAlign() const +{ + Q_D(const QQuickText); + return d->vAlign; +} + +void QQuickText::setVAlign(VAlignment align) +{ + Q_D(QQuickText); + if (d->vAlign == align) + return; + + d->vAlign = align; + emit verticalAlignmentChanged(align); +} + +/*! + \qmlproperty enumeration QtQuick2::Text::wrapMode + + Set this property to wrap the text to the Text item's width. The text will only + wrap if an explicit width has been set. wrapMode can be one of: + + \list + \o Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l paintedWidth will exceed a set width. + \o Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l paintedWidth will exceed a set width. + \o Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word. + \o Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word. + \endlist +*/ +QQuickText::WrapMode QQuickText::wrapMode() const +{ + Q_D(const QQuickText); + return d->wrapMode; +} + +void QQuickText::setWrapMode(WrapMode mode) +{ + Q_D(QQuickText); + if (mode == d->wrapMode) + return; + + d->wrapMode = mode; + d->updateLayout(); + + emit wrapModeChanged(); +} + +/*! + \qmlproperty int QtQuick2::Text::lineCount + + Returns the number of lines visible in the text item. + + This property is not supported for rich text. + + \sa maximumLineCount +*/ +int QQuickText::lineCount() const +{ + Q_D(const QQuickText); + return d->lineCount; +} + +/*! + \qmlproperty bool QtQuick2::Text::truncated + + Returns true if the text has been truncated due to \l maximumLineCount + or \l elide. + + This property is not supported for rich text. + + \sa maximumLineCount, elide +*/ +bool QQuickText::truncated() const +{ + Q_D(const QQuickText); + return d->truncated; +} + +/*! + \qmlproperty int QtQuick2::Text::maximumLineCount + + Set this property to limit the number of lines that the text item will show. + If elide is set to Text.ElideRight, the text will be elided appropriately. + By default, this is the value of the largest possible integer. + + This property is not supported for rich text. + + \sa lineCount, elide +*/ +int QQuickText::maximumLineCount() const +{ + Q_D(const QQuickText); + return d->maximumLineCount; +} + +void QQuickText::setMaximumLineCount(int lines) +{ + Q_D(QQuickText); + + d->maximumLineCountValid = lines==INT_MAX ? false : true; + if (d->maximumLineCount != lines) { + d->maximumLineCount = lines; + d->updateLayout(); + emit maximumLineCountChanged(); + } +} + +void QQuickText::resetMaximumLineCount() +{ + Q_D(QQuickText); + setMaximumLineCount(INT_MAX); + d->elidePos = QPointF(); + if (d->truncated != false) { + d->truncated = false; + emit truncatedChanged(); + } +} + +/*! + \qmlproperty enumeration QtQuick2::Text::textFormat + + The way the text property should be displayed. + + Supported text formats are: + + \list + \o Text.AutoText (default) + \o Text.PlainText + \o Text.StyledText + \o Text.RichText + \endlist + + If the text format is \c Text.AutoText the text element + will automatically determine whether the text should be treated as + styled text. This determination is made using Qt::mightBeRichText(). + + Text.StyledText is an optimized format supporting some basic text + styling markup, in the style of html 3.2: + + \code + <b></b> - bold + <i></i> - italic + <br> - new line + <p> - paragraph + <u> - underlined text + <font color="color_name" size="1-7"></font> + <h1> to <h6> - headers + <a href=""> - anchor + <ol type="">, <ul type=""> and <li> - ordered and unordered lists + > < & + \endcode + + \c Text.StyledText parser is strict, requiring tags to be correctly nested. + + \table + \row + \o + \qml +Column { + Text { + font.pointSize: 24 + text: "<b>Hello</b> <i>World!</i>" + } + Text { + font.pointSize: 24 + textFormat: Text.RichText + text: "<b>Hello</b> <i>World!</i>" + } + Text { + font.pointSize: 24 + textFormat: Text.PlainText + text: "<b>Hello</b> <i>World!</i>" + } +} + \endqml + \o \image declarative-textformat.png + \endtable +*/ +QQuickText::TextFormat QQuickText::textFormat() const +{ + Q_D(const QQuickText); + return d->format; +} + +void QQuickText::setTextFormat(TextFormat format) +{ + Q_D(QQuickText); + if (format == d->format) + return; + d->format = format; + bool wasRich = d->richText; + d->richText = format == RichText; + d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text)); + + if (isComponentComplete()) { + if (!wasRich && d->richText) { + d->ensureDoc(); + d->doc->setText(d->text); + d->rightToLeftText = d->doc->toPlainText().isRightToLeft(); + d->richTextAsImage = enableImageCache(); + } else { + d->rightToLeftText = d->text.isRightToLeft(); + } + d->determineHorizontalAlignment(); + } + d->updateLayout(); + + emit textFormatChanged(d->format); +} + +/*! + \qmlproperty enumeration QtQuick2::Text::elide + + Set this property to elide parts of the text fit to the Text item's width. + The text will only elide if an explicit width has been set. + + This property cannot be used with rich text. + + Eliding can be: + \list + \o Text.ElideNone - the default + \o Text.ElideLeft + \o Text.ElideMiddle + \o Text.ElideRight + \endlist + + If this property is set to Text.ElideRight, it can be used with multiline + text. The text will only elide if \c maximumLineCount, or \c height has been set. + If both \c maximumLineCount and \c height are set, \c maximumLineCount will + apply unless the lines do not fit in the height allowed. + + If the text is a multi-length string, and the mode is not \c Text.ElideNone, + the first string that fits will be used, otherwise the last will be elided. + + Multi-length strings are ordered from longest to shortest, separated by the + Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}). +*/ +QQuickText::TextElideMode QQuickText::elideMode() const +{ + Q_D(const QQuickText); + return d->elideMode; +} + +void QQuickText::setElideMode(QQuickText::TextElideMode mode) +{ + Q_D(QQuickText); + if (mode == d->elideMode) + return; + + d->elideMode = mode; + d->updateLayout(); + + emit elideModeChanged(d->elideMode); +} + +/*! \internal */ +QRectF QQuickText::boundingRect() const +{ + Q_D(const QQuickText); + + 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 QQuickText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickText); + bool elide = d->elideMode != QQuickText::ElideNone && widthValid(); + if ((!d->internalWidthUpdate + && (newGeometry.width() != oldGeometry.width() || (elide && newGeometry.height() != oldGeometry.height()))) + && (d->wrapMode != QQuickText::NoWrap + || d->elideMode != QQuickText::ElideNone + || d->hAlign != QQuickText::AlignLeft)) { + if ((d->singleline || d->maximumLineCountValid || heightValid()) && elide) { + // We need to re-elide + d->updateLayout(); + } else { + // We just need to re-layout + d->updateSize(); + } + } + + QQuickItem::geometryChanged(newGeometry, oldGeometry); +} + +QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + Q_D(QQuickText); + + if (d->text.isEmpty()) { + delete oldNode; + return 0; + } + + QRectF bounds = boundingRect(); + + // We need to make sure the layout is done in the current thread +#if defined(Q_OS_MAC) + d->paintingThread = QThread::currentThread(); + if (d->layoutThread != d->paintingThread) + d->updateLayout(); +#endif + + // XXX todo - some styled text can be done by the QQuickTextNode + if (d->richTextAsImage || d->cacheAllTextAsImage || (qmlDisableDistanceField() && d->style != Normal)) { + bool wasDirty = d->textureImageCacheDirty; + d->textureImageCacheDirty = false; + + if (d->imageCache.isNull()) { + delete oldNode; + return 0; + } + + QSGImageNode *node = 0; + if (!oldNode || d->nodeType != QQuickTextPrivate::NodeIsTexture) { + delete oldNode; + node = QQuickItemPrivate::get(this)->sceneGraphContext()->createImageNode(); + d->texture = new QSGPlainTexture(); + wasDirty = true; + d->nodeType = QQuickTextPrivate::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 { + QQuickTextNode *node = 0; + if (!oldNode || d->nodeType != QQuickTextPrivate::NodeIsText) { + delete oldNode; + node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext()); + d->nodeType = QQuickTextPrivate::NodeIsText; + } else { + node = static_cast<QQuickTextNode *>(oldNode); + } + + node->deleteContent(); + node->setMatrix(QMatrix4x4()); + + if (d->richText) { + d->ensureDoc(); + node->addTextDocument(bounds.topLeft(), d->doc, d->color, d->style, d->styleColor); + + } else { + node->addTextLayout(QPoint(0, bounds.y()), &d->layout, d->color, d->style, d->styleColor); + if (d->elipsisLayout) + node->addTextLayout(QPoint(0, bounds.y()), d->elipsisLayout, d->color, d->style, d->styleColor); + } + + return node; + } +} + +bool QQuickText::event(QEvent *e) +{ + Q_D(QQuickText); + if (e->type() == QEvent::User) { + d->checkImageCache(); + return true; + } else { + return QQuickImplicitSizeItem::event(e); + } +} + +/*! + \qmlproperty real QtQuick2::Text::paintedWidth + + Returns the width of the text, including width past the width + which is covered due to insufficient wrapping if WrapMode is set. +*/ +qreal QQuickText::paintedWidth() const +{ + Q_D(const QQuickText); + return d->paintedSize.width(); +} + +/*! + \qmlproperty real QtQuick2::Text::paintedHeight + + Returns the height of the text, including height past the height + which is covered due to there being more text than fits in the set height. +*/ +qreal QQuickText::paintedHeight() const +{ + Q_D(const QQuickText); + return d->paintedSize.height(); +} + +/*! + \qmlproperty real QtQuick2::Text::lineHeight + + Sets the line height for the text. + The value can be in pixels or a multiplier depending on lineHeightMode. + + The default value is a multiplier of 1.0. + The line height must be a positive value. +*/ +qreal QQuickText::lineHeight() const +{ + Q_D(const QQuickText); + return d->lineHeight; +} + +void QQuickText::setLineHeight(qreal lineHeight) +{ + Q_D(QQuickText); + + if ((d->lineHeight == lineHeight) || (lineHeight < 0.0)) + return; + + d->lineHeight = lineHeight; + d->updateLayout(); + emit lineHeightChanged(lineHeight); +} + +/*! + \qmlproperty enumeration QtQuick2::Text::lineHeightMode + + This property determines how the line height is specified. + The possible values are: + + \list + \o Text.ProportionalHeight (default) - this sets the spacing proportional to the + line (as a multiplier). For example, set to 2 for double spacing. + \o Text.FixedHeight - this sets the line height to a fixed line height (in pixels). + \endlist +*/ +QQuickText::LineHeightMode QQuickText::lineHeightMode() const +{ + Q_D(const QQuickText); + return d->lineHeightMode; +} + +void QQuickText::setLineHeightMode(LineHeightMode mode) +{ + Q_D(QQuickText); + 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 QQuickText::resourcesLoading() const +{ + Q_D(const QQuickText); + return d->doc ? d->doc->resourcesLoading() : 0; +} + +/*! \internal */ +void QQuickText::componentComplete() +{ + Q_D(QQuickText); + if (d->updateOnComponentComplete) { + if (d->richText) { + d->ensureDoc(); + d->doc->setText(d->text); + d->rightToLeftText = d->doc->toPlainText().isRightToLeft(); + d->richTextAsImage = enableImageCache(); + } else { + d->rightToLeftText = d->text.isRightToLeft(); + } + d->determineHorizontalAlignment(); + } + QQuickItem::componentComplete(); + if (d->updateOnComponentComplete) + d->updateLayout(); +} + + +QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) +{ + if (styledText) { + for (int i = 0; i < layout.lineCount(); ++i) { + QTextLine line = layout.lineAt(i); + if (line.naturalTextRect().contains(mousePos)) { + int charPos = line.xToCursor(mousePos.x()); + foreach (const QTextLayout::FormatRange &formatRange, layout.additionalFormats()) { + if (formatRange.format.isAnchor() + && charPos >= formatRange.start + && charPos <= formatRange.start + formatRange.length) { + return formatRange.format.anchorHref(); + } + } + break; + } + } + } + return QString(); +} + +bool QQuickTextPrivate::isLinkActivatedConnected() +{ + static int idx = this->signalIndex("linkActivated(QString)"); + return this->isSignalConnected(idx); +} + +/*! \internal */ +void QQuickText::mousePressEvent(QMouseEvent *event) +{ + Q_D(QQuickText); + + if (d->isLinkActivatedConnected()) { + if (d->styledText) + d->activeLink = d->anchorAt(event->localPos()); + else if (d->richText && d->doc) + d->activeLink = d->doc->documentLayout()->anchorAt(event->localPos()); + } + + if (d->activeLink.isEmpty()) + event->setAccepted(false); + + // ### may malfunction if two of the same links are clicked & dragged onto each other) + + if (!event->isAccepted()) + QQuickItem::mousePressEvent(event); + +} + +/*! \internal */ +void QQuickText::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QQuickText); + + // ### confirm the link, and send a signal out + + QString link; + if (d->isLinkActivatedConnected()) { + if (d->styledText) + link = d->anchorAt(event->localPos()); + else if (d->richText && d->doc) + link = d->doc->documentLayout()->anchorAt(event->localPos()); + } + + if (!link.isEmpty() && d->activeLink == link) + emit linkActivated(d->activeLink); + else + event->setAccepted(false); + + if (!event->isAccepted()) + QQuickItem::mouseReleaseEvent(event); +} + +QT_END_NAMESPACE + +#include "qquicktext.moc" |