Update TextInput.acceptableInput on component complete.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Wed, 18 Jan 2012 04:43:43 +0000 (14:43 +1000)
committerQt by Nokia <qt-info@nokia.com>
Fri, 20 Jan 2012 06:12:19 +0000 (07:12 +0100)
QValidator doesn't notify when it's validation criteria changes so is
susceptible to order of evaluation issues.  Deferring the initial
validation will ensure validators with static criteria are correctly
applied.  Notification from QValidator on changes would solve this
for all cases: QTBUG-23694.

Task-number: QTBUG-21103
Change-Id: I920f36645fd18ce809db56b5daf73545f1d603dc
Reviewed-by: Martin Jones <martin.jones@nokia.com>
src/quick/items/qquicktextinput.cpp
src/quick/items/qquicktextinput_p_p.h
tests/auto/qtquick2/qquicktextinput/data/validators.qml
tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp

index fb96709..4ccf3a3 100644 (file)
@@ -102,6 +102,7 @@ void QQuickTextInput::componentComplete()
 
     QQuickImplicitSizeItem::componentComplete();
 
+    d->checkIsValid();
     d->updateLayout();
     updateCursorRectangle();
     if (d->cursorComponent && d->cursorComponent->isReady())
@@ -948,20 +949,32 @@ void QQuickTextInput::setValidator(QValidator* v)
         return;
 
     d->m_validator = v;
-    if (!d->hasAcceptableInput(d->m_text)) {
-        if (d->m_validInput) {
-            d->m_validInput = false;
-            emit acceptableInputChanged();
-        }
-    } else if (!d->m_validInput) {
-        d->m_validInput = true;
-        emit acceptableInputChanged();
-    }
+
+    if (isComponentComplete())
+        d->checkIsValid();
 
     emit validatorChanged();
 }
+
 #endif // QT_NO_VALIDATOR
 
+void QQuickTextInputPrivate::checkIsValid()
+{
+    Q_Q(QQuickTextInput);
+
+    ValidatorState state = hasAcceptableInput(m_text);
+    m_validInput = state != InvalidInput;
+    if (state != AcceptableInput) {
+        if (m_acceptableInput) {
+            m_acceptableInput = false;
+            emit q->acceptableInputChanged();
+        }
+    } else if (!m_acceptableInput) {
+        m_acceptableInput = true;
+        emit q->acceptableInputChanged();
+    }
+}
+
 /*!
     \qmlproperty string QtQuick2::TextInput::inputMask
 
@@ -998,7 +1011,7 @@ void QQuickTextInput::setInputMask(const QString &im)
 bool QQuickTextInput::hasAcceptableInput() const
 {
     Q_D(const QQuickTextInput);
-    return d->hasAcceptableInput(d->m_text);
+    return d->hasAcceptableInput(d->m_text) == QQuickTextInputPrivate::AcceptableInput;
 }
 
 /*!
@@ -3021,12 +3034,16 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
     if (m_textDirty) {
         // do validation
         bool wasValidInput = m_validInput;
+        bool wasAcceptable = m_acceptableInput;
         m_validInput = true;
+        m_acceptableInput = true;
 #ifndef QT_NO_VALIDATOR
         if (m_validator) {
             QString textCopy = m_text;
             int cursorCopy = m_cursor;
-            m_validInput = (m_validator->validate(textCopy, cursorCopy) != QValidator::Invalid);
+            QValidator::State state = m_validator->validate(textCopy, cursorCopy);
+            m_validInput = state != QValidator::Invalid;
+            m_acceptableInput = state == QValidator::Acceptable;
             if (m_validInput) {
                 if (m_text != textCopy) {
                     internalSetText(textCopy, cursorCopy);
@@ -3053,6 +3070,7 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
             if (m_modifiedState > m_undoState)
                 m_modifiedState = -1;
             m_validInput = true;
+            m_acceptableInput = wasAcceptable;
             m_textDirty = false;
         }
 
@@ -3065,7 +3083,7 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo
 
         updateDisplayText(alignmentChanged);
 
-        if (m_validInput != wasValidInput)
+        if (m_acceptableInput != wasAcceptable)
             emit q->acceptableInputChanged();
     }
     if (m_preeditDirty) {
@@ -3437,32 +3455,34 @@ bool QQuickTextInputPrivate::isValidInput(QChar key, QChar mask) const
 
     Otherwise returns false
 */
