Skip to content

Instantly share code, notes, and snippets.

@DavidJCobb
Last active September 8, 2021 18:49
Show Gist options
  • Save DavidJCobb/1c8bdb862955f7ce9ceb5e1096a3af48 to your computer and use it in GitHub Desktop.
Save DavidJCobb/1c8bdb862955f7ce9ceb5e1096a3af48 to your computer and use it in GitHub Desktop.
QPlainTextEdit maxlength implementation
// given:
QPlainTextEdit* widget;
int maxlength;
auto* doc = widget->document();
assert(doc);
QObject::connect(doc, &QTextDocument::contentsChange, this, [maxlength, widget](int pos, int removed, int added) {
if (added - removed <= 0)
return;
if (maxlength < 0)
return;
auto text = widget->toPlainText();
if (text.size() <= maxlength)
return;
//
auto* doc = widget->document();
auto cur = QTextCursor(doc);
const auto blocker = QSignalBlocker(doc);
cur.setPosition(pos);
cur.setPosition(pos + added, QTextCursor::MoveMode::KeepAnchor);
if (cur.hasSelection()) {
cur.deleteChar();
text = widget->toPlainText();
}
if (text.size() > maxlength) { // failsafe
text = text.left(maxlength);
widget->setPlainText(text);
}
QApplication::beep();
});
// Other approaches (some seen on the web), and their flaws:
// EVENT FILTER: Hook the keyEvent and block input if already at the max length
// - Fails if the user pastes a multi-character string in.
// - seriously, this is so flimsy, why does anyone suggest it
// TRUNCATE: Hook QPlainTextEdit::textChanged and truncate the length
// - Intended to create the appearance of preventing input by instantly reverting it
// - Fails if the user types into the middle of the value: we truncate the end
// - Fails if the user pastes in a long string: we don't know how many characters to delete
// - Leveraging QPlainTextEdit::textCursor fixes the middle-typing issue but not the pasting issue
// UNDO: Use QPlainTextEdit::undo
// - Properly handles multi-character operations including pastes...
// - ...except for pressing and holding a key.
// - If you hold "A" until the max length is reached, all "A"s are deleted, not just the last.
@DavidJCobb
Copy link
Author

It's worth noting that I think this would double textChanged events. The QPlainTextEdit internals hook the same signals on QTextDocument, and signal/slot connections are handled in the order they're made, so by the time we have a chance to enforce the max length, I believe textChange will have already been fired.

My approach is to subclass QPlainTextEdit and use a variant on this function which emits a signal when input isn't rejected; essentially it's a duplicate of the textChange signal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment