Checking input of QT inputs - c++

I have a fairly complex dialog which the inputs are numbers with different allowed ranges. I was wondering what is the cleanest pattern to guarantee that my QLineEdits have correct input values.
The obvious way of doing this seems to check input values when the user clicks the OK button. Problem I have is that some of the GUI controls depend on the value of other inputs. So the code seems to be getting a bit nasty by having me to branch the logic of the controls for all the cases where a input has a wrong value.
Is there a nice pattern for this type of situation?
I was thinking about subclassing QLineEdit and using the focusOutEvent to check for the input of the dialogs. If the input is incorrect I would default the value and trigger the logic. This would guarantee that each lineedit is responsible for it's own validation. Is there an obvious pitfall by doing this?
QValidators are awesome, problem is when their state is intermediate.

Use the signals provided by QLineEdit and build a small validation class of slots. It'll be easier than subclassing them directly, and allow you more fine grained control.

You may very well work with subclassing QLineEdit, since it is quite easy by just setting up a connection to the appropriate signals.
class foo : public QLineEdit
{
Q_OBJECT
// ... staff here
private:
void signal_control()
{
connect(this,SIGNAL(textChanged(const QString & )),this, SLOT(text_validate(const QString & )));
private slots:
void text_validate(const QString &)
{
// validate your text here
}
};
You may also build a different class that just listens to signals generated from your QLineEdit object and validates separately. Befriending it may be a good idea.

Related

Item views: setSelectionModel and support for row editing

In my Qt (6.3.1) application, for a model I developed, I noticed the submit() method being called all the time.
After some debugging, I noticed, in void QTableView::setSelectionModel/QTreeView::setSelectionModel, this:
if (d->selectionModel) {
// support row editing
connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
d->model, SLOT(submit()));
}
The documentation for QAbstractItemModel::submit() mentions "this function is typically used for row editing", which means this is done on purpose.
I have got more than 1 problem with this way of doing things, compared to the alternative of letting/requiring application developers to create the connection themselves:
Views do not seem to have a property to stop this connection from being created, hence the behavior is more than just a default, it is mandatory.
I do not see any way to know what to do except looking through Qt's source code. I had rather have to create the connection myself if I want it.
Only QSqlTableModel seems to have a mechanism to handle this (editStrategy()) but I could find nothing in neither QAbstractItemModel nor QAbstractTableModel.
-> what would be a good reason to want this connection above to be always created? Or am I perhaps wrong thinking this design is a bad one?
Answering my own question after 4 weeks without another answer nor any comment.
Despite having found a solution that seems to work in every case (see below), I still think this very design choice made by Qt as well as other special case they implemented are bad choices, would be interested to read other opinions in the comments.
Better than disconnecting signals, the solution I ended up implementing was to subclass QIdentityProxyModel and create an attribute to block the calls to submit (+ optionally revert).
void MyModel::revert() {
if (forwardRevertCalls)
QIdentityProxyModel::revert();
}
bool MyModel::submit() {
if (forwardSubmitCalls)
return QIdentityProxyModel::submit();
else
return false;
}
The reason for this choice is because of another special case in QStyledItemDelegate::eventFilter. Found in the documentation:
If the editor's type is QTextEdit or QPlainTextEdit then Enter and Return keys are not handled.
And I suppose things like QSpinBox do not behave this way.
This was causing submit to be called whenever I pressed Enter to validate an input in my model and change the selected row in 1 input; more precisely, it would execute case QAbstractItemDelegate::SubmitModelCache in QAbstractItemView::closeEditor.

How to display a blinking cursor in QLineEdit during read-only

