Introduce TextInput::ensureVisible(int position)
authorJ-P Nurmi <jpnurmi@digia.com>
Fri, 16 May 2014 12:35:23 +0000 (14:35 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Wed, 21 May 2014 11:11:18 +0000 (13:11 +0200)
This is required for TextField to be able to implement text selection
handles. TextField needs to be able to ensure that the appropriate
character position becomes visible when the handles are moved around.

[ChangeLog][QtQuick][TextInput] Added TextInput::ensureVisible(int pos)
method to be able to control the scrolling position of a TextInput that
has automatic scrolling enabled.

Task-number: QTBUG-38934
Change-Id: Id77eafcda6324d10868e0798519e5b712a0d33ed
Reviewed-by: Alan Alpert <aalpert@blackberry.com>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
src/quick/items/qquicktextinput.cpp
src/quick/items/qquicktextinput_p.h
src/quick/items/qquicktextinput_p_p.h
tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp

index 41eb5c0..883d0c7 100644 (file)
@@ -881,6 +881,8 @@ void QQuickTextInput::setFocusOnPress(bool b)
 
     Whether the TextInput should scroll when the text is longer than the width. By default this is
     set to true.
+
+    \sa ensureVisible()
 */
 bool QQuickTextInput::autoScroll() const
 {
@@ -1721,33 +1723,24 @@ void QQuickTextInput::geometryChanged(const QRectF &newGeometry,
     QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
 }
 
-void QQuickTextInputPrivate::updateHorizontalScroll()
+void QQuickTextInputPrivate::ensureVisible(int position, int preeditCursor, int preeditLength)
 {
     Q_Q(QQuickTextInput);
-#ifndef QT_NO_IM
-    QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor + m_preeditCursor);
-    const int preeditLength = m_textLayout.preeditAreaText().length();
-#else
-    QTextLine currentLine = m_textLayout.lineForTextPosition(m_cursor);
-#endif
+    QTextLine textLine = m_textLayout.lineForTextPosition(position + preeditCursor);
     const qreal width = qMax<qreal>(0, q->width());
     qreal cix = 0;
     qreal widthUsed = 0;
-    if (currentLine.isValid()) {
-#ifndef QT_NO_IM
-        cix = currentLine.cursorToX(m_cursor + preeditLength);
-#else
-        cix = currentLine.cursorToX(m_cursor);
-#endif
+    if (textLine.isValid()) {
+        cix = textLine.cursorToX(position + preeditLength);
         const qreal cursorWidth = cix >= 0 ? cix : width - cix;
-        widthUsed = qMax(currentLine.naturalTextWidth(), cursorWidth);
+        widthUsed = qMax(textLine.naturalTextWidth(), cursorWidth);
     }
     int previousScroll = hscroll;
 
-    if (!autoScroll || widthUsed <=  width || m_echoMode == QQuickTextInput::NoEcho) {
+    if (widthUsed <= width) {
         hscroll = 0;
     } else {
-        Q_ASSERT(currentLine.isValid());
+        Q_ASSERT(textLine.isValid());
         if (cix - hscroll >= width) {
             // text doesn't fit, cursor is to the right of br (scroll right)
             hscroll = cix - width;
@@ -1767,7 +1760,7 @@ void QQuickTextInputPrivate::updateHorizontalScroll()
         if (preeditLength > 0) {
             // check to ensure long pre-edit text doesn't push the cursor
             // off to the left
-             cix = currentLine.cursorToX(m_cursor + qMax(0, m_preeditCursor - 1));
+             cix = textLine.cursorToX(position + qMax(0, preeditCursor - 1));
              if (cix < hscroll)
                  hscroll = cix;
         }
@@ -1777,6 +1770,20 @@ void QQuickTextInputPrivate::updateHorizontalScroll()
         textLayoutDirty = true;
 }
 
+void QQuickTextInputPrivate::updateHorizontalScroll()
+{
+    if (autoScroll && m_echoMode != QQuickTextInput::NoEcho) {
+#ifndef QT_NO_IM
+        const int preeditLength = m_textLayout.preeditAreaText().length();
+        ensureVisible(m_cursor, m_preeditCursor, preeditLength);
+#else
+        ensureVisible(m_cursor);
+#endif
+    } else {
+        hscroll = 0;
+    }
+}
+
 void QQuickTextInputPrivate::updateVerticalScroll()
 {
     Q_Q(QQuickTextInput);
@@ -2634,14 +2641,16 @@ void QQuickTextInputPrivate::init()
     }
 }
 
-void QQuickTextInput::updateCursorRectangle()
+void QQuickTextInput::updateCursorRectangle(bool scroll)
 {
     Q_D(QQuickTextInput);
     if (!isComponentComplete())
         return;
 
-    d->updateHorizontalScroll();
-    d->updateVerticalScroll();
+    if (scroll) {
+        d->updateHorizontalScroll();
+        d->updateVerticalScroll();
+    }
     d->updateType = QQuickTextInputPrivate::UpdatePaintNode;
     update();
     emit cursorRectangleChanged();
@@ -4402,5 +4411,21 @@ void QQuickTextInputPrivate::deleteEndOfLine()
     finishChange(priorState);
 }
 
+/*!
+    \qmlmethod QtQuick::TextInput::ensureVisible(int position)
+    \since 5.4
+
+    Scrolls the contents of the text input so that the specified character
+    \a position is visible inside the boundaries of the text input.
+
+    \sa autoScroll
+*/
+void QQuickTextInput::ensureVisible(int position)
+{
+    Q_D(QQuickTextInput);
+    d->ensureVisible(position);
+    updateCursorRectangle(false);
+}
+
 QT_END_NAMESPACE
 
index 211aba8..66cabb9 100644 (file)
@@ -355,11 +355,12 @@ public Q_SLOTS:
     void redo();
     void insert(int position, const QString &text);
     void remove(int start, int end);
+    Q_REVISION(3) void ensureVisible(int position);
 
 private Q_SLOTS:
     void selectionChanged();
     void createCursor();
-    void updateCursorRectangle();
+    void updateCursorRectangle(bool scroll = true);
     void q_canPasteChanged();
     void q_updateAlignment();
     void triggerPreprocess();
index 2cf1276..facc635 100644 (file)
@@ -148,6 +148,7 @@ public:
 
     void init();
     void startCreatingCursor();
+    void ensureVisible(int position, int preeditCursor = 0, int preeditLength = 0);
     void updateHorizontalScroll();
     void updateVerticalScroll();
     bool determineHorizontalAlignment();
index 8451b52..fc1be16 100644 (file)
@@ -232,6 +232,8 @@ private slots:
     void baselineOffset_data();
     void baselineOffset();
 
+    void ensureVisible();
+
 private:
     void simulateKey(QWindow *, int key);
 
@@ -6464,6 +6466,50 @@ void tst_qquicktextinput::baselineOffset()
     }
 }
 
+void tst_qquicktextinput::ensureVisible()
+{
+    QQmlComponent component(&engine);
+    component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
+    QScopedPointer<QObject> object(component.create());
+    QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
+    QVERIFY(input);
+
+    input->setWidth(QFontMetrics(input->font()).averageCharWidth() * 3);
+    input->setText("Hello World");
+
+    QTextLayout layout;
+    layout.setText(input->text());
+    layout.setFont(input->font());
+
+    if (!qmlDisableDistanceField()) {
+        QTextOption option;
+        option.setUseDesignMetrics(true);
+        layout.setTextOption(option);
+    }
+    layout.beginLayout();
+    QTextLine line = layout.createLine();
+    layout.endLayout();
+
+    input->ensureVisible(0);
+
+    QCOMPARE(input->boundingRect().x(), qreal(0));
+    QCOMPARE(input->boundingRect().y(), qreal(0));
+    QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
+    QCOMPARE(input->boundingRect().height(), line.height());
+
+    QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
+    QVERIFY(cursorSpy.isValid());
+
+    input->ensureVisible(input->length());
+
+    QCOMPARE(cursorSpy.count(), 1);
+
+    QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
+    QCOMPARE(input->boundingRect().y(), qreal(0));
+    QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
+    QCOMPARE(input->boundingRect().height(), line.height());
+}
+
 QTEST_MAIN(tst_qquicktextinput)
 
 #include "tst_qquicktextinput.moc"