Add wrapMode and verticalAlignment properties to TextInput.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Thu, 15 Dec 2011 01:36:54 +0000 (11:36 +1000)
committerQt by Nokia <qt-info@nokia.com>
Tue, 20 Dec 2011 06:33:37 +0000 (07:33 +0100)
Wrap mode provides an alternative to horizontal scrolling when the width
of the text exceeds the width of the TextInput. With auto scroll
wrapping introdoces an implicit verticalAlignment so support setting it
explicitly as well.

Task-number: QTBUG-22305
Task-number: QTBUG-16203
Change-Id: I1bd3a5335edb3ac48df3d5ccd8ae7274caa91883
Reviewed-by: Martin Jones <martin.jones@nokia.com>
doc/src/declarative/whatsnew.qdoc
src/quick/items/qquicktextinput.cpp
src/quick/items/qquicktextinput_p.h
src/quick/items/qquicktextinput_p_p.h
tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml
tests/auto/qtquick2/qquicktextinput/data/positionAt.qml
tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp
tests/testapplications/text/textinput.qml

index 2ae4671..ef23d15 100644 (file)
@@ -127,6 +127,9 @@ Text improvements:
 TextEdit:
  - the default value of the textFormat property is now PlainText instead of AutoText.
 
+TextInput has new wrapMode and verticalAlignment properties, and the positionAt function now takes
+a y parameter.
+
 PathView now has a \c currentItem property
 
 ListView and GridView:
index 649e29d..b6bc33c 100644 (file)
@@ -92,6 +92,18 @@ QQuickTextInput::~QQuickTextInput()
 {
 }
 
