From 5b9e19ff591f9460adc7a85fa9fe822c05ab3b41 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Wed, 11 Jan 2012 15:49:36 +1000 Subject: [PATCH] Add support for resizing fonts to fit Text dimensions. This adds a mode where if the content of a Text item doesn't fit within its bounds the font size is reduced during layout until it does or a minimum font size is reached. Task-number: QTBUG-22832 Change-Id: I6198ef03899e2f21b32e313548966ef4b0e3bff1 Reviewed-by: Andrew den Exter Reviewed-by: Yann Bodson --- src/quick/items/qquicktext.cpp | 515 +++++++++++----- src/quick/items/qquicktext_p.h | 24 + src/quick/items/qquicktext_p_p.h | 10 +- .../qtquick2/qquicktext/data/fontSizeMode.qml | 24 + .../qtquick2/qquicktext/tst_qquicktext.cpp | 564 +++++++++++++++++- tests/testapplications/text/text.qml | 29 +- 6 files changed, 1000 insertions(+), 166 deletions(-) create mode 100644 tests/auto/qtquick2/qquicktext/data/fontSizeMode.qml diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index 00a40ab54d..704dfc9593 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -58,6 +58,8 @@ #include #include +#include +#include #include #include @@ -70,21 +72,21 @@ extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled; DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) DEFINE_BOOL_CONFIG_OPTION(enableImageCache, QML_ENABLE_TEXT_IMAGE_CACHE); -QString QQuickTextPrivate::elideChar = QString(0x2026); +const QChar QQuickTextPrivate::elideChar = QChar(0x2026); QQuickTextPrivate::QQuickTextPrivate() : color((QRgb)0), style(QQuickText::Normal), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop), elideMode(QQuickText::ElideNone), format(QQuickText::AutoText), wrapMode(QQuickText::NoWrap), lineHeight(1), lineHeightMode(QQuickText::ProportionalHeight), lineCount(1), maximumLineCount(INT_MAX), - maximumLineCountValid(false), - imageCache(0), texture(0), + maximumLineCountValid(false), fontSizeMode(QQuickText::FixedSize), minimumPixelSize(12), + minimumPointSize(12), imageCache(0), texture(0), imageCacheDirty(false), updateOnComponentComplete(true), richText(false), styledText(false), singleline(false), cacheAllTextAsImage(true), disableDistanceField(false), internalWidthUpdate(false), requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false), layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), textHasChanged(true), - needToUpdateLayout(false), naturalWidth(0), doc(0), elipsisLayout(0), textLine(0), nodeType(NodeIsNull), + needToUpdateLayout(false), naturalWidth(0), doc(0), elideLayout(0), textLine(0), nodeType(NodeIsNull), updateType(UpdatePaintNode), nbActiveDownloads(0) #if defined(Q_OS_MAC) @@ -262,7 +264,7 @@ QSet QQuickTextDocumentWithImageResources::errors; QQuickTextPrivate::~QQuickTextPrivate() { - delete elipsisLayout; + delete elideLayout; delete textLine; textLine = 0; delete imageCache; qDeleteAll(imgTags); @@ -303,41 +305,16 @@ void QQuickTextPrivate::updateLayout() // Setup instance of QTextLayout for all cases other than richtext if (!richText) { - if (elipsisLayout) { - delete elipsisLayout; - elipsisLayout = 0; - } - layout.setFont(font); - if (text.isEmpty()) { - if (!layout.text().isEmpty()) - layout.setText(text); - } else if (!styledText) { - layout.clearAdditionalFormats(); - QString tmp = text; - tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); - singleline = !tmp.contains(QChar::LineSeparator); - if (singleline && !maximumLineCountValid && elideMode != QQuickText::ElideNone && q->widthValid() && wrapMode == QQuickText::NoWrap) { - if (q->width() <= 0) { - tmp = QString(); - } else { - QFontMetrics fm(font); - tmp = fm.elidedText(tmp,(Qt::TextElideMode)elideMode,q->width()); - } - if (tmp != text) { - layoutTextElided = true; - if (!truncated) { - truncated = true; - emit q->truncatedChanged(); - } - } - } - layout.setText(tmp); - } else { - singleline = false; - if (textHasChanged) { + if (textHasChanged) { + if (styledText && !text.isEmpty()) { QDeclarativeStyledText::parse(text, layout, imgTags, qmlContext(q), !maximumLineCountValid); - textHasChanged = false; + } else { + layout.clearAdditionalFormats(); + QString tmp = text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + layout.setText(tmp); } + textHasChanged = false; } } else { ensureDoc(); @@ -502,10 +479,15 @@ 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(); + return m_line->lineNumber() + m_lineOffset; return 0; } @@ -576,7 +558,7 @@ bool QQuickTextPrivate::isLineLaidOutConnected() return this->isSignalConnected(idx); } -void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, qreal elideWidth = 0) +void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, int lineOffset) { Q_Q(QQuickText); @@ -591,10 +573,11 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, textLine->setLine(&line); textLine->setY(height); textLine->setHeight(0); + textLine->setLineOffset(lineOffset); // use the text item's width by default if it has one and wrap is on if (q->widthValid() && q->wrapMode() != QQuickText::NoWrap) - textLine->setWidth(q->width() - elideWidth); + textLine->setWidth(q->width()); else textLine->setWidth(INT_MAX); if (lineHeight != 1.0) @@ -623,54 +606,19 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, Returns the size of the final text. This can be used to position the text vertically (the text is already absolutely positioned horizontally). */ + QRect QQuickTextPrivate::setupTextLayout() { - // ### text layout handling should be profiled and optimized as needed - // what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine); Q_Q(QQuickText); layout.setCacheEnabled(true); - qreal lineWidth = 0; - int visibleCount = 0; - - //set manual width - if (q->widthValid()) - lineWidth = q->width(); - QTextOption textOption = layout.textOption(); textOption.setAlignment(Qt::Alignment(q->effectiveHAlign())); textOption.setWrapMode(QTextOption::WrapMode(wrapMode)); if (!cacheAllTextAsImage && !richTextAsImage && !disableDistanceField) textOption.setUseDesignMetrics(true); layout.setTextOption(textOption); - - QFontMetrics fm(layout.font()); - elidePos = QPointF(); - - if (requireImplicitWidth && q->widthValid()) { - // requires an extra layout - QString elidedText; - if (layoutTextElided) { - // We have provided elided text to the layout, but we must calculate unelided width. - elidedText = layout.text(); - layout.setText(text); - } - layout.beginLayout(); - forever { - QTextLine line = layout.createLine(); - if (!line.isValid()) - break; - } - layout.endLayout(); - QRectF br; - for (int i = 0; i < layout.lineCount(); ++i) { - QTextLine line = layout.lineAt(i); - br = br.united(line.naturalTextRect()); - } - naturalWidth = br.width(); - if (layoutTextElided) - layout.setText(elidedText); - } + layout.setFont(font); if ((q->widthValid() && q->width() <= 0. && elideMode != QQuickText::ElideNone) || (q->heightValid() && q->height() <= 0. && wrapMode != QQuickText::NoWrap && elideMode == QQuickText::ElideRight)) { @@ -684,103 +632,255 @@ QRect QQuickTextPrivate::setupTextLayout() emit q->lineCountChanged(); } + if (requireImplicitWidth) { + // Layout to determine the implicit width. + layout.beginLayout(); + + for (int i = 0; i < maximumLineCount; ++i) { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + } + layout.endLayout(); + naturalWidth = layout.maximumWidth(); + layout.clearLayout(); + } + + QFontMetrics fm(font); qreal height = (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : fm.height() * lineHeight; return QRect(0, 0, 0, height); } - qreal height = 0; + const int lineWidth = q->widthValid() ? q->width() : INT_MAX; + const bool customLayout = isLineLaidOutConnected(); + const bool wasTruncated = truncated; + + const bool singlelineElide = !styledText && elideMode != QQuickText::ElideNone && q->widthValid(); + const bool multilineElide = !styledText + && elideMode == QQuickText::ElideRight + && q->widthValid() + && (q->heightValid() || maximumLineCountValid); + const bool canWrap = wrapMode != QQuickText::NoWrap && q->widthValid(); + + const bool horizontalFit = fontSizeMode & QQuickText::HorizontalFit && q->widthValid(); + const bool verticalFit = fontSizeMode & QQuickText::VerticalFit + && (q->heightValid() || (maximumLineCountValid && canWrap)); + const bool pixelSize = font.pixelSize() != -1; + const 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; + QRectF br; - bool truncate = layoutTextElided; - bool customLayout = isLineLaidOutConnected(); - bool multilineElideEnabled = elideMode == QQuickText::ElideRight && q->widthValid() && wrapMode != QQuickText::NoWrap; + QFont scaledFont = font; - layout.beginLayout(); - if (!lineWidth) - lineWidth = INT_MAX; - int linesLeft = maximumLineCount; - int visibleTextLength = 0; + QTextLine line; + int visibleCount = 0; + bool elide; + qreal height = 0; + QString elideText; + bool once = true; - forever { - QTextLine line = layout.createLine(); - if (!line.isValid()) - break; + naturalWidth = 0; - visibleCount++; + do { + if (!once) { + if (pixelSize) + scaledFont.setPixelSize(scaledFontSize); + else + scaledFont.setPointSize(scaledFontSize); + layout.setFont(scaledFont); + } + layout.beginLayout(); - qreal preLayoutHeight = height; - if (customLayout) - setupCustomLineGeometry(line, height); - else if (lineWidth) - setLineGeometry(line, lineWidth, height); + bool wrapped = false; + bool truncateHeight = false; + truncated = false; + elide = false; + int characterCount = 0; + int unwrappedLineCount = 1; + height = 0; + br = QRectF(); + line = layout.createLine(); + for (visibleCount = 1; ; ++visibleCount) { + qreal preLayoutHeight = height; + + if (customLayout) { + setupCustomLineGeometry(line, height); + } else { + setLineGeometry(line, lineWidth, height); + } - bool elide = false; - if (multilineElideEnabled && q->heightValid() && height > q->height()) { - // This line does not fit in the remaining area. - elide = true; - if (visibleCount > 1) { - --visibleCount; + if (multilineElide && height > q->height() && visibleCount > 1) { + truncated = true; + truncateHeight = true; height = preLayoutHeight; - setLineGeometry(line, 0.0, height); - line.setPosition(QPointF(FLT_MAX,FLT_MAX)); - line = layout.lineAt(visibleCount-1); + + elide = true; + characterCount = line.textStart() + line.textLength(); + + QTextLine previousLine = layout.lineAt(visibleCount - 2); + elideText = layoutText.mid(previousLine.textStart(), previousLine.textLength()); + if (layoutText.at(line.textStart() - 1) != QChar::LineSeparator) { + line.setLineWidth(INT_MAX); + elideText += layoutText.mid(line.textStart(), line.textLength()); + } else { + elideText[elideText.length() - 1] = elideChar; + } + line.setLineWidth(0); + line.setPosition(QPointF(FLT_MAX, FLT_MAX)); + line = previousLine; + --visibleCount; + height -= (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : previousLine.height() * lineHeight; + break; } - } else { - visibleTextLength += line.textLength(); - } - if (elide || (maximumLineCountValid && --linesLeft == 0)) { - if (visibleTextLength < text.length()) { - truncate = true; - height = preLayoutHeight; - if (multilineElideEnabled) { - qreal elideWidth = fm.width(elideChar); - // Need to correct for alignment - if (customLayout) - setupCustomLineGeometry(line, height, elideWidth); - else - setLineGeometry(line, lineWidth - elideWidth, height); - if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) { - line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y())); - elidePos.setX(line.naturalTextRect().left() - elideWidth); + QTextLine nextLine = layout.createLine(); + if (!nextLine.isValid()) { + characterCount = line.textStart() + line.textLength(); + if (singlelineElide && visibleCount == 1 && line.naturalTextWidth() > lineWidth) { + truncated = true; + height = preLayoutHeight; + elide = true; + elideText = layoutText.mid(line.textStart(), line.textLength()); + } else { + br = br.united(line.naturalTextRect()); + } + break; + } else { + const bool wrappedLine = layoutText.at(nextLine.textStart() - 1) != QChar::LineSeparator; + wrapped |= wrappedLine; + + if (!wrappedLine) + ++unwrappedLineCount; + + if (visibleCount == maximumLineCount) { + truncated = true; + characterCount = nextLine.textStart() + nextLine.textLength(); + + if (multilineElide) { + height = preLayoutHeight; + elide = true; + elideText = layoutText.mid(line.textStart(), line.textLength()); + if (wrappedLine) { + nextLine.setLineWidth(INT_MAX); + elideText += layoutText.mid(nextLine.textStart(), nextLine.textLength()); + } else { + elideText[elideText.length() - 1] = elideChar; + } + elideText += elideChar; } else { - elidePos.setX(line.naturalTextRect().right()); + br = br.united(line.naturalTextRect()); } - elidePos.setY(line.position().y()); - if (!elipsisLayout) - elipsisLayout = new QTextLayout(elideChar, layout.font()); - elipsisLayout->beginLayout(); - QTextLine el = elipsisLayout->createLine(); - el.setPosition(elidePos); - elipsisLayout->endLayout(); - br = br.united(el.naturalTextRect()); + nextLine.setLineWidth(0); + nextLine.setPosition(QPointF(FLT_MAX, FLT_MAX)); + break; } - br = br.united(line.naturalTextRect()); - break; } + br = br.united(line.naturalTextRect()); + line = nextLine; } - br = br.united(line.naturalTextRect()); - } - layout.endLayout(); - br.moveTop(0); - //Update truncated - if (truncated != truncate) { - truncated = truncate; - emit q->truncatedChanged(); + layout.endLayout(); + + if (once) { + naturalWidth = layout.maximumWidth(); + once = false; + + if (requireImplicitWidth + && characterCount < layoutText.length() + && unwrappedLineCount < maximumLineCount) { + // Use a new layout to get the maximum width for the remaining text. Using a + // different layout excludes the truncated text from rendering. + QTextLayout widthLayout(layoutText.mid(characterCount), scaledFont); + widthLayout.setTextOption(layout.textOption()); + + for (; unwrappedLineCount <= maximumLineCount; ++unwrappedLineCount) { + QTextLine line = widthLayout.createLine(); + if (!line.isValid()) + break; + } + naturalWidth = qMax(naturalWidth, widthLayout.maximumWidth()); + } + } + + QRectF unelidedRect = br.united(line.naturalTextRect()); + + if (horizontalFit) { + if (unelidedRect.width() > lineWidth || (!verticalFit && wrapped)) { + largeFont = scaledFontSize - 1; + scaledFontSize = (smallFont + largeFont) / 2; + if (smallFont > largeFont) + break; + continue; + } else if (!verticalFit) { + smallFont = scaledFontSize; + scaledFontSize = (smallFont + largeFont + 1) / 2; + if (smallFont == largeFont) + break; + } + } + + if (verticalFit) { + if (truncateHeight || (q->heightValid() && unelidedRect.height() > q->height())) { + largeFont = scaledFontSize - 1; + scaledFontSize = (smallFont + largeFont + 1) / 2; + if (smallFont > largeFont) + break; + } else { + smallFont = scaledFontSize; + scaledFontSize = (smallFont + largeFont + 1) / 2; + if (smallFont == largeFont) + break; + } + } + } while (horizontalFit || verticalFit); + + if (elide) { + if (!elideLayout) + elideLayout = new QTextLayout; + elideLayout->setFont(layout.font()); + elideLayout->setTextOption(layout.textOption()); + elideLayout->setText(elideText); + elideLayout->setText(elideLayout->engine()->elidedText(Qt::TextElideMode(elideMode), lineWidth)); + elideLayout->beginLayout(); + + QTextLine elidedLine = elideLayout->createLine(); + elidedLine.setPosition(QPointF(0, height)); + if (customLayout) { + setupCustomLineGeometry(elidedLine, height, line.lineNumber()); + } else { + setLineGeometry(elidedLine, lineWidth, height); + } + elideLayout->endLayout(); + + br = br.united(elidedLine.naturalTextRect()); + + if (visibleCount > 1) + line.setPosition(QPointF(FLT_MAX, FLT_MAX)); + else + layout.clearLayout(); + } else { + delete elideLayout; + elideLayout = 0; } if (!customLayout) br.setHeight(height); - if (!q->widthValid()) - naturalWidth = br.width(); - //Update the number of visible lines if (lineCount != visibleCount) { lineCount = visibleCount; emit q->lineCountChanged(); } + + if (truncated != wasTruncated) + emit q->truncatedChanged(); + return QRect(qRound(br.x()), qRound(br.y()), qCeil(br.width()), qCeil(br.height())); } @@ -1921,31 +2021,32 @@ void QQuickText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeo bool leftAligned = effectiveHAlign() == QQuickText::AlignLeft; bool wrapped = d->wrapMode != QQuickText::NoWrap; bool elide = d->elideMode != QQuickText::ElideNone; + bool scaleFont = d->fontSizeMode != QQuickText::FixedSize && (widthValid() || heightValid()); if ((!widthChanged && !heightChanged) || d->internalWidthUpdate) goto geomChangeDone; - if (leftAligned && !wrapped && !elide) + if (leftAligned && !wrapped && !elide && !scaleFont) goto geomChangeDone; // left aligned unwrapped text without eliding never needs relayout - if (!widthChanged && !wrapped && d->singleline) + if (!widthChanged && !wrapped && d->singleline && !scaleFont) goto geomChangeDone; // only height has changed which doesn't affect single line unwrapped text - if (!widthChanged && wrapped && d->elideMode != QQuickText::ElideRight) + if (!widthChanged && wrapped && d->elideMode != QQuickText::ElideRight && !scaleFont) goto geomChangeDone; // only height changed and no multiline eliding. if (leftAligned && d->elideMode == QQuickText::ElideRight && !d->truncated && d->singleline - && !wrapped && newGeometry.width() > oldGeometry.width()) + && !wrapped && newGeometry.width() > oldGeometry.width() && !scaleFont) goto geomChangeDone; // Eliding not affected if we're not currently truncated and we get wider. - if (d->elideMode == QQuickText::ElideRight && wrapped && newGeometry.height() > oldGeometry.height()) { + if (d->elideMode == QQuickText::ElideRight && wrapped && newGeometry.height() > oldGeometry.height() && !scaleFont) { if (!d->truncated) goto geomChangeDone; // Multiline eliding not affected if we're not currently truncated and we get higher. if (d->maximumLineCountValid && d->lineCount == d->maximumLineCount) goto geomChangeDone; // Multiline eliding not affected if we're already at max line count and we get higher. } - if (d->updateOnComponentComplete || (elide && widthValid())) { + if (d->updateOnComponentComplete) { // We need to re-elide d->updateLayout(); } else { @@ -2048,8 +2149,8 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data } else if (d->elideMode == QQuickText::ElideNone || bounds.width() > 0.) { node->addTextLayout(QPoint(0, bounds.y()), &d->layout, d->color, d->style, d->styleColor); - if (d->elipsisLayout) - node->addTextLayout(QPoint(0, bounds.y()), d->elipsisLayout, d->color, d->style, d->styleColor); + if (d->elideLayout) + node->addTextLayout(QPoint(0, bounds.y()), d->elideLayout, d->color, d->style, d->styleColor); } foreach (QDeclarativeStyledTextImgTag *img, d->visibleImgTags) { @@ -2072,6 +2173,15 @@ bool QQuickText::event(QEvent *e) } } +void QQuickText::updatePolish() +{ + Q_D(QQuickText); + if (d->updateLayoutOnPolish) + d->updateLayout(); + else + d->updateSize(); +} + /*! \qmlproperty real QtQuick2::Text::paintedWidth @@ -2153,6 +2263,105 @@ void QQuickText::setLineHeightMode(LineHeightMode mode) emit lineHeightModeChanged(mode); } +/*! + \qmlproperty enumeration QtQuick2::Text::fontSizeMode + + This property specifies how the font size of the displayed text is determined. + The possible values are: + + \list + \o Text.FixedSize (default) - The size specified by \l font.pixelSize + or \l font.pointSize is used. + \o Text.HorizontalFit - The largest size up to the size specified that fits + within the width of the item without wrapping is used. + \o Text.VerticalFit - The largest size up to the size specified that fits + the height of the item is used. + \o Text.Fit - The largest size up to the size specified the 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. + + 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; + + polish(); + + d->fontSizeMode = mode; + emit fontSizeModeChanged(); +} + +/*! + \qmlproperty int QtQuick2::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())) + polish(); + d->minimumPixelSize = size; + emit minimumPixelSizeChanged(); +} + +/*! + \qmlproperty int QtQuick2::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())) + polish(); + d->minimumPointSize = size; + emit minimumPointSizeChanged(); +} + /*! Returns the number of resources (images) that are being loaded asynchronously. */ diff --git a/src/quick/items/qquicktext_p.h b/src/quick/items/qquicktext_p.h index ddc9d38c75..0630fe4510 100644 --- a/src/quick/items/qquicktext_p.h +++ b/src/quick/items/qquicktext_p.h @@ -63,6 +63,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem Q_ENUMS(TextElideMode) Q_ENUMS(WrapMode) Q_ENUMS(LineHeightMode) + Q_ENUMS(FontSizeMode) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) @@ -84,6 +85,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem Q_PROPERTY(qreal lineHeight READ lineHeight WRITE setLineHeight NOTIFY lineHeightChanged) Q_PROPERTY(LineHeightMode lineHeightMode READ lineHeightMode WRITE setLineHeightMode NOTIFY lineHeightModeChanged) Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl RESET resetBaseUrl NOTIFY baseUrlChanged) + Q_PROPERTY(int minimumPixelSize READ minimumPixelSize WRITE setMinimumPixelSize NOTIFY minimumPixelSizeChanged) + Q_PROPERTY(int minimumPointSize READ minimumPointSize WRITE setMinimumPointSize NOTIFY minimumPointSizeChanged) + Q_PROPERTY(FontSizeMode fontSizeMode READ fontSizeMode WRITE setFontSizeMode NOTIFY fontSizeModeChanged) public: QQuickText(QQuickItem *parent=0); @@ -118,6 +122,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem enum LineHeightMode { ProportionalHeight, FixedHeight }; + enum FontSizeMode { FixedSize = 0x0, HorizontalFit = 0x01, VerticalFit = 0x02, + Fit = HorizontalFit | VerticalFit }; + QString text() const; void setText(const QString &); @@ -163,10 +170,20 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem LineHeightMode lineHeightMode() const; void setLineHeightMode(LineHeightMode); + QUrl baseUrl() const; void setBaseUrl(const QUrl &url); void resetBaseUrl(); + int minimumPixelSize() const; + void setMinimumPixelSize(int size); + + int minimumPointSize() const; + void setMinimumPointSize(int size); + + FontSizeMode fontSizeMode() const; + void setFontSizeMode(FontSizeMode mode); + virtual void componentComplete(); int resourcesLoading() const; // mainly for testing @@ -195,6 +212,9 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem void paintedSizeChanged(); void lineHeightChanged(qreal lineHeight); void lineHeightModeChanged(LineHeightMode mode); + void fontSizeModeChanged(); + void minimumPixelSizeChanged(); + void minimumPointSizeChanged(); void effectiveHorizontalAlignmentChanged(); void lineLaidOut(QQuickTextLine *line); void baseUrlChanged(); @@ -207,6 +227,8 @@ class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); virtual bool event(QEvent *); + void updatePolish(); + private Q_SLOTS: void q_imagesLoaded(); void triggerPreprocess(); @@ -231,6 +253,7 @@ class Q_AUTOTEST_EXPORT QQuickTextLine : public QObject QQuickTextLine(); void setLine(QTextLine* line); + void setLineOffset(int offset); int number() const; qreal width() const; @@ -248,6 +271,7 @@ class Q_AUTOTEST_EXPORT QQuickTextLine : public QObject private: QTextLine *m_line; qreal m_height; + int m_lineOffset; }; QT_END_NAMESPACE diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h index e7b0478e21..7585026df8 100644 --- a/src/quick/items/qquicktext_p_p.h +++ b/src/quick/items/qquicktext_p_p.h @@ -103,9 +103,12 @@ class Q_AUTOTEST_EXPORT QQuickTextPrivate : public QQuickImplicitSizeItemPrivate int lineCount; int maximumLineCount; int maximumLineCountValid; + QQuickText::FontSizeMode fontSizeMode; + int minimumPixelSize; + int minimumPointSize; QPointF elidePos; - static QString elideChar; + static const QChar elideChar; void markDirty(); bool invalidateImageCache(); @@ -115,6 +118,7 @@ class Q_AUTOTEST_EXPORT QQuickTextPrivate : public QQuickImplicitSizeItemPrivate bool imageCacheDirty:1; bool updateOnComponentComplete:1; + bool updateLayoutOnPolish:1; bool richText:1; bool styledText:1; bool singleline:1; @@ -141,13 +145,13 @@ class Q_AUTOTEST_EXPORT QQuickTextPrivate : public QQuickImplicitSizeItemPrivate QQuickTextDocumentWithImageResources *doc; QRect setupTextLayout(); - void setupCustomLineGeometry(QTextLine &line, qreal &height, qreal elideWidth); + void setupCustomLineGeometry(QTextLine &line, qreal &height, int lineOffset = 0); QPixmap textLayoutImage(bool drawStyle); void drawTextLayout(QPainter *p, const QPointF &pos, bool drawStyle); bool isLinkActivatedConnected(); QString anchorAt(const QPointF &pos); QTextLayout layout; - QTextLayout *elipsisLayout; + QTextLayout *elideLayout; QQuickTextLine *textLine; static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource); diff --git a/tests/auto/qtquick2/qquicktext/data/fontSizeMode.qml b/tests/auto/qtquick2/qquicktext/data/fontSizeMode.qml new file mode 100644 index 0000000000..20f7535365 --- /dev/null +++ b/tests/auto/qtquick2/qquicktext/data/fontSizeMode.qml @@ -0,0 +1,24 @@ +import QtQuick 2.0 + +Item { + width: 300 + height: 200 + + Rectangle { + anchors.fill: myText + border.width: 1 + } + + Text { + id: myText + objectName: "myText" + width: 250 + height: 41 + minimumPointSize: 8 + minimumPixelSize: 8 + font.pixelSize: 30 + font.family: "Helvetica" + } + + TextInput { focus: true; objectName: "input" } +} diff --git a/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp b/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp index cac65196eb..f59df3dd72 100644 --- a/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/qtquick2/qquicktext/tst_qquicktext.cpp @@ -113,6 +113,10 @@ private slots: void imgTagsElide(); void imgTagsUpdates(); void imgTagsError(); + void fontSizeMode_data(); + void fontSizeMode(); + void fontSizeModeMultiline_data(); + void fontSizeModeMultiline(); private: QStringList standard; @@ -1403,7 +1407,7 @@ void tst_qquicktext::lineHeight() qreal h = myText->height(); myText->setLineHeight(1.5); - QVERIFY(myText->height() == qCeil(h * 1.5)); + QCOMPARE(myText->height(), qreal(qCeil(h * 1.5))); myText->setLineHeightMode(QQuickText::FixedHeight); myText->setLineHeight(20); @@ -1427,18 +1431,35 @@ void tst_qquicktext::lineHeight() void tst_qquicktext::implicitSize_data() { QTest::addColumn("text"); + QTest::addColumn("width"); QTest::addColumn("wrap"); - QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << "Text.NoWrap"; - QTest::newRow("richtext") << "The quick red fox jumped over the lazy brown dog" << "Text.NoWrap"; - QTest::newRow("plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "Text.Wrap"; - QTest::newRow("richtext_wrap") << "The quick red fox jumped over the lazy brown dog" << "Text.Wrap"; + QTest::addColumn("elide"); + QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideNone"; + QTest::newRow("richtext") << "The quick red fox jumped over the lazy brown dog" <<" 50" << "Text.NoWrap" << "Text.ElideNone"; + QTest::newRow("plain, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideNone"; + QTest::newRow("plain, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideRight"; + QTest::newRow("plain, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideRight"; + QTest::newRow("richtext, 0 width") << "The quick red fox jumped over the lazy brown dog" <<" 0" << "Text.NoWrap" << "Text.ElideNone"; + QTest::newRow("plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone"; + QTest::newRow("richtext_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone"; + QTest::newRow("plain_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone"; + QTest::newRow("plain_wrap, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideRight"; + QTest::newRow("plain_wrap, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideRight"; + QTest::newRow("richtext_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone"; } void tst_qquicktext::implicitSize() { QFETCH(QString, text); + QFETCH(QString, width); QFETCH(QString, wrap); - QString componentStr = "import QtQuick 2.0\nText { text: \"" + text + "\"; width: 50; wrapMode: " + wrap + " }"; + QFETCH(QString, elide); + QString componentStr = "import QtQuick 2.0\nText { " + "text: \"" + text + "\"; " + "width: " + width + "; " + "wrapMode: " + wrap + "; " + "elide: " + elide + "; " + "maximumLineCount: 1 }"; QDeclarativeComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); @@ -1598,6 +1619,537 @@ void tst_qquicktext::imgTagsError() delete textObject; } +void tst_qquicktext::fontSizeMode_data() +{ + QTest::addColumn("text"); + QTest::addColumn("canElide"); + QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << true; + QTest::newRow("richtext") << "The quick red fox jumped over the lazy brown dog" << false; +} + +void tst_qquicktext::fontSizeMode() +{ + QFETCH(QString, text); + QFETCH(bool, canElide); + + QQuickView *canvas = createView(testFile("fontSizeMode.qml")); + canvas->show(); + + QQuickText *myText = canvas->rootObject()->findChild("myText"); + QVERIFY(myText != 0); + + myText->setText(text); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + + qreal originalWidth = myText->paintedWidth(); + qreal originalHeight = myText->paintedHeight(); + + // The original text unwrapped should exceed the width of the item. + QVERIFY(originalWidth > myText->width()); + QVERIFY(originalHeight < myText->height()); + + QFont font = myText->font(); + font.setPixelSize(64); + + myText->setFont(font); + myText->setFontSizeMode(QQuickText::HorizontalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Font size reduced to fit within the width of the item. + qreal horizontalFitWidth = myText->paintedWidth(); + qreal horizontalFitHeight = myText->paintedHeight(); + QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding + QVERIFY(horizontalFitHeight <= myText->height() + 2); + + if (canElide) { + // Elide won't affect the size with HorizontalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideLeft); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideMiddle); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::VerticalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Font size increased to fill the height of the item. + qreal verticalFitHeight = myText->paintedHeight(); + QVERIFY(myText->paintedWidth() > myText->width()); + QVERIFY(verticalFitHeight <= myText->height() + 2); + QVERIFY(verticalFitHeight > originalHeight); + + if (canElide) { + // Elide won't affect the height of a single line with VerticalFit but will crop the width. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(myText->truncated()); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideLeft); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(myText->truncated()); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideMiddle); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(myText->truncated()); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::Fit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Should be the same as HorizontalFit with no wrapping. + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + if (canElide) { + // Elide won't affect the size with Fit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideLeft); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideMiddle); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::FixedSize); + myText->setWrapMode(QQuickText::Wrap); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + + originalWidth = myText->paintedWidth(); + originalHeight = myText->paintedHeight(); + + // The original text wrapped should exceed the height of the item. + QVERIFY(originalWidth <= myText->width() + 2); + QVERIFY(originalHeight > myText->height()); + + myText->setFontSizeMode(QQuickText::HorizontalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the + // same size as without text wrapping. + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + if (canElide) { + // Elide won't affect the size with HorizontalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::VerticalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // VerticalFit should reduce the size to the wrapped text within the vertical height. + verticalFitHeight = myText->paintedHeight(); + qreal verticalFitWidth = myText->paintedWidth(); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(verticalFitHeight <= myText->height() + 2); + QVERIFY(verticalFitHeight < originalHeight); + + if (canElide) { + // Elide won't affect the height or width of a wrapped text with VerticalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::Fit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Should be the same as VerticalFit with wrapping. + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + if (canElide) { + // Elide won't affect the size with Fit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::FixedSize); + myText->setMaximumLineCount(2); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + + // The original text wrapped should exceed the height of the item. + QVERIFY(originalWidth <= myText->width() + 2); + QVERIFY(originalHeight > myText->height()); + + myText->setFontSizeMode(QQuickText::HorizontalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the + // same size as without text wrapping. + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + if (canElide) { + // Elide won't affect the size with HorizontalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::VerticalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // VerticalFit should reduce the size to the wrapped text within the vertical height. + verticalFitHeight = myText->paintedHeight(); + verticalFitWidth = myText->paintedWidth(); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(verticalFitHeight <= myText->height() + 2); + QVERIFY(verticalFitHeight < originalHeight); + + if (canElide) { + // Elide won't affect the height or width of a wrapped text with VerticalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::Fit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Should be the same as VerticalFit with wrapping. + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + if (canElide) { + // Elide won't affect the size with Fit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } +} + +void tst_qquicktext::fontSizeModeMultiline_data() +{ + QTest::addColumn("text"); + QTest::addColumn("canElide"); + QTest::newRow("plain") << "The quick red fox jumped\n over the lazy brown dog" << true; + QTest::newRow("richtext") << "The quick red fox jumped
over the lazy brown dog
" << false; +} + +void tst_qquicktext::fontSizeModeMultiline() +{ + QFETCH(QString, text); + QFETCH(bool, canElide); + + QQuickView *canvas = createView(testFile("fontSizeMode.qml")); + canvas->show(); + + QQuickText *myText = canvas->rootObject()->findChild("myText"); + QVERIFY(myText != 0); + + myText->setText(text); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + + qreal originalWidth = myText->paintedWidth(); + qreal originalHeight = myText->paintedHeight(); + QCOMPARE(myText->lineCount(), 2); + + // The original text unwrapped should exceed the width and height of the item. + QVERIFY(originalWidth > myText->width()); + QVERIFY(originalHeight > myText->height()); + + QFont font = myText->font(); + font.setPixelSize(64); + + myText->setFont(font); + myText->setFontSizeMode(QQuickText::HorizontalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Font size reduced to fit within the width of the item. + QCOMPARE(myText->lineCount(), 2); + qreal horizontalFitWidth = myText->paintedWidth(); + qreal horizontalFitHeight = myText->paintedHeight(); + QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding + QVERIFY(horizontalFitHeight > myText->height()); + + if (canElide) { + // Right eliding will remove the last line + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(myText->truncated()); + QCOMPARE(myText->lineCount(), 1); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(myText->paintedHeight() <= myText->height() + 2); + + // Left or middle eliding wont have any effect. + myText->setElideMode(QQuickText::ElideLeft); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideMiddle); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::VerticalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Font size reduced to fit within the height of the item. + qreal verticalFitWidth = myText->paintedWidth(); + qreal verticalFitHeight = myText->paintedHeight(); + QVERIFY(verticalFitWidth <= myText->width() + 2); + QVERIFY(verticalFitHeight <= myText->height() + 2); + + if (canElide) { + // Elide will have no effect. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideLeft); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideMiddle); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::Fit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Should be the same as VerticalFit with no wrapping. + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + if (canElide) { + // Elide won't affect the size with Fit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideLeft); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideMiddle); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::FixedSize); + myText->setWrapMode(QQuickText::Wrap); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + + originalWidth = myText->paintedWidth(); + originalHeight = myText->paintedHeight(); + + // The original text wrapped should exceed the height of the item. + QVERIFY(originalWidth <= myText->width() + 2); + QVERIFY(originalHeight > myText->height()); + + myText->setFontSizeMode(QQuickText::HorizontalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the + // same size as without text wrapping. + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + if (canElide) { + // Text will be elided vertically with HorizontalFit + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(myText->truncated()); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(myText->paintedHeight() <= myText->height() + 2); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::VerticalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // VerticalFit should reduce the size to the wrapped text within the vertical height. + verticalFitHeight = myText->paintedHeight(); + verticalFitWidth = myText->paintedWidth(); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(verticalFitHeight <= myText->height() + 2); + QVERIFY(verticalFitHeight < originalHeight); + + if (canElide) { + // Elide won't affect the height or width of a wrapped text with VerticalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::Fit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Should be the same as VerticalFit with wrapping. + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + if (canElide) { + // Elide won't affect the size with Fit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::FixedSize); + myText->setMaximumLineCount(2); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + + // The original text wrapped should exceed the height of the item. + QVERIFY(originalWidth <= myText->width() + 2); + QVERIFY(originalHeight > myText->height()); + + myText->setFontSizeMode(QQuickText::HorizontalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the + // same size as without text wrapping. + QCOMPARE(myText->paintedWidth(), horizontalFitWidth); + QCOMPARE(myText->paintedHeight(), horizontalFitHeight); + + if (canElide) { + // Elide won't affect the size with HorizontalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(myText->truncated()); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(myText->paintedHeight() <= myText->height() + 2); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::VerticalFit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // VerticalFit should reduce the size to the wrapped text within the vertical height. + verticalFitHeight = myText->paintedHeight(); + verticalFitWidth = myText->paintedWidth(); + QVERIFY(myText->paintedWidth() <= myText->width() + 2); + QVERIFY(verticalFitHeight <= myText->height() + 2); + QVERIFY(verticalFitHeight < originalHeight); + + if (canElide) { + // Elide won't affect the height or width of a wrapped text with VerticalFit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } + + myText->setFontSizeMode(QQuickText::Fit); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + // Should be the same as VerticalFit with wrapping. + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + if (canElide) { + // Elide won't affect the size with Fit. + myText->setElideMode(QQuickText::ElideRight); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + QVERIFY(!myText->truncated()); + QCOMPARE(myText->paintedWidth(), verticalFitWidth); + QCOMPARE(myText->paintedHeight(), verticalFitHeight); + + myText->setElideMode(QQuickText::ElideNone); + QTRY_COMPARE(QQuickItemPrivate::get(myText)->polishScheduled, false); + } +} + QTEST_MAIN(tst_qquicktext) #include "tst_qquicktext.moc" diff --git a/tests/testapplications/text/text.qml b/tests/testapplications/text/text.qml index 7f3574e8b9..4f4aa89ea3 100644 --- a/tests/testapplications/text/text.qml +++ b/tests/testapplications/text/text.qml @@ -48,12 +48,16 @@ Rectangle { Item { id: textpanel - height: 360 - width: 440 + + anchors.fill: parent + anchors.rightMargin: controlpanel.width + Text { id: textelement height: parent.height - 20; width: parent.width - 20 anchors.centerIn: parent + anchors.fill: parent; + anchors.margins: 10 text: { textvalue.model.get(textvalue.currentIndex).value } textFormat: { formatvalue.model.get(formatvalue.currentIndex).value } @@ -79,6 +83,8 @@ Rectangle { smooth: { smoothvalue.model.get(smoothvalue.currentIndex).value } style: { stylevalue.model.get(stylevalue.currentIndex).value } styleColor: { stylecolorvalue.model.get(stylecolorvalue.currentIndex).value } + fontSizeMode : { fontsizemodevalue.model.get(fontsizemodevalue.currentIndex).value } + minimumPointSize : { minimumpointsizevalue.model.get(minimumpointsizevalue.currentIndex).value } Rectangle{ color: "transparent"; border.color: "green"; anchors.fill: parent } } @@ -204,11 +210,11 @@ Rectangle { ControlView { id: pixelvalue controlname: "Pixel" - model: ListModel { ListElement { name: "-1"; value: -1 } ListElement { name: "8"; value: 8 } ListElement { name: "20"; value: 20 } } } + model: ListModel { ListElement { name: "-1"; value: -1 } ListElement { name: "8"; value: 8 } ListElement { name: "20"; value: 20 } ListElement { name: "50"; value: 20 } } } ControlView { id: pointvalue controlname: "Point" - model: ListModel { ListElement { name: "-1"; value: -1 } ListElement { name: "8"; value: 8 } ListElement { name: "20"; value: 20 } } } + model: ListModel { ListElement { name: "-1"; value: -1 } ListElement { name: "8"; value: 8 } ListElement { name: "20"; value: 20 } ListElement { name: "50"; value: 20 } } } ControlView { id: strikeoutvalue controlname: "Strike" @@ -267,6 +273,21 @@ Rectangle { controlname: "Wrap" model: ListModel { ListElement { name: "None"; value: Text.NoWrap } ListElement { name: "Word"; value: Text.WordWrap } ListElement { name: "Anywhere"; value: Text.WrapAnywhere } ListElement { name: "Wrap"; value: Text.Wrap } } } + ControlView { + id: fontsizemodevalue + controlname: "FontSize" + model: ListModel { ListElement { name: "FixedSize"; value: Text.FixedSize } ListElement { name: "Horizontal"; value: Text.HorizontalFit } + ListElement { name: "Vertical"; value: Text.VerticalFit } ListElement { name: "Fit"; value: Text.Fit } } } + ControlView { + id: minimumpixelsizevalue + controlname: "MinPixelSize" + model: ListModel { ListElement { name: "8"; value: 8 } ListElement { name: "12"; value: 12 } + ListElement { name: "24"; value: 24 } ListElement { name: "32"; value: 32 } } } + ControlView { + id: minimumpointsizevalue + controlname: "MinPointSize" + model: ListModel { ListElement { name: "8"; value: 8 } ListElement { name: "12"; value: 12 } + ListElement { name: "24"; value: 24 } ListElement { name: "32"; value: 32 } } } } } }