An error occurred while loading the file. Please try again.
-
Vignesh Venkatasubramanian authored
Changing webmdec to use libwebm for WebM file parsing. Change-Id: I2a57a7b44dbed05eaa04409e1e75e6fc03b30fbc
dbd24710
qquicktext.cpp 79.23 KiB
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the QtQml 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/qqmlglobal_p.h>
#include <private/qsgadaptationlayer_p.h>
#include "qquicktextnode_p.h"
#include "qquickimage_p_p.h"
#include "qquicktextutil_p.h"
#include <QtQuick/private/qsgtexture_p.h>
#include <QtQml/qqmlinfo.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 <QtGui/qinputmethod.h>
#include <private/qtextengine_p.h>
#include <private/qquickstyledtext_p.h>
#include <QtQuick/private/qquickpixmapcache_p.h>
#include <qmath.h>
#include <limits.h>
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
QT_BEGIN_NAMESPACE
const QChar QQuickTextPrivate::elideChar = QChar(0x2026);
QQuickTextPrivate::QQuickTextPrivate()
: elideLayout(0), textLine(0), lineWidth(0)
, color(0xFF000000), linkColor(0xFF0000FF), styleColor(0xFF000000)
, lineCount(1), multilengthEos(-1)
, elideMode(QQuickText::ElideNone), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop)
, format(QQuickText::AutoText), wrapMode(QQuickText::NoWrap)
, style(QQuickText::Normal)
, renderType(QQuickText::QtRendering)
, updateType(UpdatePaintNode)
, maximumLineCountValid(false), updateOnComponentComplete(true), richText(false)
, styledText(false), widthExceeded(false), heightExceeded(false), internalWidthUpdate(false)
, requireImplicitSize(false), implicitWidthValid(false), implicitHeightValid(false)
, truncated(false), hAlignImplicit(true), rightToLeftText(false)
, layoutTextElided(false), textHasChanged(true), needToUpdateLayout(false), formatModifiesFontSize(false)
{
}
QQuickTextPrivate::ExtraData::ExtraData()
: lineHeight(1.0)
, doc(0)
, minimumPixelSize(12)
, minimumPointSize(12)
, nbActiveDownloads(0)
, maximumLineCount(INT_MAX)
, lineHeightMode(QQuickText::ProportionalHeight)
, fontSizeMode(QQuickText::FixedSize)
{
}
void QQuickTextPrivate::init()
{
Q_Q(QQuickText);
q->setAcceptedMouseButtons(Qt::LeftButton);
q->setFlag(QQuickItem::ItemHasContents);
}
QQuickTextDocumentWithImageResources::QQuickTextDocumentWithImageResources(QQuickItem *parent)
: QTextDocument(parent), outstanding(0)
{
setUndoRedoEnabled(false);
documentLayout()->registerHandler(QTextFormat::ImageObject, this);
}
QQuickTextDocumentWithImageResources::~QQuickTextDocumentWithImageResources()
{
if (!m_resources.isEmpty())
qDeleteAll(m_resources);
}
QVariant QQuickTextDocumentWithImageResources::loadResource(int type, const QUrl &name)
{
QQmlContext *context = qmlContext(parent());
QUrl url = m_baseUrl.resolved(name);
if (type == QTextDocument::ImageResource) {
QQuickPixmap *p = loadPixmap(context, url);
return p->image();
}
return QTextDocument::loadResource(type,url); // The *resolved* URL
}
void QQuickTextDocumentWithImageResources::requestFinished()
{
outstanding--;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
if (outstanding == 0) {
markContentsDirty(0, characterCount());
emit imagesLoaded();
}
}
void QQuickTextDocumentWithImageResources::clear()
{
clearResources();
QTextDocument::clear();
}
QSizeF QQuickTextDocumentWithImageResources::intrinsicSize(
QTextDocument *, int, const QTextFormat &format)
{
if (format.isImageFormat()) {
QTextImageFormat imageFormat = format.toImageFormat();
const bool hasWidth = imageFormat.hasProperty(QTextFormat::ImageWidth);
const int width = qRound(imageFormat.width());
const bool hasHeight = imageFormat.hasProperty(QTextFormat::ImageHeight);
const int height = qRound(imageFormat.height());
QSizeF size(width, height);
if (!hasWidth || !hasHeight) {
QQmlContext *context = qmlContext(parent());
QUrl url = m_baseUrl.resolved(QUrl(imageFormat.name()));
QQuickPixmap *p = loadPixmap(context, url);
if (!p->isReady()) {
if (!hasWidth)
size.setWidth(16);
if (!hasHeight)
size.setHeight(16);
return size;
}
QSize implicitSize = p->implicitSize();
if (!hasWidth) {
if (!hasHeight)
size.setWidth(implicitSize.width());
else
size.setWidth(qRound(height * (implicitSize.width() / (qreal) implicitSize.height())));
}
if (!hasHeight) {
if (!hasWidth)
size.setHeight(implicitSize.height());
else
size.setHeight(qRound(width * (implicitSize.height() / (qreal) implicitSize.width())));
}
}
return size;
}
return QSizeF();
}
void QQuickTextDocumentWithImageResources::drawObject(
QPainter *, const QRectF &, QTextDocument *, int, const QTextFormat &)
{
}
QImage QQuickTextDocumentWithImageResources::image(const QTextImageFormat &format)
{
QQmlContext *context = qmlContext(parent());
QUrl url = m_baseUrl.resolved(QUrl(format.name()));
QQuickPixmap *p = loadPixmap(context, url);
return p->image();
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
}
void QQuickTextDocumentWithImageResources::setBaseUrl(const QUrl &url, bool clear)
{
m_baseUrl = url;
if (clear) {
clearResources();
markContentsDirty(0, characterCount());
}
}
QQuickPixmap *QQuickTextDocumentWithImageResources::loadPixmap(
QQmlContext *context, const QUrl &url)
{
QHash<QUrl, QQuickPixmap *>::Iterator iter = m_resources.find(url);
if (iter == m_resources.end()) {
QQuickPixmap *p = new QQuickPixmap(context->engine(), url);
iter = m_resources.insert(url, p);
if (p->isLoading()) {
p->connectFinished(this, SLOT(requestFinished()));
outstanding++;
}
}
QQuickPixmap *p = *iter;
if (p->isError()) {
if (!errors.contains(url)) {
errors.insert(url);
qmlInfo(parent()) << p->error();
}
}
return p;
}
void QQuickTextDocumentWithImageResources::clearResources()
{
foreach (QQuickPixmap *pixmap, m_resources)
pixmap->clear(this);
qDeleteAll(m_resources);
m_resources.clear();
outstanding = 0;
}
void QQuickTextDocumentWithImageResources::setText(const QString &text)
{
clearResources();
#ifndef QT_NO_TEXTHTMLPARSER
setHtml(text);
#else
setPlainText(text);
#endif
}
QSet<QUrl> QQuickTextDocumentWithImageResources::errors;
QQuickTextPrivate::~QQuickTextPrivate()
{
delete elideLayout;
delete textLine; textLine = 0;
qDeleteAll(imgTags);
imgTags.clear();
}
qreal QQuickTextPrivate::getImplicitWidth() const
{
if (!requireImplicitSize) {
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
// 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->requireImplicitSize = true;
me->updateSize();
}
return implicitWidth;
}
qreal QQuickTextPrivate::getImplicitHeight() const
{
if (!requireImplicitSize) {
QQuickTextPrivate *me = const_cast<QQuickTextPrivate*>(this);
me->requireImplicitSize = true;
me->updateSize();
}
return implicitHeight;
}
/*!
\qmlproperty enumeration QtQuick2::Text::renderType
Override the default rendering type for this component.
Supported render types are:
\list
\li Text.QtRendering - the default
\li Text.NativeRendering
\endlist
Select Text.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
*/
QQuickText::RenderType QQuickText::renderType() const
{
Q_D(const QQuickText);
return d->renderType;
}
void QQuickText::setRenderType(QQuickText::RenderType renderType)
{
Q_D(QQuickText);
if (d->renderType == renderType)
return;
d->renderType = renderType;
emit renderTypeChanged();
if (isComponentComplete())
d->updateLayout();
}
void QQuickText::q_imagesLoaded()
{
Q_D(QQuickText);
d->updateLayout();
}
void QQuickTextPrivate::updateLayout()
{
Q_Q(QQuickText);
if (!q->isComponentComplete()) {
updateOnComponentComplete = true;
return;
}
updateOnComponentComplete = false;
layoutTextElided = false;
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
if (!visibleImgTags.isEmpty())
visibleImgTags.clear();
needToUpdateLayout = false;
// Setup instance of QTextLayout for all cases other than richtext
if (!richText) {
if (textHasChanged) {
if (styledText && !text.isEmpty()) {
layout.setFont(font);
// needs temporary bool because formatModifiesFontSize is in a bit-field
bool fontSizeModified = false;
QQuickStyledText::parse(text, layout, imgTags, q->baseUrl(), qmlContext(q), !maximumLineCountValid, &fontSizeModified);
formatModifiesFontSize = fontSizeModified;
} else {
layout.clearAdditionalFormats();
QString tmp = text;
multilengthEos = tmp.indexOf(QLatin1Char('\x9c'));
if (multilengthEos != -1) {
tmp = tmp.mid(0, multilengthEos);
tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
} else if (tmp.contains(QLatin1Char('\n'))) {
// Replace always does a detach. Checking for the new line character first
// means iterating over those items again if found but prevents a realloc
// otherwise.
tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
}
layout.setText(tmp);
}
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 = extra->doc->begin(); it != extra->doc->end(); it = it.next()) {
QTextCursor cursor(it);
cursor.mergeBlockFormat(blockFormat);
}
}
updateSize();
if (needToUpdateLayout) {
needToUpdateLayout = false;
textHasChanged = true;
updateLayout();
}
}
void QQuickText::imageDownloadFinished()
{
Q_D(QQuickText);
(d->extra->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->extra.isAllocated() && d->extra->nbActiveDownloads == 0) {
bool needToUpdateLayout = false;
foreach (QQuickStyledTextImgTag *img, d->visibleImgTags) {
if (!img->size.isValid()) {
img->size = img->pix->implicitSize();
needToUpdateLayout = true;
}
}
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
if (needToUpdateLayout) {
d->textHasChanged = true;
d->updateLayout();
} else {
d->updateType = QQuickTextPrivate::UpdatePaintNode;
update();
}
}
}
void QQuickTextPrivate::updateBaseline(qreal baseline, qreal dy)
{
Q_Q(QQuickText);
qreal yoff = 0;
if (q->heightValid()) {
if (vAlign == QQuickText::AlignBottom)
yoff = dy;
else if (vAlign == QQuickText::AlignVCenter)
yoff = dy/2;
}
q->setBaselineOffset(baseline + yoff);
}
void QQuickTextPrivate::updateSize()
{
Q_Q(QQuickText);
if (!q->isComponentComplete()) {
updateOnComponentComplete = true;
return;
}
if (!requireImplicitSize) {
emit q->implicitWidthChanged();
emit q->implicitHeightChanged();
// if the implicitWidth is used, then updateSize() has already been called (recursively)
if (requireImplicitSize)
return;
}
if (text.isEmpty() && !isLineLaidOutConnected() && fontSizeMode() == QQuickText::FixedSize) {
// How much more expensive is it to just do a full layout on an empty string here?
// There may be subtle differences in the height and baseline calculations between
// QTextLayout and QFontMetrics and the number of variables that can affect the size
// and position of a line is increasing.
QFontMetricsF fm(font);
qreal fontHeight = qCeil(fm.height()); // QScriptLine and therefore QTextLine rounds up
if (!richText) { // line height, so we will as well.
fontHeight = lineHeightMode() == QQuickText::FixedHeight
? lineHeight()
: fontHeight * lineHeight();
}
updateBaseline(fm.ascent(), q->height() - fontHeight);
q->setImplicitSize(0, fontHeight);
layedOutTextRect = QRectF(0, 0, 0, fontHeight);
emit q->contentSizeChanged();
updateType = UpdatePaintNode;
q->update();
return;
}
QSizeF size(0, 0);
QSizeF previousSize = layedOutTextRect.size();
//setup instance of QTextLayout for all cases other than richtext
if (!richText) {
qreal baseline = 0;
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
QRectF textRect = setupTextLayout(&baseline);
if (internalWidthUpdate) // probably the result of a binding loop, but by letting it
return; // get this far we'll get a warning to that effect if it is.
layedOutTextRect = textRect;
size = textRect.size();
updateBaseline(baseline, q->height() - size.height());
} else {
widthExceeded = true; // always relayout rich text on width changes..
heightExceeded = false; // rich text layout isn't affected by height changes.
ensureDoc();
extra->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));
option.setUseDesignMetrics(renderType != QQuickText::NativeRendering);
extra->doc->setDefaultTextOption(option);
qreal naturalWidth = 0;
if (requireImplicitSize && q->widthValid()) {
extra->doc->setTextWidth(-1);
naturalWidth = extra->doc->idealWidth();
const bool wasInLayout = internalWidthUpdate;
internalWidthUpdate = true;
q->setImplicitWidth(naturalWidth);
internalWidthUpdate = wasInLayout;
}
if (internalWidthUpdate)
return;
if (wrapMode != QQuickText::NoWrap && q->widthValid())
extra->doc->setTextWidth(q->width());
else
extra->doc->setTextWidth(extra->doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug)
widthExceeded = extra->doc->textWidth() < extra->doc->idealWidth();
QSizeF dsize = extra->doc->size();
layedOutTextRect = QRectF(QPointF(0,0), dsize);
size = QSizeF(extra->doc->idealWidth(),dsize.height());
QFontMetricsF fm(font);
updateBaseline(fm.ascent(), q->height() - size.height());
//### need to confirm cost of always setting these for richText
internalWidthUpdate = true;
qreal iWidth = -1;
if (!q->widthValid())
iWidth = size.width();
if (iWidth > -1)
q->setImplicitSize(iWidth, size.height());
internalWidthUpdate = false;
if (iWidth == -1)
q->setImplicitHeight(size.height());
}
if (layedOutTextRect.size() != previousSize)
emit q->contentSizeChanged();
updateType = UpdatePaintNode;
q->update();
}
QQuickTextLine::QQuickTextLine()
: QObject(), m_line(0), m_height(0)
{
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
}
void QQuickTextLine::setLine(QTextLine *line)
{
m_line = line;
}
void QQuickTextLine::setLineOffset(int offset)
{
m_lineOffset = offset;
}
int QQuickTextLine::number() const
{
if (m_line)
return m_line->lineNumber() + m_lineOffset;
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)
{
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
if (m_line)
m_line->setPosition(QPointF(m_line->x(), y));
}
void QQuickText::doLayout()
{
Q_D(QQuickText);
d->updateSize();
}
bool QQuickTextPrivate::isLineLaidOutConnected()
{
Q_Q(QQuickText);
IS_SIGNAL_CONNECTED(q, QQuickText, lineLaidOut, (QQuickTextLine *));
}
void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, int lineOffset)
{
Q_Q(QQuickText);
if (!textLine)
textLine = new QQuickTextLine;
textLine->setLine(&line);
textLine->setY(height);
textLine->setHeight(0);
textLine->setLineOffset(lineOffset);
// 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());
else
textLine->setWidth(INT_MAX);
if (lineHeight() != 1.0)
textLine->setHeight((lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight());
emit q->lineLaidOut(textLine);
height += textLine->height();
}
void QQuickTextPrivate::elideFormats(
const int start, const int length, int offset, QList<QTextLayout::FormatRange> *elidedFormats)
{
const int end = start + length;
QList<QTextLayout::FormatRange> formats = layout.additionalFormats();
for (int i = 0; i < formats.count(); ++i) {
QTextLayout::FormatRange format = formats.at(i);
const int formatLength = qMin(format.start + format.length, end) - qMax(format.start, start);
if (formatLength > 0) {
format.start = qMax(offset, format.start - start + offset);
format.length = formatLength;
elidedFormats->append(format);
}
}
}
QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QTextLine *nextLine) const
{
if (nextLine) {
return layout.engine()->elidedText(
Qt::TextElideMode(elideMode),
QFixed::fromReal(lineWidth),
0,
line.textStart(),
line.textLength() + nextLine->textLength());
} else {
QString elideText = layout.text().mid(line.textStart(), line.textLength());
if (!styledText) {
// QFontMetrics won't help eliding styled text.
elideText[elideText.length() - 1] = elideChar;
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
// Appending the elide character may push the line over the maximum width
// in which case the elided text will need to be elided.
QFontMetricsF metrics(layout.font());
if (metrics.width(elideChar) + line.naturalTextWidth() >= lineWidth)
elideText = metrics.elidedText(elideText, Qt::TextElideMode(elideMode), lineWidth);
}
return elideText;
}
}
/*!
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).
*/
QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
{
Q_Q(QQuickText);
bool singlelineElide = elideMode != QQuickText::ElideNone && q->widthValid();
bool multilineElide = elideMode == QQuickText::ElideRight
&& q->widthValid()
&& (q->heightValid() || maximumLineCountValid);
if ((!requireImplicitSize || (implicitWidthValid && implicitHeightValid))
&& ((singlelineElide && q->width() <= 0.) || (multilineElide && q->heightValid() && q->height() <= 0.))) {
// we are elided and we have a zero width or height
widthExceeded = q->widthValid() && q->width() <= 0.;
heightExceeded = q->heightValid() && q->height() <= 0.;
if (!truncated) {
truncated = true;
emit q->truncatedChanged();
}
if (lineCount) {
lineCount = 0;
emit q->lineCountChanged();
}
QFontMetricsF fm(font);
qreal height = (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : qCeil(fm.height()) * lineHeight();
*baseline = fm.ascent();
return QRectF(0, 0, 0, height);
}
bool shouldUseDesignMetrics = renderType != QQuickText::NativeRendering;
layout.setCacheEnabled(true);
QTextOption textOption = layout.textOption();
if (textOption.alignment() != q->effectiveHAlign()
|| textOption.wrapMode() != QTextOption::WrapMode(wrapMode)
|| textOption.useDesignMetrics() != shouldUseDesignMetrics) {
textOption.setAlignment(Qt::Alignment(q->effectiveHAlign()));
textOption.setWrapMode(QTextOption::WrapMode(wrapMode));
textOption.setUseDesignMetrics(shouldUseDesignMetrics);
layout.setTextOption(textOption);
}
if (layout.font() != font)
layout.setFont(font);
lineWidth = (q->widthValid() || implicitWidthValid) && q->width() > 0
? q->width()
: FLT_MAX;
qreal maxHeight = q->heightValid() ? q->height() : FLT_MAX;
const bool customLayout = isLineLaidOutConnected();
const bool wasTruncated = truncated;
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
bool canWrap = wrapMode != QQuickText::NoWrap && q->widthValid();
bool horizontalFit = fontSizeMode() & QQuickText::HorizontalFit && q->widthValid();
bool verticalFit = fontSizeMode() & QQuickText::VerticalFit
&& (q->heightValid() || (maximumLineCountValid && canWrap));
const bool pixelSize = font.pixelSize() != -1;
QString layoutText = layout.text();
int largeFont = pixelSize ? font.pixelSize() : font.pointSize();
int smallFont = fontSizeMode() != QQuickText::FixedSize
? qMin(pixelSize ? minimumPixelSize() : minimumPointSize(), largeFont)
: largeFont;
int scaledFontSize = largeFont;
widthExceeded = q->width() <= 0 && (singlelineElide || canWrap || horizontalFit);
heightExceeded = q->height() <= 0 && (multilineElide || verticalFit);
QRectF br;
QFont scaledFont = font;
int visibleCount = 0;
bool elide;
qreal height = 0;
QString elideText;
bool once = true;
int elideStart = 0;
int elideEnd = 0;
int eos = multilengthEos;
// Repeated layouts with reduced font sizes or abbreviated strings may be required if the text
// doesn't fit within the item dimensions, or a binding to implicitWidth/Height changes
// the item dimensions.
for (;;) {
if (!once) {
if (pixelSize)
scaledFont.setPixelSize(scaledFontSize);
else
scaledFont.setPointSize(scaledFontSize);
if (layout.font() != scaledFont)
layout.setFont(scaledFont);
}
layout.beginLayout();
bool wrapped = false;
bool truncateHeight = false;
truncated = false;
elide = false;
int unwrappedLineCount = 1;
int maxLineCount = maximumLineCount();
height = 0;
qreal naturalHeight = 0;
qreal previousHeight = 0;
br = QRectF();
QRectF unelidedRect;
QTextLine line = layout.createLine();
for (visibleCount = 1; ; ++visibleCount) {
if (customLayout) {
setupCustomLineGeometry(line, naturalHeight);
} else {
setLineGeometry(line, lineWidth, naturalHeight);
}
unelidedRect = br.united(line.naturalTextRect());
// Elide the previous line if the accumulated height of the text exceeds the height
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
// of the element.
if (multilineElide && naturalHeight > maxHeight && visibleCount > 1) {
elide = true;
heightExceeded = true;
if (eos != -1) // There's an abbreviated string available, skip the rest as it's
break; // all going to be discarded.
truncated = true;
truncateHeight = true;
visibleCount -= 1;
QTextLine previousLine = layout.lineAt(visibleCount - 1);
elideText = layoutText.at(line.textStart() - 1) != QChar::LineSeparator
? elidedText(lineWidth, previousLine, &line)
: elidedText(lineWidth, previousLine);
elideStart = previousLine.textStart();
// elideEnd isn't required for right eliding.
height = previousHeight;
break;
}
const QTextLine previousLine = line;
line = layout.createLine();
if (!line.isValid()) {
if (singlelineElide && visibleCount == 1 && previousLine.naturalTextWidth() > lineWidth) {
// Elide a single previousLine of text if its width exceeds the element width.
elide = true;
widthExceeded = true;
if (eos != -1) // There's an abbreviated string available.
break;
truncated = true;
elideText = layout.engine()->elidedText(
Qt::TextElideMode(elideMode),
QFixed::fromReal(lineWidth),
0,
previousLine.textStart(),
previousLine.textLength());
elideStart = previousLine.textStart();
elideEnd = elideStart + previousLine.textLength();
} else {
br = unelidedRect;
height = naturalHeight;
}
break;
} else {
const bool wrappedLine = layoutText.at(line.textStart() - 1) != QChar::LineSeparator;
wrapped |= wrappedLine;
if (!wrappedLine)
++unwrappedLineCount;
// Stop if the maximum number of lines has been reached and elide the last line
// if enabled.
if (visibleCount == maxLineCount) {
truncated = true;
heightExceeded |= wrapped;
if (multilineElide) {
elide = true;
if (eos != -1) // There's an abbreviated string available
break;
elideText = wrappedLine
? elidedText(lineWidth, previousLine, &line)
: elidedText(lineWidth, previousLine);
elideStart = previousLine.textStart();
// elideEnd isn't required for right eliding.
} else {
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
br = unelidedRect;
height = naturalHeight;
}
break;
}
}
br = unelidedRect;
previousHeight = height;
height = naturalHeight;
}
widthExceeded |= wrapped;
// Save the implicit size of the text on the first layout only.
if (once) {
once = false;
// If implicit sizes are required layout any additional lines up to the maximum line
// count.
if ((requireImplicitSize) && line.isValid() && unwrappedLineCount < maxLineCount) {
// Layout the remainder of the wrapped lines up to maxLineCount to get the implicit
// height.
for (int lineCount = layout.lineCount(); lineCount < maxLineCount; ++lineCount) {
line = layout.createLine();
if (!line.isValid())
break;
if (layoutText.at(line.textStart() - 1) == QChar::LineSeparator)
++unwrappedLineCount;
setLineGeometry(line, lineWidth, naturalHeight);
}
// Create the remainder of the unwrapped lines up to maxLineCount to get the
// implicit width.
if (line.isValid() && layoutText.at(line.textStart() + line.textLength()) != QChar::LineSeparator)
line = layout.createLine();
for (; line.isValid() && unwrappedLineCount <= maxLineCount; ++unwrappedLineCount)
line = layout.createLine();
}
layout.endLayout();
const qreal naturalWidth = layout.maximumWidth();
bool wasInLayout = internalWidthUpdate;
internalWidthUpdate = true;
q->setImplicitSize(naturalWidth, naturalHeight);
internalWidthUpdate = wasInLayout;
// Update any variables that are dependent on the validity of the width or height.
singlelineElide = elideMode != QQuickText::ElideNone && q->widthValid();
multilineElide = elideMode == QQuickText::ElideRight
&& q->widthValid()
&& (q->heightValid() || maximumLineCountValid);
canWrap = wrapMode != QQuickText::NoWrap && q->widthValid();
horizontalFit = fontSizeMode() & QQuickText::HorizontalFit && q->widthValid();
verticalFit = fontSizeMode() & QQuickText::VerticalFit
&& (q->heightValid() || (maximumLineCountValid && canWrap));
const qreal oldWidth = lineWidth;
const qreal oldHeight = maxHeight;
lineWidth = q->widthValid() && q->width() > 0 ? q->width() : naturalWidth;
maxHeight = q->heightValid() ? q->height() : FLT_MAX;
// If the width of the item has changed and it's possible the result of wrapping,
// eliding, or scaling has changed do another layout.
if ((lineWidth < qMin(oldWidth, naturalWidth) || (widthExceeded && lineWidth > oldWidth))
&& (singlelineElide || multilineElide || canWrap || horizontalFit)) {
widthExceeded = false;
heightExceeded = false;
continue;
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
}
// If the height of the item has changed and it's possible the result of eliding,
// line count truncation or scaling has changed, do another layout.
if ((maxHeight < qMin(oldHeight, naturalHeight) || (heightExceeded && maxHeight > oldHeight))
&& (multilineElide || (canWrap && maximumLineCountValid))) {
widthExceeded = false;
heightExceeded = false;
continue;
}
// If the horizontal alignment is not left and the width was not valid we need to relayout
// now that we know the maximum line width.
if (!implicitWidthValid && lineCount > 1 && q->effectiveHAlign() != QQuickText::AlignLeft) {
widthExceeded = false;
heightExceeded = false;
continue;
}
} else {
layout.endLayout();
}
// If the next needs to be elided and there's an abbreviated string available
// go back and do another layout with the abbreviated string.
if (eos != -1 && elide) {
int start = eos + 1;
eos = text.indexOf(QLatin1Char('\x9c'), start);
layoutText = text.mid(start, eos != -1 ? eos - start : -1);
layoutText.replace(QLatin1Char('\n'), QChar::LineSeparator);
layout.setText(layoutText);
textHasChanged = true;
continue;
}
br.moveTop(0);
if (!horizontalFit && !verticalFit)
break;
// Try and find a font size that better fits the dimensions of the element.
if (horizontalFit) {
if (unelidedRect.width() > lineWidth || (!verticalFit && wrapped)) {
widthExceeded = true;
largeFont = scaledFontSize - 1;
if (smallFont > largeFont)
break;
scaledFontSize = (smallFont + largeFont) / 2;
if (pixelSize)
scaledFont.setPixelSize(scaledFontSize);
else
scaledFont.setPointSize(scaledFontSize);
continue;
} else if (!verticalFit) {
smallFont = scaledFontSize;
if (smallFont == largeFont)
break;
scaledFontSize = (smallFont + largeFont + 1) / 2;
}
}
if (verticalFit) {
if (truncateHeight || unelidedRect.height() > maxHeight) {
heightExceeded = true;
largeFont = scaledFontSize - 1;
if (smallFont > largeFont)
break;
scaledFontSize = (smallFont + largeFont) / 2;
} else {
smallFont = scaledFontSize;
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
if (smallFont == largeFont)
break;
scaledFontSize = (smallFont + largeFont + 1) / 2;
}
}
}
implicitWidthValid = true;
implicitHeightValid = true;
if (eos != multilengthEos)
truncated = true;
if (elide) {
if (!elideLayout) {
elideLayout = new QTextLayout;
elideLayout->setCacheEnabled(true);
}
if (styledText) {
QList<QTextLayout::FormatRange> formats;
switch (elideMode) {
case QQuickText::ElideRight:
elideFormats(elideStart, elideText.length() - 1, 0, &formats);
break;
case QQuickText::ElideLeft:
elideFormats(elideEnd - elideText.length() + 1, elideText.length() - 1, 1, &formats);
break;
case QQuickText::ElideMiddle: {
const int index = elideText.indexOf(elideChar);
if (index != -1) {
elideFormats(elideStart, index, 0, &formats);
elideFormats(
elideEnd - elideText.length() + index + 1,
elideText.length() - index - 1,
index + 1,
&formats);
}
break;
}
default:
break;
}
elideLayout->setAdditionalFormats(formats);
}
elideLayout->setFont(layout.font());
elideLayout->setTextOption(layout.textOption());
elideLayout->setText(elideText);
elideLayout->beginLayout();
QTextLine elidedLine = elideLayout->createLine();
elidedLine.setPosition(QPointF(0, height));
if (customLayout) {
setupCustomLineGeometry(elidedLine, height, visibleCount - 1);
} else {
setLineGeometry(elidedLine, lineWidth, height);
}
elideLayout->endLayout();
br = br.united(elidedLine.naturalTextRect());
if (visibleCount == 1)
layout.clearLayout();
} else {
delete elideLayout;
elideLayout = 0;
}
QTextLine firstLine = visibleCount == 1 && elideLayout
? elideLayout->lineAt(0)
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
: layout.lineAt(0);
Q_ASSERT(firstLine.isValid());
*baseline = firstLine.y() + firstLine.ascent();
if (!customLayout)
br.setHeight(height);
//Update the number of visible lines
if (lineCount != visibleCount) {
lineCount = visibleCount;
emit q->lineCountChanged();
}
if (truncated != wasTruncated)
emit q->truncatedChanged();
return br;
}
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<QQuickStyledTextImgTag *> imagesInLine;
foreach (QQuickStyledTextImgTag *image, imgTags) {
if (image->position >= line.textStart() &&
image->position < line.textStart() + line.textLength()) {
if (!image->pix) {
QUrl url = q->baseUrl().resolved(image->url);
image->pix = new QQuickPixmap(qmlEngine(q), url, image->size);
if (image->pix->isLoading()) {
image->pix->connectFinished(q, SLOT(imageDownloadFinished()));
if (!extra.isAllocated() || !extra->nbActiveDownloads)
extra.value().nbActiveDownloads = 0;
extra->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 == QQuickStyledTextImgTag::Top)
image->pos.setY(0);
else if (image->align == QQuickStyledTextImgTag::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()));
}
1191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
}
foreach (QQuickStyledTextImgTag *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();
}
/*!
Ensures the QQuickTextPrivate::doc variable is set to a valid text document
*/
void QQuickTextPrivate::ensureDoc()
{
if (!extra.isAllocated() || !extra->doc) {
Q_Q(QQuickText);
extra.value().doc = new QQuickTextDocumentWithImageResources(q);
extra->doc->setPageSize(QSizeF(0, 0));
extra->doc->setDocumentMargin(0);
extra->doc->setBaseUrl(q->baseUrl());
qmlobject_connect(extra->doc, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()),
q, QQuickText, SLOT(q_imagesLoaded()));
}
}
/*!
\qmlclass Text QQuickText
\inqmlmodule QtQuick 2
\ingroup qtquick-visual
\inherits Item
\brief Specifies how to add formatted text to a scene
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.
1261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
\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.
*/
/*!
\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
\li number (read-only)
\li x
\li y
\li width
\li height
\endlist
For example, this will move the first 5 lines of a Text item 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 qml/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.
1331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
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
\li Font.Light
\li Font.Normal - the default
\li Font.DemiBold
\li Font.Bold
\li 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.
1401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
*/
/*!
\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
\li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
\li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
\li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
\li Font.SmallCaps - This alters the text to be rendered in small-caps type.
\li 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) {
// if the format changes the size of the text
// with headings or <font> tag, we need to re-parse
if (d->formatModifiesFontSize)
d->textHasChanged = true;
d->implicitWidthValid = false;
d->implicitHeightValid = false;
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
1471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
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->extra->doc->setText(n);
d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
} else {
d->rightToLeftText = d->text.isRightToLeft();
}
d->determineHorizontalAlignment();
}
d->textHasChanged = true;
d->implicitWidthValid = false;
d->implicitHeightValid = false;
qDeleteAll(d->imgTags);
d->imgTags.clear();
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 QColor::fromRgba(d->color);
}
void QQuickText::setColor(const QColor &color)
{
Q_D(QQuickText);
QRgb rgb = color.rgba();
if (d->color == rgb)
return;