diff --git a/doc/src/declarative/whatsnew.qdoc b/doc/src/declarative/whatsnew.qdoc index 2ae46714b6b5cab3b89d964121056bd0412468fa..ef23d15f62c26d37c28e12aab082ae65cbb210fd 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 649e29d6bcde38bb4b7f3d567aa67f3006fa9d53..b6bc33c7675061216d206d95b1ed5f7bf5cb1921 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<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(); } /*! @@ -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 7a07de60a3b6af3af1f9a94ddf14e14076142b01..447f3330ece6ba8abbf17be2c1c7747745d3cdfa 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 <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(); diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index b410bfd187c5be535030786913b66958ffe424e4..a5fa6d58b8e5e482f4809221ef57ed02c310eb98 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -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; diff --git a/tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml b/tests/auto/qtquick2/qquicktextinput/data/horizontalAlignment.qml index e0fef4c11e793ea1568aa26ec042334653b2ba2e..89934532e3c1994c06e7d9ad1526ad678f4b2159 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 1840462c87ed95fccd813acb471d951f396a6180..edb47441075356f98afd2280966c9e20f73fe6e6 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 6b6fd73b017c5ec6a73bf9affacb4bfd1e080580..a626cb2929145a5a39c3719d78b1866352be2cb3 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 <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() diff --git a/tests/testapplications/text/textinput.qml b/tests/testapplications/text/textinput.qml index 98f262837257be8367339e171f90c27d6f59f7d6..271466d59afff899d66c9abbc2832f08f25109ba 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 }