From 59e607a2ce1e1fbb5324040c26554ecc22d8b1fd Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Thu, 15 Dec 2011 11:36:54 +1000 Subject: [PATCH] Add wrapMode and verticalAlignment properties to TextInput. 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 --- doc/src/declarative/whatsnew.qdoc | 3 + src/quick/items/qquicktextinput.cpp | 389 +++++++++++++----- src/quick/items/qquicktextinput_p.h | 36 +- src/quick/items/qquicktextinput_p_p.h | 40 +- .../data/horizontalAlignment.qml | 3 +- .../qquicktextinput/data/positionAt.qml | 1 + .../qquicktextinput/tst_qquicktextinput.cpp | 192 +++++++-- tests/testapplications/text/textinput.qml | 2 + 8 files changed, 489 insertions(+), 177 deletions(-) diff --git a/doc/src/declarative/whatsnew.qdoc b/doc/src/declarative/whatsnew.qdoc index 2ae46714b6..ef23d15f62 100644 --- a/doc/src/declarative/whatsnew.qdoc +++ b/doc/src/declarative/whatsnew.qdoc @@ -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: diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 649e29d6bc..b6bc33c767 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -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 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(); } /*! @@ -2275,21 +2441,6 @@ void QQuickTextInputPrivate::updatePasswordEchoEditing(bool editing) updateDisplayText(); } -/*! - \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 @@ -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; diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h index 7a07de60a3..447f3330ec 100644 --- a/src/quick/items/qquicktextinput_p.h +++ b/src/quick/items/qquicktextinput_p.h @@ -44,6 +44,7 @@ #define QQUICKTEXTINPUT_P_H #include "qquickimplicitsizeitem_p.h" +#include #include 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem QQuickTextInput(QQuickItem * parent=0); ~QQuickTextInput(); + void componentComplete(); + enum EchoMode {//To match QLineEdit::EchoMode Normal, NoEcho, @@ -111,6 +119,20 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem 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(); diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index b410bfd187..a5fa6d58b8 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -81,7 +81,7 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr , textNode(0) , m_maskData(0) , hscroll(0) - , oldScroll(0) + , vscroll(0) , m_cursor(0) , m_preeditCursor(0) , m_cursorWidth(1) @@ -97,6 +97,8 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr , 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr , 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr void init(); void startCreatingCursor(); void updateHorizontalScroll(); + void updateVerticalScroll(); bool determineHorizontalAlignment(); bool setHAlign(QQuickTextInput::HAlignment, bool forceAlign = false); void mirrorChange(); @@ -186,13 +190,14 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr QPoint tripleClickStartPoint; QList m_transactions; QVector 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr 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 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr void setCursorBlinkPeriod(int msec); void resetCursorBlinkTimer(); + void updateLayout(); + private: void init(const QString &txt); void removeSelectedText(); @@ -425,7 +418,6 @@ class Q_AUTOTEST_EXPORT QQuickTextInputPrivate : public QQuickImplicitSizeItemPr inline void separate() { m_separator = true; } - // masking void parseInputMask(const QString &maskFields); bool isValidInput(QChar key, QChar mask) const; diff --git a/tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml b/tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml index e0fef4c11e..89934532e3 100644 --- a/tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml +++ b/tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml @@ -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 diff --git a/tests/auto/qtquick2/qquicktextinput/data/positionAt.qml b/tests/auto/qtquick2/qquicktextinput/data/positionAt.qml index 1840462c87..edb4744107 100644 --- a/tests/auto/qtquick2/qquicktextinput/data/positionAt.qml +++ b/tests/auto/qtquick2/qquicktextinput/data/positionAt.qml @@ -4,5 +4,6 @@ TextInput{ focus: true objectName: "myInput" width: 50 + height: 100 text: "AAAAAAAAAAAAAAAAAAAAAAAAAAAA" } diff --git a/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp index 6b6fd73b01..a626cb2929 100644 --- a/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp @@ -43,6 +43,7 @@ #include "../../shared/util.h" #include #include +#include #include #include #include @@ -83,6 +84,15 @@ QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& return expectfile; } +template static T evaluate(QObject *scope, const QString &expression) +{ + QDeclarativeExpression expr(qmlContext(scope), scope, expression); + T result = expr.evaluate().value(); + if (expr.hasError()) + qWarning() << expr.error().toString(); + return result; +} + typedef QPair 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(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(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("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(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(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1); + QCOMPARE(evaluate(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos); // Check without autoscroll... textinputObject->setAutoScroll(false); - pos = textinputObject->positionAt(textinputObject->width()/2); + pos = evaluate(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(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1); + QCOMPARE(evaluate(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()); - QGuiApplication::sendEvent(qGuiApp->inputPanel()->inputItem(), &inputEvent); + { QInputMethodEvent inputEvent(preeditText, QList()); + 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(textinputObject, QString("positionAt(%1)").arg(0)), 0); + QCOMPARE(evaluate(textinputObject, QString("positionAt(%1)").arg(x0 / 2)), 0); + QCOMPARE(evaluate(textinputObject, QString("positionAt(%1)").arg(x0)), 0); // Verify positioning returns to normal after the preedit text. - QCOMPARE(textinputObject->positionAt(x1), 1); + QCOMPARE(evaluate(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(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y0)), pos); + QCOMPARE(evaluate(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y0)), pos + 1); + + int newLinePos = evaluate(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y1)); + QVERIFY(newLinePos > pos); + QCOMPARE(evaluate(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(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(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(input, QString("positionAt(%1)").arg(0)), 0); + QCOMPARE(evaluate(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(input, QString("positionAt(%1)").arg(0)), 0); + QCOMPARE(evaluate(input, QString("positionAt(%1)").arg(input->width())), 5); } void tst_qquicktextinput::preeditCursorRectangle() diff --git a/tests/testapplications/text/textinput.qml b/tests/testapplications/text/textinput.qml index 98f2628372..271466d59a 100644 --- a/tests/testapplications/text/textinput.qml +++ b/tests/testapplications/text/textinput.qml @@ -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 }