Search code examples
c++regexqtqcompleter

Auto completion based on regex


I have a QLineEdit with a QRegularExpressionValidator where the allowed input is:

^(?<sign>[<>=]|>=|<=)(?<value>\d+\.?\d*)(?<unit>[mc]{0,1}m[²2]\/s|St|cSt)$

So for example:

"<15m²/s"   // good
">=3.14cSt" // good
"27mm2/s"   // bad

I search a way to fill a QCompleter based on this regex.
So if the cursor is on the empty QLineEdit the completer proposes:
>, <, =, >= or <=.
After the sign, propose nothing and after the last number, propose:
mm²/s, cm²/s, m²/s, St or cSt
My need is to create a QStringList by reading the allowed sign and unit part of the regex and insert this QStringList in the QCompleter because it is based on a QAbstractItemModel.


Solution

  • I found a workaround by subclassing QLineEdit but it is not the best solution. If someone has better, I take.
    lineeditregexgroup.h:

    #ifndef LINEEDITREGEXGROUP_H
    #define LINEEDITREGEXGROUP_H
    
    #include <QLineEdit>
    #include <QMap>
    
    class QCompleter;
    class QStringListModel;
    class QRegularExpression;
    class QRegularExpressionValidator;
    
    class LineEditRegexGroup : public QLineEdit
    {
        Q_OBJECT
    
    public:
        LineEditRegexGroup(const QString pattern, QWidget *parent = Q_NULLPTR);
        ~LineEditRegexGroup();
        static QMap<QString, QStringList> mapCompleter;
    
    public slots:
        void checkValidity(const QString &text);
    
    protected:
        virtual void mouseReleaseEvent(QMouseEvent *e);
    
    private:
        QString                     curGroup;
        QCompleter                  *completer;
        QStringListModel            *listCompleter;
        QRegularExpression          *regex;
        QRegularExpressionValidator *validator;
    
    };
    
    #endif // LINEEDITREGEXGROUP_H
    

    lineeditregexgroup.cpp:

    #include "lineeditregexgroup.h"
    #include <QCompleter>
    #include <QStringListModel>
    #include <QRegularExpression>
    #include <QRegularExpressionValidator>
    #include <QAbstractItemView>
    
    QMap<QString, QStringList> LineEditRegexGroup::mapCompleter = {    
        { "sign", QStringList() << "<" << ">" << "=" << "<=" << ">=" },
        { "unit", QStringList() << "mm²/s" << "cm²/s" << "m²/s" << "St" << "cSt" }
    };
    
    LineEditRegexGroup::LineEditRegexGroup(const QString pattern, QWidget *parent)
        : QLineEdit(parent)
    {
        completer = new QCompleter(this);
        listCompleter = new QStringListModel(completer);
        regex = new QRegularExpression(pattern, QRegularExpression::NoPatternOption);
        validator = new QRegularExpressionValidator(*regex, this);
    
        completer->setModel(listCompleter);
        completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
        setCompleter(completer);
        setValidator(validator);
        curGroup = regex->namedCaptureGroups().at(1);
    
        connect(this, SIGNAL(textEdited(QString)), this, SLOT(checkValidity(QString)));
    }
    
    LineEditRegexGroup::~LineEditRegexGroup()
    {
        // dtor ...
    }
    
    void LineEditRegexGroup::checkValidity(const QString &text)
    {
        bool valid = true;
        int nbCapture = 0;
        QRegularExpressionMatch match = regex->match(text);
        for (int i=1; i<=regex->captureCount() && valid; i++){
            /* +1 to check only groups, not the global string */
            valid &= !match.captured(i).isEmpty();
            if (valid)
                nbCapture++;
            curGroup = regex->namedCaptureGroups().at(i);
            if (curGroup == "value")
                curGroup = regex->namedCaptureGroups().at(i-1);
        }
        if (nbCapture < regex->captureCount()){
            QStringList new_model = mapCompleter.value(curGroup);
            if (curGroup == regex->namedCaptureGroups().at(regex->captureCount())){
                new_model.replaceInStrings(QRegularExpression("^(.*)$"), text+"\\1");
            }
            listCompleter->setStringList(new_model);
            completer->complete();
        }
    }
    
    void LineEditRegexGroup::mouseReleaseEvent(QMouseEvent *e)
    {
        QLineEdit::mouseReleaseEvent(e);
        if (text().isEmpty())
            checkValidity("");
    }
    

    And call it like this:

    new LineEditRegexGroup(
        "^(?:(?<sign>[<>=]|>=|<=|)"
        "(?:(?<value>\\d+\\.?\\d*)"
        "(?:(?<unit>[mc]{0,1}m[²2]\\/s|St|cSt))?)?)?$",
        parent
    );
    

    Result:
    LineEditRegexGroup