+void QQuickTextInput::componentComplete()
+{
+    Q_D(QQuickTextInput);
+
+    QQuickImplicitSizeItem::componentComplete();
+
+    d->updateLayout();
+    updateCursorRectangle();
+    if (d->cursorComponent && d->cursorComponent->isReady())
+        createCursor();
+}
+
 /*!
     \qmlproperty string QtQuick2::TextInput::text
 
@@ -251,12 +263,8 @@ void QQuickTextInput::setFont(const QFont &font)
         d->font.setPointSizeF(size/2.0);
     }
     if (oldFont != d->font) {
-        d->updateDisplayText();
-        updateSize();
+        d->updateLayout();
         updateCursorRectangle();
-        if (d->cursorItem) {
-            d->cursorItem->setHeight(QFontMetrics(d->font).height());
-        }
     }
     emit fontChanged(d->sourceFont);
 }
@@ -338,6 +346,7 @@ void QQuickTextInput::setSelectedTextColor(const QColor &color)
 /*!
     \qmlproperty enumeration QtQuick2::TextInput::horizontalAlignment
     \qmlproperty enumeration QtQuick2::TextInput::effectiveHorizontalAlignment
+    \qmlproperty enumeration QtQuick2::TextInput::verticalAlignment
 
     Sets the horizontal alignment of the text within the TextInput item's
     width and height. By default, the text alignment follows the natural alignment
@@ -353,6 +362,9 @@ void QQuickTextInput::setSelectedTextColor(const QColor &color)
     The valid values for \c horizontalAlignment are \c TextInput.AlignLeft, \c TextInput.AlignRight and
     \c TextInput.AlignHCenter.
 
+    Valid values for \c verticalAlignment are \c TextEdit.AlignTop (default),
+    \c TextEdit.AlignBottom \c TextEdit.AlignVCenter.
+
     When using the attached property LayoutMirroring::enabled to mirror application
     layouts, the horizontal alignment of text will also be mirrored. However, the property
     \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
@@ -370,6 +382,7 @@ void QQuickTextInput::setHAlign(HAlignment align)
     bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
     d->hAlignImplicit = false;
     if (d->setHAlign(align, forceAlign) && isComponentComplete()) {
+        d->updateLayout();
         updateCursorRectangle();
     }
 }
@@ -379,6 +392,7 @@ void QQuickTextInput::resetHAlign()
     Q_D(QQuickTextInput);
     d->hAlignImplicit = true;
     if (d->determineHorizontalAlignment() && isComponentComplete()) {
+        d->updateLayout();
         updateCursorRectangle();
     }
 }
@@ -429,6 +443,56 @@ bool QQuickTextInputPrivate::determineHorizontalAlignment()
     return false;
 }
 
+QQuickTextInput::VAlignment QQuickTextInput::vAlign() const
+{
+    Q_D(const QQuickTextInput);
+    return d->vAlign;
+}
+
+void QQuickTextInput::setVAlign(QQuickTextInput::VAlignment alignment)
+{
+    Q_D(QQuickTextInput);
+    if (alignment == d->vAlign)
+        return;
+    d->vAlign = alignment;
+    emit verticalAlignmentChanged(d->vAlign);
+    if (isComponentComplete()) {
+        updateCursorRectangle();
+    }
+}
+
+/*!
+    \qmlproperty enumeration QtQuick2::TextInput::wrapMode
+
+    Set this property to wrap the text to the TextEdit item's width.
+    The text will only wrap if an explicit width has been set.
+
+    \list
+    \o TextInput.NoWrap - no wrapping will be performed. If the text contains insufficient newlines, then implicitWidth will exceed a set width.
+    \o TextInput.WordWrap - wrapping is done on word boundaries only. If a word is too long, implicitWidth will exceed a set width.
+    \o TextInput.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
+    \o TextInput.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
+    \endlist
+
+    The default is TextInput.NoWrap. If you set a width, consider using TextInput.Wrap.
+*/
+QQuickTextInput::WrapMode QQuickTextInput::wrapMode() const
+{
+    Q_D(const QQuickTextInput);
+    return d->wrapMode;
+}
+
+void QQuickTextInput::setWrapMode(WrapMode mode)
+{
+    Q_D(QQuickTextInput);
+    if (mode == d->wrapMode)
+        return;
+    d->wrapMode = mode;
+    d->updateLayout();
+    updateCursorRectangle();
+    emit wrapModeChanged();
+}
+
 void QQuickTextInputPrivate::mirrorChange()
 {
     Q_Q(QQuickTextInput);
@@ -567,12 +631,20 @@ void QQuickTextInput::setCursorPosition(int cp)
 QRect QQuickTextInput::cursorRectangle() const
 {
     Q_D(const QQuickTextInput);
-    QTextLine l = d->m_textLayout.lineAt(0);
+
     int c = d->m_cursor;
     if (d->m_preeditCursor != -1)
         c += d->m_preeditCursor;
-    return QRect(qRound(l.cursorToX(c)) - d->hscroll, 0, d->m_cursorWidth, l.height());
+    if (d->m_echoMode == NoEcho || !isComponentComplete())
+        c = 0;
+    QTextLine l = d->m_textLayout.lineForTextPosition(c);
+    return QRect(
+            qRound(l.cursorToX(c) - d->hscroll),
+            qRound(l.y() - d->vscroll),
+            d->m_cursorWidth,
+            qCeil(l.height()));
 }
+
 /*!
     \qmlproperty int QtQuick2::TextInput::selectionStart
 
@@ -686,7 +758,6 @@ void QQuickTextInput::setAutoScroll(bool b)
 
     d->autoScroll = b;
     //We need to repaint so that the scrolling is taking into account.
-    updateSize(true);
     updateCursorRectangle();
     emit autoScrollChanged(d->autoScroll);
 }
@@ -908,9 +979,8 @@ void QQuickTextInput::setEchoMode(QQuickTextInput::EchoMode echo)
     d->m_echoMode = echo;
     d->m_passwordEchoEditing = false;
     d->updateInputMethodHints();
-
     d->updateDisplayText();
-    q_textChanged();
+    updateCursorRectangle();
 
     emit echoModeChanged(echoMode());
 }
@@ -982,6 +1052,9 @@ void QQuickTextInputPrivate::startCreatingCursor()
 void QQuickTextInput::createCursor()
 {
     Q_D(QQuickTextInput);
+    if (!isComponentComplete())
+        return;
+
     if (d->cursorComponent->isError()) {
         qmlInfo(this, d->cursorComponent->errors()) << tr("Could not load cursor delegate");
         return;
@@ -1001,10 +1074,12 @@ void QQuickTextInput::createCursor()
         return;
     }
 
+    QRectF r = cursorRectangle();
+
     QDeclarative_setParent_noEvent(d->cursorItem, this);
     d->cursorItem->setParentItem(this);
-    d->cursorItem->setX(d->cursorToX());
-    d->cursorItem->setHeight(d->calculateTextHeight());
+    d->cursorItem->setPos(r.topLeft());
+    d->cursorItem->setHeight(r.height());
 }
 
 /*!
@@ -1022,19 +1097,22 @@ QRectF QQuickTextInput::positionToRectangle(int pos) const
     if (pos > d->m_cursor)
         pos += d->preeditAreaText().length();
     QTextLine l = d->m_textLayout.lineAt(0);
-    return QRectF( l.cursorToX(pos) - d->hscroll, 0.0, d->m_cursorWidth, l.height());
+    return QRectF(l.cursorToX(pos) - d->hscroll, 0.0, d->m_cursorWidth, l.height());
 }
 
 /*!
-    \qmlmethod int QtQuick2::TextInput::positionAt(int x, CursorPosition position = CursorBetweenCharacters)
+    \qmlmethod int QtQuick2::TextInput::positionAt(real x, real y, CursorPosition position = CursorBetweenCharacters)
 
     This function returns the character position at
-    x pixels from the left of the textInput. Position 0 is before the
+    x and y pixels from the top left  of the textInput. Position 0 is before the
     first character, position 1 is after the first character but before the second,
     and so on until position text.length, which is after all characters.
 
     This means that for all x values before the first character this function returns 0,
-    and for all x values after the last character this function returns text.length.
+    and for all x values after the last character this function returns text.length.  If
+    the y value is above the text the position will be that of the nearest character on
+    the first line line and if it is below the text the position of the nearest character
+    on the last line will be returned.
 
     The cursor position type specifies how the cursor position should be resolved.
 
@@ -1043,15 +1121,33 @@ QRectF QQuickTextInput::positionToRectangle(int pos) const
     \o TextInput.CursorOnCharacter - Returns the position before the character that is nearest x.
     \endlist
 */
-int QQuickTextInput::positionAt(int x) const
-{
-    return positionAt(x, CursorBetweenCharacters);
-}
 
-int QQuickTextInput::positionAt(int x, CursorPosition position) const
+void QQuickTextInput::positionAt(QDeclarativeV8Function *args) const
 {
     Q_D(const QQuickTextInput);
-    int pos = d->m_textLayout.lineAt(0).xToCursor(x + d->hscroll, QTextLine::CursorPosition(position));
+
+    qreal x = 0;
+    qreal y = 0;
+    QTextLine::CursorPosition position = QTextLine::CursorBetweenCharacters;
+
+    if (args->Length() < 1)
+        return;
+
+    int i = 0;
+    v8::Local<v8::Value> arg = (*args)[i];
+    x = arg->NumberValue();
+
+    if (++i < args->Length()) {
+        arg = (*args)[i];
+        y = arg->NumberValue();
+    }
+
+    if (++i < args->Length()) {
+        arg = (*args)[i];
+        position = QTextLine::CursorPosition(arg->Int32Value());
+    }
+
+    int pos = d->positionAt(x, y, position);
     const int cursor = d->m_cursor;
     if (pos > cursor) {
         const int preeditLength = d->preeditAreaText().length();
@@ -1059,7 +1155,22 @@ int QQuickTextInput::positionAt(int x, CursorPosition position) const
                 ? pos - preeditLength
                 : cursor;
     }
-    return pos;
+    args->returnValue(v8::Int32::New(pos));
+}
+
+int QQuickTextInputPrivate::positionAt(int x, int y, QTextLine::CursorPosition position) const
+{
+    x += hscroll;
+    y += vscroll;
+    QTextLine line = m_textLayout.lineAt(0);
+    for (int i = 1; i < m_textLayout.lineCount(); ++i) {
+        QTextLine nextLine = m_textLayout.lineAt(i);
+
+        if (y < (line.rect().bottom() + nextLine.y()) / 2)
+            break;
+        line = nextLine;
+    }
+    return line.xToCursor(x, position);
 }
 
 void QQuickTextInput::keyPressEvent(QKeyEvent* ev)
@@ -1107,7 +1218,7 @@ void QQuickTextInput::mouseDoubleClickEvent(QMouseEvent *event)
 
     if (d->selectByMouse && event->button() == Qt::LeftButton) {
         d->commitPreedit();
-        int cursor = d->xToPos(event->localPos().x());
+        int cursor = d->positionAt(event->localPos());
         d->selectWordAtPos(cursor);
         event->setAccepted(true);
         if (!d->hasPendingTripleClick()) {
@@ -1150,7 +1261,7 @@ void QQuickTextInput::mousePressEvent(QMouseEvent *event)
         return;
 
     bool mark = (event->modifiers() & Qt::ShiftModifier) && d->selectByMouse;
-    int cursor = d->xToPos(event->localPos().x());
+    int cursor = d->positionAt(event->localPos());
     d->moveCursor(cursor, mark);
     event->setAccepted(true);
 }
@@ -1165,12 +1276,12 @@ void QQuickTextInput::mouseMoveEvent(QMouseEvent *event)
 
         if (d->composeMode()) {
             // start selection
-            int startPos = d->xToPos(d->pressPos.x());
-            int currentPos = d->xToPos(event->localPos().x());
+            int startPos = d->positionAt(d->pressPos);
+            int currentPos = d->positionAt(event->localPos());
             if (startPos != currentPos)
                 d->setSelection(startPos, currentPos - startPos);
         } else {
-            moveCursorSelection(d->xToPos(event->localPos().x()), d->mouseSelectionMode);
+            moveCursorSelection(d->positionAt(event->localPos()), d->mouseSelectionMode);
         }
         event->setAccepted(true);
     } else {
@@ -1205,7 +1316,7 @@ bool QQuickTextInputPrivate::sendMouseEventToInputContext(QMouseEvent *event)
 {
 #if !defined QT_NO_IM
     if (composeMode()) {
-        int tmp_cursor = xToPos(event->localPos().x());
+        int tmp_cursor = positionAt(event->localPos());
         int mousePos = tmp_cursor - m_cursor;
         if (mousePos >= 0 && mousePos <= m_textLayout.preeditAreaText().length()) {
             if (event->type() == QEvent::MouseButtonRelease) {
@@ -1284,37 +1395,26 @@ bool QQuickTextInput::event(QEvent* ev)
 void QQuickTextInput::geometryChanged(const QRectF &newGeometry,
                                   const QRectF &oldGeometry)
 {
-    if (newGeometry.width() != oldGeometry.width()) {
-        updateSize();
-        updateCursorRectangle();
-    }
+    Q_D(QQuickTextInput);
+    if (newGeometry.width() != oldGeometry.width())
+        d->updateLayout();
+    updateCursorRectangle();
     QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
 }
 
 void QQuickTextInputPrivate::updateHorizontalScroll()
 {
     Q_Q(QQuickTextInput);
+    QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor + m_preeditCursor);
     const int preeditLength = m_textLayout.preeditAreaText().length();
     const int width = q->width();
-    int widthUsed = calculateTextWidth();
+    int widthUsed = currentLine.isValid() ? qRound(currentLine.naturalTextWidth()) : 0;
+    int previousScroll = hscroll;
 
-    if (!autoScroll || widthUsed <=  width) {
-        QQuickTextInput::HAlignment effectiveHAlign = q->effectiveHAlign();
-        // text fits in br; use hscroll for alignment
-        switch (effectiveHAlign & ~(Qt::AlignAbsolute|Qt::AlignVertical_Mask)) {
-        case Qt::AlignRight:
-            hscroll = widthUsed - width;
-            break;
-        case Qt::AlignHCenter:
-            hscroll = (widthUsed - width) / 2;
-            break;
-        default:
-            // Left
-            hscroll = 0;
-            break;
-        }
+    if (!autoScroll || widthUsed <=  width || m_echoMode == QQuickTextInput::NoEcho) {
+        hscroll = 0;
     } else {
-        int cix = qRound(cursorToX(m_cursor + preeditLength));
+        int cix = qRound(currentLine.cursorToX(m_cursor + preeditLength));
         if (cix - hscroll >= width) {
             // text doesn't fit, cursor is to the right of br (scroll right)
             hscroll = cix - width;
@@ -1329,12 +1429,64 @@ void QQuickTextInputPrivate::updateHorizontalScroll()
         if (preeditLength > 0) {
             // check to ensure long pre-edit text doesn't push the cursor
             // off to the left
-             cix = qRound(cursorToX(
-                     m_cursor + qMax(0, m_preeditCursor - 1)));
+             cix = qRound(currentLine.cursorToX(m_cursor + qMax(0, m_preeditCursor - 1)));
              if (cix < hscroll)
                  hscroll = cix;
         }
     }
+    if (previousScroll != hscroll)
+        textLayoutDirty = true;
+}
+
+void QQuickTextInputPrivate::updateVerticalScroll()
+{
+    Q_Q(QQuickTextInput);
+    const int preeditLength = m_textLayout.preeditAreaText().length();
+    const int height = q->height();
+    int heightUsed = boundingRect.height();
+    int previousScroll = vscroll;
+
+    if (!autoScroll || heightUsed <=  height) {
+        // text fits in br; use vscroll for alignment
+        switch (vAlign & ~(Qt::AlignAbsolute|Qt::AlignHorizontal_Mask)) {
+        case Qt::AlignBottom:
+            vscroll = heightUsed - height;
+            break;
+        case Qt::AlignVCenter:
+            vscroll = (heightUsed - height) / 2;
+            break;
+        default:
+            // Top
+            vscroll = 0;
+            break;
+        }
+    } else {
+        QRectF r = m_textLayout.lineForTextPosition(m_cursor + preeditLength).rect();
+        int top = qFloor(r.top());
+        int bottom = qCeil(r.bottom());
+
+        if (bottom - vscroll >= height) {
+            // text doesn't fit, cursor is to the below the br (scroll down)
+            vscroll = bottom - height;
+        } else if (top - vscroll < 0 && vscroll < heightUsed) {
+            // text doesn't fit, cursor is above br (scroll up)
+            vscroll = top;
+        } else if (heightUsed - vscroll < height) {
+            // text doesn't fit, text document is to the left of br; align
+            // right
+            vscroll = heightUsed - height;
+        }
+        if (preeditLength > 0) {
+            // check to ensure long pre-edit text doesn't push the cursor
+            // off the top
+             top = qRound(m_textLayout.lineForTextPosition(
+                    m_cursor + qMax(0, m_preeditCursor - 1)).rect().top());
+             if (top < vscroll)
+                 vscroll = top;
+        }
+    }
+    if (previousScroll != vscroll)
+        textLayoutDirty = true;
 }
 
 QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
@@ -1364,12 +1516,11 @@ QSGNode *QQuickTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
 
         QPoint offset = QPoint(0,0);
         QFontMetrics fm = QFontMetrics(d->font);
-        QRect br(boundingRect().toRect());
         if (d->autoScroll) {
             // the y offset is there to keep the baseline constant in case we have script changes in the text.
-            offset = br.topLeft() - QPoint(d->hscroll, d->ascent() - fm.ascent());
+            offset = -QPoint(d->hscroll, d->vscroll + d->m_ascent - fm.ascent());
         } else {
-            offset = QPoint(d->hscroll, 0);
+            offset = -QPoint(d->hscroll, d->vscroll);
         }
 
         if (!d->m_textLayout.text().isEmpty()) {
@@ -1554,10 +1705,8 @@ void QQuickTextInput::setPasswordCharacter(const QString &str)
     if (str.length() < 1)
         return;
     d->m_passwordCharacter = str.constData()[0];
-    d->updateDisplayText();
-    if (d->m_echoMode == Password || d->m_echoMode == PasswordEchoOnEdit) {
-        updateSize();
-    }
+    if (d->m_echoMode == Password || d->m_echoMode == PasswordEchoOnEdit)
+        d->updateDisplayText();
     emit passwordCharacterChanged();
 }
 
@@ -1883,8 +2032,10 @@ void QQuickTextInputPrivate::init()
             q, SLOT(q_canPasteChanged()));
     canPaste = !m_readOnly && QGuiApplication::clipboard()->text().length() != 0;
 #endif // QT_NO_CLIPBOARD
-    updateDisplayText();
-    q->updateSize();
+    m_textLayout.beginLayout();
+    m_textLayout.createLine();
+    m_textLayout.endLayout();
+
     imHints &= ~Qt::ImhMultiLine;
     oldValidity = hasAcceptableInput(m_text);
     lastSelectionStart = 0;
@@ -1903,13 +2054,19 @@ void QQuickTextInputPrivate::init()
 void QQuickTextInput::updateCursorRectangle()
 {
     Q_D(QQuickTextInput);
-    d->determineHorizontalAlignment();
+    if (!isComponentComplete())
+        return;
+
     d->updateHorizontalScroll();
-    updateRect();//TODO: Only update rect between pos's
+    d->updateVerticalScroll();
+    update();
     updateMicroFocus();
     emit cursorRectangleChanged();
-    if (d->cursorItem)
-        d->cursorItem->setX(d->cursorToX() - d->hscroll);
+    if (d->cursorItem) {
+        QRectF r = cursorRectangle();
+        d->cursorItem->setPos(r.topLeft());
+        d->cursorItem->setHeight(r.height());
+    }
 }
 
 void QQuickTextInput::selectionChanged()
@@ -1932,21 +2089,6 @@ void QQuickTextInput::selectionChanged()
     }
 }
 
-void QQuickTextInput::q_textChanged()
-{
-    Q_D(QQuickTextInput);
-    emit textChanged();
-    emit displayTextChanged();
-    updateSize();
-    d->determineHorizontalAlignment();
-    d->updateHorizontalScroll();
-    updateMicroFocus();
-    if (hasAcceptableInput() != d->oldValidity) {
-        d->oldValidity = hasAcceptableInput();
-        emit acceptableInputChanged();
-    }
-}
-
 void QQuickTextInputPrivate::showCursor()
 {
     if (textNode != 0 && textNode->cursorNode() != 0)
@@ -1975,26 +2117,17 @@ void QQuickTextInput::updateRect(const QRect &r)
 QRectF QQuickTextInput::boundingRect() const
 {
     Q_D(const QQuickTextInput);
-    QRectF r = QQuickImplicitSizeItem::boundingRect();
 
+    QRectF r = d->boundingRect;
     int cursorWidth = d->cursorItem ? d->cursorItem->width() : d->m_cursorWidth;
 
     // Could include font max left/right bearings to either side of rectangle.
 
     r.setRight(r.right() + cursorWidth);
+    r.translate(-d->hscroll, -d->vscroll);
     return r;
 }
 
-void QQuickTextInput::updateSize(bool needsRedraw)
-{
-    Q_D(QQuickTextInput);
-    int w = width();
-    int h = height();
-    setImplicitSize(d->calculateTextWidth(), d->calculateTextHeight());
-    if (w==width() && h==height() && needsRedraw)
-        update();
-}
-
 void QQuickTextInput::q_canPasteChanged()
 {
     Q_D(QQuickTextInput);
@@ -2041,20 +2174,53 @@ void QQuickTextInputPrivate::updateDisplayText(bool forceUpdate)
             uc[i] = QChar(0x0020);
     }
 
-    m_textLayout.setText(str);
+    if (str != orig || forceUpdate) {
+        m_textLayout.setText(str);
+        updateLayout(); // polish?
+        emit q_func()->displayTextChanged();
+    }
+}
+
+void QQuickTextInputPrivate::updateLayout()
+{
+    Q_Q(QQuickTextInput);
+
+    if (!q->isComponentComplete())
+        return;
 
     QTextOption option = m_textLayout.textOption();
     option.setTextDirection(m_layoutDirection);
     option.setFlags(QTextOption::IncludeTrailingSpaces);
+    option.setWrapMode(QTextOption::WrapMode(wrapMode));
+    option.setAlignment(Qt::Alignment(q->effectiveHAlign()));
     m_textLayout.setTextOption(option);
+    m_textLayout.setFont(font);
 
+    boundingRect = QRectF();
     m_textLayout.beginLayout();
-    QTextLine l = m_textLayout.createLine();
+    QTextLine line = m_textLayout.createLine();
+    qreal lineWidth = q->widthValid() ? q->width() : INT_MAX;
+    qreal height = 0;
+    QTextLine firstLine = line;
+    do {
+        line.setLineWidth(lineWidth);
+        line.setPosition(QPointF(line.position().x(), height));
+        boundingRect = boundingRect.united(line.naturalTextRect());
+
+        height += line.height();
+        line = m_textLayout.createLine();
+    } while (line.isValid());
     m_textLayout.endLayout();
-    m_ascent = qRound(l.ascent());
 
-    if (str != orig || forceUpdate)
-        emit q_func()->displayTextChanged();
+    option.setWrapMode(QTextOption::NoWrap);
+    m_textLayout.setTextOption(option);
+
+    m_ascent = qRound(firstLine.ascent());
+    textLayoutDirty = true;
+
+    q->update();
+    q->setImplicitSize(qCeil(boundingRect.width()), qCeil(boundingRect.height()));
+
 }
 
 #ifndef QT_NO_CLIPBOARD
@@ -2117,7 +2283,7 @@ void QQuickTextInputPrivate::commitPreedit()
     m_preeditCursor = 0;
     m_textLayout.setPreeditArea(-1, QString());
     m_textLayout.clearAdditionalFormats();
-    updateDisplayText(/*force*/ true);
+    updateLayout();
 }
 
 /*!
@@ -2278,21 +2444,6 @@ void QQuickTextInputPrivate::updatePasswordEchoEditing(bool editing)
 /*!
     \internal
 
-    Returns the cursor position of the given \a x pixel value in relation
-    to the displayed text.  The given \a betweenOrOn specified what kind
-    of cursor position is requested.
-*/
-int QQuickTextInputPrivate::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const
-{
-    Q_Q(const QQuickTextInput);
-    QRect cr = q->boundingRect().toRect();
-    x-= cr.x() - hscroll;
-    return m_textLayout.lineAt(0).xToCursor(x, betweenOrOn);
-}
-
-/*!
-    \internal
-
     Fixes the current text so that it is valid given any set validators.
 
     Returns true if the text was changed.  Otherwise returns false.
@@ -2340,7 +2491,6 @@ void QQuickTextInputPrivate::moveCursor(int pos, bool mark)
             anchor = m_cursor;
         m_selstart = qMin(anchor, pos);
         m_selend = qMax(anchor, pos);
-        updateDisplayText();
     } else {
         internalDeselect();
     }
@@ -2368,6 +2518,7 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
             || event->replacementLength() > 0;
     bool cursorPositionChanged = false;
     bool selectionChange = false;
+    m_preeditDirty = event->preeditString() != preeditAreaText();
 
     if (isGettingInput) {
         // If any text is being input, remove selected text.
@@ -2442,6 +2593,7 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
         }
     }
     m_textLayout.setAdditionalFormats(formats);
+
     updateDisplayText(/*force*/ true);
     if (cursorPositionChanged)
         emitCursorPositionChanged();
@@ -2542,8 +2694,17 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
 
         if (m_textDirty) {
             m_textDirty = false;
-            q_func()->q_textChanged();
+            m_preeditDirty = false;
+            determineHorizontalAlignment();
+            emit q->textChanged();
         }
+
+        if (m_validInput != wasValidInput)
+            emit q->acceptableInputChanged();
+    }
+    if (m_preeditDirty) {
+        m_preeditDirty = false;
+        determineHorizontalAlignment();
     }
     if (m_selDirty) {
         m_selDirty = false;
index 7a07de6..447f333 100644 (file)
@@ -44,6 +44,7 @@
 #define QQUICKTEXTINPUT_P_H
 
 #include "qquickimplicitsizeitem_p.h"
+#include <QtGui/qtextoption.h>
 #include <QtGui/qvalidator.h>
 
 QT_BEGIN_HEADER
@@ -56,8 +57,11 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem
 {
     Q_OBJECT
     Q_ENUMS(HAlignment)
+    Q_ENUMS(VAlignment)
+    Q_ENUMS(WrapMode)
     Q_ENUMS(EchoMode)
     Q_ENUMS(SelectionMode)
+    Q_ENUMS(CursorPosition)
 
     Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
     Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
@@ -66,6 +70,8 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem
     Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
     Q_PROPERTY(HAlignment horizontalAlignment READ hAlign WRITE setHAlign RESET resetHAlign NOTIFY horizontalAlignmentChanged)
     Q_PROPERTY(HAlignment effectiveHorizontalAlignment READ effectiveHAlign NOTIFY effectiveHorizontalAlignmentChanged)
+    Q_PROPERTY(VAlignment verticalAlignment READ vAlign WRITE setVAlign NOTIFY verticalAlignmentChanged)
+    Q_PROPERTY(WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged)
 
     Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged)
     Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible NOTIFY cursorVisibleChanged)
@@ -98,6 +104,8 @@ public:
     QQuickTextInput(QQuickItem * parent=0);
     ~QQuickTextInput();
 
+    void componentComplete();
+
     enum EchoMode {//To match QLineEdit::EchoMode
         Normal,
         NoEcho,
@@ -111,6 +119,20 @@ public:
         AlignHCenter = Qt::AlignHCenter
     };
 
+    enum VAlignment {
+        AlignTop = Qt::AlignTop,
+        AlignBottom = Qt::AlignBottom,
+        AlignVCenter = Qt::AlignVCenter
+    };
+
+    enum WrapMode {
+        NoWrap = QTextOption::NoWrap,
+        WordWrap = QTextOption::WordWrap,
+        WrapAnywhere = QTextOption::WrapAnywhere,
+        WrapAtWordBoundaryOrAnywhere = QTextOption::WrapAtWordBoundaryOrAnywhere, // COMPAT
+        Wrap = QTextOption::WrapAtWordBoundaryOrAnywhere
+    };
+
     enum SelectionMode {
         SelectCharacters,
         SelectWords
@@ -121,9 +143,9 @@ public:
         CursorOnCharacter
     };
 
+
     //Auxilliary functions needed to control the TextInput from QML
-    Q_INVOKABLE int positionAt(int x) const;
-    Q_INVOKABLE int positionAt(int x, CursorPosition position) const;
+    Q_INVOKABLE void positionAt(QDeclarativeV8Function *args) const;
     Q_INVOKABLE QRectF positionToRectangle(int pos) const;
     Q_INVOKABLE void moveCursorSelection(int pos);
     Q_INVOKABLE void moveCursorSelection(int pos, SelectionMode mode);
@@ -151,6 +173,12 @@ public:
     void resetHAlign();
     HAlignment effectiveHAlign() const;
 
+    VAlignment vAlign() const;
+    void setVAlign(VAlignment align);
+
+    WrapMode wrapMode() const;
+    void setWrapMode(WrapMode w);
+
     bool isReadOnly() const;
     void setReadOnly(bool);
 
@@ -226,6 +254,8 @@ Q_SIGNALS:
     void selectedTextColorChanged(const QColor &color);
     void fontChanged(const QFont &font);
     void horizontalAlignmentChanged(HAlignment alignment);
+    void verticalAlignmentChanged(VAlignment alignment);
+    void wrapModeChanged();
     void readOnlyChanged(bool isReadOnly);
     void cursorVisibleChanged(bool isCursorVisible);
     void cursorDelegateChanged();
@@ -273,8 +303,6 @@ public Q_SLOTS:
 #endif
 
 private Q_SLOTS:
-    void updateSize(bool needsRedraw = true);
-    void q_textChanged();
     void selectionChanged();
     void createCursor();
     void updateCursorRectangle();
index b410bfd..a5fa6d5 100644 (file)
@@ -81,7 +81,7 @@ public:
         , textNode(0)
         , m_maskData(0)
         , hscroll(0)
-        , oldScroll(0)
+        , vscroll(0)
         , m_cursor(0)
         , m_preeditCursor(0)
         , m_cursorWidth(1)
@@ -97,6 +97,8 @@ public:
         , m_selend(0)
         , style(QQuickText::Normal)
         , hAlign(QQuickTextInput::AlignLeft)
+        , vAlign(QQuickTextInput::AlignTop)
+        , wrapMode(QQuickTextInput::NoWrap)
         , mouseSelectionMode(QQuickTextInput::SelectCharacters)
         , inputMethodHints(Qt::ImhNone)
         , m_layoutDirection(Qt::LayoutDirectionAuto)
@@ -116,6 +118,7 @@ public:
         , m_readOnly(0)
         , m_echoMode(QQuickTextInput::Normal)
         , m_textDirty(0)
+        , m_preeditDirty(0)
         , m_selDirty(0)
         , m_validInput(1)
         , m_blinkStatus(0)
@@ -130,6 +133,7 @@ public:
     void init();
     void startCreatingCursor();
     void updateHorizontalScroll();
+    void updateVerticalScroll();
     bool determineHorizontalAlignment();
     bool setHAlign(QQuickTextInput::HAlignment, bool forceAlign = false);
     void mirrorChange();
@@ -186,13 +190,14 @@ public:
     QPoint tripleClickStartPoint;
     QList<int> m_transactions;
     QVector<Command> m_history;
+    QRectF boundingRect;
 
     int lastSelectionStart;
     int lastSelectionEnd;
     int oldHeight;
     int oldWidth;
     int hscroll;
-    int oldScroll;
+    int vscroll;
     int m_cursor;
     int m_preeditCursor;
     int m_cursorWidth;
@@ -209,6 +214,8 @@ public:
 
     QQuickText::TextStyle style;
     QQuickTextInput::HAlignment hAlign;
+    QQuickTextInput::VAlignment vAlign;
+    QQuickTextInput::WrapMode wrapMode;
     QQuickTextInput::SelectionMode mouseSelectionMode;
     Qt::InputMethodHints inputMethodHints;
     Qt::LayoutDirection m_layoutDirection;
@@ -232,6 +239,7 @@ public:
     uint m_readOnly : 1;
     uint m_echoMode : 2;
     uint m_textDirty : 1;
+    uint m_preeditDirty : 1;
     uint m_selDirty : 1;
     uint m_validInput : 1;
     uint m_blinkStatus : 1;
@@ -269,10 +277,6 @@ public:
     bool allSelected() const { return !m_text.isEmpty() && m_selstart == 0 && m_selend == (int)m_text.length(); }
     bool hasSelectedText() const { return !m_text.isEmpty() && m_selend > m_selstart; }
 
-    int calculateTextHeight() const { return qRound(m_textLayout.lineAt(0).height()); }
-    int calculateTextWidth() const { return qRound(m_textLayout.lineAt(0).naturalTextWidth()); }
-    int ascent() const { return m_ascent; }
-
     void setSelection(int start, int length);
 
     inline QString selectedText() const { return hasSelectedText() ? m_text.mid(m_selstart, m_selend - m_selstart) : QString(); }
@@ -281,12 +285,10 @@ public:
 
     int selectionStart() const { return hasSelectedText() ? m_selstart : -1; }
     int selectionEnd() const { return hasSelectedText() ? m_selend : -1; }
-    bool inSelection(int x) const
-    {
-        if (m_selstart >= m_selend)
-            return false;
-        int pos = xToPos(x, QTextLine::CursorOnCharacter);
-        return pos >= m_selstart && pos < m_selend;
+
+    int positionAt(int x, int y, QTextLine::CursorPosition position) const;
+    int positionAt(const QPointF &point, QTextLine::CursorPosition position = QTextLine::CursorBetweenCharacters) const {
+        return positionAt(point.x(), point.y(), position);
     }
 
     void removeSelection()
@@ -333,17 +335,6 @@ public:
     void home(bool mark) { moveCursor(0, mark); }
     void end(bool mark) { moveCursor(q_func()->text().length(), mark); }
 
-    int xToPos(int x, QTextLine::CursorPosition = QTextLine::CursorBetweenCharacters) const;
-
-    qreal cursorToX(int cursor) const { return m_textLayout.lineAt(0).cursorToX(cursor); }
-    qreal cursorToX() const
-    {
-        int cursor = m_cursor;
-        if (m_preeditCursor != -1)
-            cursor += m_preeditCursor;
-        return cursorToX(cursor);
-    }
-
     void backspace();
     void del();
     void deselect() { internalDeselect(); finishChange(); }
@@ -398,6 +389,8 @@ public:
     void setCursorBlinkPeriod(int msec);
     void resetCursorBlinkTimer();
 
+    void updateLayout();
+
 private:
     void init(const QString &txt);
     void removeSelectedText();
@@ -425,7 +418,6 @@ private:
 
     inline void separate() { m_separator = true; }
 
-
     // masking
     void parseInputMask(const QString &maskFields);
     bool isValidInput(QChar key, QChar mask) const;
index e0fef4c..8993453 100644 (file)
@@ -10,10 +10,11 @@ Rectangle {
     Rectangle {
         anchors.centerIn: parent
         width: 60
-        height: 20
+        height: 60
         color: "green"
 
         TextInput {
+            objectName: "text"
             id: text
             anchors.fill: parent
             text: top.text
index 1840462..edb4744 100644 (file)
@@ -4,5 +4,6 @@ TextInput{
     focus: true
     objectName: "myInput"
     width: 50
+    height: 100
     text: "AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
 }
index 6b6fd73..a626cb2 100644 (file)
@@ -43,6 +43,7 @@
 #include "../../shared/util.h"
 #include <private/qinputpanel_p.h>
 #include <QtDeclarative/qdeclarativeengine.h>
+#include <QtDeclarative/qdeclarativeexpression.h>
 #include <QFile>
 #include <QtQuick/qquickview.h>
 #include <QtGui/qguiapplication.h>
@@ -83,6 +84,15 @@ QString createExpectedFileIfNotFound(const QString& filebasename, const QImage&
     return expectfile;
 }
 
+template <typename T> static T evaluate(QObject *scope, const QString &expression)
+{
+    QDeclarativeExpression expr(qmlContext(scope), scope, expression);
+    T result = expr.evaluate().value<T>();
+    if (expr.hasError())
+        qWarning() << expr.error().toString();
+    return result;
+}
+
 typedef QPair<int, QChar> Key;
 
 class tst_qquicktextinput : public QObject
@@ -100,6 +110,7 @@ private slots:
     void width();
     void font();
     void color();
+    void wrap();
     void selection();
     void isRightToLeft_data();
     void isRightToLeft();
@@ -115,6 +126,7 @@ private slots:
     void horizontalAlignment_data();
     void horizontalAlignment();
     void horizontalAlignment_RightToLeft();
+    void verticalAlignment();
 
     void positionAt();
 
@@ -479,6 +491,41 @@ void tst_qquicktextinput::color()
     }
 }
 
+void tst_qquicktextinput::wrap()
+{
+    int textHeight = 0;
+    // for specified width and wrap set true
+    {
+        QDeclarativeComponent textComponent(&engine);
+        textComponent.setData("import QtQuick 2.0\nTextInput { text: \"Hello\"; wrapMode: Text.WrapAnywhere; width: 300 }", QUrl::fromLocalFile(""));
+        QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
+        textHeight = textObject->height();
+
+        QVERIFY(textObject != 0);
+        QVERIFY(textObject->wrapMode() == QQuickTextInput::WrapAnywhere);
+        QCOMPARE(textObject->width(), 300.);
+
+        delete textObject;
+    }
+
+    for (int i = 0; i < standard.count(); i++) {
+        QString componentStr = "import QtQuick 2.0\nTextInput { wrapMode: Text.WrapAnywhere; width: 30; text: \"" + standard.at(i) + "\" }";
+        QDeclarativeComponent textComponent(&engine);
+        textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
+        QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
+
+        QVERIFY(textObject != 0);
+        QCOMPARE(textObject->width(), 30.);
+        QVERIFY(textObject->height() > textHeight);
+
+        int oldHeight = textObject->height();
+        textObject->setWidth(100);
+        QVERIFY(textObject->height() < oldHeight);
+
+        delete textObject;
+    }
+}
+
 void tst_qquicktextinput::selection()
 {
     QString testStr = standard[0];
@@ -1178,37 +1225,37 @@ void tst_qquicktextinput::horizontalAlignment_RightToLeft()
 
     QQuickTextInputPrivate *textInputPrivate = QQuickTextInputPrivate::get(textInput);
     QVERIFY(textInputPrivate != 0);
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     // implicit alignment should follow the reading direction of RTL text
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     // explicitly left aligned
     textInput->setHAlign(QQuickTextInput::AlignLeft);
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
-    QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() < canvas.width()/2);
 
     // explicitly right aligned
     textInput->setHAlign(QQuickTextInput::AlignRight);
     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     // explicitly center aligned
     textInput->setHAlign(QQuickTextInput::AlignHCenter);
     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignHCenter);
-    QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
-    QVERIFY(-textInputPrivate->hscroll + textInputPrivate->width > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() < canvas.width()/2);
+    QVERIFY(textInput->boundingRect().right() > canvas.width()/2);
 
     // reseted alignment should go back to following the text reading direction
     textInput->resetHAlign();
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     // mirror the text item
     QQuickItemPrivate::get(textInput)->setLayoutMirror(true);
@@ -1216,19 +1263,19 @@ void tst_qquicktextinput::horizontalAlignment_RightToLeft()
     // mirrored implicit alignment should continue to follow the reading direction of the text
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     // explicitly right aligned behaves as left aligned
     textInput->setHAlign(QQuickTextInput::AlignRight);
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
     QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
-    QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() < canvas.width()/2);
 
     // mirrored explicitly left aligned behaves as right aligned
     textInput->setHAlign(QQuickTextInput::AlignLeft);
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
     QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     // disable mirroring
     QQuickItemPrivate::get(textInput)->setLayoutMirror(false);
@@ -1238,7 +1285,7 @@ void tst_qquicktextinput::horizontalAlignment_RightToLeft()
     // English text should be implicitly left aligned
     textInput->setText("Hello world!");
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
-    QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() < canvas.width()/2);
 
     canvas.requestActivateWindow();
     QTest::qWaitForWindowShown(&canvas);
