| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $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 "qquicktextdocument_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> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE) |
| |
| const QChar QQuickTextPrivate::elideChar = QChar(0x2026); |
| |
| QQuickTextPrivate::QQuickTextPrivate() |
| : fontInfo(font), elideLayout(nullptr), textLine(nullptr), 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(QQuickTextUtil::textRenderType<QQuickText>()) |
| , 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) |
| , polishSize(false) |
| , updateSizeRecursionGuard(false) |
| { |
| implicitAntialiasing = true; |
| } |
| |
| QQuickTextPrivate::ExtraData::ExtraData() |
| : padding(0) |
| , topPadding(0) |
| , leftPadding(0) |
| , rightPadding(0) |
| , bottomPadding(0) |
| , explicitTopPadding(false) |
| , explicitLeftPadding(false) |
| , explicitRightPadding(false) |
| , explicitBottomPadding(false) |
| , lineHeight(1.0) |
| , doc(nullptr) |
| , minimumPixelSize(12) |
| , minimumPointSize(12) |
| , nbActiveDownloads(0) |
| , maximumLineCount(INT_MAX) |
| , lineHeightValid(false) |
| , lineHeightMode(QQuickText::ProportionalHeight) |
| , fontSizeMode(QQuickText::FixedSize) |
| { |
| } |
| |
| void QQuickTextPrivate::init() |
| { |
| Q_Q(QQuickText); |
| q->setAcceptedMouseButtons(Qt::LeftButton); |
| q->setFlag(QQuickItem::ItemHasContents); |
| } |
| |
| QQuickTextPrivate::~QQuickTextPrivate() |
| { |
| delete elideLayout; |
| delete textLine; textLine = nullptr; |
| |
| if (extra.isAllocated()) { |
| qDeleteAll(extra->imgTags); |
| extra->imgTags.clear(); |
| } |
| } |
| |
| qreal QQuickTextPrivate::getImplicitWidth() const |
| { |
| if (!requireImplicitSize) { |
| // 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; |
| } |
| |
| qreal QQuickTextPrivate::availableWidth() const |
| { |
| Q_Q(const QQuickText); |
| return q->width() - q->leftPadding() - q->rightPadding(); |
| } |
| |
| qreal QQuickTextPrivate::availableHeight() const |
| { |
| Q_Q(const QQuickText); |
| return q->height() - q->topPadding() - q->bottomPadding(); |
| } |
| |
| void QQuickTextPrivate::setTopPadding(qreal value, bool reset) |
| { |
| Q_Q(QQuickText); |
| qreal oldPadding = q->topPadding(); |
| if (!reset || extra.isAllocated()) { |
| extra.value().topPadding = value; |
| extra.value().explicitTopPadding = !reset; |
| } |
| if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { |
| updateSize(); |
| emit q->topPaddingChanged(); |
| } |
| } |
| |
| void QQuickTextPrivate::setLeftPadding(qreal value, bool reset) |
| { |
| Q_Q(QQuickText); |
| qreal oldPadding = q->leftPadding(); |
| if (!reset || extra.isAllocated()) { |
| extra.value().leftPadding = value; |
| extra.value().explicitLeftPadding = !reset; |
| } |
| if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { |
| updateSize(); |
| emit q->leftPaddingChanged(); |
| } |
| } |
| |
| void QQuickTextPrivate::setRightPadding(qreal value, bool reset) |
| { |
| Q_Q(QQuickText); |
| qreal oldPadding = q->rightPadding(); |
| if (!reset || extra.isAllocated()) { |
| extra.value().rightPadding = value; |
| extra.value().explicitRightPadding = !reset; |
| } |
| if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { |
| updateSize(); |
| emit q->rightPaddingChanged(); |
| } |
| } |
| |
| void QQuickTextPrivate::setBottomPadding(qreal value, bool reset) |
| { |
| Q_Q(QQuickText); |
| qreal oldPadding = q->bottomPadding(); |
| if (!reset || extra.isAllocated()) { |
| extra.value().bottomPadding = value; |
| extra.value().explicitBottomPadding = !reset; |
| } |
| if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) { |
| updateSize(); |
| emit q->bottomPaddingChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::antialiasing |
| |
| Used to decide if the Text should use antialiasing or not. Only Text |
| with renderType of Text.NativeRendering can disable antialiasing. |
| |
| The default is true. |
| */ |
| |
| void QQuickText::q_updateLayout() |
| { |
| Q_D(QQuickText); |
| d->updateLayout(); |
| } |
| |
| void QQuickTextPrivate::updateLayout() |
| { |
| Q_Q(QQuickText); |
| if (!q->isComponentComplete()) { |
| updateOnComponentComplete = true; |
| return; |
| } |
| updateOnComponentComplete = false; |
| layoutTextElided = false; |
| |
| if (extra.isAllocated()) |
| extra->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; |
| QList<QQuickStyledTextImgTag*> someImgTags = extra.isAllocated() ? extra->imgTags : QList<QQuickStyledTextImgTag*>(); |
| QQuickStyledText::parse(text, layout, someImgTags, q->baseUrl(), qmlContext(q), !maximumLineCountValid, &fontSizeModified); |
| if (someImgTags.size() || extra.isAllocated()) |
| extra.value().imgTags = someImgTags; |
| formatModifiesFontSize = fontSizeModified; |
| multilengthEos = -1; |
| } else { |
| QString tmp = text; |
| multilengthEos = tmp.indexOf(QLatin1Char('\x9c')); |
| if (multilengthEos != -1) |
| tmp = tmp.mid(0, multilengthEos); |
| tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); |
| layout.setText(tmp); |
| } |
| textHasChanged = false; |
| } |
| } else if (extra.isAllocated() && extra->lineHeightValid) { |
| 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(); |
| } |
| |
| q->polish(); |
| } |
| |
| 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; |
| for (QQuickStyledTextImgTag *img : qAsConst(d->extra->visibleImgTags)) { |
| if (!img->size.isValid()) { |
| img->size = img->pix->implicitSize(); |
| needToUpdateLayout = true; |
| } |
| } |
| |
| if (needToUpdateLayout) { |
| d->textHasChanged = true; |
| d->updateLayout(); |
| } else { |
| d->updateType = QQuickTextPrivate::UpdatePaintNode; |
| update(); |
| } |
| } |
| } |
| |
| void QQuickTextPrivate::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 + q->topPadding()); |
| } |
| |
| void QQuickTextPrivate::signalSizeChange(const QSizeF &previousSize) |
| { |
| Q_Q(QQuickText); |
| |
| if (layedOutTextRect.size() != previousSize) { |
| emit q->contentSizeChanged(); |
| if (layedOutTextRect.width() != previousSize.width()) |
| emit q->contentWidthChanged(layedOutTextRect.width()); |
| if (layedOutTextRect.height() != previousSize.height()) |
| emit q->contentHeightChanged(layedOutTextRect.height()); |
| } |
| } |
| |
| void QQuickTextPrivate::updateSize() |
| { |
| Q_Q(QQuickText); |
| |
| if (!q->isComponentComplete()) { |
| updateOnComponentComplete = true; |
| return; |
| } |
| |
| if (!requireImplicitSize) { |
| implicitWidthChanged(); |
| implicitHeightChanged(); |
| // if the implicitWidth is used, then updateSize() has already been called (recursively) |
| if (requireImplicitSize) |
| return; |
| } |
| |
| qreal hPadding = q->leftPadding() + q->rightPadding(); |
| qreal vPadding = q->topPadding() + q->bottomPadding(); |
| |
| const QSizeF previousSize = layedOutTextRect.size(); |
| |
| 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 - vPadding); |
| q->setImplicitSize(hPadding, fontHeight + vPadding); |
| layedOutTextRect = QRectF(0, 0, 0, fontHeight); |
| advance = QSizeF(); |
| signalSizeChange(previousSize); |
| updateType = UpdatePaintNode; |
| q->update(); |
| return; |
| } |
| |
| QSizeF size(0, 0); |
| |
| //setup instance of QTextLayout for all cases other than richtext |
| if (!richText) { |
| qreal baseline = 0; |
| 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() - vPadding); |
| } 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 + hPadding); |
| internalWidthUpdate = wasInLayout; |
| } |
| if (internalWidthUpdate) |
| return; |
| |
| extra->doc->setPageSize(QSizeF(q->width(), -1)); |
| if (q->widthValid() && (wrapMode != QQuickText::NoWrap || extra->doc->idealWidth() < availableWidth())) |
| extra->doc->setTextWidth(availableWidth()); |
| else |
| extra->doc->setTextWidth(extra->doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug) |
| |
| 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() - vPadding); |
| |
| //### need to confirm cost of always setting these for richText |
| internalWidthUpdate = true; |
| qreal oldWidth = q->width(); |
| qreal iWidth = -1; |
| if (!q->widthValid()) |
| iWidth = size.width(); |
| if (iWidth > -1) |
| q->setImplicitSize(iWidth + hPadding, size.height() + vPadding); |
| internalWidthUpdate = false; |
| |
| // If the implicit width update caused a recursive change of the width, |
| // we will have skipped integral parts of the layout due to the |
| // internalWidthUpdate recursion guard. To make sure everything is up |
| // to date, we need to run a second pass over the layout when updateSize() |
| // is done. |
| if (!qFuzzyCompare(q->width(), oldWidth) && !updateSizeRecursionGuard) { |
| updateSizeRecursionGuard = true; |
| updateSize(); |
| updateSizeRecursionGuard = false; |
| } else { |
| if (iWidth == -1) |
| q->setImplicitHeight(size.height() + vPadding); |
| |
| QTextBlock firstBlock = extra->doc->firstBlock(); |
| while (firstBlock.layout()->lineCount() == 0) |
| firstBlock = firstBlock.next(); |
| |
| QTextBlock lastBlock = extra->doc->lastBlock(); |
| while (lastBlock.layout()->lineCount() == 0) |
| lastBlock = lastBlock.previous(); |
| |
| if (firstBlock.lineCount() > 0 && lastBlock.lineCount() > 0) { |
| QTextLine firstLine = firstBlock.layout()->lineAt(0); |
| QTextLine lastLine = lastBlock.layout()->lineAt(lastBlock.layout()->lineCount() - 1); |
| advance = QSizeF(lastLine.horizontalAdvance(), |
| (lastLine.y() + lastBlock.layout()->position().y()) - (firstLine.y() + firstBlock.layout()->position().y())); |
| } else { |
| advance = QSizeF(); |
| } |
| } |
| } |
| |
| signalSizeChange(previousSize); |
| updateType = UpdatePaintNode; |
| q->update(); |
| } |
| |
| QQuickTextLine::QQuickTextLine() |
| : QObject(), m_line(nullptr), m_height(0), m_lineOffset(0) |
| { |
| } |
| |
| 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) |
| { |
| if (m_line) |
| m_line->setPosition(QPointF(m_line->x(), y)); |
| } |
| |
| 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 or text must be aligned |
| if (q->widthValid() && (q->wrapMode() != QQuickText::NoWrap || |
| q->effectiveHAlign() != QQuickText::AlignLeft)) |
| textLine->setWidth(availableWidth()); |
| 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, QVector<QTextLayout::FormatRange> *elidedFormats) |
| { |
| const int end = start + length; |
| const QVector<QTextLayout::FormatRange> formats = layout.formats(); |
| 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; |
| // 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.horizontalAdvance(elideChar) + line.naturalTextWidth() >= lineWidth) |
| elideText = metrics.elidedText(elideText, Qt::TextElideMode(elideMode), lineWidth); |
| } |
| return elideText; |
| } |
| } |
| |
| void QQuickTextPrivate::clearFormats() |
| { |
| layout.clearFormats(); |
| if (elideLayout) |
| elideLayout->clearFormats(); |
| } |
| |
| /*! |
| 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 && availableWidth() <= 0.) |
| || (multilineElide && q->heightValid() && availableHeight() <= 0.))) { |
| // we are elided and we have a zero width or height |
| widthExceeded = q->widthValid() && availableWidth() <= 0.; |
| heightExceeded = q->heightValid() && availableHeight() <= 0.; |
| |
| if (!truncated) { |
| truncated = true; |
| emit q->truncatedChanged(); |
| } |
| if (lineCount) { |
| lineCount = 0; |
| emit q->lineCountChanged(); |
| } |
| |
| if (qFuzzyIsNull(q->width())) { |
| layout.setText(QString()); |
| textHasChanged = true; |
| } |
| |
| 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; |
| if (extra.isAllocated()) |
| extra->visibleImgTags.clear(); |
| 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() ? availableHeight() : FLT_MAX; |
| |
| const bool customLayout = isLineLaidOutConnected(); |
| const bool wasTruncated = truncated; |
| |
| 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; |
| |
| bool widthChanged = false; |
| widthExceeded = availableWidth() <= 0 && (singlelineElide || canWrap || horizontalFit); |
| heightExceeded = availableHeight() <= 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; |
| bool noBreakLastLine = multilineElide && (wrapMode == QQuickText::Wrap || wrapMode == QQuickText::WordWrap); |
| |
| 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 (noBreakLastLine && visibleCount == maxLineCount) |
| layout.engine()->option.setWrapMode(QTextOption::WrapAnywhere); |
| if (customLayout) { |
| setupCustomLineGeometry(line, naturalHeight); |
| } else { |
| setLineGeometry(line, lineWidth, naturalHeight); |
| } |
| if (noBreakLastLine && visibleCount == maxLineCount) |
| layout.engine()->option.setWrapMode(QTextOption::WrapMode(wrapMode)); |
| |
| unelidedRect = br.united(line.naturalTextRect()); |
| |
| // Elide the previous line if the accumulated height of the text exceeds the height |
| // 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(line.width(), previousLine, &line) |
| : elidedText(line.width(), 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() > previousLine.width()) { |
| // 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(previousLine.width()), |
| 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(previousLine.width(), previousLine, &line) |
| : elidedText(previousLine.width(), previousLine); |
| elideStart = previousLine.textStart(); |
| // elideEnd isn't required for right eliding. |
| } else { |
| 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. |
| const int eol = line.isValid() |
| ? line.textStart() + line.textLength() |
| : layoutText.length(); |
| if (eol < layoutText.length() && layoutText.at(eol) != 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 + q->leftPadding() + q->rightPadding(), naturalHeight + q->topPadding() + q->bottomPadding()); |
| 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; |
| |
| const qreal availWidth = availableWidth(); |
| const qreal availHeight = availableHeight(); |
| |
| lineWidth = q->widthValid() && availWidth > 0 ? availWidth : naturalWidth; |
| maxHeight = q->heightValid() ? availHeight : FLT_MAX; |
| |
| // If the width of the item has changed and it's possible the result of wrapping, |
| // eliding, scaling has changed, or the text is not left aligned do another layout. |
| if ((!qFuzzyCompare(lineWidth, oldWidth) || (widthExceeded && lineWidth > oldWidth)) |
| && (singlelineElide || multilineElide || canWrap || horizontalFit |
| || q->effectiveHAlign() != QQuickText::AlignLeft)) { |
| widthChanged = true; |
| widthExceeded = lineWidth >= qMin(oldWidth, naturalWidth); |
| heightExceeded = false; |
| continue; |
| } |
| |
| // 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 (!q->widthValid() && !implicitWidthValid && unwrappedLineCount > 1 && q->effectiveHAlign() != QQuickText::AlignLeft) { |
| widthExceeded = false; |
| heightExceeded = false; |
| continue; |
| } |
| } else if (widthChanged) { |
| widthChanged = false; |
| if (line.isValid()) { |
| for (int lineCount = layout.lineCount(); lineCount < maxLineCount; ++lineCount) { |
| line = layout.createLine(); |
| if (!line.isValid()) |
| break; |
| setLineGeometry(line, lineWidth, naturalHeight); |
| } |
| } |
| layout.endLayout(); |
| |
| bool wasInLayout = internalWidthUpdate; |
| internalWidthUpdate = true; |
| q->setImplicitHeight(naturalHeight + q->topPadding() + q->bottomPadding()); |
| internalWidthUpdate = wasInLayout; |
| |
| multilineElide = elideMode == QQuickText::ElideRight |
| && q->widthValid() |
| && (q->heightValid() || maximumLineCountValid); |
| verticalFit = fontSizeMode() & QQuickText::VerticalFit |
| && (q->heightValid() || (maximumLineCountValid && canWrap)); |
| |
| const qreal oldHeight = maxHeight; |
| maxHeight = q->heightValid() ? availableHeight() : FLT_MAX; |
| // 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; |
| } |
| } 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); |
| |
| // Find the advance of the text layout |
| if (layout.lineCount() > 0) { |
| QTextLine firstLine = layout.lineAt(0); |
| QTextLine lastLine = layout.lineAt(layout.lineCount() - 1); |
| advance = QSizeF(lastLine.horizontalAdvance(), |
| lastLine.y() - firstLine.y()); |
| } else { |
| advance = QSizeF(); |
| } |
| |
| 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; |
| if (smallFont == largeFont) |
| break; |
| scaledFontSize = (smallFont + largeFont + 1) / 2; |
| } |
| } |
| } |
| |
| implicitWidthValid = true; |
| implicitHeightValid = true; |
| |
| QFontInfo scaledFontInfo(scaledFont); |
| if (fontInfo.weight() != scaledFontInfo.weight() |
| || fontInfo.pixelSize() != scaledFontInfo.pixelSize() |
| || fontInfo.italic() != scaledFontInfo.italic() |
| || !qFuzzyCompare(fontInfo.pointSizeF(), scaledFontInfo.pointSizeF()) |
| || fontInfo.family() != scaledFontInfo.family() |
| || fontInfo.styleName() != scaledFontInfo.styleName()) { |
| fontInfo = scaledFontInfo; |
| emit q->fontInfoChanged(); |
| } |
| |
| if (eos != multilengthEos) |
| truncated = true; |
| |
| assignedFont = QFontInfo(font).family(); |
| |
| if (elide) { |
| if (!elideLayout) { |
| elideLayout = new QTextLayout; |
| elideLayout->setCacheEnabled(true); |
| } |
| QTextEngine *engine = layout.engine(); |
| if (engine && engine->hasFormats()) { |
| QVector<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->setFormats(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 = nullptr; |
| } |
| |
| QTextLine firstLine = visibleCount == 1 && elideLayout |
| ? elideLayout->lineAt(0) |
| : 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 (extra.isAllocated() && extra->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; |
| |
| if (extra.isAllocated()) { |
| for (QQuickStyledTextImgTag *image : qAsConst(extra->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()) { |
| qmlWarning(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())); |
| } |
| } |
| } |
| |
| for (QQuickStyledTextImgTag *image : qAsConst(imagesInLine)) { |
| totalLineHeight = qMax(totalLineHeight, textTop + image->pos.y() + image->size.height()); |
| const int leadX = line.cursorToX(image->position); |
| const int trailX = line.cursorToX(image->position, QTextLine::Trailing); |
| const bool rtl = trailX < leadX; |
| image->pos.setX(leadX + (rtl ? (-image->offset - image->size.width()) : image->offset)); |
| image->pos.setY(image->pos.y() + height + textTop); |
| extra->visibleImgTags << image; |
| } |
| |
| line.setPosition(QPointF(line.position().x(), height + textTop)); |
| height += (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : totalLineHeight * lineHeight(); |
| } |
| |
| /*! |
| Returns the y offset when aligning text with a non-1.0 lineHeight |
| */ |
| int QQuickTextPrivate::lineHeightOffset() const |
| { |
| QFontMetricsF fm(font); |
| qreal fontHeight = qCeil(fm.height()); // QScriptLine and therefore QTextLine rounds up |
| return lineHeightMode() == QQuickText::FixedHeight ? fontHeight - lineHeight() |
| : (1.0 - lineHeight()) * fontHeight; |
| } |
| |
| /*! |
| 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_updateLayout())); |
| } |
| } |
| |
| /*! |
| \qmltype Text |
| \instantiates QQuickText |
| \inqmlmodule QtQuick |
| \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. |
| |
| \sa {Qt Quick Examples - Text#Fonts}{Fonts example} |
| */ |
| QQuickText::QQuickText(QQuickItem *parent) |
| : QQuickImplicitSizeItem(*(new QQuickTextPrivate), parent) |
| { |
| Q_D(QQuickText); |
| d->init(); |
| } |
| |
| QQuickText::QQuickText(QQuickTextPrivate &dd, QQuickItem *parent) |
| : QQuickImplicitSizeItem(dd, parent) |
| { |
| Q_D(QQuickText); |
| d->init(); |
| } |
| |
| QQuickText::~QQuickText() |
| { |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::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 QtQuick::Text::lineLaidOut(object line) |
| |
| This signal is emitted for each line of text that is laid out during the layout |
| process. The specified \a line object provides more details about the line that |
| is currently being laid out. |
| |
| 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 the specified \a line object 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 |
| |
| The corresponding handler is \c onLineLaidOut. |
| */ |
| |
| /*! |
| \qmlsignal QtQuick::Text::linkActivated(string link) |
| |
| This signal is emitted 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 |
| "See the \l{http://qt-project.org}{Qt Project website}." |
| |
| Clicking on the highlighted link will output |
| \tt{http://qt-project.org link activated} to the console. |
| |
| The corresponding handler is \c onLinkActivated. |
| */ |
| |
| /*! |
| \qmlproperty string QtQuick::Text::font.family |
| |
| Sets the family name of the font. |
| |
| The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". |
| If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. |
| If the family isn't available a family will be set using the font matching algorithm. |
| */ |
| |
| /*! |
| \qmlproperty string QtQuick::Text::font.styleName |
| \since 5.6 |
| |
| Sets the style name of the font. |
| |
| The style name is case insensitive. If set, the font will be matched against style name instead |
| of the font properties \l font.weight, \l font.bold and \l font.italic. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::font.bold |
| |
| Sets whether the font weight is bold. |
| */ |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::font.weight |
| |
| Sets the font's weight. |
| |
| The weight can be one of: |
| \list |
| \li Font.Thin |
| \li Font.Light |
| \li Font.ExtraLight |
| \li Font.Normal - the default |
| \li Font.Medium |
| \li Font.DemiBold |
| \li Font.Bold |
| \li Font.ExtraBold |
| \li Font.Black |
| \endlist |
| |
| \qml |
| Text { text: "Hello"; font.weight: Font.DemiBold } |
| \endqml |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::font.italic |
| |
| Sets whether the font has an italic style. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::font.underline |
| |
| Sets whether the text is underlined. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::font.strikeout |
| |
| Sets whether the font has a strikeout style. |
| */ |
| |
| /*! |
| \qmlproperty real QtQuick::Text::font.pointSize |
| |
| Sets the font size in points. The point size must be greater than zero. |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::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 QtQuick::Text::font.letterSpacing |
| |
| Sets the letter spacing for the font. |
| |
| Letter spacing changes the default spacing between individual letters in the font. |
| A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. |
| */ |
| |
| /*! |
| \qmlproperty real QtQuick::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 QtQuick::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 |
| */ |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::font.hintingPreference |
| \since 5.8 |
| |
| Sets the preferred hinting on the text. This is a hint to the underlying text rendering system |
| to use a certain level of hinting, and has varying support across platforms. See the table in |
| the documentation for QFont::HintingPreference for more details. |
| |
| \note This property only has an effect when used together with render type Text.NativeRendering. |
| |
| \list |
| \value Font.PreferDefaultHinting - Use the default hinting level for the target platform. |
| \value Font.PreferNoHinting - If possible, render text without hinting the outlines |
| of the glyphs. The text layout will be typographically accurate, using the same metrics |
| as are used e.g. when printing. |
| \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting, |
| but align glyphs to the pixel grid in the vertical direction. The text will appear |
| crisper on displays where the density is too low to give an accurate rendering |
| of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's |
| layout will be scalable to higher density devices (such as printers) without impacting |
| details such as line breaks. |
| \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and |
| vertical directions. The text will be altered to optimize legibility on the target |
| device, but since the metrics will depend on the target size of the text, the positions |
| of glyphs, line breaks, and other typographical detail will not scale, meaning that a |
| text layout may look different on devices with different pixel densities. |
| \endlist |
| |
| \qml |
| Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting } |
| \endqml |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::font.kerning |
| \since 5.10 |
| |
| Enables or disables the kerning OpenType feature when shaping the text. Disabling this may |
| improve performance when creating or changing the text, at the expense of some cosmetic |
| features. The default value is true. |
| |
| \qml |
| Text { text: "OATS FLAVOUR WAY"; font.kerning: false } |
| \endqml |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::font.preferShaping |
| \since 5.10 |
| |
| Sometimes, a font will apply complex rules to a set of characters in order to |
| display them correctly. In some writing systems, such as Brahmic scripts, this is |
| required in order for the text to be legible, but in e.g. Latin script, it is merely |
| a cosmetic feature. Setting the \c preferShaping property to false will disable all |
| such features when they are not required, which will improve performance in most cases. |
| |
| The default value is true. |
| |
| \qml |
| Text { text: "Some text"; font.preferShaping: false } |
| \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 (!antialiasing()) |
| d->font.setStyleStrategy(QFont::NoAntialias); |
| |
| 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); |
| } |
| |
| void QQuickText::itemChange(ItemChange change, const ItemChangeData &value) |
| { |
| Q_D(QQuickText); |
| Q_UNUSED(value); |
| switch (change) { |
| case ItemAntialiasingHasChanged: |
| if (!antialiasing()) |
| d->font.setStyleStrategy(QFont::NoAntialias); |
| else |
| d->font.setStyleStrategy(QFont::PreferAntialias); |
| d->implicitWidthValid = false; |
| d->implicitHeightValid = false; |
| d->updateLayout(); |
| break; |
| |
| case ItemDevicePixelRatioHasChanged: |
| if (d->renderType == NativeRendering) { |
| // Native rendering optimizes for a given pixel grid, so its results must not be scaled. |
| // Text layout code respects the current device pixel ratio automatically, we only need |
| // to rerun layout after the ratio changed. |
| // Changes of implicit size should be minimal; they are hard to avoid. |
| d->implicitWidthValid = false; |
| d->implicitHeightValid = false; |
| d->updateLayout(); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| QQuickItem::itemChange(change, value); |
| } |
| |
| /*! |
| \qmlproperty string QtQuick::Text::text |
| |
| The text to display. Text supports both plain and rich text strings. |
| |
| The item will try to automatically determine whether the text should |
| be treated as styled text. This determination is made using Qt::mightBeRichText(). |
| */ |
| QString QQuickText::text() const |
| { |
| Q_D(const QQuickText); |
| return d->text; |
| } |
| |
| void QQuickText::setText(const QString &n) |
| { |
| Q_D(QQuickText); |
| if (d->text == n) |
| return; |
| |
| d->markdownText = d->format == MarkdownText; |
| d->richText = d->format == RichText || d->markdownText; |
| d->styledText = d->format == StyledText || (d->format == AutoText && Qt::mightBeRichText(n)); |
| d->text = n; |
| if (isComponentComplete()) { |
| if (d->richText) { |
| d->ensureDoc(); |
| if (d->markdownText) |
| d->extra->doc->setMarkdownText(n); |
| else |
| d->extra->doc->setText(n); |
| d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); |
| } else { |
| d->clearFormats(); |
| d->rightToLeftText = d->text.isRightToLeft(); |
| } |
| d->determineHorizontalAlignment(); |
| } |
| d->textHasChanged = true; |
| d->implicitWidthValid = false; |
| d->implicitHeightValid = false; |
| |
| if (d->extra.isAllocated()) { |
| qDeleteAll(d->extra->imgTags); |
| d->extra->imgTags.clear(); |
| } |
| d->updateLayout(); |
| setAcceptHoverEvents(d->richText || d->styledText); |
| emit textChanged(d->text); |
| } |
| |
| /*! |
| \qmlproperty color QtQuick::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; |
| |
| d->color = rgb; |
| if (isComponentComplete()) { |
| d->updateType = QQuickTextPrivate::UpdatePaintNode; |
| update(); |
| } |
| emit colorChanged(); |
| } |
| |
| /*! |
| \qmlproperty color QtQuick::Text::linkColor |
| |
| The color of links in the text. |
| |
| This property works with the StyledText \l textFormat, but not with RichText. |
| Link color in RichText can be specified by including CSS style tags in the |
| text. |
| */ |
| |
| QColor QQuickText::linkColor() const |
| { |
| Q_D(const QQuickText); |
| return QColor::fromRgba(d->linkColor); |
| } |
| |
| void QQuickText::setLinkColor(const QColor &color) |
| { |
| Q_D(QQuickText); |
| QRgb rgb = color.rgba(); |
| if (d->linkColor == rgb) |
| return; |
| |
| d->linkColor = rgb; |
| if (isComponentComplete()) { |
| d->updateType = QQuickTextPrivate::UpdatePaintNode; |
| update(); |
| } |
| emit linkColorChanged(); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::style |
| |
| Set an additional text style. |
| |
| Supported text styles are: |
| \list |
| \li Text.Normal - the default |
| \li Text.Outline |
| \li Text.Raised |
| \li Text.Sunken |
| \endlist |
| |
| \qml |
| Row { |
| Text { font.pointSize: 24; text: "Normal" } |
| Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" } |
| Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" } |
| Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" } |
| } |
| \endqml |
| |
| \image declarative-textstyle.png |
| */ |
| QQuickText::TextStyle QQuickText::style() const |
| { |
| Q_D(const QQuickText); |
| return d->style; |
| } |
| |
| void QQuickText::setStyle(QQuickText::TextStyle style) |
| { |
| Q_D(QQuickText); |
| if (d->style == style) |
| return; |
| |
| d->style = style; |
| if (isComponentComplete()) { |
| d->updateType = QQuickTextPrivate::UpdatePaintNode; |
| update(); |
| } |
| emit styleChanged(d->style); |
| } |
| |
| /*! |
| \qmlproperty color QtQuick::Text::styleColor |
| |
| Defines the secondary color used by text styles. |
| |
| \c styleColor is used as the outline color for outlined text, and as the |
| shadow color for raised or sunken text. If no style has been set, it is not |
| used at all. |
| |
| \qml |
| Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" } |
| \endqml |
| |
| \sa style |
| */ |
| QColor QQuickText::styleColor() const |
| { |
| Q_D(const QQuickText); |
| return QColor::fromRgba(d->styleColor); |
| } |
| |
| void QQuickText::setStyleColor(const QColor &color) |
| { |
| Q_D(QQuickText); |
| QRgb rgb = color.rgba(); |
| if (d->styleColor == rgb) |
| return; |
| |
| d->styleColor = rgb; |
| if (isComponentComplete()) { |
| d->updateType = QQuickTextPrivate::UpdatePaintNode; |
| update(); |
| } |
| emit styleColorChanged(); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::horizontalAlignment |
| \qmlproperty enumeration QtQuick::Text::verticalAlignment |
| \qmlproperty enumeration QtQuick::Text::effectiveHorizontalAlignment |
| |
| Sets the horizontal and vertical alignment of the text within the Text items |
| width and height. By default, the text is vertically aligned to the top. Horizontal |
| alignment follows the natural alignment of the text, for example text that is read |
| from left to right will be aligned to the left. |
| |
| The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight, \c Text.AlignHCenter and |
| \c Text.AlignJustify. The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom |
| and \c Text.AlignVCenter. |
| |
| Note that for a single line of text, the size of the text is the area of the text. In this common case, |
| all alignments are equivalent. If you want the text to be, say, centered in its parent, then you will |
| need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to |
| that of the parent. |
| |
| When using the attached property LayoutMirroring::enabled to mirror application |
| layouts, the horizontal alignment of text will also be mirrored. However, the property |
| \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment |
| of Text, use the read-only property \c effectiveHorizontalAlignment. |
| */ |
| QQuickText::HAlignment QQuickText::hAlign() const |
| { |
| Q_D(const QQuickText); |
| return d->hAlign; |
| } |
| |
| void QQuickText::setHAlign(HAlignment align) |
| { |
| Q_D(QQuickText); |
| bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; |
| d->hAlignImplicit = false; |
| if (d->setHAlign(align, forceAlign) && isComponentComplete()) |
| d->updateLayout(); |
| } |
| |
| void QQuickText::resetHAlign() |
| { |
| Q_D(QQuickText); |
| d->hAlignImplicit = true; |
| if (isComponentComplete() && d->determineHorizontalAlignment()) |
| d->updateLayout(); |
| } |
| |
| QQuickText::HAlignment QQuickText::effectiveHAlign() const |
| { |
| Q_D(const QQuickText); |
| QQuickText::HAlignment effectiveAlignment = d->hAlign; |
| if (!d->hAlignImplicit && d->effectiveLayoutMirror) { |
| switch (d->hAlign) { |
| case QQuickText::AlignLeft: |
| effectiveAlignment = QQuickText::AlignRight; |
| break; |
| case QQuickText::AlignRight: |
| effectiveAlignment = QQuickText::AlignLeft; |
| break; |
| default: |
| break; |
| } |
| } |
| return effectiveAlignment; |
| } |
| |
| bool QQuickTextPrivate::setHAlign(QQuickText::HAlignment alignment, bool forceAlign) |
| { |
| Q_Q(QQuickText); |
| if (hAlign != alignment || forceAlign) { |
| QQuickText::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); |
| hAlign = alignment; |
| |
| emit q->horizontalAlignmentChanged(hAlign); |
| if (oldEffectiveHAlign != q->effectiveHAlign()) |
| emit q->effectiveHorizontalAlignmentChanged(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool QQuickTextPrivate::determineHorizontalAlignment() |
| { |
| if (hAlignImplicit) { |
| #if QT_CONFIG(im) |
| bool alignToRight = text.isEmpty() ? QGuiApplication::inputMethod()->inputDirection() == Qt::RightToLeft : rightToLeftText; |
| #else |
| bool alignToRight = rightToLeftText; |
| #endif |
| return setHAlign(alignToRight ? QQuickText::AlignRight : QQuickText::AlignLeft); |
| } |
| return false; |
| } |
| |
| void QQuickTextPrivate::mirrorChange() |
| { |
| Q_Q(QQuickText); |
| if (q->isComponentComplete()) { |
| if (!hAlignImplicit && (hAlign == QQuickText::AlignRight || hAlign == QQuickText::AlignLeft)) { |
| updateLayout(); |
| emit q->effectiveHorizontalAlignmentChanged(); |
| } |
| } |
| } |
| |
| QQuickText::VAlignment QQuickText::vAlign() const |
| { |
| Q_D(const QQuickText); |
| return d->vAlign; |
| } |
| |
| void QQuickText::setVAlign(VAlignment align) |
| { |
| Q_D(QQuickText); |
| if (d->vAlign == align) |
| return; |
| |
| d->vAlign = align; |
| |
| if (isComponentComplete()) |
| d->updateLayout(); |
| |
| emit verticalAlignmentChanged(align); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::wrapMode |
| |
| Set this property to wrap the text to the Text item's width. The text will only |
| wrap if an explicit width has been set. wrapMode can be one of: |
| |
| \list |
| \li Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l contentWidth will exceed a set width. |
| \li Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l contentWidth will exceed a set width. |
| \li Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word. |
| \li Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word. |
| \endlist |
| */ |
| QQuickText::WrapMode QQuickText::wrapMode() const |
| { |
| Q_D(const QQuickText); |
| return d->wrapMode; |
| } |
| |
| void QQuickText::setWrapMode(WrapMode mode) |
| { |
| Q_D(QQuickText); |
| if (mode == d->wrapMode) |
| return; |
| |
| d->wrapMode = mode; |
| d->updateLayout(); |
| |
| emit wrapModeChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::Text::lineCount |
| |
| Returns the number of lines visible in the text item. |
| |
| This property is not supported for rich text. |
| |
| \sa maximumLineCount |
| */ |
| int QQuickText::lineCount() const |
| { |
| Q_D(const QQuickText); |
| return d->lineCount; |
| } |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::truncated |
| |
| Returns true if the text has been truncated due to \l maximumLineCount |
| or \l elide. |
| |
| This property is not supported for rich text. |
| |
| \sa maximumLineCount, elide |
| */ |
| bool QQuickText::truncated() const |
| { |
| Q_D(const QQuickText); |
| return d->truncated; |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::Text::maximumLineCount |
| |
| Set this property to limit the number of lines that the text item will show. |
| If elide is set to Text.ElideRight, the text will be elided appropriately. |
| By default, this is the value of the largest possible integer. |
| |
| This property is not supported for rich text. |
| |
| \sa lineCount, elide |
| */ |
| int QQuickText::maximumLineCount() const |
| { |
| Q_D(const QQuickText); |
| return d->maximumLineCount(); |
| } |
| |
| void QQuickText::setMaximumLineCount(int lines) |
| { |
| Q_D(QQuickText); |
| |
| d->maximumLineCountValid = lines==INT_MAX ? false : true; |
| if (d->maximumLineCount() != lines) { |
| d->extra.value().maximumLineCount = lines; |
| d->implicitHeightValid = false; |
| d->updateLayout(); |
| emit maximumLineCountChanged(); |
| } |
| } |
| |
| void QQuickText::resetMaximumLineCount() |
| { |
| Q_D(QQuickText); |
| setMaximumLineCount(INT_MAX); |
| if (d->truncated != false) { |
| d->truncated = false; |
| emit truncatedChanged(); |
| } |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::textFormat |
| |
| The way the text property should be displayed. |
| |
| Supported text formats are: |
| |
| \list |
| \li Text.AutoText (default) |
| \li Text.PlainText |
| \li Text.StyledText |
| \li Text.RichText |
| \endlist |
| |
| If the text format is \c Text.AutoText the Text item |
| will automatically determine whether the text should be treated as |
| styled text. This determination is made using Qt::mightBeRichText() |
| which uses a fast and therefore simple heuristic. It mainly checks |
| whether there is something that looks like a tag before the first |
| line break. Although the result may be correct for common cases, |
| there is no guarantee. |
| |
| Text.StyledText is an optimized format supporting some basic text |
| styling markup, in the style of HTML 3.2: |
| |
| \code |
| <b></b> - bold |
| <del></del> - strike out (removed content) |
| <s></s> - strike out (no longer accurate or no longer relevant content) |
| <strong></strong> - bold |
| <i></i> - italic |
| <br> - new line |
| <p> - paragraph |
| <u> - underlined text |
| <font color="color_name" size="1-7"></font> |
| <h1> to <h6> - headers |
| <a href=""> - anchor |
| <img src="" align="top,middle,bottom" width="" height=""> - inline images |
| <ol type="">, <ul type=""> and <li> - ordered and unordered lists |
| <pre></pre> - preformatted |
| > < & |
| \endcode |
| |
| \c Text.StyledText parser is strict, requiring tags to be correctly nested. |
| |
| \table |
| \row |
| \li |
| \qml |
| Column { |
| Text { |
| font.pointSize: 24 |
| text: "<b>Hello</b> <i>World!</i>" |
| } |
| Text { |
| font.pointSize: 24 |
| textFormat: Text.RichText |
| text: "<b>Hello</b> <i>World!</i>" |
| } |
| Text { |
| font.pointSize: 24 |
| textFormat: Text.PlainText |
| text: "<b>Hello</b> <i>World!</i>" |
| } |
| } |
| \endqml |
| \li \image declarative-textformat.png |
| \endtable |
| |
| Text.RichText supports a larger subset of HTML 4, as described on the |
| \l {Supported HTML Subset} page. You should prefer using Text.PlainText |
| or Text.StyledText instead, as they offer better performance. |
| */ |
| QQuickText::TextFormat QQuickText::textFormat() const |
| { |
| Q_D(const QQuickText); |
| return d->format; |
| } |
| |
| void QQuickText::setTextFormat(TextFormat format) |
| { |
| Q_D(QQuickText); |
| if (format == d->format) |
| return; |
| d->format = format; |
| bool wasRich = d->richText; |
| d->markdownText = format == MarkdownText; |
| d->richText = format == RichText || d->markdownText; |
| d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text)); |
| |
| if (isComponentComplete()) { |
| if (!wasRich && d->richText) { |
| d->ensureDoc(); |
| d->extra->doc->setText(d->text); |
| d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); |
| } else { |
| d->clearFormats(); |
| d->rightToLeftText = d->text.isRightToLeft(); |
| d->textHasChanged = true; |
| } |
| d->determineHorizontalAlignment(); |
| } |
| d->updateLayout(); |
| setAcceptHoverEvents(d->richText || d->styledText); |
| setAcceptedMouseButtons(d->richText || d->styledText ? Qt::LeftButton : Qt::NoButton); |
| |
| emit textFormatChanged(d->format); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::elide |
| |
| Set this property to elide parts of the text fit to the Text item's width. |
| The text will only elide if an explicit width has been set. |
| |
| This property cannot be used with rich text. |
| |
| Eliding can be: |
| \list |
| \li Text.ElideNone - the default |
| \li Text.ElideLeft |
| \li Text.ElideMiddle |
| \li Text.ElideRight |
| \endlist |
| |
| If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped} |
| text. The text will only elide if \c maximumLineCount, or \c height has been set. |
| If both \c maximumLineCount and \c height are set, \c maximumLineCount will |
| apply unless the lines do not fit in the height allowed. |
| |
| If the text is a multi-length string, and the mode is not \c Text.ElideNone, |
| the first string that fits will be used, otherwise the last will be elided. |
| |
| Multi-length strings are ordered from longest to shortest, separated by the |
| Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}). |
| */ |
| QQuickText::TextElideMode QQuickText::elideMode() const |
| { |
| Q_D(const QQuickText); |
| return d->elideMode; |
| } |
| |
| void QQuickText::setElideMode(QQuickText::TextElideMode mode) |
| { |
| Q_D(QQuickText); |
| if (mode == d->elideMode) |
| return; |
| |
| d->elideMode = mode; |
| d->updateLayout(); |
| |
| emit elideModeChanged(mode); |
| } |
| |
| /*! |
| \qmlproperty url QtQuick::Text::baseUrl |
| |
| This property specifies a base URL which is used to resolve relative URLs |
| within the text. |
| |
| Urls are resolved to be within the same directory as the target of the base |
| URL meaning any portion of the path after the last '/' will be ignored. |
| |
| \table |
| \header \li Base URL \li Relative URL \li Resolved URL |
| \row \li http://qt-project.org/ \li images/logo.png \li http://qt-project.org/images/logo.png |
| \row \li http://qt-project.org/index.html \li images/logo.png \li http://qt-project.org/images/logo.png |
| \row \li http://qt-project.org/content \li images/logo.png \li http://qt-project.org/content/images/logo.png |
| \row \li http://qt-project.org/content/ \li images/logo.png \li http://qt-project.org/content/images/logo.png |
| \row \li http://qt-project.org/content/index.html \li images/logo.png \li http://qt-project.org/content/images/logo.png |
| \row \li http://qt-project.org/content/index.html \li ../images/logo.png \li http://qt-project.org/images/logo.png |
| \row \li http://qt-project.org/content/index.html \li /images/logo.png \li http://qt-project.org/images/logo.png |
| \endtable |
| |
| The default value is the url of the QML file instantiating the Text item. |
| */ |
| |
| QUrl QQuickText::baseUrl() const |
| { |
| Q_D(const QQuickText); |
| if (!d->extra.isAllocated() || d->extra->baseUrl.isEmpty()) { |
| if (QQmlContext *context = qmlContext(this)) |
| return context->baseUrl(); |
| else |
| return QUrl(); |
| } else { |
| return d->extra->baseUrl; |
| } |
| } |
| |
| void QQuickText::setBaseUrl(const QUrl &url) |
| { |
| Q_D(QQuickText); |
| if (baseUrl() != url) { |
| d->extra.value().baseUrl = url; |
| |
| if (d->richText) { |
| d->ensureDoc(); |
| d->extra->doc->setBaseUrl(url); |
| } |
| if (d->styledText) { |
| d->textHasChanged = true; |
| if (d->extra.isAllocated()) { |
| qDeleteAll(d->extra->imgTags); |
| d->extra->imgTags.clear(); |
| } |
| d->updateLayout(); |
| } |
| emit baseUrlChanged(); |
| } |
| } |
| |
| void QQuickText::resetBaseUrl() |
| { |
| if (QQmlContext *context = qmlContext(this)) |
| setBaseUrl(context->baseUrl()); |
| else |
| setBaseUrl(QUrl()); |
| } |
| |
| /*! \internal */ |
| QRectF QQuickText::boundingRect() const |
| { |
| Q_D(const QQuickText); |
| |
| QRectF rect = d->layedOutTextRect; |
| rect.moveLeft(QQuickTextUtil::alignedX(rect.width(), width(), effectiveHAlign())); |
| rect.moveTop(QQuickTextUtil::alignedY(rect.height() + d->lineHeightOffset(), height(), d->vAlign)); |
| |
| if (d->style != Normal) |
| rect.adjust(-1, 0, 1, 2); |
| // Could include font max left/right bearings to either side of rectangle. |
| |
| return rect; |
| } |
| |
| QRectF QQuickText::clipRect() const |
| { |
| Q_D(const QQuickText); |
| |
| QRectF rect = QQuickImplicitSizeItem::clipRect(); |
| if (d->style != Normal) |
| rect.adjust(-1, 0, 1, 2); |
| return rect; |
| } |
| |
| /*! \internal */ |
| void QQuickText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) |
| { |
| Q_D(QQuickText); |
| if (d->text.isEmpty()) { |
| QQuickItem::geometryChanged(newGeometry, oldGeometry); |
| return; |
| } |
| |
| bool widthChanged = newGeometry.width() != oldGeometry.width(); |
| bool heightChanged = newGeometry.height() != oldGeometry.height(); |
| bool wrapped = d->wrapMode != QQuickText::NoWrap; |
| bool elide = d->elideMode != QQuickText::ElideNone; |
| bool scaleFont = d->fontSizeMode() != QQuickText::FixedSize && (widthValid() || heightValid()); |
| bool verticalScale = (d->fontSizeMode() & QQuickText::VerticalFit) && heightValid(); |
| |
| bool widthMaximum = newGeometry.width() >= oldGeometry.width() && !d->widthExceeded; |
| bool heightMaximum = newGeometry.height() >= oldGeometry.height() && !d->heightExceeded; |
| |
| bool verticalPositionChanged = heightChanged && d->vAlign != AlignTop; |
| |
| if ((!widthChanged && !heightChanged) || d->internalWidthUpdate) |
| goto geomChangeDone; |
| |
| if ((effectiveHAlign() != QQuickText::AlignLeft && widthChanged) || verticalPositionChanged) { |
| // If the width has changed and we're not left aligned do an update so the text is |
| // repositioned even if a full layout isn't required. And the same for vertical. |
| d->updateType = QQuickTextPrivate::UpdatePaintNode; |
| update(); |
| } |
| |
| if (!wrapped && !elide && !scaleFont && !verticalPositionChanged) |
| goto geomChangeDone; // left aligned unwrapped text without eliding never needs relayout |
| |
| if (elide // eliding and dimensions were and remain invalid; |
| && ((widthValid() && oldGeometry.width() <= 0 && newGeometry.width() <= 0) |
| || (heightValid() && oldGeometry.height() <= 0 && newGeometry.height() <= 0))) { |
| goto geomChangeDone; |
| } |
| |
| if (widthMaximum && heightMaximum && !d->isLineLaidOutConnected() && !verticalPositionChanged) // Size is sufficient and growing. |
| goto geomChangeDone; |
| |
| if (!(widthChanged || widthMaximum) && !d->isLineLaidOutConnected()) { // only height has changed |
| if (newGeometry.height() > oldGeometry.height()) { |
| if (!d->heightExceeded && !qFuzzyIsNull(oldGeometry.height())) { |
| // Height is adequate and growing, and it wasn't 0 previously. |
| goto geomChangeDone; |
| } |
| if (d->lineCount == d->maximumLineCount()) // Reached maximum line and height is growing. |
| goto geomChangeDone; |
| } else if (newGeometry.height() < oldGeometry.height()) { |
| if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0) // A single line won't be truncated until the text is 0 height. |
| goto geomChangeDone; |
| |
| if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count. |
| && d->elideMode != QQuickText::ElideRight |
| && !(d->maximumLineCountValid && d->widthExceeded)) { |
| goto geomChangeDone; |
| } |
| } |
| } else if (!heightChanged && widthMaximum) { |
| goto geomChangeDone; |
| } |
| |
| if (d->updateOnComponentComplete || d->textHasChanged) { |
| // We need to re-elide |
| d->updateLayout(); |
| } else { |
| // We just need to re-layout |
| d->updateSize(); |
| } |
| |
| geomChangeDone: |
| QQuickItem::geometryChanged(newGeometry, oldGeometry); |
| } |
| |
| void QQuickText::triggerPreprocess() |
| { |
| Q_D(QQuickText); |
| if (d->updateType == QQuickTextPrivate::UpdateNone) |
| d->updateType = QQuickTextPrivate::UpdatePreprocess; |
| update(); |
| } |
| |
| QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) |
| { |
| Q_UNUSED(data); |
| Q_D(QQuickText); |
| |
| if (d->text.isEmpty()) { |
| delete oldNode; |
| return nullptr; |
| } |
| |
| if (d->updateType != QQuickTextPrivate::UpdatePaintNode && oldNode != nullptr) { |
| // Update done in preprocess() in the nodes |
| d->updateType = QQuickTextPrivate::UpdateNone; |
| return oldNode; |
| } |
| |
| d->updateType = QQuickTextPrivate::UpdateNone; |
| |
| const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height() + d->lineHeightOffset(), d->availableHeight(), d->vAlign) + topPadding(); |
| |
| QQuickTextNode *node = nullptr; |
| if (!oldNode) |
| node = new QQuickTextNode(this); |
| else |
| node = static_cast<QQuickTextNode *>(oldNode); |
| |
| node->setUseNativeRenderer(d->renderType == NativeRendering); |
| node->deleteContent(); |
| node->setMatrix(QMatrix4x4()); |
| |
| const QColor color = QColor::fromRgba(d->color); |
| const QColor styleColor = QColor::fromRgba(d->styleColor); |
| const QColor linkColor = QColor::fromRgba(d->linkColor); |
| |
| if (d->richText) { |
| const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), d->availableWidth(), effectiveHAlign()) + leftPadding(); |
| d->ensureDoc(); |
| node->addTextDocument(QPointF(dx, dy), d->extra->doc, color, d->style, styleColor, linkColor); |
| } else if (d->layedOutTextRect.width() > 0) { |
| const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, d->availableWidth(), effectiveHAlign()) + leftPadding(); |
| int unelidedLineCount = d->lineCount; |
| if (d->elideLayout) |
| unelidedLineCount -= 1; |
| if (unelidedLineCount > 0) { |
| node->addTextLayout( |
| QPointF(dx, dy), |
| &d->layout, |
| color, d->style, styleColor, linkColor, |
| QColor(), QColor(), -1, -1, |
| 0, unelidedLineCount); |
| } |
| if (d->elideLayout) |
| node->addTextLayout(QPointF(dx, dy), d->elideLayout, color, d->style, styleColor, linkColor); |
| |
| if (d->extra.isAllocated()) { |
| for (QQuickStyledTextImgTag *img : qAsConst(d->extra->visibleImgTags)) { |
| QQuickPixmap *pix = img->pix; |
| if (pix && pix->isReady()) |
| node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, pix->width(), pix->height()), pix->image()); |
| } |
| } |
| } |
| |
| // The font caches have now been initialized on the render thread, so they have to be |
| // invalidated before we can use them from the main thread again. |
| invalidateFontCaches(); |
| |
| return node; |
| } |
| |
| void QQuickText::updatePolish() |
| { |
| Q_D(QQuickText); |
| // If the fonts used for rendering are different from the ones used in the GUI thread, |
| // it means we will get warnings and corrupted text. If this case is detected, we need |
| // to update the text layout before creating the scenegraph nodes. |
| if (!d->assignedFont.isEmpty() && QFontInfo(d->font).family() != d->assignedFont) |
| d->polishSize = true; |
| |
| if (d->polishSize) { |
| d->updateSize(); |
| d->polishSize = false; |
| } |
| invalidateFontCaches(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Text::contentWidth |
| |
| Returns the width of the text, including width past the width |
| which is covered due to insufficient wrapping if WrapMode is set. |
| */ |
| qreal QQuickText::contentWidth() const |
| { |
| Q_D(const QQuickText); |
| return d->layedOutTextRect.width(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Text::contentHeight |
| |
| Returns the height of the text, including height past the height |
| which is covered due to there being more text than fits in the set height. |
| */ |
| qreal QQuickText::contentHeight() const |
| { |
| Q_D(const QQuickText); |
| return d->layedOutTextRect.height(); |
| } |
| |
| /*! |
| \qmlproperty real QtQuick::Text::lineHeight |
| |
| Sets the line height for the text. |
| The value can be in pixels or a multiplier depending on lineHeightMode. |
| |
| The default value is a multiplier of 1.0. |
| The line height must be a positive value. |
| */ |
| qreal QQuickText::lineHeight() const |
| { |
| Q_D(const QQuickText); |
| return d->lineHeight(); |
| } |
| |
| void QQuickText::setLineHeight(qreal lineHeight) |
| { |
| Q_D(QQuickText); |
| |
| if ((d->lineHeight() == lineHeight) || (lineHeight < 0.0)) |
| return; |
| |
| d->extra.value().lineHeightValid = true; |
| d->extra.value().lineHeight = lineHeight; |
| d->implicitHeightValid = false; |
| d->updateLayout(); |
| emit lineHeightChanged(lineHeight); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::lineHeightMode |
| |
| This property determines how the line height is specified. |
| The possible values are: |
| |
| \list |
| \li Text.ProportionalHeight (default) - this sets the spacing proportional to the |
| line (as a multiplier). For example, set to 2 for double spacing. |
| \li Text.FixedHeight - this sets the line height to a fixed line height (in pixels). |
| \endlist |
| */ |
| QQuickText::LineHeightMode QQuickText::lineHeightMode() const |
| { |
| Q_D(const QQuickText); |
| return d->lineHeightMode(); |
| } |
| |
| void QQuickText::setLineHeightMode(LineHeightMode mode) |
| { |
| Q_D(QQuickText); |
| if (mode == d->lineHeightMode()) |
| return; |
| |
| d->implicitHeightValid = false; |
| d->extra.value().lineHeightValid = true; |
| d->extra.value().lineHeightMode = mode; |
| d->updateLayout(); |
| |
| emit lineHeightModeChanged(mode); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::fontSizeMode |
| |
| This property specifies how the font size of the displayed text is determined. |
| The possible values are: |
| |
| \list |
| \li Text.FixedSize (default) - The size specified by \l font.pixelSize |
| or \l font.pointSize is used. |
| \li Text.HorizontalFit - The largest size up to the size specified that fits |
| within the width of the item without wrapping is used. |
| \li Text.VerticalFit - The largest size up to the size specified that fits |
| the height of the item is used. |
| \li Text.Fit - The largest size up to the size specified that fits within the |
| width and height of the item is used. |
| \endlist |
| |
| The font size of fitted text has a minimum bound specified by the |
| minimumPointSize or minimumPixelSize property and maximum bound specified |
| by either the \l font.pointSize or \l font.pixelSize properties. |
| |
| \qml |
| Text { text: "Hello"; fontSizeMode: Text.Fit; minimumPixelSize: 10; font.pixelSize: 72 } |
| \endqml |
| |
| If the text does not fit within the item bounds with the minimum font size |
| the text will be elided as per the \l elide property. |
| */ |
| |
| QQuickText::FontSizeMode QQuickText::fontSizeMode() const |
| { |
| Q_D(const QQuickText); |
| return d->fontSizeMode(); |
| } |
| |
| void QQuickText::setFontSizeMode(FontSizeMode mode) |
| { |
| Q_D(QQuickText); |
| if (d->fontSizeMode() == mode) |
| return; |
| |
| d->polishSize = true; |
| polish(); |
| |
| d->extra.value().fontSizeMode = mode; |
| emit fontSizeModeChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::Text::minimumPixelSize |
| |
| This property specifies the minimum font pixel size of text scaled by the |
| fontSizeMode property. |
| |
| If the fontSizeMode is Text.FixedSize or the \l font.pixelSize is -1 this |
| property is ignored. |
| */ |
| |
| int QQuickText::minimumPixelSize() const |
| { |
| Q_D(const QQuickText); |
| return d->minimumPixelSize(); |
| } |
| |
| void QQuickText::setMinimumPixelSize(int size) |
| { |
| Q_D(QQuickText); |
| if (d->minimumPixelSize() == size) |
| return; |
| |
| if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) { |
| d->polishSize = true; |
| polish(); |
| } |
| d->extra.value().minimumPixelSize = size; |
| emit minimumPixelSizeChanged(); |
| } |
| |
| /*! |
| \qmlproperty int QtQuick::Text::minimumPointSize |
| |
| This property specifies the minimum font point \l size of text scaled by |
| the fontSizeMode property. |
| |
| If the fontSizeMode is Text.FixedSize or the \l font.pointSize is -1 this |
| property is ignored. |
| */ |
| |
| int QQuickText::minimumPointSize() const |
| { |
| Q_D(const QQuickText); |
| return d->minimumPointSize(); |
| } |
| |
| void QQuickText::setMinimumPointSize(int size) |
| { |
| Q_D(QQuickText); |
| if (d->minimumPointSize() == size) |
| return; |
| |
| if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) { |
| d->polishSize = true; |
| polish(); |
| } |
| d->extra.value().minimumPointSize = size; |
| emit minimumPointSizeChanged(); |
| } |
| |
| /*! |
| Returns the number of resources (images) that are being loaded asynchronously. |
| */ |
| int QQuickText::resourcesLoading() const |
| { |
| Q_D(const QQuickText); |
| if (d->richText && d->extra.isAllocated() && d->extra->doc) |
| return d->extra->doc->resourcesLoading(); |
| return 0; |
| } |
| |
| /*! \internal */ |
| void QQuickText::componentComplete() |
| { |
| Q_D(QQuickText); |
| if (d->updateOnComponentComplete) { |
| if (d->richText) { |
| d->ensureDoc(); |
| if (d->markdownText) |
| d->extra->doc->setMarkdownText(d->text); |
| else |
| d->extra->doc->setText(d->text); |
| d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft(); |
| } else { |
| d->rightToLeftText = d->text.isRightToLeft(); |
| } |
| d->determineHorizontalAlignment(); |
| } |
| QQuickItem::componentComplete(); |
| if (d->updateOnComponentComplete) |
| d->updateLayout(); |
| } |
| |
| QString QQuickTextPrivate::anchorAt(const QTextLayout *layout, const QPointF &mousePos) |
| { |
| for (int i = 0; i < layout->lineCount(); ++i) { |
| QTextLine line = layout->lineAt(i); |
| if (line.naturalTextRect().contains(mousePos)) { |
| int charPos = line.xToCursor(mousePos.x(), QTextLine::CursorOnCharacter); |
| const auto formats = layout->formats(); |
| for (const QTextLayout::FormatRange &formatRange : formats) { |
| if (formatRange.format.isAnchor() |
| && charPos >= formatRange.start |
| && charPos < formatRange.start + formatRange.length) { |
| return formatRange.format.anchorHref(); |
| } |
| } |
| break; |
| } |
| } |
| return QString(); |
| } |
| |
| QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const |
| { |
| Q_Q(const QQuickText); |
| QPointF translatedMousePos = mousePos; |
| translatedMousePos.rx() -= q->leftPadding(); |
| translatedMousePos.ry() -= q->topPadding() + QQuickTextUtil::alignedY(layedOutTextRect.height() + lineHeightOffset(), availableHeight(), vAlign); |
| if (styledText) { |
| QString link = anchorAt(&layout, translatedMousePos); |
| if (link.isEmpty() && elideLayout) |
| link = anchorAt(elideLayout, translatedMousePos); |
| return link; |
| } else if (richText && extra.isAllocated() && extra->doc) { |
| translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), availableWidth(), q->effectiveHAlign()); |
| return extra->doc->documentLayout()->anchorAt(translatedMousePos); |
| } |
| return QString(); |
| } |
| |
| bool QQuickTextPrivate::isLinkActivatedConnected() |
| { |
| Q_Q(QQuickText); |
| IS_SIGNAL_CONNECTED(q, QQuickText, linkActivated, (const QString &)); |
| } |
| |
| /*! \internal */ |
| void QQuickText::mousePressEvent(QMouseEvent *event) |
| { |
| Q_D(QQuickText); |
| |
| QString link; |
| if (d->isLinkActivatedConnected()) |
| link = d->anchorAt(event->localPos()); |
| |
| if (link.isEmpty()) { |
| event->setAccepted(false); |
| } else { |
| d->extra.value().activeLink = link; |
| } |
| |
| // ### may malfunction if two of the same links are clicked & dragged onto each other) |
| |
| if (!event->isAccepted()) |
| QQuickItem::mousePressEvent(event); |
| } |
| |
| |
| /*! \internal */ |
| void QQuickText::mouseReleaseEvent(QMouseEvent *event) |
| { |
| Q_D(QQuickText); |
| |
| // ### confirm the link, and send a signal out |
| |
| QString link; |
| if (d->isLinkActivatedConnected()) |
| link = d->anchorAt(event->localPos()); |
| |
| if (!link.isEmpty() && d->extra.isAllocated() && d->extra->activeLink == link) |
| emit linkActivated(d->extra->activeLink); |
| else |
| event->setAccepted(false); |
| |
| if (!event->isAccepted()) |
| QQuickItem::mouseReleaseEvent(event); |
| } |
| |
| bool QQuickTextPrivate::isLinkHoveredConnected() |
| { |
| Q_Q(QQuickText); |
| IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &)); |
| } |
| |
| /*! |
| \qmlsignal QtQuick::Text::linkHovered(string link) |
| \since 5.2 |
| |
| This signal is emitted when the user hovers 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. |
| |
| The corresponding handler is \c onLinkHovered. |
| |
| \sa hoveredLink, linkAt() |
| */ |
| |
| /*! |
| \qmlproperty string QtQuick::Text::hoveredLink |
| \since 5.2 |
| |
| This property contains the link string when the user hovers a link |
| embedded in the text. The link must be in rich text or HTML format |
| and the \a hoveredLink string provides access to the particular link. |
| |
| \sa linkHovered, linkAt() |
| */ |
| |
| QString QQuickText::hoveredLink() const |
| { |
| Q_D(const QQuickText); |
| if (const_cast<QQuickTextPrivate *>(d)->isLinkHoveredConnected()) { |
| if (d->extra.isAllocated()) |
| return d->extra->hoveredLink; |
| } else { |
| #if QT_CONFIG(cursor) |
| if (QQuickWindow *wnd = window()) { |
| QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0)); |
| return d->anchorAt(pos); |
| } |
| #endif // cursor |
| } |
| return QString(); |
| } |
| |
| void QQuickTextPrivate::processHoverEvent(QHoverEvent *event) |
| { |
| Q_Q(QQuickText); |
| qCDebug(DBG_HOVER_TRACE) << q; |
| QString link; |
| if (isLinkHoveredConnected()) { |
| if (event->type() != QEvent::HoverLeave) |
| link = anchorAt(event->posF()); |
| |
| if ((!extra.isAllocated() && !link.isEmpty()) || (extra.isAllocated() && extra->hoveredLink != link)) { |
| extra.value().hoveredLink = link; |
| emit q->linkHovered(extra->hoveredLink); |
| } |
| } |
| event->setAccepted(!link.isEmpty()); |
| } |
| |
| void QQuickText::hoverEnterEvent(QHoverEvent *event) |
| { |
| Q_D(QQuickText); |
| d->processHoverEvent(event); |
| } |
| |
| void QQuickText::hoverMoveEvent(QHoverEvent *event) |
| { |
| Q_D(QQuickText); |
| d->processHoverEvent(event); |
| } |
| |
| void QQuickText::hoverLeaveEvent(QHoverEvent *event) |
| { |
| Q_D(QQuickText); |
| d->processHoverEvent(event); |
| } |
| |
| /*! |
| \qmlproperty enumeration QtQuick::Text::renderType |
| |
| Override the default rendering type for this component. |
| |
| Supported render types are: |
| \list |
| \li Text.QtRendering |
| \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. |
| |
| The default rendering type is determined by \l QQuickWindow::textRenderType(). |
| */ |
| 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(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::Text::doLayout() |
| \deprecated |
| |
| Use \l forceLayout() instead. |
| */ |
| void QQuickText::doLayout() |
| { |
| forceLayout(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::Text::forceLayout() |
| \since 5.9 |
| |
| Triggers a re-layout of the displayed text. |
| */ |
| void QQuickText::forceLayout() |
| { |
| Q_D(QQuickText); |
| d->updateSize(); |
| } |
| |
| /*! |
| \qmlmethod QtQuick::Text::linkAt(real x, real y) |
| \since 5.3 |
| |
| Returns the link string at point \a x, \a y in content coordinates, |
| or an empty string if no link exists at that point. |
| |
| \sa hoveredLink |
| */ |
| QString QQuickText::linkAt(qreal x, qreal y) const |
| { |
| Q_D(const QQuickText); |
| return d->anchorAt(QPointF(x, y)); |
| } |
| |
| /*! |
| * \internal |
| * |
| * Invalidates font caches owned by the text objects owned by the element |
| * to work around the fact that text objects cannot be used from multiple threads. |
| */ |
| void QQuickText::invalidateFontCaches() |
| { |
| Q_D(QQuickText); |
| |
| if (d->richText && d->extra.isAllocated() && d->extra->doc != nullptr) { |
| QTextBlock block; |
| for (block = d->extra->doc->firstBlock(); block.isValid(); block = block.next()) { |
| if (block.layout() != nullptr && block.layout()->engine() != nullptr) |
| block.layout()->engine()->resetFontEngineCache(); |
| } |
| } else { |
| if (d->layout.engine() != nullptr) |
| d->layout.engine()->resetFontEngineCache(); |
| } |
| } |
| |
| /*! |
| \since 5.6 |
| \qmlproperty real QtQuick::Text::padding |
| \qmlproperty real QtQuick::Text::topPadding |
| \qmlproperty real QtQuick::Text::leftPadding |
| \qmlproperty real QtQuick::Text::bottomPadding |
| \qmlproperty real QtQuick::Text::rightPadding |
| |
| These properties hold the padding around the content. This space is reserved |
| in addition to the contentWidth and contentHeight. |
| */ |
| qreal QQuickText::padding() const |
| { |
| Q_D(const QQuickText); |
| return d->padding(); |
| } |
| |
| void QQuickText::setPadding(qreal padding) |
| { |
| Q_D(QQuickText); |
| if (qFuzzyCompare(d->padding(), padding)) |
| return; |
| |
| d->extra.value().padding = padding; |
| d->updateSize(); |
| emit paddingChanged(); |
| if (!d->extra.isAllocated() || !d->extra->explicitTopPadding) |
| emit topPaddingChanged(); |
| if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding) |
| emit leftPaddingChanged(); |
| if (!d->extra.isAllocated() || !d->extra->explicitRightPadding) |
| emit rightPaddingChanged(); |
| if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding) |
| emit bottomPaddingChanged(); |
| } |
| |
| void QQuickText::resetPadding() |
| { |
| setPadding(0); |
| } |
| |
| qreal QQuickText::topPadding() const |
| { |
| Q_D(const QQuickText); |
| if (d->extra.isAllocated() && d->extra->explicitTopPadding) |
| return d->extra->topPadding; |
| return d->padding(); |
| } |
| |
| void QQuickText::setTopPadding(qreal padding) |
| { |
| Q_D(QQuickText); |
| d->setTopPadding(padding); |
| } |
| |
| void QQuickText::resetTopPadding() |
| { |
| Q_D(QQuickText); |
| d->setTopPadding(0, true); |
| } |
| |
| qreal QQuickText::leftPadding() const |
| { |
| Q_D(const QQuickText); |
| if (d->extra.isAllocated() && d->extra->explicitLeftPadding) |
| return d->extra->leftPadding; |
| return d->padding(); |
| } |
| |
| void QQuickText::setLeftPadding(qreal padding) |
| { |
| Q_D(QQuickText); |
| d->setLeftPadding(padding); |
| } |
| |
| void QQuickText::resetLeftPadding() |
| { |
| Q_D(QQuickText); |
| d->setLeftPadding(0, true); |
| } |
| |
| qreal QQuickText::rightPadding() const |
| { |
| Q_D(const QQuickText); |
| if (d->extra.isAllocated() && d->extra->explicitRightPadding) |
| return d->extra->rightPadding; |
| return d->padding(); |
| } |
| |
| void QQuickText::setRightPadding(qreal padding) |
| { |
| Q_D(QQuickText); |
| d->setRightPadding(padding); |
| } |
| |
| void QQuickText::resetRightPadding() |
| { |
| Q_D(QQuickText); |
| d->setRightPadding(0, true); |
| } |
| |
| qreal QQuickText::bottomPadding() const |
| { |
| Q_D(const QQuickText); |
| if (d->extra.isAllocated() && d->extra->explicitBottomPadding) |
| return d->extra->bottomPadding; |
| return d->padding(); |
| } |
| |
| void QQuickText::setBottomPadding(qreal padding) |
| { |
| Q_D(QQuickText); |
| d->setBottomPadding(padding); |
| } |
| |
| void QQuickText::resetBottomPadding() |
| { |
| Q_D(QQuickText); |
| d->setBottomPadding(0, true); |
| } |
| |
| /*! |
| \qmlproperty string QtQuick::Text::fontInfo.family |
| \since 5.9 |
| |
| The family name of the font that has been resolved for the current font |
| and fontSizeMode. |
| */ |
| |
| /*! |
| \qmlproperty string QtQuick::Text::fontInfo.styleName |
| \since 5.9 |
| |
| The style name of the font info that has been resolved for the current font |
| and fontSizeMode. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::fontInfo.bold |
| \since 5.9 |
| |
| The bold state of the font info that has been resolved for the current font |
| and fontSizeMode. This is true if the weight of the resolved font is bold or higher. |
| */ |
| |
| /*! |
| \qmlproperty int QtQuick::Text::fontInfo.weight |
| \since 5.9 |
| |
| The weight of the font info that has been resolved for the current font |
| and fontSizeMode. |
| */ |
| |
| /*! |
| \qmlproperty bool QtQuick::Text::fontInfo.italic |
| \since 5.9 |
| |
| The italic state of the font info that has been resolved for the current font |
| and fontSizeMode. |
| */ |
| |
| /*! |
| \qmlproperty real QtQuick::Text::fontInfo.pointSize |
| \since 5.9 |
| |
| The pointSize of the font info that has been resolved for the current font |
| and fontSizeMode. |
| */ |
| |
| /*! |
| \qmlproperty string QtQuick::Text::fontInfo.pixelSize |
| \since 5.9 |
| |
| The pixel size of the font info that has been resolved for the current font |
| and fontSizeMode. |
| */ |
| QJSValue QQuickText::fontInfo() const |
| { |
| Q_D(const QQuickText); |
| |
| QJSEngine *engine = qjsEngine(this); |
| if (!engine) { |
| qmlWarning(this) << "fontInfo: item has no JS engine"; |
| return QJSValue(); |
| } |
| |
| QJSValue value = engine->newObject(); |
| value.setProperty(QStringLiteral("family"), d->fontInfo.family()); |
| value.setProperty(QStringLiteral("styleName"), d->fontInfo.styleName()); |
| value.setProperty(QStringLiteral("bold"), d->fontInfo.bold()); |
| value.setProperty(QStringLiteral("weight"), d->fontInfo.weight()); |
| value.setProperty(QStringLiteral("italic"), d->fontInfo.italic()); |
| value.setProperty(QStringLiteral("pointSize"), d->fontInfo.pointSizeF()); |
| value.setProperty(QStringLiteral("pixelSize"), d->fontInfo.pixelSize()); |
| return value; |
| } |
| |
| /*! |
| \qmlproperty size QtQuick::Text::advance |
| \since 5.10 |
| |
| The distance, in pixels, from the baseline origin of the first |
| character of the text item, to the baseline origin of the first |
| character in a text item occurring directly after this one |
| in a text flow. |
| |
| Note that the advance can be negative if the text flows from |
| the right to the left. |
| */ |
| QSizeF QQuickText::advance() const |
| { |
| Q_D(const QQuickText); |
| return d->advance; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qquicktext_p.cpp" |