I have a QLineEdit
with an input mask, so that some kind of code can easily be entered (or pasted). Since you can place the cursor anywhere in the QLineEdit
even if there is no text (because there is a placeholder from the input mask):
If people are careless and unattentive enough this leads to them typing in the middle of the text box whereas they should start typing at the beginning. I tried the trivial way of ensuring that the cursor is at the start upon focus by installing an event filter:
bool MyWindowPrivate::eventFilter(QObject * object, QEvent * event)
{
if (object == ui.tbFoo && event->type() == QEvent::FocusIn) {
ui.tbFoo->setCursorPosition(0);
}
return false;
}
This works fine with keybaord focus, i.e. when pressing ⇆ or ⇧+⇆, but when clicking with the mouse the cursor always ends up where I clicked. My guess would be that QLineEdit
sets the cursor position upon click itself after it got focus, thereby undoing my position change.
Digging a little deeper, the following events are raised when clicking¹ and thus changing focus, in that order:
FocusIn
MouseButtonPress
MouseButtonRelease
I can't exactly catch mouse clicks in the event filter, so is there a good method of setting the cursor position to start only when the control is being focused (whether by mouse or keyboard)?
¹ Side note: I hate that Qt has no documentation whatsoever about signal/event orders for common scenarios such as this.
Below is an implementation that's factored into a separate class. It defers the setting of the cursor to after any pending events are posted for the object, thus sidestepping the issue of event order.
#include <QApplication>
#include <QLineEdit>
#include <QFormLayout>
#include <QMetaObject>
// Note: A helpful implementation of
// QDebug operator<<(QDebug str, const QEvent * ev)
// is given in http://stackoverflow.com/q/22535469/1329652
/// Returns a cursor to zero position on a QLineEdit on focus-in.
class ReturnOnFocus : public QObject {
Q_OBJECT
/// Catches FocusIn events on the target line edit, and appends a call
/// to resetCursor at the end of the event queue.
bool eventFilter(QObject * obj, QEvent * ev) {
QLineEdit * w = qobject_cast<QLineEdit*>(obj);
// w is nullptr if the object isn't a QLineEdit
if (w && ev->type() == QEvent::FocusIn) {
QMetaObject::invokeMethod(this, "resetCursor",
Qt::QueuedConnection, Q_ARG(QWidget*, w));
}
// A base QObject is free to be an event filter itself
return QObject::eventFilter(obj, ev);
}
// Q_INVOKABLE is invokable, but is not a slot
/// Resets the cursor position of a given widget.
/// The widget must be a line edit.
Q_INVOKABLE void resetCursor(QWidget * w) {
static_cast<QLineEdit*>(w)->setCursorPosition(0);
}
public:
ReturnOnFocus(QObject * parent = 0) : QObject(parent) {}
/// Installs the reset functionality on a given line edit
void installOn(QLineEdit * ed) { ed->installEventFilter(this); }
};
class Ui : public QWidget {
QFormLayout m_layout;
QLineEdit m_maskedLine, m_line;
ReturnOnFocus m_return;
public:
Ui() : m_layout(this) {
m_layout.addRow(&m_maskedLine);
m_layout.addRow(&m_line);
m_maskedLine.setInputMask("NNNN-NNNN-NNNN-NNNN");
m_return.installOn(&m_maskedLine);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Ui ui;
ui.show();
return a.exec();
}
#include "main.moc"