@@ -1261,12 +1308,12 @@ void tst_qquicktextinput::horizontalAlignment_RightToLeft()
     QCOMPARE(textInput->hAlign(), QGuiApplication::keyboardInputDirection() == Qt::LeftToRight ?
                                   QQuickTextInput::AlignLeft : QQuickTextInput::AlignRight);
     if (QGuiApplication::keyboardInputDirection() == Qt::LeftToRight)
-        QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
+        QVERIFY(textInput->boundingRect().left() < canvas.width()/2);
     else
-        QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+        QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
     textInput->setHAlign(QQuickTextInput::AlignRight);
     QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
-    QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
+    QVERIFY(textInput->boundingRect().left() > canvas.width()/2);
 
     QString componentStr = "import QtQuick 2.0\nTextInput {}";
     QDeclarativeComponent textComponent(&engine);
@@ -1277,6 +1324,31 @@ void tst_qquicktextinput::horizontalAlignment_RightToLeft()
     delete textObject;
 }
 
+void tst_qquicktextinput::verticalAlignment()
+{
+    QQuickView canvas(QUrl::fromLocalFile(TESTDATA("horizontalAlignment.qml")));
+    QQuickTextInput *textInput = canvas.rootObject()->findChild<QQuickTextInput*>("text");
+    QVERIFY(textInput != 0);
+    canvas.show();
+
+    QQuickTextInputPrivate *textInputPrivate = QQuickTextInputPrivate::get(textInput);
+    QVERIFY(textInputPrivate != 0);
+
+    QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignTop);
+    QVERIFY(textInput->boundingRect().bottom() < canvas.height() / 2);
+
+    // bottom aligned
+    textInput->setVAlign(QQuickTextInput::AlignBottom);
+    QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignBottom);
+    QVERIFY(textInput->boundingRect().top () > canvas.height() / 2);
+
+    // explicitly center aligned
+    textInput->setVAlign(QQuickTextInput::AlignVCenter);
+    QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignVCenter);
+    QVERIFY(textInput->boundingRect().top() < canvas.height() / 2);
+    QVERIFY(textInput->boundingRect().bottom() > canvas.height() / 2);
+}
+
 void tst_qquicktextinput::positionAt()
 {
     QQuickView canvas(QUrl::fromLocalFile(TESTDATA("positionAt.qml")));
@@ -1290,7 +1362,7 @@ void tst_qquicktextinput::positionAt()
 
     // Check autoscrolled...
 
-    int pos = textinputObject->positionAt(textinputObject->width()/2);
+    int pos = evaluate<int>(textinputObject, QString("positionAt(%1)").arg(textinputObject->width()/2));
 
     QTextLayout layout(textinputObject->text());
     layout.setFont(textinputObject->font());
@@ -1312,12 +1384,12 @@ void tst_qquicktextinput::positionAt()
     QVERIFY(textLeftWidthEnd >= textWidth - textinputObject->width() / 2);
 
     int x = textinputObject->positionToRectangle(pos + 1).x() - 1;
-    QCOMPARE(textinputObject->positionAt(x, QQuickTextInput::CursorBetweenCharacters), pos + 1);
-    QCOMPARE(textinputObject->positionAt(x, QQuickTextInput::CursorOnCharacter), pos);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
 
     // Check without autoscroll...
     textinputObject->setAutoScroll(false);
-    pos = textinputObject->positionAt(textinputObject->width()/2);
+    pos = evaluate<int>(textinputObject, QString("positionAt(%1)").arg(textinputObject->width() / 2));
 
     textLeftWidthBegin = floor(line.cursorToX(pos - 1));
     textLeftWidthEnd = ceil(line.cursorToX(pos + 1));
@@ -1326,8 +1398,8 @@ void tst_qquicktextinput::positionAt()
     QVERIFY(textLeftWidthEnd >= textinputObject->width() / 2);
 
     x = textinputObject->positionToRectangle(pos + 1).x() - 1;
-    QCOMPARE(textinputObject->positionAt(x, QQuickTextInput::CursorBetweenCharacters), pos + 1);
-    QCOMPARE(textinputObject->positionAt(x, QQuickTextInput::CursorOnCharacter), pos);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
 
     const qreal x0 = textinputObject->positionToRectangle(pos).x();
     const qreal x1 = textinputObject->positionToRectangle(pos + 1).x();
@@ -1336,17 +1408,33 @@ void tst_qquicktextinput::positionAt()
     textinputObject->setText(textinputObject->text().mid(pos));
     textinputObject->setCursorPosition(0);
 
-    QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
-    QGuiApplication::sendEvent(qGuiApp->inputPanel()->inputItem(), &inputEvent);
+    {   QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
+        QGuiApplication::sendEvent(qGuiApp->inputPanel()->inputItem(), &inputEvent); }
 
     // Check all points within the preedit text return the same position.
-    QCOMPARE(textinputObject->positionAt(0), 0);
-    QCOMPARE(textinputObject->positionAt(x0 / 2), 0);
-    QCOMPARE(textinputObject->positionAt(x0), 0);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(0)), 0);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0 / 2)), 0);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0)), 0);
 
     // Verify positioning returns to normal after the preedit text.
