Fix multiline eliding and support eliding when height is set.
authorMartin Jones <martin.jones@nokia.com>
Mon, 28 Nov 2011 05:32:31 +0000 (15:32 +1000)
committerQt by Nokia <qt-info@nokia.com>
Mon, 28 Nov 2011 06:45:43 +0000 (07:45 +0100)
Task-number: QTBUG-22920, QTBUG-22116

Change-Id: Ibe78ce1b0b438eec32955b986a8740f173cd082f
Reviewed-by: Yann Bodson <yann.bodson@nokia.com>

src/declarative/items/qquicktext.cpp
src/declarative/items/qquicktext_p_p.h
tests/auto/declarative/qquicktext/data/multilineelide.qml [new file with mode: 0644]
tests/auto/declarative/qquicktext/tst_qquicktext.cpp

index e11e4be..e3286f0 100644 (file)
@@ -106,7 +106,7 @@ QQuickTextPrivate::QQuickTextPrivate()
   richText(false), styledText(false), singleline(false), cacheAllTextAsImage(true), internalWidthUpdate(false),
   requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false),
   layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), textHasChanged(true),
-  naturalWidth(0), doc(0), textLine(0), nodeType(NodeIsNull)
+  naturalWidth(0), doc(0), elipsisLayout(0), textLine(0), nodeType(NodeIsNull)
 
 #if defined(Q_OS_MAC)
 , layoutThread(0), paintingThread(0)
@@ -202,6 +202,7 @@ QSet<QUrl> QQuickTextDocumentWithImageResources::errors;
 
 QQuickTextPrivate::~QQuickTextPrivate()
 {
+    delete elipsisLayout;
     delete textLine; textLine = 0;
 }
 
@@ -224,17 +225,21 @@ void QQuickTextPrivate::updateLayout()
         updateOnComponentComplete = true;
         return;
     }