title pretty much says it all. I have a read-only text box on a form where users can edit the contents of this text box through buttons on the form. The form is basically a keypad. As users click the buttons, a digit will be added to the value in the text box.
Technically, the final application will be running on a machine with no keyboard but a touchscreen. Users interact with the application using the touchscreen and they should not be installing keyboards on the machine but in the event they do, I am making the text box read-only.
Now, how can I have the text box's cursor still blink even though it is read only?
I am wondering if I need to do something similar to this user's solution:
Hide QLineEdit blinking cursor
I have also tried using the setFocus method and I am looking into style sheets. However, nothing has panned out.
Other answers have given you technical solutions to your question. However, I think that you are going in a wrong direction. You want a QLineEdit that is read only, but with a cursor and still accepts input from a virtual keyboard... yeah, so it is not really read only... It is not smelling good.
And in general, arbitrarily and actively disabling standard functions is not a good idea. Especially, if it means hacking your way around standard widget behaviors an semantics to do it.
Let's think from the start. What is the issue of accepting input from a keyboard?
From your question I would dare to guess that you want to make sure that the QLineEdit only accepts digits, and forbid the user to input other characters.
If I am right what you want is a QValidator, either a QIntvalidator or a QRegExpValidator. Then you can let the users use a keyboard, but they will only be able to input digits, like they would with your virtual keyboard.
Create a class whiwh inherits from QLineEdit and ignore the key events (events triggered when the user press a key). It will make your line edit read only but without the look-and-feel:
class LineEdit: public QLineEdit
{
Q_OBJECT
public:
LineEdit(QWidget* parent=nullptr): QLineEdit(parent)
{
}
virtual void keyPressEvent(QKeyEvent* event)
{
event->ignore();
}
public slots:
void add(QString const& textToAdd)
{
setText(text() + textToAdd);
}
};
An usage example (the timer simulates the virtual keyboard):
LineEdit* line = new LineEdit;
line->show();
QTimer timer;
timer.setInterval(2000);
QObject::connect(&timer, &QTimer::timeout, [=]() { line->add("a"); });
timer.start();
Romha Korev's answer will appear to work, but it won't catch everything. It can still be possible to paste or drag&drop text into the line edit, or as a result of a locale dependent input-method keyboard event. I don't know all the various ways text can end up being entered into the line edit that way. You'd be hunting for holes to plug.
So I propose to abuse a QValidator for this. Do not set your line edit to read-only mode. Create your own validator that blocks all input unless you specifically disable it:
class InputBlockerValidator final: public QValidator
{
Q_OBJECT
public:
void enable()
{ is_active_ = true; }
void disable()
{ is_active_ = false; }
QValidator::State validate(QString& /*input*/, int& /*pos*/) const override
{
if (is_active_) {
return QValidator::Invalid;
}
return QValidator::Acceptable;
}
private:
bool is_active_ = true;
};
Now set an instance of this as the validator of your line edit:
// ...
private:
QLineEdit lineedit_;
InputBlockerValidator validator_;
// ...
lineedit_.setValidator(&validator_);
Then, whenever you insert text into the line edit, disable and re-enable the validator:
validator_.disable();
lineedit_.insert(text_to_be_inserted);
validator_.enable();
Do not ever call setText() on the line edit. For some reason, this permanently prevents the validator from blocking input. I don't know if this is intended or a Qt bug. Only use insert(). To simulate setText(), use clear() followed by insert().

How to add hyperlinks in Qt without QLabel?

