Fix horizontal alignment of unwrapped text in TextEdit.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Tue, 10 Jul 2012 01:30:53 +0000 (11:30 +1000)
committerQt by Nokia <qt-info@nokia.com>
Mon, 16 Jul 2012 00:30:52 +0000 (02:30 +0200)
When the actual width of the text in a QTextDocument exceeds the
specificed textWidth it is aligned left irregardless of the text
alignment.  Compensate by doing our own alignment of the laid out
document.

Change-Id: I7df900316ffb3ecdf01ddb053480a60a8258182d
Reviewed-by: Martin Jones <martin.jones@nokia.com>
src/quick/items/qquicktextedit.cpp
src/quick/items/qquicktextedit_p_p.h
tests/auto/quick/qquicktextedit/data/mouseselection_align_bl.qml [new file with mode: 0644]
tests/auto/quick/qquicktextedit/data/mouseselection_align_center.qml [new file with mode: 0644]
tests/auto/quick/qquicktextedit/data/mouseselection_align_tr.qml [new file with mode: 0644]
tests/auto/quick/qquicktextedit/data/positionAt.qml
tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp

index df13075..38a510e 100644 (file)
@@ -782,7 +782,7 @@ QRectF QQuickTextEdit::positionToRectangle(int pos) const
     Q_D(const QQuickTextEdit);
     QTextCursor c(d->document);
     c.setPosition(pos);
-    return d->control->cursorRect(c).translated(0, d->yoff);
+    return d->control->cursorRect(c).translated(d->xoff, d->yoff);
 
 }
 
