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;