From ece70ccba8718804bf8f42dafdcda00f8caa4d1a Mon Sep 17 00:00:00 2001
From: Andrew den Exter <andrew.den-exter@nokia.com>
Date: Wed, 15 Feb 2012 11:40:59 +1000
Subject: [PATCH] Avoid an unnecessary layout when eliding text.

Query the elided text from the initial layout rather than doing a
second layout of the line to be elided.

Change-Id: I399f99a11046013c0c51add2e2f2dd14b959831a
Reviewed-by: Martin Jones <martin.jones@nokia.com>
---
 src/quick/items/qquicktext.cpp   | 83 +++++++++++++++++++++++---------
 src/quick/items/qquicktext_p_p.h |  1 +
 2 files changed, 62 insertions(+), 22 deletions(-)

diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp
index 241086cc83..ce030938a9 100644
--- a/src/quick/items/qquicktext.cpp
+++ b/src/quick/items/qquicktext.cpp
@@ -589,6 +589,28 @@ void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height,
 #endif
 }
 
+QString QQuickTextPrivate::elidedText(int lineWidth, const QTextLine &line, QTextLine *nextLine) const
+{
+    if (nextLine) {
+        nextLine->setLineWidth(INT_MAX);
+        return layout.engine()->elidedText(
+                Qt::TextElideMode(elideMode),
+                lineWidth,
+                0,
+                line.textStart(),
+                line.textLength() + nextLine->textLength());
+    } else {
+        QString elideText = layout.text().mid(line.textStart(), line.textLength());
+        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.width(elideChar) + line.naturalTextWidth() >= lineWidth)
+            elideText = metrics.elidedText(elideText, Qt::TextElideMode(elideMode), lineWidth);
+        return elideText;
+    }
+}
+
 /*!
     Lays out the QQuickTextPrivate::layout QTextLayout in the constraints of the QQuickText.
 
@@ -677,6 +699,8 @@ QRect QQuickTextPrivate::setupTextLayout()
 
     int eos = multilengthEos;
 
+    // Repeated layouts with reduced font sizes or abbreviated strings may be required if the text
+    // doesn't fit within the element dimensions.
     for (;;) {
         if (!once) {
             if (pixelSize)
@@ -705,22 +729,25 @@ QRect QQuickTextPrivate::setupTextLayout()
                 setLineGeometry(line, lineWidth, height);
             }
 
+            // Elide the previous line if the accumulated height of the text exceeds the height
+            // of the element.
             if (multilineElide && height > q->height() && visibleCount > 1) {
+                elide = 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;
                 height = preLayoutHeight;
 
-                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;
-                }
+                elideText = layoutText.at(line.textStart() - 1) != QChar::LineSeparator
+                        ? elidedText(lineWidth, previousLine, &line)
+                        : elidedText(lineWidth, previousLine);
+                // The previous line is the last one visible so move the current one off somewhere
+                // out of the way and back everything up one line.
                 line.setLineWidth(0);
                 line.setPosition(QPointF(FLT_MAX, FLT_MAX));
                 line = previousLine;
@@ -733,10 +760,19 @@ QRect QQuickTextPrivate::setupTextLayout()
             if (!nextLine.isValid()) {
                 characterCount = line.textStart() + line.textLength();
                 if (singlelineElide && visibleCount == 1 && line.naturalTextWidth() > lineWidth) {
+                    // Elide a single line of  text if its width exceeds the element width.
+                    elide = true;
+                    if (eos != -1) // There's an abbreviated string available.
+                        break;
+
                     truncated = true;
                     height = preLayoutHeight;
-                    elide = true;
-                    elideText = layoutText.mid(line.textStart(), line.textLength());
+                    elideText = layout.engine()->elidedText(
+                            Qt::TextElideMode(elideMode),
+                            lineWidth,
+                            0,
+                            line.textStart(),
+                            line.textLength());
                 } else {
                     br = br.united(line.naturalTextRect());
                 }
@@ -748,21 +784,20 @@ QRect QQuickTextPrivate::setupTextLayout()
                 if (!wrappedLine)
                     ++unwrappedLineCount;
 
+                // Stop if the maximum number of lines has been reached and elide the last line
+                // if enabled.
                 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;
+                        if (eos != -1)  // There's an abbreviated string available
+                            break;
+                        height = preLayoutHeight;
+                        elideText = wrappedLine
+                                ? elidedText(lineWidth, line, &nextLine)
+                                : elidedText(lineWidth, line);
                     } else {
                         br = br.united(line.naturalTextRect());
                     }
@@ -778,6 +813,7 @@ QRect QQuickTextPrivate::setupTextLayout()
         layout.endLayout();
         br.moveTop(0);
 
+        // Save the implicitWidth of the text on the first layout only.
         if (once) {
             naturalWidth = layout.maximumWidth();
             once = false;
@@ -799,6 +835,8 @@ QRect QQuickTextPrivate::setupTextLayout()
             }
         }
 
+        // 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);
@@ -809,6 +847,10 @@ QRect QQuickTextPrivate::setupTextLayout()
             continue;
         }
 
+        if (!horizontalFit && !verticalFit)
+            break;
+
+        // Try and find a font size that better fits the dimensions of the element.
         QRectF unelidedRect = br.united(line.naturalTextRect());
 
         if (horizontalFit) {
@@ -840,8 +882,6 @@ QRect QQuickTextPrivate::setupTextLayout()
             }
         }
 
-        if (!horizontalFit && !verticalFit)
-            break;
     }
 
     if (eos != multilengthEos)
@@ -853,7 +893,6 @@ QRect QQuickTextPrivate::setupTextLayout()
         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();
diff --git a/src/quick/items/qquicktext_p_p.h b/src/quick/items/qquicktext_p_p.h
index 0a7fc743b5..a03b403879 100644
--- a/src/quick/items/qquicktext_p_p.h
+++ b/src/quick/items/qquicktext_p_p.h
@@ -83,6 +83,7 @@ public:
     QTextDocument *textDocument();
     bool isLineLaidOutConnected();
     void setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height);
+    QString elidedText(int lineWidth, const QTextLine &line, QTextLine *nextLine = 0) const;
 
     QString text;
     QUrl baseUrl;
-- 
GitLab