Question

How to use QLineEdit's built-in undo/redo functionality while implementing custom input logic?

I am implementing a custom editor for amounts of money for a cashier's efficient work. Basic features are:

  • The format is always ^\d+,\d{2}$;
  • When cursor is left to comma, it edits the integer part (e.g., when 0,00 is edited, the first digit input replaces the zero; i.e., 0,00 becomes 1,00 after '1' is entered);
  • Comma or period entered moves cursor to the fractional part;
  • Deletes and backspaces respect the format.

I can't use masks since they can't manage arbitrary length without unnecessary spaces. I can't use validators because of they are not enough flexible to handle logic of editing leading zero in place. That's why I sublassed QLineEdit and wrote the necessary logic myself.

The question is: is there a way to support undo functionality on QLineEdit's own logic? I use setText(), that resets undo/redo history. I could make two stacks of states and override undo() and redo() correspondingly, or make sequences of selections and insertions/deletions (what can cause unnecessary blinks), but I feel there may be even simpler way.

class MoneyLineEdit : public QLineEdit {
    Q_OBJECT
public:
    MoneyLineEdit(QWidget *parent = 0);

protected:
    void keyPressEvent(QKeyEvent * event);
};

void MoneyLineEdit::keyPressEvent(QKeyEvent *event) {
    if (event->key() == Qt::Key_Comma || event->key() == Qt::Key_Period) {
        setCursorPosition(text().length() - 2);
    } else {
        QString text = this->text();
        int pos = cursorPosition();
        if (event->key() == Qt::Key_Backspace) {
            if (pos == text.length()) {
                text.replace(text.length() - 1, 1, QChar('0'));
                --pos;
            } else if (pos == text.length() - 1) {
                text.replace(text.length() - 2, 1, text.at(text.length() - 1));
                text.replace(text.length() - 1, 1, QChar('0'));
                --pos;
            } else if (pos == text.length() - 2) {
                --pos;
            } else if (text.length() == 4 && pos == 1) {
                text.replace(0, 1, QChar('0'));
            }
            setText(text);
            setCursorPosition(pos);
        } else if ((pos == text.length() - 3 || pos == text.length() - 2) && event->key() == Qt::Key_Delete && selectionStart() == -1){
            text.replace(text.length() - 2, 1, text.at(text.length() - 1));
            text.replace(text.length() - 1, 1, QChar('0'));
            setText(text);
            setCursorPosition(pos);
        } else if (pos >= text.length() - 2 && pos <= text.length() - 1 && !event->text().isEmpty()){
            text.replace(cursorPosition(), 1, event->text());
            ++pos;
            setText(text);
            setCursorPosition(pos);
        } else if ((pos == 0 || pos == 1) && !text.isEmpty() && text.length() == 4 && text.at(0) == QChar('0') && !event->text().isEmpty()) {
            text.replace(0, 1, event->text());
            setText(text);
            setCursorPosition(1);
        } else {
            QLineEdit::keyPressEvent(event);
        }
    }
}
Was it helpful?

Solution

Obviously, two stacks of Pair(text,cursorPosition) for undo()/redo() overloaded are sufficient.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top