-bool QQuickTextInputPrivate::hasAcceptableInput(const QString &str) const
+QQuickTextInputPrivate::ValidatorState QQuickTextInputPrivate::hasAcceptableInput(const QString &str) const
 {
 #ifndef QT_NO_VALIDATOR
     QString textCopy = str;
     int cursorCopy = m_cursor;
-    if (m_validator && m_validator->validate(textCopy, cursorCopy)
-        != QValidator::Acceptable)
-        return false;
+    if (m_validator) {
+        QValidator::State state = m_validator->validate(textCopy, cursorCopy);
+        if (state != QValidator::Acceptable)
+            return ValidatorState(state);
+    }
 #endif
 
     if (!m_maskData)
-        return true;
+        return AcceptableInput;
 
     if (str.length() != m_maxLength)
-        return false;
+        return InvalidInput;
 
     for (int i=0; i < m_maxLength; ++i) {
         if (m_maskData[i].separator) {
             if (str.at(i) != m_maskData[i].maskChar)
-                return false;
+                return InvalidInput;
         } else {
             if (!isValidInput(str.at(i), m_maskData[i].maskChar))
-                return false;
+                return InvalidInput;
         }
     }
-    return true;
+    return AcceptableInput;
 }
 
 /*!
index 44ea777..74f17f0 100644 (file)
@@ -125,6 +125,7 @@ public:
         , m_preeditDirty(0)
         , m_selDirty(0)
         , m_validInput(1)
+        , m_acceptableInput(1)
         , m_blinkStatus(0)
         , m_passwordEchoEditing(false)
     {
@@ -251,6 +252,7 @@ public:
     uint m_preeditDirty : 1;
     uint m_selDirty : 1;
     uint m_validInput : 1;
+    uint m_acceptableInput : 1;
     uint m_blinkStatus : 1;
     uint m_passwordEchoEditing;
 
@@ -432,10 +434,23 @@ private:
 
     inline void separate() { m_separator = true; }
 
+    enum ValidatorState {
+#ifndef QT_NO_VALIDATOR
+        InvalidInput        = QValidator::Invalid,
+        IntermediateInput   = QValidator::Intermediate,
+        AcceptableInput     = QValidator::Acceptable
+#else
+        Invalid,
+        Intermediate,
+        Acceptable
+#endif
+    };
+
     // masking
     void parseInputMask(const QString &maskFields);
     bool isValidInput(QChar key, QChar mask) const;
-    bool hasAcceptableInput(const QString &text) const;
+    ValidatorState hasAcceptableInput(const QString &text) const;
+    void checkIsValid();
     QString maskString(uint pos, const QString &str, bool clear = false) const;
     QString clearString(uint pos, uint len) const;
     QString stripString(const QString &str) const;
index 0a074ce..0ba87e0 100644 (file)
@@ -4,19 +4,26 @@ Item {
     property variant intInput: intInput
     property variant dblInput: dblInput
     property variant strInput: strInput
+    property variant unvalidatedInput: unvalidatedInput
 
     width: 800; height: 600;
 
     Column{
         TextInput { id: intInput;
+            property bool acceptable: acceptableInput
             validator: IntValidator{top: 11; bottom: 2}
         }
         TextInput { id: dblInput;
+            property bool acceptable: acceptableInput
             validator: DoubleValidator{top: 12.12; bottom: 2.93; decimals: 2; notation: DoubleValidator.StandardNotation}
         }
         TextInput { id: strInput;
+            property bool acceptable: acceptableInput
             validator: RegExpValidator { regExp: /[a-zA-z]{2,4}/ }
         }
+        TextInput { id: unvalidatedInput
+            property bool acceptable: acceptableInput
+        }
     }
         
 }
index 8cc35f3..4ae5b52 100644 (file)
@@ -1573,139 +1573,217 @@ void tst_qquicktextinput::validators()
 
     QQuickTextInput *intInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("intInput")));
     QVERIFY(intInput);
+    QSignalSpy intSpy(intInput, SIGNAL(acceptableInputChanged()));
     intInput->setFocus(true);
     QTRY_VERIFY(intInput->hasActiveFocus());
+    QCOMPARE(intInput->hasAcceptableInput(), false);
+    QCOMPARE(intInput->property("acceptable").toBool(), false);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(intInput->text(), QLatin1String("1"));
     QCOMPARE(intInput->hasAcceptableInput(), false);
+    QCOMPARE(intInput->property("acceptable").toBool(), false);
+    QCOMPARE(intSpy.count(), 0);
     QTest::keyPress(&canvas, Qt::Key_2);
     QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(intInput->text(), QLatin1String("1"));
     QCOMPARE(intInput->hasAcceptableInput(), false);
+    QCOMPARE(intInput->property("acceptable").toBool(), false);
+    QCOMPARE(intSpy.count(), 0);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QCOMPARE(intInput->text(), QLatin1String("11"));
     QCOMPARE(intInput->hasAcceptableInput(), true);
+    QCOMPARE(intInput->property("acceptable").toBool(), true);
+    QCOMPARE(intSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_0);
     QTest::keyRelease(&canvas, Qt::Key_0, Qt::NoModifier ,10);
     QTest::qWait(50);
     QCOMPARE(intInput->text(), QLatin1String("11"));
     QCOMPARE(intInput->hasAcceptableInput(), true);
+    QCOMPARE(intInput->property("acceptable").toBool(), true);
+    QCOMPARE(intSpy.count(), 1);
 
     QQuickTextInput *dblInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("dblInput")));
-    QTRY_VERIFY(dblInput);
+    QVERIFY(dblInput);
+    QSignalSpy dblSpy(dblInput, SIGNAL(acceptableInputChanged()));
     dblInput->setFocus(true);
     QVERIFY(dblInput->hasActiveFocus() == true);
+    QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 0);
     QTest::keyPress(&canvas, Qt::Key_2);
     QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
     QCOMPARE(dblInput->hasAcceptableInput(), true);
+    QCOMPARE(dblInput->property("acceptable").toBool(), true);
+    QCOMPARE(dblSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_Period);
     QTest::keyRelease(&canvas, Qt::Key_Period, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
     QCOMPARE(dblInput->hasAcceptableInput(), true);
+    QCOMPARE(dblInput->property("acceptable").toBool(), true);
+    QCOMPARE(dblSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
     QCOMPARE(dblInput->hasAcceptableInput(), true);
+    QCOMPARE(dblInput->property("acceptable").toBool(), true);
+    QCOMPARE(dblSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
     QCOMPARE(dblInput->hasAcceptableInput(), true);
+    QCOMPARE(dblInput->property("acceptable").toBool(), true);
+    QCOMPARE(dblSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
     QCOMPARE(dblInput->hasAcceptableInput(), true);
+    QCOMPARE(dblInput->property("acceptable").toBool(), true);
+    QCOMPARE(dblSpy.count(), 1);
 
     // Ensure the validator doesn't prevent characters being removed.
     dblInput->setValidator(intInput->validator());
     QCOMPARE(dblInput->text(), QLatin1String("12.11"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 2);
     QTest::keyPress(&canvas, Qt::Key_Backspace);
     QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 2);
     // Once unacceptable input is in anything goes until it reaches an acceptable state again.
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblSpy.count(), 2);
     QTest::keyPress(&canvas, Qt::Key_Backspace);
     QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 2);
     QTest::keyPress(&canvas, Qt::Key_Backspace);
     QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 2);
     QTest::keyPress(&canvas, Qt::Key_Backspace);
     QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 2);
     QTest::keyPress(&canvas, Qt::Key_Backspace);
     QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
     QCOMPARE(dblInput->hasAcceptableInput(), false);
+    QCOMPARE(dblInput->property("acceptable").toBool(), false);
+    QCOMPARE(dblSpy.count(), 2);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QCOMPARE(dblInput->text(), QLatin1String("11"));
+    QCOMPARE(dblInput->property("acceptable").toBool(), true);
     QCOMPARE(dblInput->hasAcceptableInput(), true);
+    QCOMPARE(dblSpy.count(), 3);
 
     QQuickTextInput *strInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("strInput")));
-    QTRY_VERIFY(strInput);
+    QVERIFY(strInput);
+    QSignalSpy strSpy(strInput, SIGNAL(acceptableInputChanged()));
     strInput->setFocus(true);
     QVERIFY(strInput->hasActiveFocus() == true);
+    QCOMPARE(strInput->hasAcceptableInput(), false);
+    QCOMPARE(strInput->property("acceptable").toBool(), false);
     QTest::keyPress(&canvas, Qt::Key_1);
     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(strInput->text(), QLatin1String(""));
     QCOMPARE(strInput->hasAcceptableInput(), false);
+    QCOMPARE(strInput->property("acceptable").toBool(), false);
+    QCOMPARE(strSpy.count(), 0);
     QTest::keyPress(&canvas, Qt::Key_A);
     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(strInput->text(), QLatin1String("a"));
     QCOMPARE(strInput->hasAcceptableInput(), false);
+    QCOMPARE(strInput->property("acceptable").toBool(), false);
+    QCOMPARE(strSpy.count(), 0);
     QTest::keyPress(&canvas, Qt::Key_A);
     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(strInput->text(), QLatin1String("aa"));
     QCOMPARE(strInput->hasAcceptableInput(), true);
+    QCOMPARE(strInput->property("acceptable").toBool(), true);
+    QCOMPARE(strSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_A);
     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(strInput->text(), QLatin1String("aaa"));
     QCOMPARE(strInput->hasAcceptableInput(), true);
+    QCOMPARE(strInput->property("acceptable").toBool(), true);
+    QCOMPARE(strSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_A);
     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
     QCOMPARE(strInput->hasAcceptableInput(), true);
+    QCOMPARE(strInput->property("acceptable").toBool(), true);
+    QCOMPARE(strSpy.count(), 1);
     QTest::keyPress(&canvas, Qt::Key_A);
     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
     QTest::qWait(50);
     QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
     QCOMPARE(strInput->hasAcceptableInput(), true);
+    QCOMPARE(strInput->property("acceptable").toBool(), true);
+    QCOMPARE(strSpy.count(), 1);
+
+    QQuickTextInput *unvalidatedInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("unvalidatedInput")));
+    QVERIFY(unvalidatedInput);
+    QSignalSpy unvalidatedSpy(unvalidatedInput, SIGNAL(acceptableInputChanged()));
+    unvalidatedInput->setFocus(true);
+    QVERIFY(unvalidatedInput->hasActiveFocus() == true);
+    QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
+    QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
+    QTest::keyPress(&canvas, Qt::Key_1);
+    QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
+    QTest::qWait(50);
+    QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1"));
+    QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
+    QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
+    QCOMPARE(unvalidatedSpy.count(), 0);
+    QTest::keyPress(&canvas, Qt::Key_A);
+    QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
+    QTest::qWait(50);
+    QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1a"));
+    QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
+    QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
+    QCOMPARE(unvalidatedSpy.count(), 0);
 }
 
 void tst_qquicktextinput::inputMethods()