From a150880ae6ea698a2e979c93498db4f3bb66c1b0 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Mon, 25 Jul 2011 13:25:15 +1000 Subject: Delay masking the last character in Password echo mode. If QT_GUI_PASSWORD_ECHO_DELAY is defined in qplatformdefs.h with an integer value in milliseconds, QLineEdit and TextInput will display the last character entered unmasked for that delay period and then mask the character as normal. If QT_GUI_PASSWORD_ECHO_DELAY is not defined then the behaviour is unchanged. Task-number: QTBUG-17003 Reviewed-by: Martin Jones (cherry picked from commit f9e7aee2019d321edd655bfde7de43f20a106971) Conflicts: src/declarative/graphicsitems/qdeclarativetextinput.cpp tests/auto/declarative/qdeclarativetextinput/tst_qdeclarativetextinput.cpp Change-Id: I3683223189b7176e4ef5081ee315c95a0efb9cfe Reviewed-on: http://codereview.qt.nokia.com/2060 Reviewed-by: Qt Sanity Bot Reviewed-by: Andrew den Exter --- src/gui/widgets/qlinecontrol.cpp | 54 ++++++++++++++++++++++++++++++++-- src/gui/widgets/qlinecontrol_p.h | 26 +++++++++++++++- tests/auto/qlineedit/tst_qlineedit.cpp | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/src/gui/widgets/qlinecontrol.cpp b/src/gui/widgets/qlinecontrol.cpp index bf36033c08..84674a51d3 100644 --- a/src/gui/widgets/qlinecontrol.cpp +++ b/src/gui/widgets/qlinecontrol.cpp @@ -59,6 +59,22 @@ QT_BEGIN_NAMESPACE +#ifdef QT_GUI_PASSWORD_ECHO_DELAY +static int qt_passwordEchoDelay = QT_GUI_PASSWORD_ECHO_DELAY; +#endif + +/*! + \macro QT_GUI_PASSWORD_ECHO_DELAY + + \internal + + Defines the amount of time in milliseconds the last entered character + should be displayed unmasked in the Password echo mode. + + If not defined in qplatformdefs.h there will be no delay in masking + password characters. +*/ + /*! \internal @@ -74,9 +90,25 @@ void QLineControl::updateDisplayText(bool forceUpdate) else str = m_text; - if (m_echoMode == QLineEdit::Password || (m_echoMode == QLineEdit::PasswordEchoOnEdit - && !m_passwordEchoEditing)) + if (m_echoMode == QLineEdit::Password) { str.fill(m_passwordCharacter); +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + if (m_passwordEchoTimer != 0 && !str.isEmpty()) { + int cursor = m_text.length() - 1; + QChar uc = m_text.at(cursor); + str[cursor] = uc; + if (cursor > 0 && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) { + // second half of a surrogate, check if we have the first half as well, + // if yes restore both at once + uc = m_text.at(cursor - 1); + if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00) + str[cursor - 1] = uc; + } + } +#endif + } else if (m_echoMode == QLineEdit::PasswordEchoOnEdit && !m_passwordEchoEditing) { + str.fill(m_passwordCharacter); + } // replace certain non-printable characters with spaces (to avoid // drawing boxes when using fonts that don't have glyphs for such @@ -311,6 +343,7 @@ void QLineControl::init(const QString &txt) */ void QLineControl::updatePasswordEchoEditing(bool editing) { + cancelPasswordEchoTimer(); m_passwordEchoEditing = editing; updateDisplayText(); } @@ -640,6 +673,7 @@ bool QLineControl::finishChange(int validateFromState, bool update, bool edited) */ void QLineControl::internalSetText(const QString &txt, int pos, bool edited) { + cancelPasswordEchoTimer(); internalDeselect(); emit resetInputContext(); QString oldText = m_text; @@ -692,6 +726,13 @@ void QLineControl::addCommand(const Command &cmd) */ void QLineControl::internalInsert(const QString &s) { +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + if (m_echoMode == QLineEdit::Password) { + if (m_passwordEchoTimer != 0) + killTimer(m_passwordEchoTimer); + m_passwordEchoTimer = startTimer(qt_passwordEchoDelay); + } +#endif if (hasSelectedText()) addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); if (m_maskData) { @@ -729,6 +770,7 @@ void QLineControl::internalInsert(const QString &s) void QLineControl::internalDelete(bool wasBackspace) { if (m_cursor < (int) m_text.length()) { + cancelPasswordEchoTimer(); if (hasSelectedText()) addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)), @@ -755,6 +797,7 @@ void QLineControl::internalDelete(bool wasBackspace) void QLineControl::removeSelectedText() { if (m_selstart < m_selend && m_selend <= (int) m_text.length()) { + cancelPasswordEchoTimer(); separate(); int i ; addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend)); @@ -1153,6 +1196,7 @@ void QLineControl::internalUndo(int until) { if (!isUndoAvailable()) return; + cancelPasswordEchoTimer(); internalDeselect(); while (m_undoState && m_undoState > until) { Command& cmd = m_history[--m_undoState]; @@ -1357,6 +1401,12 @@ void QLineControl::timerEvent(QTimerEvent *event) } else if (event->timerId() == m_tripleClickTimer) { killTimer(m_tripleClickTimer); m_tripleClickTimer = 0; +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + } else if (event->timerId() == m_passwordEchoTimer) { + killTimer(m_passwordEchoTimer); + m_passwordEchoTimer = 0; + updateDisplayText(); +#endif } } diff --git a/src/gui/widgets/qlinecontrol_p.h b/src/gui/widgets/qlinecontrol_p.h index c6105f8472..44bd7214be 100644 --- a/src/gui/widgets/qlinecontrol_p.h +++ b/src/gui/widgets/qlinecontrol_p.h @@ -66,6 +66,8 @@ #include "QtGui/qcompleter.h" #include "QtGui/qaccessible.h" +#include "qplatformdefs.h" + QT_BEGIN_HEADER QT_BEGIN_NAMESPACE @@ -85,6 +87,9 @@ public: m_ascent(0), m_maxLength(32767), m_lastCursorPos(-1), m_tripleClickTimer(0), m_maskData(0), m_modifiedState(0), m_undoState(0), m_selstart(0), m_selend(0), m_passwordEchoEditing(false) +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + , m_passwordEchoTimer(0) +#endif { init(txt); } @@ -222,6 +227,7 @@ public: uint echoMode() const { return m_echoMode; } void setEchoMode(uint mode) { + cancelPasswordEchoTimer(); m_echoMode = mode; m_passwordEchoEditing = false; updateDisplayText(); @@ -271,7 +277,13 @@ public: QString preeditAreaText() const { return m_textLayout.preeditAreaText(); } void updatePasswordEchoEditing(bool editing); - bool passwordEchoEditing() const { return m_passwordEchoEditing; } + bool passwordEchoEditing() const { +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + if (m_passwordEchoTimer != 0) + return true; +#endif + return m_passwordEchoEditing ; + } QChar passwordCharacter() const { return m_passwordCharacter; } void setPasswordCharacter(const QChar &character) { m_passwordCharacter = character; updateDisplayText(); } @@ -426,6 +438,18 @@ private: bool m_passwordEchoEditing; QChar m_passwordCharacter; +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + int m_passwordEchoTimer; +#endif + void cancelPasswordEchoTimer() + { +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + if (m_passwordEchoTimer != 0) { + killTimer(m_passwordEchoTimer); + m_passwordEchoTimer = 0; + } +#endif + } Q_SIGNALS: void cursorPositionChanged(int, int); diff --git a/tests/auto/qlineedit/tst_qlineedit.cpp b/tests/auto/qlineedit/tst_qlineedit.cpp index 92574ea01b..d178e26476 100644 --- a/tests/auto/qlineedit/tst_qlineedit.cpp +++ b/tests/auto/qlineedit/tst_qlineedit.cpp @@ -72,6 +72,8 @@ #include "qcommonstyle.h" #include "qstyleoption.h" +#include "qplatformdefs.h" + QT_BEGIN_NAMESPACE class QPainter; QT_END_NAMESPACE @@ -180,6 +182,10 @@ private slots: void echoMode(); void passwordEchoOnEdit(); +#ifdef QT_GUI_PASSWORD_ECHO_DELAY + void passwordEchoDelay(); +#endif + void maxLength_mask_data(); void maxLength_mask(); @@ -1679,6 +1685,51 @@ void tst_QLineEdit::passwordEchoOnEdit() testWidget->setEchoMode(QLineEdit::Normal); } +#ifdef QT_GUI_PASSWORD_ECHO_DELAY +void tst_QLineEdit::passwordEchoDelay() +{ + QStyleOptionFrameV2 opt; + QChar fillChar = testWidget->style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter, &opt, testWidget); + + testWidget->setEchoMode(QLineEdit::Password); + testWidget->setFocus(); + testWidget->raise(); + QTRY_VERIFY(testWidget->hasFocus()); + + QTest::keyPress(testWidget, '0'); + QTest::keyPress(testWidget, '1'); + QTest::keyPress(testWidget, '2'); + QCOMPARE(testWidget->displayText(), QString(2, fillChar) + QLatin1Char('2')); + QTest::keyPress(testWidget, '3'); + QTest::keyPress(testWidget, '4'); + QCOMPARE(testWidget->displayText(), QString(4, fillChar) + QLatin1Char('4')); + QTest::keyPress(testWidget, Qt::Key_Backspace); + QCOMPARE(testWidget->displayText(), QString(4, fillChar)); + QTest::keyPress(testWidget, '4'); + QCOMPARE(testWidget->displayText(), QString(4, fillChar) + QLatin1Char('4')); + QTest::qWait(QT_GUI_PASSWORD_ECHO_DELAY); + QTRY_COMPARE(testWidget->displayText(), QString(5, fillChar)); + QTest::keyPress(testWidget, '5'); + QCOMPARE(testWidget->displayText(), QString(5, fillChar) + QLatin1Char('5')); + testWidget->clearFocus(); + QVERIFY(!testWidget->hasFocus()); + QCOMPARE(testWidget->displayText(), QString(6, fillChar)); + testWidget->setFocus(); + QTRY_VERIFY(testWidget->hasFocus()); + QCOMPARE(testWidget->displayText(), QString(6, fillChar)); + QTest::keyPress(testWidget, '6'); + QCOMPARE(testWidget->displayText(), QString(6, fillChar) + QLatin1Char('6')); + + QInputMethodEvent ev; + ev.setCommitString(QLatin1String("7")); + QApplication::sendEvent(testWidget, &ev); + QCOMPARE(testWidget->displayText(), QString(7, fillChar) + QLatin1Char('7')); + + // restore clean state + testWidget->setEchoMode(QLineEdit::Normal); +} +#endif + void tst_QLineEdit::maxLength_mask_data() { QTest::addColumn("mask"); -- cgit v1.2.3