I have some labels and layouts nested inside a QWidget to build a part of a sidebar. Each QWidget is its own section and one component currently looks like this:
To my understanding, you can only set hyperlinks with QLabel, but I'm trying to get the whole area between the white lines clickable. This is including the icon and the whitespace. Is there any way to achieve this?
This got marked as a duplicate to the opposite of what I was asking, so I'd like to reiterate that I'm trying to implement a hyperlink without QLabel.
You can easily have a widget open a link on click:
class Link : public QWidget {
Q_OBJECT
public:
Link(QUrl url, QWidget p = nullptr) : QWidget(p), _url(url) {}
QUrl _url;
void mouseReleaseEvent(QMouseEvent *) { QDesktopServices::openUrl(_url); }
}
You can avoid any extra signals and connections, and have each link widget store its own link internally, the url can be set on construction and changed at any time. Not using signals and slots makes it easier to change the link too, without having to disconnect previous connections.
IMO going for a signals and slots solution is only justified when you want different arbitrary behavior. In this case you always want the same - to open a particular link, so you might as well hardcode that and go for an easier and more computationally efficient solution.
I would just manually catch the SIGNAL for clicked() and use desktop services to open the url in code.
bool QDesktopServices::openUrl ( const QUrl & url ) [static]
Opens the given url in the appropriate Web browser for the user's desktop environment, and returns true if successful; otherwise returns false.
http://doc.qt.io/qt-4.8/signalsandslots.html
Using this type of syntax, or in the designer, you can also connect a signal to a slot.
connect(widgetThatRepresentsURL, SIGNAL(clicked()),
handlerThatWillOpenTheURL, SLOT(clicked_on_url()));
For widgets that don't have a signal set up for clicked (or whatever event you are interested in), you can subclass the widget in question and reimplement...
void QWidget::mousePressEvent ( QMouseEvent * event ) [virtual protected]
Specifically for creating a signal, there is emit. I've used this in the past like the following
void Cell::focusInEvent(QFocusEvent *e)
{
emit focus(this, true);
QLineEdit::focusInEvent(e);
}
with the following in the header
signals:
void focus(Cell *, bool);

How to break the tab order chain of widgets in Qt?

In Qt you can define the tab order by using the Qt Designer or by using C++. The relationships between widgets are set relatively to each other, so there is no index or such thing. What I want right now is to "break" the circular chain of widgets so that I get a beginning and an end of the chain.
A circular tab order would be:
A - B
| |
D - C
I want (note missing link between A and D):
A - B
|
D - C
which is more like a line instead of a circle:
A - B - C - D
So the user "stops" at one end and has to go back using the other direction.
Update: I have another idea now. What if i reimplement:
bool QWidget::focusNextPrevChild(bool next)
According to the documentation one can use this to implement custom focus behavior.
In my dynamic scenario where buttons in the GUI are adjusted at run-time I will have to overload the function and set, for example, an internal flag allowFocusNext and allowFocusPrev which then ignores the focus request if necessary. I will report back here, when I have tried it. Meanwhile any comments are welcome!? :-)
I found a solution, but it is a bit hacky. The QWidget::setTabOrder will not allow to chain a widget with itself, so this approach won't help (even if you are using focus proxies)
However, you can define a "Focus Forwarder":
class FocusForwarder : public QWidget
{
public:
explicit FocusForwarder(QWidget *proxy) :
QWidget((QWidget *) proxy->parent()),
m_proxy(proxy)
{
setFocusPolicy(Qt::TabFocus);
}
protected:
void focusInEvent(QFocusEvent *) {
m_proxy->setFocus();
}
private:
QWidget *m_proxy;
};
And add them at the beginning and end of you chain:
FocusForwarder *w1 = new FocusForwarder(ui->bA);
FocusForwarder *w2 = new FocusForwarder(ui->bD);
QWidget::setTabOrder(w1, ui->bA);
QWidget::setTabOrder(ui->bA, ui->bB);
QWidget::setTabOrder(ui->bB, ui->bC);
QWidget::setTabOrder(ui->bC, ui->bD);
QWidget::setTabOrder(ui->bD, w2);
Details
For setTabOrder to work, the widgets must be in the same window. To ensure this, the Forwarder is placed in the proxy's parent (in the initializer list).
For this mechanism, the focus direction (Tab or Shit+Tab) does not matter. As soon as a FocusFowarder receives the focus, it will "forward" it to its proxy.
The direction is handled by Qt internally. You just add "sentinels" around your chain.
Use in QtDesigner
When you want to use it in QtDesigner, you'd create a Widget and promote it to the forwarder. As you cannot set the proxy directly, you could add a dynamic property for the proxy's name, like this:
class FocusForwarderDesigner : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString proxyName READ proxyName WRITE setProxyName)
public:
QString proxyName() {
return (m_proxy) ? m_proxy->objectName() : QString::null;
}
void setProxyName(QString name) {
m_proxy = parent()->findChild<QWidget *>(name);
}
explicit FocusForwarderDesigner(QWidget *parent = NULL) :
QWidget(parent) {}
protected:
void focusInEvent(QFocusEvent *) {
if (m_proxy) m_proxy->setFocus();
}
private:
QWidget *m_proxy;
}
In the designer, you would add a string-property with name proxyName and set it to the proxy's name. Don't forget to set the focus policy to Tab Focus in designer.
After some additional thoughts I post an answer to my own question because it is a working solution but it is not ideal. Therefore, I'm still searching for a better one! As a note, my application mainly relies on mouse wheel interactions for changing the focus of widgets.
In my question I mentioned that overriding:
bool focusNextPrevChild(bool next)
could lead to a working system. The "receiving" widget would simply ignore the focus by returning "true" if it is marked as "last item" or "first item" and the "next" parameter would lead to a circular behavior. Although this works for the tab and space+tab key combinations, there are cases where focusNextPrevChild is not called explicitly. In my case it is not called for focus changes related to mouse wheel events.
What I do instead is overriding:
void wheelEvent(QWheelEvent* event)
This gives me direct control over all the focus events related to the mouse wheel. My overridden function looks like this:
void SelectionIconButton::wheelEvent(QWheelEvent* event)
{
bool next = event->delta() > 0;
if (m_IsLastInFocusChain && next) {
event->accept();
return;
}
if (m_IsFirstInFocusChain && !next) {
event->accept();
return;
}
QPushButton::wheelEvent(event);
}
So this system's requirements are:
Each widget has to somehow implement two bools and handle their
state.
Each of those widgets has to be configured either at startup
or in dynamic screens during appliation use
Listening only to
wheelEvent does not allow me to handle tab key and space+tab key
combinations
You see that this solution works but it involves some effort to apply it to a large application. I was thinking about a more general solution. Maybe a global list that is updated when a screen is changing. This global list would then somehow decide if a focus change is allowed or not. Unfortunately, this again is problematic with mouse wheel events because some widgets are "active" and the wheel event does not even want to change focus but alter the value in an input field, for example, instead.
Edit:
I might have to add that the default implementation of QWidget::wheelEvent() and QPushButton::wheelEvent() and many more Qt-Widgets just ignore the event by setting event->ignore().
In my application all those ignored events are caught at a high level widget which then interprets the QWheelEvent and uses its delta to call focusPreNextChild() the right amount of time.

