From 6e5944df6349c080b4e4550e2fd085cfc34cf207 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Tue, 10 Jul 2012 11:30:53 +1000 Subject: [PATCH] Fix horizontal alignment of unwrapped text in TextEdit. 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 --- src/quick/items/qquicktextedit.cpp | 97 +++++++------------- src/quick/items/qquicktextedit_p_p.h | 3 +- .../data/mouseselection_align_bl.qml | 14 +++ .../data/mouseselection_align_center.qml | 14 +++ .../data/mouseselection_align_tr.qml | 14 +++ .../auto/quick/qquicktextedit/data/positionAt.qml | 1 + .../quick/qquicktextedit/tst_qquicktextedit.cpp | 67 +++++++++++++- 7 files changed, 139 insertions(+), 71 deletions(-) create mode 100644 tests/auto/quick/qquicktextedit/data/mouseselection_align_bl.qml create mode 100644 tests/auto/quick/qquicktextedit/data/mouseselection_align_center.qml create mode 100644 tests/auto/quick/qquicktextedit/data/mouseselection_align_tr.qml diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index df13075..38a510e 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -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(); } } diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index f412aac..c02e34c 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -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 index 0000000..4387cbc --- /dev/null +++ b/tests/auto/quick/qquicktextedit/data/mouseselection_align_bl.qml @@ -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 index 0000000..e8ee14c --- /dev/null +++ b/tests/auto/quick/qquicktextedit/data/mouseselection_align_center.qml @@ -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 index 0000000..a69bd8d --- /dev/null +++ b/tests/auto/quick/qquicktextedit/data/mouseselection_align_tr.qml @@ -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 +} diff --git a/tests/auto/quick/qquicktextedit/data/positionAt.qml b/tests/auto/quick/qquicktextedit/data/positionAt.qml index 1909328..af0950c 100644 --- a/tests/auto/quick/qquicktextedit/data/positionAt.qml +++ b/tests/auto/quick/qquicktextedit/data/positionAt.qml @@ -5,5 +5,6 @@ TextEdit { objectName: "myInput" width: 50 height: 25 + font.pixelSize: 12 text: "This is\n a long piece of text" } diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 562f24e..ffff922 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -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 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 &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("horizontalAlignment"); + QTest::addColumn("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(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); -- 1.7.2.5