thibsc
thibsc

Reputation: 4049

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.

Upvotes: 1

Views: 576

Answers (1)

thibsc
thibsc

Reputation: 4049

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

Upvotes: 1

Related Questions