Is there an easier way to implement an edit box for floating point values in Qt?

75 views Asked by At

Below is my current implementation of a numeric edit box with the following properties.

  1. Only allows the user to edit the value as governed by a QDoubleValidator with notation set to QDoubleValidator::StandardNotation.

  2. Always contains a value between min and max if the edit box does not have focus, or is in a special state, referred to as "indeterminate state" in the code below, which essentially means it is empty but not disabled.

  3. If the user is editing the number and either presses return or gives focus to another widget, and the value the edit box contains is not valid, it clamps to the range [min,max] if it contains an out-of-bounds value or it is set to default_value if there is not a number, even out of bounds, in the edit box (in practice this can only occur if the user deletes the contents of the edit box).

I am wondering if I am missing some easier way of achieving the functionality I want that perhaps does not require a custom class. In particular it seems strange to me that the signals QLineEdit::returnPressed() and QLineEdit::editingFinished() do not seem to fire if the number fails validation by QDoubleValidator: this means there is no way to call something like my make_acceptable_value() function in a slot rather than by inheriting from QLineEdit and overriding functions.

Basically I am only able to use the validator as a way of filtering the text the user can type in the box while editing but all other types of validation, correction, and formatting have to be handled manually.

Is there some better way to do this that I am missing?

There are problems with this code -- such as it doesn't "prettify" the value in make_acceptable_value and it always fires the value_changed() signal on focus loss, even if the value did not change -- that I can fix but it is only going to get more verbose.

class number_edit : public QLineEdit {
    
    Q_OBJECT
    
    void  make_acceptable_value() {
        if (!hasAcceptableInput()) {
            auto validr = static_cast<const QDoubleValidator*>(validator());
            auto val = value();
            if (!val) {
                set_value(default_val_);
            } else if (*val < validr->bottom()) {
                set_value(validr->bottom());
            } else {
                set_value(validr->top());
            }
        }
    }

    void handle_done_editing() {
        make_acceptable_value();
        emit value_changed( *value() );
    }

    void keyPressEvent(QKeyEvent* e) override {
        QLineEdit::keyPressEvent(e);
        if (e->key() == Qt::Key_Return) {
            handle_done_editing();
        } 
    }

    void focusOutEvent(QFocusEvent* event) override {
        handle_done_editing();
    }

    double default_val_;

public:

    number_edit(double val, double default_val, double min, double max, int decimals) :
            default_val_(default_val) {
        auto validator = new QDoubleValidator(min, max, decimals, this);
        validator->setNotation(QDoubleValidator::StandardNotation);
        setValidator(validator);
        set_value(val);
        make_acceptable_value();
    }

    bool is_indeterminate() const {
        return text() == "";
    }

    void set_indeterminate() {
        setText("");
    }

    void set_value(double v) {
        setText(QString::number(v));
        make_acceptable_value();
    }

    std::optional<double> value() const {
        if (is_indeterminate()) {
            return {};
        }
        return text().toDouble();
    }
}
1

There are 1 answers

0
jwezorek On

If you don't want to inherit from QLineEdit as in the question, the best option is to use QDoubleSpinBox instead of a QLineEdit plus QDoubleValidator.