Add length property and insert, remove and getText functions to TextInput.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Wed, 4 Jan 2012 00:53:26 +0000 (10:53 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 4 Jan 2012 05:58:07 +0000 (06:58 +0100)
These mimic the functionality of their pre-existing counterparts in
TextEdit.

Change-Id: Idcb7549cd07ed3c287ab00b7828a3158690faf0e
Reviewed-by: Martin Jones <martin.jones@nokia.com>
src/quick/items/qquicktextinput.cpp
src/quick/items/qquicktextinput_p.h
tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp

index b4f31e3..b8763fd 100644 (file)
@@ -135,6 +135,42 @@ void QQuickTextInput::setText(const QString &s)
     d->internalSetText(s, -1, false);
 }
 
+/*!
+    \qmlproperty int QtQuick2::TextInput::length
+
+    Returns the total number of characters in the TextInput item.
+
+    If the TextInput has an inputMask the length will include mask characters and may differ
+    from the length of the string returned by the \l text property.
+
+    This property can be faster than querying the length the \l text property as it doesn't
+    require any copying or conversion of the TextInput's internal string data.
+*/
+
+int QQuickTextInput::length() const
+{
+    Q_D(const QQuickTextInput);
+    return d->m_text.length();
+}
+
+/*!
+    \qmlmethod string QtQuick2::TextInput::getText(int start, int end)
+
+    Returns the section of text that is between the \a start and \a end positions.
+
+    If the TextInput has an inputMask the length will include mask characters.
+*/
+
+QString QQuickTextInput::getText(int start, int end) const
+{
+    Q_D(const QQuickTextInput);
+
+    if (start > end)
+        qSwap(start, end);
+
+    return d->m_text.mid(start, end - start);
+}
+
 QString QQuickTextInputPrivate::realText() const
 {
     QString res = m_maskData ? stripString(m_text) : m_text;
@@ -693,7 +729,7 @@ int QQuickTextInput::selectionEnd() const
 void QQuickTextInput::select(int start, int end)
 {
     Q_D(QQuickTextInput);
-    if (start < 0 || end < 0 || start > text().length() || end > text().length())
+    if (start < 0 || end < 0 || start > d->m_text.length() || end > d->m_text.length())
         return;
     d->setSelection(start, end-start);
 }
@@ -1666,6 +1702,158 @@ void QQuickTextInput::paste()
 #endif // QT_NO_CLIPBOARD
 
 /*!
+    \qmlmethod void QtQuick2::TextInput::insert(int position, string text)
+
+    Inserts \a text into the TextInput at position.
+*/
+
+void QQuickTextInput::insert(int position, const QString &text)
+{
+    Q_D(QQuickTextInput);
+#ifdef QT_GUI_PASSWORD_ECHO_DELAY
+    if (d->m_echoMode == QQuickTextInput::Password)
+        d->m_passwordEchoTimer.start(qt_passwordEchoDelay, this);
+#endif
+
+    if (position < 0 || position > d->m_text.length())
+        return;
+
+    const int priorState = d->m_undoState;
+
+    QString insertText = text;
+
+    if (d->hasSelectedText()) {
+        d->addCommand(QQuickTextInputPrivate::Command(
+                QQuickTextInputPrivate::SetSelection, d->m_cursor, 0, d->m_selstart, d->m_selend));
+    }
+    if (d->m_maskData) {
+        insertText = d->maskString(position, insertText);
+        for (int i = 0; i < insertText.length(); ++i) {
+            d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::DeleteSelection, position + i, d->m_text.at(position + i), -1, -1));
+            d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::Insert, position + i, insertText.at(i), -1, -1));
+        }
+        d->m_text.replace(position, insertText.length(), insertText);
+        if (!insertText.isEmpty())
+            d->m_textDirty = true;
+        if (position < d->m_selend && position + insertText.length() > d->m_selstart)
+            d->m_selDirty = true;
+    } else {
+        int remaining = d->m_maxLength - d->m_text.length();
+        if (remaining != 0) {
+            insertText = insertText.left(remaining);
+            d->m_text.insert(position, insertText);
+            for (int i = 0; i < insertText.length(); ++i)
+               d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::Insert, position + i, insertText.at(i), -1, -1));
+            if (d->m_cursor >= position)
+                d->m_cursor += insertText.length();
+            if (d->m_selstart >= position)
+                d->m_selstart += insertText.length();
+            if (d->m_selend >= position)
+                d->m_selend += insertText.length();
+            d->m_textDirty = true;
+            if (position >= d->m_selstart && position <= d->m_selend)
+                d->m_selDirty = true;
+        }
+    }
+
+    d->addCommand(QQuickTextInputPrivate::Command(
+            QQuickTextInputPrivate::SetSelection, d->m_cursor, 0, d->m_selstart, d->m_selend));
+    d->finishChange(priorState);
+
+    if (d->lastSelectionStart != d->lastSelectionEnd) {
+        if (d->m_selstart != d->lastSelectionStart) {
+            d->lastSelectionStart = d->m_selstart;
+            emit selectionStartChanged();
+        }
+        if (d->m_selend != d->lastSelectionEnd) {
+            d->lastSelectionEnd = d->m_selend;
+            emit selectionEndChanged();
+        }
+    }
+}
+
+/*!
+    \qmlmethod string QtQuick2::TextInput::getText(int start, int end)
+
+    Removes the section of text that is between the \a start and \a end positions from the TextInput.
+*/
+
+void QQuickTextInput::remove(int start, int end)
+{
+    Q_D(QQuickTextInput);
+
+    start = qBound(0, start, d->m_text.length());
+    end = qBound(0, end, d->m_text.length());
+
+    if (start > end)
+        qSwap(start, end);
+    else if (start == end)
+        return;
+
+    if (start < d->m_selend && end > d->m_selstart)
+        d->m_selDirty = true;
+
+    const int priorState = d->m_undoState;
+
+    d->addCommand(QQuickTextInputPrivate::Command(
+            QQuickTextInputPrivate::SetSelection, d->m_cursor, 0, d->m_selstart, d->m_selend));
+
+    if (start <= d->m_cursor && d->m_cursor < end) {
+        // cursor is within the selection. Split up the commands
+        // to be able to restore the correct cursor position
+        for (int i = d->m_cursor; i >= start; --i) {
+            d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::DeleteSelection, i, d->m_text.at(i), -1, 1));
+        }
+        for (int i = end - 1; i > d->m_cursor; --i) {
+            d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::DeleteSelection, i - d->m_cursor + start - 1, d->m_text.at(i), -1, -1));
+        }
+    } else {
+        for (int i = end - 1; i >= start; --i) {
+            d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::RemoveSelection, i, d->m_text.at(i), -1, -1));
+        }
+    }
+    if (d->m_maskData) {
+        d->m_text.replace(start, end - start,  d->clearString(start, end - start));
+        for (int i = 0; i < end - start; ++i) {
+            d->addCommand(QQuickTextInputPrivate::Command(
+                    QQuickTextInputPrivate::Insert, start + i, d->m_text.at(start + i), -1, -1));
+        }
+    } else {
+        d->m_text.remove(start, end - start);
+
+        if (d->m_cursor > start)
+            d->m_cursor -= qMin(d->m_cursor, end) - start;
+        if (d->m_selstart > start)
+            d->m_selstart -= qMin(d->m_selstart, end) - start;
+        if (d->m_selend > end)
+            d->m_selend -= qMin(d->m_selend, end) - start;
+    }
+    d->addCommand(QQuickTextInputPrivate::Command(
+            QQuickTextInputPrivate::SetSelection, d->m_cursor, 0, d->m_selstart, d->m_selend));
+
+    d->m_textDirty = true;
+    d->finishChange(priorState);
+
+    if (d->lastSelectionStart != d->lastSelectionEnd) {
+        if (d->m_selstart != d->lastSelectionStart) {
+            d->lastSelectionStart = d->m_selstart;
+            emit selectionStartChanged();
+        }
+        if (d->m_selend != d->lastSelectionEnd) {
+            d->lastSelectionEnd = d->m_selend;
+            emit selectionEndChanged();
+        }
+    }
+}
+
+
+/*!
     \qmlmethod void QtQuick2::TextInput::selectWord()
 
     Causes the word closest to the current cursor position to be selected.
@@ -2732,11 +2920,13 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
         m_preeditDirty = false;
         determineHorizontalAlignment();
     }
+
     if (m_selDirty) {
         m_selDirty = false;
         emit q->selectionChanged();
     }
     emitCursorPositionChanged();
+
     return true;
 }
 
@@ -3225,12 +3415,12 @@ QString QQuickTextInputPrivate::stripString(const QString &str) const
 
     QString s;
     int end = qMin(m_maxLength, (int)str.length());
-    for (int i = 0; i < end; ++i)
+    for (int i = 0; i < end; ++i) {
         if (m_maskData[i].separator)
             s += m_maskData[i].maskChar;
-        else
-            if (str[i] != m_blank)
-                s += str[i];
+        else if (str[i] != m_blank)
+            s += str[i];
+    }
 
     return s;
 }
index 0b63e60..bc4b8f8 100644 (file)
@@ -64,6 +64,7 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem
     Q_ENUMS(CursorPosition)
 
     Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
+    Q_PROPERTY(int length READ length NOTIFY textChanged)
     Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
     Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor NOTIFY selectionColorChanged)
     Q_PROPERTY(QColor selectedTextColor READ selectedTextColor WRITE setSelectedTextColor NOTIFY selectedTextColorChanged)
@@ -156,6 +157,8 @@ public:
     QString text() const;
     void setText(const QString &);
 
+    int length() const;
+
     QFont font() const;
     void setFont(const QFont &font);
 
@@ -240,6 +243,8 @@ public:
     Qt::InputMethodHints imHints() const;
     void setIMHints(Qt::InputMethodHints hints);
 
+    Q_INVOKABLE QString getText(int start, int end) const;
+
 Q_SIGNALS:
     void textChanged();
     void cursorPositionChanged();
@@ -301,6 +306,8 @@ public Q_SLOTS:
     void copy();
     void paste();
 #endif
+    void insert(int position, const QString &text);
+    void remove(int start, int end);
 
 private Q_SLOTS:
     void selectionChanged();
index 0286cea..08e638d 100644 (file)
@@ -162,6 +162,13 @@ private slots:
     void inputMethodComposing();
     void cursorRectangleSize();
 
+    void getText_data();
+    void getText();
+    void insert_data();
+    void insert();
+    void remove_data();
+    void remove();
+
     void keySequence_data();
     void keySequence();
 
@@ -281,6 +288,7 @@ void tst_qquicktextinput::text()
 
         QVERIFY(textinputObject != 0);
         QCOMPARE(textinputObject->text(), QString(""));
+        QCOMPARE(textinputObject->length(), 0);
 
         delete textinputObject;
     }
@@ -294,6 +302,7 @@ void tst_qquicktextinput::text()
 
         QVERIFY(textinputObject != 0);
         QCOMPARE(textinputObject->text(), standard.at(i));
+        QCOMPARE(textinputObject->length(), standard.at(i).length());
 
         delete textinputObject;
     }
@@ -1470,8 +1479,12 @@ void tst_qquicktextinput::masks()
     QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
     QVERIFY(textinputObject->text().length() == 0);
     QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
+    QCOMPARE(textinputObject->length(), 8);
     for (int i=0; i<10; i++) {
         QTRY_COMPARE(qMin(i,8), textinputObject->text().length());
+        QCOMPARE(textinputObject->length(), 8);
+        QCOMPARE(textinputObject->getText(0, qMin(i, 8)), QString(qMin(i, 8), 'a'));
+        QCOMPARE(textinputObject->getText(qMin(i, 8), 8), QString(8 - qMin(i, 8), ' '));
         QCOMPARE(i>=4, textinputObject->hasAcceptableInput());
         //simulateKey(&canvas, Qt::Key_A);
         QTest::keyPress(&canvas, Qt::Key_A);
@@ -2811,6 +2824,790 @@ void tst_qquicktextinput::QTBUG_19956_data()
     QTest::newRow("doublevalidator") << "qtbug-19956double.qml";
 }
 
+
+void tst_qquicktextinput::getText_data()
+{
+    QTest::addColumn<QString>("text");
+    QTest::addColumn<QString>("inputMask");
+    QTest::addColumn<int>("start");
+    QTest::addColumn<int>("end");
+    QTest::addColumn<QString>("expectedText");
+
+    QTest::newRow("all plain text")
+            << standard.at(0)
+            << QString()
+            << 0 << standard.at(0).length()
+            << standard.at(0);
+
+    QTest::newRow("plain text sub string")
+            << standard.at(0)
+            << QString()
+            << 0 << 12
+            << standard.at(0).mid(0, 12);
+
+    QTest::newRow("plain text sub string reversed")
+            << standard.at(0)
+            << QString()
+            << 12 << 0
+            << standard.at(0).mid(0, 12);
+
+    QTest::newRow("plain text cropped beginning")
+            << standard.at(0)
+            << QString()
+            << -3 << 4
+            << standard.at(0).mid(0, 4);
+
+    QTest::newRow("plain text cropped end")
+            << standard.at(0)
+            << QString()
+            << 23 << standard.at(0).length() + 8
+            << standard.at(0).mid(23);
+
+    QTest::newRow("plain text cropped beginning and end")
+            << standard.at(0)
+            << QString()
+            << -9 << standard.at(0).length() + 4
+            << standard.at(0);
+}
+
+void tst_qquicktextinput::getText()
+{
+    QFETCH(QString, text);
+    QFETCH(QString, inputMask);
+    QFETCH(int, start);
+    QFETCH(int, end);
+    QFETCH(QString, expectedText);
+
+    QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
+    QDeclarativeComponent textInputComponent(&engine);
+    textInputComponent.setData(componentStr.toLatin1(), QUrl());
+    QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
+    QVERIFY(textInput != 0);
+
+    QCOMPARE(textInput->getText(start, end), expectedText);
+}
+
+void tst_qquicktextinput::insert_data()
+{
+    QTest::addColumn<QString>("text");
+    QTest::addColumn<QString>("inputMask");
+    QTest::addColumn<int>("selectionStart");
+    QTest::addColumn<int>("selectionEnd");
+    QTest::addColumn<int>("insertPosition");
+    QTest::addColumn<QString>("insertText");
+    QTest::addColumn<QString>("expectedText");
+    QTest::addColumn<int>("expectedSelectionStart");
+    QTest::addColumn<int>("expectedSelectionEnd");
+    QTest::addColumn<int>("expectedCursorPosition");
+    QTest::addColumn<bool>("selectionChanged");
+    QTest::addColumn<bool>("cursorPositionChanged");
+
+    QTest::newRow("at cursor position (beginning)")
+            << standard.at(0)
+            << QString()
+            << 0 << 0 << 0
+            << QString("Hello")
+            << QString("Hello") + standard.at(0)
+            << 5 << 5 << 5
+            << false << true;
+
+    QTest::newRow("at cursor position (end)")
+            << standard.at(0)
+            << QString()
+            << standard.at(0).length() << standard.at(0).length() << standard.at(0).length()
+            << QString("Hello")
+            << standard.at(0) + QString("Hello")
+            << standard.at(0).length() + 5 << standard.at(0).length() + 5 << standard.at(0).length() + 5
+            << false << true;
+
+    QTest::newRow("at cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 18 << 18 << 18
+            << QString("Hello")
+            << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
+            << 23 << 23 << 23
+            << false << true;
+
+    QTest::newRow("after cursor position (beginning)")
+            << standard.at(0)
+            << QString()
+            << 0 << 0 << 18
+            << QString("Hello")
+            << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("before cursor position (end)")
+            << standard.at(0)
+            << QString()
+            << standard.at(0).length() << standard.at(0).length() << 18
+            << QString("Hello")
+            << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
+            << standard.at(0).length() + 5 << standard.at(0).length() + 5 << standard.at(0).length() + 5
+            << false << true;
+
+    QTest::newRow("before cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 18 << 18 << 0
+            << QString("Hello")
+            << QString("Hello") + standard.at(0)
+            << 23 << 23 << 23
+            << false << true;
+
+    QTest::newRow("after cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 18 << 18 << standard.at(0).length()
+            << QString("Hello")
+            << standard.at(0) + QString("Hello")
+            << 18 << 18 << 18
+            << false << false;
+
+    QTest::newRow("before selection")
+            << standard.at(0)
+            << QString()
+            << 14 << 19 << 0
+            << QString("Hello")
+            << QString("Hello") + standard.at(0)
+            << 19 << 24 << 24
+            << false << true;
+
+    QTest::newRow("before reversed selection")
+            << standard.at(0)
+            << QString()
+            << 19 << 14 << 0
+            << QString("Hello")
+            << QString("Hello") + standard.at(0)
+            << 19 << 24 << 19
+            << false << true;
+
+    QTest::newRow("after selection")
+            << standard.at(0)
+            << QString()
+            << 14 << 19 << standard.at(0).length()
+            << QString("Hello")
+            << standard.at(0) + QString("Hello")
+            << 14 << 19 << 19
+            << false << false;
+
+    QTest::newRow("after reversed selection")
+            << standard.at(0)
+            << QString()
+            << 19 << 14 << standard.at(0).length()
+            << QString("Hello")
+            << standard.at(0) + QString("Hello")
+            << 14 << 19 << 14
+            << false << false;
+
+    QTest::newRow("into selection")
+            << standard.at(0)
+            << QString()
+            << 14 << 19 << 18
+            << QString("Hello")
+            << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
+            << 14 << 24 << 24
+            << true << true;
+
+    QTest::newRow("into reversed selection")
+            << standard.at(0)
+            << QString()
+            << 19 << 14 << 18
+            << QString("Hello")
+            << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
+            << 14 << 24 << 14
+            << true << false;
+
+    QTest::newRow("rich text into plain text")
+            << standard.at(0)
+            << QString()
+            << 0 << 0 << 0
+            << QString("<b>Hello</b>")
+            << QString("<b>Hello</b>") + standard.at(0)
+            << 12 << 12 << 12
+            << false << true;
+
+    QTest::newRow("before start")
+            << standard.at(0)
+            << QString()
+            << 0 << 0 << -3
+            << QString("Hello")
+            << standard.at(0)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("past end")
+            << standard.at(0)
+            << QString()
+            << 0 << 0 << standard.at(0).length() + 3
+            << QString("Hello")
+            << standard.at(0)
+            << 0 << 0 << 0
+            << false << false;
+
+    const QString inputMask = "009.009.009.009";
+    const QString ip = "192.168.2.14";
+
+    QTest::newRow("mask: at cursor position (beginning)")
+            << ip
+            << inputMask
+            << 0 << 0 << 0
+            << QString("125")
+            << QString("125.168.2.14")
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: at cursor position (end)")
+            << ip
+            << inputMask
+            << inputMask.length() << inputMask.length() << inputMask.length()
+            << QString("8")
+            << ip
+            << inputMask.length() << inputMask.length() << inputMask.length()
+            << false << false;
+
+    QTest::newRow("mask: at cursor position (middle)")
+            << ip
+            << inputMask
+            << 6 << 6 << 6
+            << QString("75.2")
+            << QString("192.167.5.24")
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: after cursor position (beginning)")
+            << ip
+            << inputMask
+            << 0 << 0 << 6
+            << QString("75.2")
+            << QString("192.167.5.24")
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: before cursor position (end)")
+            << ip
+            << inputMask
+            << inputMask.length() << inputMask.length() << 6
+            << QString("75.2")
+            << QString("192.167.5.24")
+            << inputMask.length() << inputMask.length() << inputMask.length()
+            << false << false;
+
+    QTest::newRow("mask: before cursor position (middle)")
+            << ip
+            << inputMask
+            << 6 << 6 << 0
+            << QString("125")
+            << QString("125.168.2.14")
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: after cursor position (middle)")
+            << ip
+            << inputMask
+            << 6 << 6 << 13
+            << QString("8")
+            << "192.168.2.18"
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: before selection")
+            << ip
+            << inputMask
+            << 6 << 8 << 0
+            << QString("125")
+            << QString("125.168.2.14")
+            << 6 << 8 << 8
+            << false << false;
+
+    QTest::newRow("mask: before reversed selection")
+            << ip
+            << inputMask
+            << 8 << 6 << 0
+            << QString("125")
+            << QString("125.168.2.14")
+            << 6 << 8 << 6
+            << false << false;
+
+    QTest::newRow("mask: after selection")
+            << ip
+            << inputMask
+            << 6 << 8 << 13
+            << QString("8")
+            << "192.168.2.18"
+            << 6 << 8 << 8
+            << false << false;
+
+    QTest::newRow("mask: after reversed selection")
+            << ip
+            << inputMask
+            << 8 << 6 << 13
+            << QString("8")
+            << "192.168.2.18"
+            << 6 << 8 << 6
+            << false << false;
+
+    QTest::newRow("mask: into selection")
+            << ip
+            << inputMask
+            << 5 << 8 << 6
+            << QString("75.2")
+            << QString("192.167.5.24")
+            << 5 << 8 << 8
+            << true << false;
+
+    QTest::newRow("mask: into reversed selection")
+            << ip
+            << inputMask
+            << 8 << 5 << 6
+            << QString("75.2")
+            << QString("192.167.5.24")
+            << 5 << 8 << 5
+            << true << false;
+
+    QTest::newRow("mask: before start")
+            << ip
+            << inputMask
+            << 0 << 0 << -3
+            << QString("4")
+            << ip
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: past end")
+            << ip
+            << inputMask
+            << 0 << 0 << ip.length() + 3
+            << QString("4")
+            << ip
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: invalid characters")
+            << ip
+            << inputMask
+            << 0 << 0 << 0
+            << QString("abc")
+            << QString("192.168.2.14")
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: mixed validity")
+            << ip
+            << inputMask
+            << 0 << 0 << 0
+            << QString("a1b2c5")
+            << QString("125.168.2.14")
+            << 0 << 0 << 0
+            << false << false;
+}
+
+void tst_qquicktextinput::insert()
+{
+    QFETCH(QString, text);
+    QFETCH(QString, inputMask);
+    QFETCH(int, selectionStart);
+    QFETCH(int, selectionEnd);
+    QFETCH(int, insertPosition);
+    QFETCH(QString, insertText);
+    QFETCH(QString, expectedText);
+    QFETCH(int, expectedSelectionStart);
+    QFETCH(int, expectedSelectionEnd);
+    QFETCH(int, expectedCursorPosition);
+    QFETCH(bool, selectionChanged);
+    QFETCH(bool, cursorPositionChanged);
+
+    QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
+    QDeclarativeComponent textInputComponent(&engine);
+    textInputComponent.setData(componentStr.toLatin1(), QUrl());
+    QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
+    QVERIFY(textInput != 0);
+
+    textInput->select(selectionStart, selectionEnd);
+
+    QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
+    QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
+    QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
+    QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
+    QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
+
+    textInput->insert(insertPosition, insertText);
+
+    QCOMPARE(textInput->text(), expectedText);
+    QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
+
+    QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
+    QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
+    QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
+
+    if (selectionStart > selectionEnd)
+        qSwap(selectionStart, selectionEnd);
+
+    QCOMPARE(selectionSpy.count() > 0, selectionChanged);
+    QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
+    QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
+    QCOMPARE(textSpy.count() > 0, text != expectedText);
+    QCOMPARE(cursorPositionSpy.count() > 0, cursorPositionChanged);
+}
+
+void tst_qquicktextinput::remove_data()
+{
+    QTest::addColumn<QString>("text");
+    QTest::addColumn<QString>("inputMask");
+    QTest::addColumn<int>("selectionStart");
+    QTest::addColumn<int>("selectionEnd");
+    QTest::addColumn<int>("removeStart");
+    QTest::addColumn<int>("removeEnd");
+    QTest::addColumn<QString>("expectedText");
+    QTest::addColumn<int>("expectedSelectionStart");
+    QTest::addColumn<int>("expectedSelectionEnd");
+    QTest::addColumn<int>("expectedCursorPosition");
+    QTest::addColumn<bool>("selectionChanged");
+    QTest::addColumn<bool>("cursorPositionChanged");
+
+    QTest::newRow("from cursor position (beginning)")
+            << standard.at(0)
+            << QString()
+            << 0 << 0
+            << 0 << 5
+            << standard.at(0).mid(5)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("to cursor position (beginning)")
+            << standard.at(0)
+            << QString()
+            << 0 << 0
+            << 5 << 0
+            << standard.at(0).mid(5)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("to cursor position (end)")
+            << standard.at(0)
+            << QString()
+            << standard.at(0).length() << standard.at(0).length()
+            << standard.at(0).length() << standard.at(0).length() - 5
+            << standard.at(0).mid(0, standard.at(0).length() - 5)
+            << standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
+            << false << true;
+
+    QTest::newRow("to cursor position (end)")
+            << standard.at(0)
+            << QString()
+            << standard.at(0).length() << standard.at(0).length()
+            << standard.at(0).length() - 5 << standard.at(0).length()
+            << standard.at(0).mid(0, standard.at(0).length() - 5)
+            << standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
+            << false << true;
+
+    QTest::newRow("from cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 18 << 18
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << 18 << 18 << 18
+            << false << false;
+
+    QTest::newRow("to cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 23 << 23
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << 18 << 18 << 18
+            << false << true;
+
+    QTest::newRow("after cursor position (beginning)")
+            << standard.at(0)
+            << QString()
+            << 0 << 0
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("before cursor position (end)")
+            << standard.at(0)
+            << QString()
+            << standard.at(0).length() << standard.at(0).length()
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
+            << false << true;
+
+    QTest::newRow("before cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 23 << 23
+            << 0 << 5
+            << standard.at(0).mid(5)
+            << 18 << 18 << 18
+            << false << true;
+
+    QTest::newRow("after cursor position (middle)")
+            << standard.at(0)
+            << QString()
+            << 18 << 18
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << 18 << 18 << 18
+            << false << false;
+
+    QTest::newRow("before selection")
+            << standard.at(0)
+            << QString()
+            << 14 << 19
+            << 0 << 5
+            << standard.at(0).mid(5)
+            << 9 << 14 << 14
+            << false << true;
+
+    QTest::newRow("before reversed selection")
+            << standard.at(0)
+            << QString()
+            << 19 << 14
+            << 0 << 5
+            << standard.at(0).mid(5)
+            << 9 << 14 << 9
+            << false << true;
+
+    QTest::newRow("after selection")
+            << standard.at(0)
+            << QString()
+            << 14 << 19
+            << standard.at(0).length() - 5 << standard.at(0).length()
+            << standard.at(0).mid(0, standard.at(0).length() - 5)
+            << 14 << 19 << 19
+            << false << false;
+
+    QTest::newRow("after reversed selection")
+            << standard.at(0)
+            << QString()
+            << 19 << 14
+            << standard.at(0).length() - 5 << standard.at(0).length()
+            << standard.at(0).mid(0, standard.at(0).length() - 5)
+            << 14 << 19 << 14
+            << false << false;
+
+    QTest::newRow("from selection")
+            << standard.at(0)
+            << QString()
+            << 14 << 24
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << 14 << 19 << 19
+            << true << true;
+
+    QTest::newRow("from reversed selection")
+            << standard.at(0)
+            << QString()
+            << 24 << 14
+            << 18 << 23
+            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
+            << 14 << 19 << 14
+            << true << false;
+
+    QTest::newRow("cropped beginning")
+            << standard.at(0)
+            << QString()
+            << 0 << 0
+            << -3 << 4
+            << standard.at(0).mid(4)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("cropped end")
+            << standard.at(0)
+            << QString()
+            << 0 << 0
+            << 23 << standard.at(0).length() + 8
+            << standard.at(0).mid(0, 23)
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("cropped beginning and end")
+            << standard.at(0)
+            << QString()
+            << 0 << 0
+            << -9 << standard.at(0).length() + 4
+            << QString()
+            << 0 << 0 << 0
+            << false << false;
+
+    const QString inputMask = "009.009.009.009";
+    const QString ip = "192.168.2.14";
+
+    QTest::newRow("mask: from cursor position")
+            << ip
+            << inputMask
+            << 6 << 6
+            << 6 << 9
+            << QString("192.16..14")
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: to cursor position")
+            << ip
+            << inputMask
+            << 6 << 6
+            << 2 << 6
+            << QString("19.8.2.14")
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: before cursor position")
+            << ip
+            << inputMask
+            << 6 << 6
+            << 0 << 2
+            << QString("2.168.2.14")
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: after cursor position")
+            << ip
+            << inputMask
+            << 6 << 6
+            << 12 << 16
+            << QString("192.168.2.")
+            << 6 << 6 << 6
+            << false << false;
+
+    QTest::newRow("mask: before selection")
+            << ip
+            << inputMask
+            << 6 << 8
+            << 0 << 2
+            << QString("2.168.2.14")
+            << 6 << 8 << 8
+            << false << false;
+
+    QTest::newRow("mask: before reversed selection")
+            << ip
+            << inputMask
+            << 8 << 6
+            << 0 << 2
+            << QString("2.168.2.14")
+            << 6 << 8 << 6
+            << false << false;
+
+    QTest::newRow("mask: after selection")
+            << ip
+            << inputMask
+            << 6 << 8
+            << 12 << 16
+            << QString("192.168.2.")
+            << 6 << 8 << 8
+            << false << false;
+
+    QTest::newRow("mask: after reversed selection")
+            << ip
+            << inputMask
+            << 8 << 6
+            << 12 << 16
+            << QString("192.168.2.")
+            << 6 << 8 << 6
+            << false << false;
+
+    QTest::newRow("mask: from selection")
+            << ip
+            << inputMask
+            << 6 << 13
+            << 8 << 10
+            << QString("192.168..14")
+            << 6 << 13 << 13
+            << true << false;
+
+    QTest::newRow("mask: from reversed selection")
+            << ip
+            << inputMask
+            << 13 << 6
+            << 8 << 10
+            << QString("192.168..14")
+            << 6 << 13 << 6
+            << true << false;
+
+    QTest::newRow("mask: cropped beginning")
+            << ip
+            << inputMask
+            << 0 << 0
+            << -3 << 4
+            << QString(".168.2.14")
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: cropped end")
+            << ip
+            << inputMask
+            << 0 << 0
+            << 13 << 28
+            << QString("192.168.2.1")
+            << 0 << 0 << 0
+            << false << false;
+
+    QTest::newRow("mask: cropped beginning and end")
+            << ip
+            << inputMask
+            << 0 << 0
+            << -9 << 28
+            << QString("...")
+            << 0 << 0 << 0
+            << false << false;
+}
+
+void tst_qquicktextinput::remove()
+{
+    QFETCH(QString, text);
+    QFETCH(QString, inputMask);
+    QFETCH(int, selectionStart);
+    QFETCH(int, selectionEnd);
+    QFETCH(int, removeStart);
+    QFETCH(int, removeEnd);
+    QFETCH(QString, expectedText);
+    QFETCH(int, expectedSelectionStart);
+    QFETCH(int, expectedSelectionEnd);
+    QFETCH(int, expectedCursorPosition);
+    QFETCH(bool, selectionChanged);
+    QFETCH(bool, cursorPositionChanged);
+
+    QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
+    QDeclarativeComponent textInputComponent(&engine);
+    textInputComponent.setData(componentStr.toLatin1(), QUrl());
+    QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
+    QVERIFY(textInput != 0);
+
+    textInput->select(selectionStart, selectionEnd);
+
+    QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
+    QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
+    QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
+    QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
+    QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
+
+    textInput->remove(removeStart, removeEnd);
+
+    QCOMPARE(textInput->text(), expectedText);
+    QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
+
+    if (selectionStart > selectionEnd)  //
+        qSwap(selectionStart, selectionEnd);
+
+    QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
+    QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
+    QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
+
+    QCOMPARE(selectionSpy.count() > 0, selectionChanged);
+    QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
+    QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
+    QCOMPARE(textSpy.count() > 0, text != expectedText);
+
+    if (cursorPositionChanged)  //
+        QVERIFY(cursorPositionSpy.count() > 0);
+}
+
 void tst_qquicktextinput::keySequence_data()
 {
     QTest::addColumn<QString>("text");