-    QCOMPARE(textinputObject->positionAt(x1), 1);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x1)), 1);
     QCOMPARE(textinputObject->positionToRectangle(1).x(), x1);
+
+    {   QInputMethodEvent inputEvent;
+        QGuiApplication::sendEvent(qGuiApp->inputPanel()->inputItem(), &inputEvent); }
+
+    // With wrapping.
+    textinputObject->setWrapMode(QQuickTextInput::WrapAnywhere);
+
+    const qreal y0 = line.height() / 2;
+    const qreal y1 = line.height() * 3 / 2;
+
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y0)), pos);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y0)), pos + 1);
+
+    int newLinePos = evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y1));
+    QVERIFY(newLinePos > pos);
+    QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y1)), newLinePos + 1);
 }
 
 void tst_qquicktextinput::maxLength()
@@ -1962,6 +2050,7 @@ void tst_qquicktextinput::cursorRectangle()
     layout.endLayout();
 
     input.setWidth(line.cursorToX(5, QTextLine::Leading));
+    input.setHeight(qCeil(line.height() * 3 / 2));
 
     QRect r;
 
@@ -1982,7 +2071,7 @@ void tst_qquicktextinput::cursorRectangle()
     }
 
     // Check the cursor rectangle remains within the input bounding rect when auto scrolling.
