Finding a QLineEdit / QComboBox that ignores diacritics

I have an application where people can enter place names in a form. This is Europe, we have to deal with names that include diacritics like Orleans, Cologne, Liege, Chateauroux. When people enter names, I want them to be able to enter non-accented characters, but still come up with a list of names that include them so that they can choose the correct accented name. The program has a long but not exhaustive list of names (people can always enter any name they like).

I already have a function that finds names based on non-diacritical match. Thus, "orle" will return "Orléans", "kol" will find "Köln", and so on.

I've tried two things:

1: A QLineEdit with a QCompleter that populates the padded list with a match using a QStringListModel. Unfortunately, this will not work, as the list will contain an accented version of the name that does not match the user-entered value, so QLineEdit does not display the name in the popup (if at all).

I also played with QAbstractItemModel until I realized that QCompleter does string match on the data returned by the model, so again "orle"! = "Orlé".

2: an editable QComboBox whose list is filled dynamically based on the text that has been entered so far. The following code is hooked up () from QComboBox :: editTextChanged (QString):

void TripFormCargoHelper::fromEdited (const QString &str)
{
  if (str.length () >= 3)
  {
    QStringList flist = m_database->findLocationStrings (str);
    flist.push_front (str); // add the text we're editing first
    bool b = box->blockSignals (true); // prevent recursive signals
    box->clear ();
    box->addItems (flist);
    box->blockSignals (b);
    box->showPopup ();
  }
  else
  {
    box->clear ();
    box->hidePopup ();
  }

      

}

This works, but only half ... I want the popup to appear when some characters have been entered [1], but that removes focus from editing the line. Clicking on edit line closes the popup, so I get catch-22 (people should be able to keep typing characters, narrowing down the search).

Any suggestions on how to make this work would be appreciated. I prefer a solution with QLineEdit. Qt version 5.4.

[1] It must be when I find a few matches, but alas.

+3


source to share


2 answers


This should work:

Go with the solution QCompleter

.
Create a class that inherits from this QCompleter

and reimplements QCompleter::splitPath

:

DiacriticFreeCompleter::DiacriticFreeCompleter(QObject *parent)
    : QCompleter(parent)
{
}

QStringList DiacriticFreeCompleter::splitPath(const QString &path) const
{
    return QStringList() << ClearedFromDiacritic(path);
}

QString DiacriticFreeCompleter::pathFromIndex(const QModelIndex &index) const
{
    // needed to use original value when value is selected
    return index.data().toString();
}

      

Now create a data model containing all cities (accented words) and under some custom return string that is diacritical free (subclassing QStringListModel

may be the easiest way, just repeat data

to specifically evaluate this role value):



DiactricFreeStringListModel::DiactricFreeStringListModel(QObject *parent)
    : QStringListModel(parent)
{
    setDiactricFreeRole(Qt::UserRole+10);
}

QVariant DiactricFreeStringListModel::data(const QModelIndex &index, int role) const
{
    if (role==diactricFreeRole()) {
        QString value = QStringListModel::data(index, Qt::DisplayRole).toString();
        return ClearedFromDiacritic(value);
    } else {
        return QStringListModel::data(index, role);
    }
}

void DiactricFreeStringListModel::setDiactricFreeRole(int role)
{
    mDiactricFreeRole = role;
}

int DiactricFreeStringListModel::diactricFreeRole() const
{
    return mDiactricFreeRole;
}

      

Now plug in this model using QCompleter

set this value to the special role completionRole and everything should work fine.

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    DiacriticFreeCompleter *completer = new DiacriticFreeCompleter(this);
    DiactricFreeStringListModel *model = new DiactricFreeStringListModel(this);
    completer->setModel(model);
    completer->setCompletionRole(model->diactricFreeRole());
    model->setStringList(QStringList()
                         << "Kraków"
                         << "Łba"
                         << "Żarów"
                         << "Źródło"
                         << "Łęg"
                         << "London"
                         << "München"
                         << "Orléans"
                         << "Köln"
                         << "Liège"
                         << "Châteauroux");
    ui->lineEdit->setCompleter(completer);
}

      

I tested it works fine. Note that in practice I have pasted the complete code here (just omitted some obvious things), so the solution is pretty straightforward.

+1


source


Thanks Marek, I was desperately looking for this, Python only (PyQT5), so I rewrote this and it works like a charm. Here is the code:



import unicodedata
from PyQt5.QtCore import QStringListModel
from PyQt5.QtWidgets import QCompleter
from PyQt5.QtCore import Qt

def strip_accents(s):
    return ''.join(c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn')

class DiacriticFreeCompleter(QCompleter):

    def splitPath(self, path):
        return [strip_accents(path).lower()]

    def pathFromIndex(self, index):
        return index.data()


class DiactricFreeStringListModel(QStringListModel):

    def __init__(self, *args, **kwargs):
        super(DiactricFreeStringListModel, self).__init__(*args, **kwargs)
        self.setDiactricFreeRole(Qt.UserRole+10)

    def data(self, index, role):
        if role == self.diactricFreeRole():
            value = super(DiactricFreeStringListModel, self).data(index, Qt.DisplayRole)
            return strip_accents(value).lower()
        else:
            return super(DiactricFreeStringListModel, self).data(index, role)

    def setDiactricFreeRole(self, role):
        self.mDiactricFreeRole = role

    def diactricFreeRole(self):
        return self.mDiactricFreeRole;

      

0


source







All Articles