@@ -797,7 +797,10 @@ QRectF QQuickTextEdit::positionToRectangle(int pos) const
 int QQuickTextEdit::positionAt(qreal x, qreal y) const
 {
     Q_D(const QQuickTextEdit);
-    int r = d->document->documentLayout()->hitTest(QPointF(x,y-d->yoff), Qt::FuzzyHit);
+    x -= d->xoff;
+    y -= d->yoff;
+
+    int r = d->document->documentLayout()->hitTest(QPointF(x, y), Qt::FuzzyHit);
     QTextCursor cursor = d->control->textCursor();
     if (r > cursor.position()) {
         // The cursor position includes positions within the preedit text, but only positions in the
@@ -808,7 +811,7 @@ int QQuickTextEdit::positionAt(qreal x, qreal y) const
                 ? layout->preeditAreaText().length()
                 : 0;
         if (preeditLength > 0
-                && d->document->documentLayout()->blockBoundingRect(cursor.block()).contains(x,y-d->yoff)) {
+                && d->document->documentLayout()->blockBoundingRect(cursor.block()).contains(x, y)) {
             r = r > cursor.position() + preeditLength
                     ? r - preeditLength
                     : cursor.position();
@@ -1324,14 +1327,14 @@ bool QQuickTextEdit::isReadOnly() const
 QRectF QQuickTextEdit::cursorRectangle() const
 {
     Q_D(const QQuickTextEdit);
-    return d->control->cursorRect().translated(0, d->yoff);
+    return d->control->cursorRect().translated(d->xoff, d->yoff);
 }
 
 bool QQuickTextEdit::event(QEvent *event)
 {
     Q_D(QQuickTextEdit);
     if (event->type() == QEvent::ShortcutOverride) {
-        d->control->processEvent(event, QPointF(0, -d->yoff));
+        d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
         return event->isAccepted();
     }
     return QQuickImplicitSizeItem::event(event);
@@ -1344,7 +1347,7 @@ Handles the given key \a event.
 void QQuickTextEdit::keyPressEvent(QKeyEvent *event)
 {
     Q_D(QQuickTextEdit);
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
     if (!event->isAccepted())
         QQuickImplicitSizeItem::keyPressEvent(event);
 }
@@ -1356,7 +1359,7 @@ Handles the given key \a event.
 void QQuickTextEdit::keyReleaseEvent(QKeyEvent *event)
 {
     Q_D(QQuickTextEdit);
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
     if (!event->isAccepted())
         QQuickImplicitSizeItem::keyReleaseEvent(event);
 }
@@ -1508,7 +1511,7 @@ Handles the given mouse \a event.
 void QQuickTextEdit::mousePressEvent(QMouseEvent *event)
 {
     Q_D(QQuickTextEdit);
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
     if (d->focusOnPress){
         bool hadActiveFocus = hasActiveFocus();
         forceActiveFocus();
@@ -1527,7 +1530,7 @@ Handles the given mouse \a event.
 void QQuickTextEdit::mouseReleaseEvent(QMouseEvent *event)
 {
     Q_D(QQuickTextEdit);
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
 
     if (!event->isAccepted())
         QQuickImplicitSizeItem::mouseReleaseEvent(event);
@@ -1540,7 +1543,7 @@ Handles the given mouse \a event.
 void QQuickTextEdit::mouseDoubleClickEvent(QMouseEvent *event)
 {
     Q_D(QQuickTextEdit);
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
     if (!event->isAccepted())
         QQuickImplicitSizeItem::mouseDoubleClickEvent(event);
 }
@@ -1552,7 +1555,7 @@ Handles the given mouse \a event.
 void QQuickTextEdit::mouseMoveEvent(QMouseEvent *event)
 {
     Q_D(QQuickTextEdit);
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
     if (!event->isAccepted())
         QQuickImplicitSizeItem::mouseMoveEvent(event);
 }
@@ -1565,7 +1568,7 @@ void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event)
 {
     Q_D(QQuickTextEdit);
     const bool wasComposing = isInputMethodComposing();
-    d->control->processEvent(event, QPointF(0, -d->yoff));
+    d->control->processEvent(event, QPointF(-d->xoff, -d->yoff));
     setCursorVisible(d->control->cursorVisible());
     if (wasComposing != isInputMethodComposing())
         emit inputMethodComposingChanged();
@@ -1577,7 +1580,7 @@ void QQuickTextEdit::itemChange(ItemChange change, const ItemChangeData &value)
     if (change == ItemActiveFocusHasChanged) {
         setCursorVisible(value.boolValue);
         QFocusEvent focusEvent(value.boolValue ? QEvent::FocusIn : QEvent::FocusOut);
-        d->control->processEvent(&focusEvent, QPointF(0, -d->yoff));
+        d->control->processEvent(&focusEvent, QPointF(-d->xoff, -d->yoff));
         if (value.boolValue) {
             q_updateAlignment();
             connect(qApp->inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
@@ -1651,9 +1654,7 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
         node->deleteContent();
         node->setMatrix(QMatrix4x4());
 
-        QRectF bounds = boundingRect();
-
-        node->addTextDocument(QPointF(0, bounds.y()), d->document, d->color, QQuickText::Normal, QColor(),
+        node->addTextDocument(QPointF(d->xoff, d->yoff), d->document, d->color, QQuickText::Normal, QColor(),
                               QColor(), d->selectionColor, d->selectedTextColor, selectionStart(),
                               selectionEnd() - 1);  // selectionEnd() returns first char after
                                                     // selection
@@ -1810,7 +1811,6 @@ void QQuickTextEdit::q_textChanged()
 void QQuickTextEdit::moveCursorDelegate()
 {
     Q_D(QQuickTextEdit);
-    d->determineHorizontalAlignment();
     updateInputMethod();
     emit cursorRectangleChanged();
     if (!d->cursorItem)
@@ -1836,7 +1836,12 @@ void QQuickTextEdit::updateSelectionMarkers()
 QRectF QQuickTextEdit::boundingRect() const
 {
     Q_D(const QQuickTextEdit);
-    QRectF r(0, -d->yoff, d->contentSize.width(), d->contentSize.height());
+    QRectF r(
+            QQuickTextUtil::alignedX(d->contentSize.width(), width(), effectiveHAlign()),
+            d->yoff,
+            d->contentSize.width(),
+            d->contentSize.height());
+
     int cursorWidth = 1;
     if (d->cursorItem)
         cursorWidth = 0;
@@ -1844,34 +1849,8 @@ QRectF QQuickTextEdit::boundingRect() const
         cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor
 
     // Could include font max left/right bearings to either side of rectangle.
-
     r.setRight(r.right() + cursorWidth);
 
-    qreal h = height();
-    switch (d->vAlign) {
-    case AlignTop:
-        break;
-    case AlignBottom:
-        r.moveTop(h - r.height());
-        break;
-    case AlignVCenter:
-        r.moveTop((h - r.height()) / 2);
-        break;
-    }
-
-    qreal w = width();
-    switch (d->hAlign) {
-    case AlignLeft:
-    case AlignJustify:
-        break;
-    case AlignRight:
-        r.moveLeft(w - r.width());
-        break;
-    case AlignHCenter:
-        r.moveLeft((w - r.width()) / 2);
-        break;
-    }
-
     return r;
 }
 
@@ -1935,34 +1914,17 @@ void QQuickTextEdit::updateSize()
         } else {
             d->document->setTextWidth(-1);
         }
-        QFontMetricsF fm(d->font);
-        qreal dy = height();
-        dy -= d->document->size().height();
-
-        qreal nyoff;
-        if (heightValid()) {
-            if (d->vAlign == AlignBottom)
-                nyoff = dy;
-            else if (d->vAlign == AlignVCenter)
-                nyoff = dy/2;
-            else
-                nyoff = 0;
-        } else {
-            nyoff = 0;
-        }
-        if (nyoff != d->yoff)
-            d->yoff = nyoff;
-        setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
 
-        //### need to comfirm cost of always setting these
+        //### need to confirm cost of always setting these
         qreal newWidth = d->document->idealWidth();
-        if (!widthValid() && d->document->textWidth() != newWidth)
-            d->document->setTextWidth(newWidth); // ### Text does not align if width is not set (QTextDoc bug)
+        if ((!widthValid() || d->wrapMode == NoWrap) && d->document->textWidth() != newWidth)
+            d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
         // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
         qreal iWidth = -1;
         if (!widthValid() && !d->requireImplicitWidth)
             iWidth = newWidth;
 
+        QFontMetricsF fm(d->font);
         qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
 
         if (iWidth > -1)
@@ -1970,6 +1932,10 @@ void QQuickTextEdit::updateSize()
         else
             setImplicitHeight(newHeight);
 
+        d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign());
+        d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign);
+        setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
+
         QSizeF size(newWidth, newHeight);
         if (d->contentSize != size) {
             d->contentSize = size;
@@ -2006,6 +1972,7 @@ void QQuickTextEdit::q_updateAlignment()
     Q_D(QQuickTextEdit);
     if (d->determineHorizontalAlignment()) {
         d->updateDefaultTextOption();
+        d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign());
         moveCursorDelegate();
     }
 }
index f412aac..c02e34c 100644 (file)
@@ -72,7 +72,7 @@ public:
 
     QQuickTextEditPrivate()
         : color(QRgb(0xFF000000)), selectionColor(QRgb(0xFF000080)), selectedTextColor(QRgb(0xFFFFFFFF))
-        , textMargin(0.0), yoff(0), font(sourceFont), cursorComponent(0), cursorItem(0), document(0), control(0)
+        , textMargin(0.0), xoff(0), yoff(0), font(sourceFont), cursorComponent(0), cursorItem(0), document(0), control(0)
         , lastSelectionStart(0), lastSelectionEnd(0), lineCount(0)
         , hAlign(QQuickTextEdit::AlignLeft), vAlign(QQuickTextEdit::AlignTop)
         , format(QQuickTextEdit::PlainText), wrapMode(QQuickTextEdit::NoWrap)
@@ -109,6 +109,7 @@ public:
     QSizeF contentSize;
 
     qreal textMargin;
+    qreal xoff;
     qreal yoff;
 
     QString text;
diff --git a/tests/auto/quick/qquicktextedit/data/mouseselection_align_bl.qml b/tests/auto/quick/qquicktextedit/data/mouseselection_align_bl.qml
new file mode 100644 (file)
index 0000000..4387cbc
--- /dev/null
@@ -0,0 +1,14 @@
+import QtQuick 2.0
+
+TextEdit {
+    focus: true
+    text:
+"0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ
+9876543210
+
+ZXYWVUTSRQPON MLKJIHGFEDCBA"
+    width: implicitWidth - 10
+    selectByMouse: true
+    horizontalAlignment: TextEdit.AlignLeft
+    verticalAlignment: TextEdit.AlignBottom
+}
diff --git a/tests/auto/quick/qquicktextedit/data/mouseselection_align_center.qml b/tests/auto/quick/qquicktextedit/data/mouseselection_align_center.qml
new file mode 100644 (file)
index 0000000..e8ee14c
--- /dev/null
@@ -0,0 +1,14 @@
+import QtQuick 2.0
+
+TextEdit {
+    focus: true
+    text:
+"0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ
+9876543210
+
+ZXYWVUTSRQPON MLKJIHGFEDCBA"
+    width: implicitWidth - 10
+    selectByMouse: true
+    horizontalAlignment: TextEdit.AlignHCenter
+    verticalAlignment: TextEdit.AlignVCenter
+}
diff --git a/tests/auto/quick/qquicktextedit/data/mouseselection_align_tr.qml b/tests/auto/quick/qquicktextedit/data/mouseselection_align_tr.qml
new file mode 100644 (file)
index 0000000..a69bd8d
--- /dev/null
@@ -0,0 +1,14 @@
+import QtQuick 2.0
+
+TextEdit {
+    focus: true
+    text:
+"0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ
+9876543210
+
+ZXYWVUTSRQPON MLKJIHGFEDCBA"
+    width: implicitWidth - 10
+    selectByMouse: true
+    horizontalAlignment: TextEdit.AlignRight
+    verticalAlignment: TextEdit.AlignTop
+}
index 1909328..af0950c 100644 (file)
@@ -5,5 +5,6 @@ TextEdit {
     objectName: "myInput"
     width: 50
     height: 25
+    font.pixelSize: 12
     text: "This is\n a long piece of text"
 }
index 562f24e..ffff922 100644 (file)
@@ -134,6 +134,7 @@ private slots:
     void dragMouseSelection();
     void inputMethodHints();
 
+    void positionAt_data();
     void positionAt();
 
     void linkActivated();
@@ -217,6 +218,8 @@ Q_DECLARE_METATYPE(IntList)
 typedef QList<Key> KeyList;
 Q_DECLARE_METATYPE(KeyList)
 
+Q_DECLARE_METATYPE(QQuickTextEdit::HAlignment)
+Q_DECLARE_METATYPE(QQuickTextEdit::VAlignment)
 Q_DECLARE_METATYPE(QQuickTextEdit::TextFormat)
 
 void tst_qquicktextedit::simulateKeys(QWindow *window, const QList<Key> &keys)
@@ -1789,6 +1792,10 @@ void tst_qquicktextedit::mouseSelection_data()
     QTest::newRow("on triple click (40,50)") << testFile("mouseselection_true.qml") << 40 << 50 << "9876543210\n\nZXYWVUTSRQPON MLKJIHGFEDCBA" << true << true << 3;
     QTest::newRow("on triple click (50,25)") << testFile("mouseselection_true.qml") << 50 << 25 << "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ\n9876543210\n\nZXYWVUTSRQPON MLKJIHGFEDCBA" << true << true << 3;
     QTest::newRow("on triple click (50,40)") << testFile("mouseselection_true.qml") << 50 << 40 << "9876543210\n\nZXYWVUTSRQPON MLKJIHGFEDCBA" << true << true << 3;
+
+    QTest::newRow("on tr align") << testFile("mouseselection_align_tr.qml") << 4 << 9 << "45678" << true << true << 1;
+    QTest::newRow("on center align") << testFile("mouseselection_align_center.qml") << 4 << 9 << "45678" << true << true << 1;
+    QTest::newRow("on bl align") << testFile("mouseselection_align_bl.qml") << 4 << 9 << "45678" << true << true << 1;
 }
 
 void tst_qquicktextedit::mouseSelection()
@@ -1943,8 +1950,26 @@ void tst_qquicktextedit::inputMethodHints()
     QCOMPARE(plainTextEdit.inputMethodHints(), Qt::ImhNone);
 }
 
+void tst_qquicktextedit::positionAt_data()
+{
+    QTest::addColumn<QQuickTextEdit::HAlignment>("horizontalAlignment");
+    QTest::addColumn<QQuickTextEdit::VAlignment>("verticalAlignment");
+
+    QTest::newRow("top-left") << QQuickTextEdit::AlignLeft << QQuickTextEdit::AlignTop;
+    QTest::newRow("bottom-left") << QQuickTextEdit::AlignLeft << QQuickTextEdit::AlignBottom;
+    QTest::newRow("center-left") << QQuickTextEdit::AlignLeft << QQuickTextEdit::AlignVCenter;
+
+    QTest::newRow("top-right") << QQuickTextEdit::AlignRight << QQuickTextEdit::AlignTop;
+    QTest::newRow("top-center") << QQuickTextEdit::AlignHCenter << QQuickTextEdit::AlignTop;
+
+    QTest::newRow("center") << QQuickTextEdit::AlignHCenter << QQuickTextEdit::AlignVCenter;
+}
+
 void tst_qquicktextedit::positionAt()
 {
+    QFETCH(QQuickTextEdit::HAlignment, horizontalAlignment);
+    QFETCH(QQuickTextEdit::VAlignment, verticalAlignment);
+
     QQuickView canvas(testFileUrl("positionAt.qml"));
     QVERIFY(canvas.rootObject() != 0);
     canvas.show();
@@ -1953,8 +1978,10 @@ void tst_qquicktextedit::positionAt()
 
     QQuickTextEdit *texteditObject = qobject_cast<QQuickTextEdit *>(canvas.rootObject());
     QVERIFY(texteditObject != 0);
+    texteditObject->setHAlign(horizontalAlignment);
+    texteditObject->setVAlign(verticalAlignment);
 
-    QTextLayout layout(texteditObject->text());
+    QTextLayout layout(texteditObject->text().replace(QLatin1Char('\n'), QChar::LineSeparator));
     layout.setFont(texteditObject->font());
 
     if (!qmlDisableDistanceField()) {
@@ -1965,15 +1992,45 @@ void tst_qquicktextedit::positionAt()
 
     layout.beginLayout();
     QTextLine line = layout.createLine();
+    line.setLineWidth(texteditObject->width());
+    QTextLine secondLine = layout.createLine();
+    secondLine.setLineWidth(texteditObject->width());
     layout.endLayout();
 
-    const int y0 = line.height() / 2;
-    const int y1 = line.height() * 3 / 2;
+    qreal y0;
+    qreal y1;
+
+    switch (verticalAlignment) {
+    case QQuickTextEdit::AlignTop:
+        y0 = line.height() / 2;
+        y1 = line.height() * 3 / 2;
+        break;
+    case QQuickTextEdit::AlignVCenter:
+        y0 = (texteditObject->height() - line.height()) / 2;
+        y1 = (texteditObject->height() + line.height()) / 2;
+        break;
+    case QQuickTextEdit::AlignBottom:
+        y0 = texteditObject->height() - line.height() * 3 / 2;
+        y1 = texteditObject->height() - line.height() / 2;
+        break;
+    }
 
+    qreal xoff;
+    switch (horizontalAlignment) {
+    case QQuickTextEdit::AlignLeft:
+        xoff = 0;
+        break;
+    case QQuickTextEdit::AlignHCenter:
+        xoff = (texteditObject->width() - secondLine.naturalTextWidth()) / 2;
+        break;
+    case QQuickTextEdit::AlignRight:
+        xoff = texteditObject->width() - secondLine.naturalTextWidth();
+        break;
+    }
     int pos = texteditObject->positionAt(texteditObject->width()/2, y0);
 
-    int widthBegin = floor(line.cursorToX(pos - 1));
-    int widthEnd = ceil(line.cursorToX(pos + 1));
+    int widthBegin = floor(xoff + line.cursorToX(pos - 1));
+    int widthEnd = ceil(xoff + line.cursorToX(pos + 1));
 
     QVERIFY(widthBegin <= texteditObject->width() / 2);
     QVERIFY(widthEnd >= texteditObject->width() / 2);