-    QVERIFY(r.left() < input.boundingRect().width());
+    QVERIFY(r.left() < input.width());
     QVERIFY(r.right() >= input.width() - error);
 
     for (int i = 6; i < text.length(); ++i) {
@@ -1994,14 +2083,50 @@ void tst_qquicktextinput::cursorRectangle()
     for (int i = text.length() - 2; i >= 0; --i) {
         input.setCursorPosition(i);
         r = input.cursorRectangle();
+        QCOMPARE(r.top(), 0);
         QVERIFY(r.right() >= 0);
         QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
     }
 
+    // Check vertical scrolling with word wrap.
+    input.setWrapMode(QQuickTextInput::WordWrap);
+    for (int i = 0; i <= 5; ++i) {
+        input.setCursorPosition(i);
+        r = input.cursorRectangle();
+
+        QVERIFY(r.left() < qCeil(line.cursorToX(i, QTextLine::Trailing)));
+        QVERIFY(r.right() >= qFloor(line.cursorToX(i , QTextLine::Leading)));
+        QCOMPARE(r.top(), 0);
+        QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
+    }
+
+    input.setCursorPosition(6);
+    r = input.cursorRectangle();
+    QCOMPARE(r.left(), 0);
+    QVERIFY(r.bottom() >= input.height() - error);
+
+    for (int i = 7; i < text.length(); ++i) {
+        input.setCursorPosition(i);
+        r = input.cursorRectangle();
+        QVERIFY(r.bottom() >= input.height() - error);
+    }
+
+    for (int i = text.length() - 2; i >= 6; --i) {
+        input.setCursorPosition(i);
+        r = input.cursorRectangle();
+        QVERIFY(r.bottom() >= input.height() - error);
+    }
+
+    for (int i = 5; i >= 0; --i) {
+        input.setCursorPosition(i);
+        r = input.cursorRectangle();
+        QCOMPARE(r.top(), 0);
+    }
+
     input.setText("Hi!");
     input.setHAlign(QQuickTextInput::AlignRight);
     r = input.cursorRectangle();
-    QVERIFY(r.left() < input.boundingRect().width());
+    QVERIFY(r.left() < input.width() + error);
     QVERIFY(r.right() >= input.width() - error);
 }
 