-
+    updateOnComponentComplete = false;
     layoutTextElided = false;
     // Setup instance of QTextLayout for all cases other than richtext
     if (!richText) {
+        if (elipsisLayout) {
+            delete elipsisLayout;
+            elipsisLayout = 0;
+        }
         layout.clearLayout();
         layout.setFont(font);
         if (!styledText) {
             QString tmp = text;
             tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
             singleline = !tmp.contains(QChar::LineSeparator);
-            if (singleline && !maximumLineCountValid && elideMode != QQuickText::ElideNone && q->widthValid()) {
+            if (singleline && !maximumLineCountValid && elideMode != QQuickText::ElideNone && q->widthValid() && wrapMode == QQuickText::NoWrap) {
                 QFontMetrics fm(font);
                 tmp = fm.elidedText(tmp,(Qt::TextElideMode)elideMode,q->width());
                 if (tmp != text) {
@@ -516,9 +521,6 @@ QRect QQuickTextPrivate::setupTextLayout()
         textOption.setUseDesignMetrics(true);
     layout.setTextOption(textOption);
 
-    bool elideText = false;
-    bool truncate = false;
-
     QFontMetrics fm(layout.font());
     elidePos = QPointF();
 
@@ -548,86 +550,87 @@ QRect QQuickTextPrivate::setupTextLayout()
     }
 
     qreal height = 0;
+    QRectF br;
+
+    bool truncate = false;
     bool customLayout = isLineLaidOutConnected();
+    bool elideEnabled = elideMode == QQuickText::ElideRight && q->widthValid();
+
+    layout.beginLayout();
+    if (!lineWidth)
+        lineWidth = INT_MAX;
+    int linesLeft = maximumLineCount;
+    int visibleTextLength = 0;
+    forever {
+        QTextLine line = layout.createLine();
+        if (!line.isValid())
+            break;
 
-    if (maximumLineCountValid) {
-        layout.beginLayout();
-        if (!lineWidth)
-            lineWidth = INT_MAX;
-        int linesLeft = maximumLineCount;
-        int visibleTextLength = 0;
-        while (linesLeft > 0) {
-            QTextLine line = layout.createLine();
-            if (!line.isValid())
-                break;
+        visibleCount++;
 
-            visibleCount++;
+        qreal preLayoutHeight = height;
+        if (customLayout) {
+            setupCustomLineGeometry(line, height);
+        } else if (lineWidth) {
+            line.setLineWidth(lineWidth);
+            line.setPosition(QPointF(line.position().x(), height));
+            height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight;
+        }
 
-            if (customLayout)
-                setupCustomLineGeometry(line, height);
-            else if (lineWidth)
-                line.setLineWidth(lineWidth);
+        bool elide = false;
+        if (elideEnabled && q->heightValid() && height > q->height()) {
+            // This line does not fit in the remaining area.
+            elide = true;
+            if (visibleCount > 1) {
+                --visibleCount;
+                height = preLayoutHeight;
+                line.setLineWidth(0.0);
+                line.setPosition(QPointF(FLT_MAX,FLT_MAX));
+                line = layout.lineAt(visibleCount-1);
+            }
+        } else {
             visibleTextLength += line.textLength();
+        }
 
-            if (--linesLeft == 0) {
-                if (visibleTextLength < text.length()) {
-                    truncate = true;
-                    if (elideMode == QQuickText::ElideRight && q->widthValid()) {
-                        qreal elideWidth = fm.width(elideChar);
-                        // Need to correct for alignment
-                        if (customLayout)
-                            setupCustomLineGeometry(line, height, elideWidth);
-                        else
-                            line.setLineWidth(lineWidth - elideWidth);
-                        if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) {
-                            line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y()));
-                            elidePos.setX(line.naturalTextRect().left() - elideWidth);
-                        } else {
-                            elidePos.setX(line.naturalTextRect().right());
-                        }
-                        elideText = true;
+        if (elide || (maximumLineCountValid && --linesLeft == 0)) {
+            if (visibleTextLength < text.length()) {
+                truncate = true;
+                if (elideEnabled) {
+                    qreal elideWidth = fm.width(elideChar);
+                    // Need to correct for alignment
+                    if (customLayout)
+                        setupCustomLineGeometry(line, height, elideWidth);
+                    else
+                        line.setLineWidth(lineWidth - elideWidth);
+                    if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) {
+                        line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y()));
+                        elidePos.setX(line.naturalTextRect().left() - elideWidth);
+                    } else {
+                        elidePos.setX(line.naturalTextRect().right());
                     }
+                    elidePos.setY(line.position().y());
+                    if (!elipsisLayout)
+                        elipsisLayout = new QTextLayout(elideChar, layout.font());
+                    elipsisLayout->beginLayout();
+                    QTextLine el = elipsisLayout->createLine();
+                    el.setPosition(elidePos);
+                    elipsisLayout->endLayout();
+                    br = br.united(el.naturalTextRect());
                 }
-            }
-        }
-        layout.endLayout();
-
-        //Update truncated
-        if (truncated != truncate) {
-            truncated = truncate;
-            emit q->truncatedChanged();
-        }
-    } else {
-        layout.beginLayout();
-        forever {
-            QTextLine line = layout.createLine();
-            if (!line.isValid())
+                br = br.united(line.naturalTextRect());
                 break;
-            visibleCount++;
-            if (customLayout)
-                setupCustomLineGeometry(line, height);
-            else {
-                if (lineWidth)
-                    line.setLineWidth(lineWidth);
             }
         }
-        layout.endLayout();
+        br = br.united(line.naturalTextRect());
     }
+    layout.endLayout();
 
-    height = 0;
-    QRectF br;
-    for (int i = 0; i < layout.lineCount(); ++i) {
-        QTextLine line = layout.lineAt(i);
-        // set line spacing
-        if (!customLayout)
-            line.setPosition(QPointF(line.position().x(), height));
-        if (elideText && i == layout.lineCount()-1) {
-            elidePos.setY(height + fm.ascent());
-            br = br.united(QRectF(elidePos, QSizeF(fm.width(elideChar), fm.ascent())));
-        }
-        br = br.united(line.naturalTextRect());
-        height += (lineHeightMode == QQuickText::FixedHeight) ? lineHeight : line.height() * lineHeight;
+    //Update truncated
+    if (truncated != truncate) {
+        truncated = truncate;
+        emit q->truncatedChanged();
     }
+
     if (!customLayout)
         br.setHeight(height);
 
@@ -1295,7 +1298,7 @@ void QQuickText::resetHAlign()
 {
     Q_D(QQuickText);
     d->hAlignImplicit = true;
-    if (d->determineHorizontalAlignment() && isComponentComplete())
+    if (isComponentComplete() && d->determineHorizontalAlignment())
         d->updateLayout();
 }
 
@@ -1335,8 +1338,7 @@ bool QQuickTextPrivate::setHAlign(QQuickText::HAlignment alignment, bool forceAl
 
 bool QQuickTextPrivate::determineHorizontalAlignment()
 {
-    Q_Q(QQuickText);
-    if (hAlignImplicit && q->isComponentComplete()) {
+    if (hAlignImplicit) {
         bool alignToRight = text.isEmpty() ? QGuiApplication::keyboardInputDirection() == Qt::RightToLeft : rightToLeftText;
         return setHAlign(alignToRight ? QQuickText::AlignRight : QQuickText::AlignLeft);
     }
@@ -1553,15 +1555,17 @@ void QQuickText::setTextFormat(TextFormat format)
     d->richText = format == RichText;
     d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text));
 
-    if (!wasRich && d->richText && isComponentComplete()) {
-        d->ensureDoc();
-        d->doc->setText(d->text);
-        d->rightToLeftText = d->doc->toPlainText().isRightToLeft();
-        d->richTextAsImage = enableImageCache();
-    } else {
-        d->rightToLeftText = d->text.isRightToLeft();
+    if (isComponentComplete()) {
+        if (!wasRich && d->richText) {
+            d->ensureDoc();
+            d->doc->setText(d->text);
+            d->rightToLeftText = d->doc->toPlainText().isRightToLeft();
+            d->richTextAsImage = enableImageCache();
+        } else {
+            d->rightToLeftText = d->text.isRightToLeft();
+        }
+        d->determineHorizontalAlignment();
     }
-    d->determineHorizontalAlignment();
     d->updateLayout();
 
     emit textFormatChanged(d->format);
@@ -1584,7 +1588,9 @@ void QQuickText::setTextFormat(TextFormat format)
     \endlist
 
     If this property is set to Text.ElideRight, it can be used with multiline
-    text. The text will only elide if maximumLineCount has been set.
+    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.
@@ -1640,11 +1646,13 @@ QRectF QQuickText::boundingRect() const
 void QQuickText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
 {
     Q_D(QQuickText);
-    if ((!d->internalWidthUpdate && newGeometry.width() != oldGeometry.width())
+    bool elide = d->elideMode != QQuickText::ElideNone && widthValid();
+    if ((!d->internalWidthUpdate
+         && (newGeometry.width() != oldGeometry.width() || (elide && newGeometry.height() != oldGeometry.height())))
             && (d->wrapMode != QQuickText::NoWrap
                 || d->elideMode != QQuickText::ElideNone
                 || d->hAlign != QQuickText::AlignLeft)) {
-        if ((d->singleline || d->maximumLineCountValid) && d->elideMode != QQuickText::ElideNone && widthValid()) {
+        if ((d->singleline || d->maximumLineCountValid || heightValid()) && elide) {
             // We need to re-elide
             d->updateLayout();
         } else {
@@ -1731,6 +1739,8 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data
 
         } else {
             node->addTextLayout(QPoint(0, bounds.y()), &d->layout, d->color, d->style, d->styleColor);
+            if (d->elipsisLayout)
+                node->addTextLayout(QPoint(0, bounds.y()), d->elipsisLayout, d->color, d->style, d->styleColor);
         }
 
         return node;
@@ -1842,9 +1852,7 @@ int QQuickText::resourcesLoading() const
 void QQuickText::componentComplete()
 {
     Q_D(QQuickText);
-    QQuickItem::componentComplete();
     if (d->updateOnComponentComplete) {
-        d->updateOnComponentComplete = false;
         if (d->richText) {
             d->ensureDoc();
             d->doc->setText(d->text);
@@ -1854,8 +1862,10 @@ void QQuickText::componentComplete()
             d->rightToLeftText = d->text.isRightToLeft();
         }
         d->determineHorizontalAlignment();
-        d->updateLayout();
     }
+    QQuickItem::componentComplete();
+    if (d->updateOnComponentComplete)
+        d->updateLayout();
 }
 
 
index 16cc29a..2035f47 100644 (file)
@@ -140,6 +140,7 @@ public:
     bool isLinkActivatedConnected();
     QString anchorAt(const QPointF &pos);
     QTextLayout layout;
+    QTextLayout *elipsisLayout;
     QList<QRectF> linesRects;
     QQuickTextLine *textLine;
 
diff --git a/tests/auto/declarative/qquicktext/data/multilineelide.qml b/tests/auto/declarative/qquicktext/data/multilineelide.qml
new file mode 100644 (file)
index 0000000..23398a8
--- /dev/null
@@ -0,0 +1,10 @@
+import QtQuick 2.0
+
+Text {
+    width: 200; height: 200
+    wrapMode: Text.WordWrap
+    elide: Text.ElideRight
+    maximumLineCount: 3
+    text: "the quick brown fox jumped over the lazy dog the quick brown fox jumped over the lazy dog"
+}
+
index 88ac312..fb37357 100644 (file)
@@ -70,6 +70,7 @@ private slots:
     void width();
     void wrap();
     void elide();
+    void multilineElide();
     void textFormat();
 
     void alignments_data();
@@ -448,6 +449,49 @@ void tst_qquicktext::elide()
     }
 }
 
+void tst_qquicktext::multilineElide()
+{
+    QQuickView *canvas = createView(TESTDATA("multilineelide.qml"));
+
+    QQuickText *myText = qobject_cast<QQuickText*>(canvas->rootObject());
+    QVERIFY(myText != 0);
+
+    QCOMPARE(myText->lineCount(), 3);
+    QCOMPARE(myText->truncated(), true);
+
+    qreal lineHeight = myText->paintedHeight() / 3.;
+
+    // reduce size and ensure fewer lines are drawn
+    myText->setHeight(lineHeight * 2);
+    QCOMPARE(myText->lineCount(), 2);
+
+    myText->setHeight(lineHeight);
+    QCOMPARE(myText->lineCount(), 1);
+
+    myText->setHeight(5);
+    QCOMPARE(myText->lineCount(), 1);
+
+    myText->setHeight(lineHeight * 3);
+    QCOMPARE(myText->lineCount(), 3);
+
+    // remove max count and show all lines.
+    myText->setHeight(1000);
+    myText->resetMaximumLineCount();
+
+    QCOMPARE(myText->truncated(), false);
+
+    // reduce size again
+    myText->setHeight(lineHeight * 2);
+    QCOMPARE(myText->lineCount(), 2);
+    QCOMPARE(myText->truncated(), true);
+
+    // change line height
+    myText->setLineHeight(1.1);
+    QCOMPARE(myText->lineCount(), 1);
+
+    delete canvas;
+}
+
 void tst_qquicktext::textFormat()
 {
     {