What signal should I capture to grab the text of Gtk::Entry before it is changed?

I am writing an application using gtkmm 3 (running Ubuntu 12.04 LTS) and working right now with the Gtk::Entry control.
I cannot find the correct signal to capture so that I can grab the Gtk::Entry buffer text before it is changed, and persist it to maintain a record of changes. I know that in some other tool-kits, there is a hook provided that facilitates such. (I believe using a "shadow buffer".)
What signal do I have to grab to do this? What is the slot's signature for this signal? Is this functionality supported at all?
Since you are changing the behaviour, it's better to inherit from Gtk::Entry:
class ValidatedEntry : public Gtk::Entry {
Glib::ustring last_valid;
virtual void on_changed()
{
Glib::ustring text = get_text();
if (... validation here ...)
set_text(last_valid); // WARNING: will call this on_changed() again
else
last_valid = text;
Gtk::Entry::on_changed(); // propagate down
}
};
BUT
This goes against usability, that's why it's not a built-in behaviour. Users won't like the text reverting back just because they miss-typed something; they might hit backspace before they realize the entry threw the wrong character away.
You should at least wait until the user presses the Enter key (i.e. signal_activate or override on_activate()), or do something less drastic, like showing a warning icon.
You could give a try to GObject's "notify" signal. It is used in conjunction with the property to spy. Connecting to "notify::text" will call your callback for each modification of the "text" property, but the first change may be the setter that will set the initial value, that you could then store. Worth a try.
Otherwise, you could try to store it on the first triggering of the "insert-text" or "delete-text" signals. Please give use some feedback if that seems to work.
I also agree with DanielKO: on an usability point of view, modifying user input is just annoying and bad practice. Better to tell her which field is wrong, put the focus there, and/or have a button to reset to defaults, but not enforce any change on user input.