@@ -2215,7 +2340,6 @@ void tst_qquicktextinput::openInputPanel()
     // check default values
     QVERIFY(input->focusOnPress());
     QVERIFY(!input->hasActiveFocus());
-    qDebug() << &input << qApp->inputPanel()->inputItem();
     QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
     QCOMPARE(qApp->inputPanel()->visible(), false);
 
@@ -2419,15 +2543,15 @@ void tst_qquicktextinput::preeditAutoScroll()
 
     // test the text is scrolled so the preedit is visible.
     sendPreeditText(preeditText.mid(0, 3), 1);
-    QVERIFY(input->positionAt(0) != 0);
+    QVERIFY(evaluate<int>(input, QString("positionAt(0)")) != 0);
     QVERIFY(input->cursorRectangle().left() < input->boundingRect().width());
     QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
 
     // test the text is scrolled back when the preedit is removed.
     QInputMethodEvent imEvent;
     QCoreApplication::sendEvent(qGuiApp->inputPanel()->inputItem(), &imEvent);
-    QCOMPARE(input->positionAt(0), 0);
-    QCOMPARE(input->positionAt(input->width()), 5);
+    QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
+    QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
     QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
 
     QTextLayout layout(preeditText);
@@ -2482,8 +2606,8 @@ void tst_qquicktextinput::preeditAutoScroll()
 
     input->setAutoScroll(false);
     sendPreeditText(preeditText.mid(0, 3), 1);
-    QCOMPARE(input->positionAt(0), 0);
-    QCOMPARE(input->positionAt(input->width()), 5);
+    QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
+    QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
 }
 
 void tst_qquicktextinput::preeditCursorRectangle()
index 98f2628..271466d 100644 (file)
@@ -73,6 +73,8 @@ Rectangle {
             font.pointSize: { pointvalue.model.get(pointvalue.currentIndex).value }
             font.pixelSize: { pixelvalue.model.get(pixelvalue.currentIndex).value }
             horizontalAlignment: { halignvalue.model.get(halignvalue.currentIndex).value }
+            verticalAlignment: { valignvalue.model.get(valignvalue.currentIndex).value }
+            wrapMode: { wrapvalue.model.get(wrapvalue.currentIndex).value }
             smooth: { smoothvalue.model.get(smoothvalue.currentIndex).value }
             selectByMouse: { mousevalue.model.get(mousevalue.currentIndex).value }
             echoMode: { echovalue.model.get(echovalue.currentIndex).value }