qtqlineedit

Display only the last character in a QLineEdit for a few seconds


QLineEdit offers 4 EchoMode states.

When typing a password, QLineEdit::Password is suitable because it displays a circle (or something similar) instead of the typed characters.

What I want to do is to hide all characters, like with QLineEdit::Password, but the last typed character is visible for a few seconds before it is replaced by a circle. This allows the user to check that they are using their password correctly.


Solution

  • Here is my solution. The last character is hidden after 500 milliseconds.

    lineeditvanishingcharacters.cpp

    #include "lineeditvanishingcharacters.h"
    
    #include <QTextLayout>
    #include <QPainter>
    
    LineEditVanishingCharacters::LineEditVanishingCharacters(QWidget *parent)
        : QLineEdit(parent)
        , m_is_new_character(false)
    {
        setEchoMode(QLineEdit::Password);
    
        connect(this, &LineEditVanishingCharacters::textEdited,
                this, &LineEditVanishingCharacters::textHasBeenEdited);
    
        m_timer = new QTimer(this);
        connect(m_timer, &QTimer::timeout, this, [this]() {
            update();
            m_is_new_character = false;
        });
        m_timer->start(500); // 0.5 second
    }
    
    void LineEditVanishingCharacters::textHasBeenEdited()
    {
        m_is_new_character = true;
        m_timer->start();
    }
    
    void LineEditVanishingCharacters::paintEvent(QPaintEvent *event)
    {
        // first paint as the default QLineEdit all characters but the last one
        if(text().isEmpty()) {
            QLineEdit::paintEvent(event);
            return;
        }
    
        // no new character has been typed, then hide the full text
        if(!m_is_new_character) {
            QLineEdit::paintEvent(event);
            return;
        }
    
        // if not focused anymore
        // then hide all characters
        if(!hasFocus()) {
            QLineEdit::paintEvent(event);
            return;
        }
    
        const QString first_characters = text().chopped(1);
        const QString last_character = text().last(1);
        setText(first_characters);
    
        QLineEdit::paintEvent(event);
    
        // restore the full text
        setText(first_characters+last_character);
    
        // ensure font() is up to date
        ensurePolished();
    
        // then, draw the last character
        // &. compute the cursor position
        const QRect cr = cursorRect();
        const QPoint pos = cr.topRight() - QPoint(cr.width(), 0.);
    
        // create the QTextLayout and add the last character to it
        QTextLayout l(last_character, font());
        l.beginLayout();
        QTextLine line = l.createLine();
        line.setLineWidth(width() - pos.x());
        line.setPosition(pos);
        l.endLayout();
    
        QPainter p(this);
        // set text color to match the textedit text color
        p.setPen(palette().text().color());
        l.draw(&p, QPoint(0, 0));
    }
    

    lineeditvanishingcharacters.h

    #ifndef LINEEDITVANISHINGCHARACTERS_H
    #define LINEEDITVANISHINGCHARACTERS_H
    
    #include <QLineEdit>
    #include <QPaintEvent>
    
    class LineEditVanishingCharacters : public QLineEdit
    {
        Q_OBJECT
    
    public:
        LineEditVanishingCharacters(QWidget *parent = nullptr);
    
    public Q_SLOTS:
        void textHasBeenEdited();
    
    protected:
        void paintEvent(QPaintEvent *event);
    
    private:
        bool m_is_new_character;
    };
    
    #endif // LINEEDITVANISHINGCHARACTERS_H