From c0e0f9f52bf3f89c6dd141e4d19573fa9a042628 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Wed, 18 Jan 2012 14:43:43 +1000 Subject: [PATCH] Update TextInput.acceptableInput on component complete. 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 --- src/quick/items/qquicktextinput.cpp | 62 ++++++++++------ src/quick/items/qquicktextinput_p_p.h | 17 ++++- .../qtquick2/qquicktextinput/data/validators.qml | 7 ++ .../qquicktextinput/tst_qquicktextinput.cpp | 82 +++++++++++++++++++++- 4 files changed, 144 insertions(+), 24 deletions(-) diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index fb96709..4ccf3a3 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -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; } /*! diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index 44ea777..74f17f0 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -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; diff --git a/tests/auto/qtquick2/qquicktextinput/data/validators.qml b/tests/auto/qtquick2/qquicktextinput/data/validators.qml index 0a074ce..0ba87e0 100644 --- a/tests/auto/qtquick2/qquicktextinput/data/validators.qml +++ b/tests/auto/qtquick2/qquicktextinput/data/validators.qml @@ -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 + } } } diff --git a/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp index 8cc35f3..4ae5b52 100644 --- a/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp @@ -1573,139 +1573,217 @@ void tst_qquicktextinput::validators() QQuickTextInput *intInput = qobject_cast(qvariant_cast(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(qvariant_cast(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(qvariant_cast(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(qvariant_cast(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() -- 2.7.4