Search code examples
qtqt5qcomboboxnon-ascii-characters

A QLineEdit/QComboBox search that ignores diacritics


I have an application where people can enter names of places in a form. This being Europe, we have to deal with names that includes diacritics like Orléans, Köln, Liège, Châteauroux. When people enter names I want them to be able to type characters without diacritics but still come up with a list of the names that include them so they can select the properly accented name. The program has a long but non-exhaustive list of names (people can always enter any name they like).

I already have a function that finds names based on a non-diacritic match. So 'orle' will return 'Orléans', 'kol' finds 'Köln', etc.

I tried two things:

1: A QLineEdit with a QCompleter that fills the list in the completer with matches using a QStringListModel. Unfortunately this does not work since the list will contain the accented version of the name, which does not match the value entered by the user, so QLineEdit does not show the name in the popup (if at all).

I also played with a QAbstractItemModel until I realized QCompleter does a string match on the data returned by the Model, so again 'orle' != 'orlé'.

2: An editable QComboBox which list gets filled dynamically depending on the text that has been entered so far. The following code is connected()ed 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 this removes the focus from the line-edit. Clicking the line-edit closes the popup, so I end up with a catch-22 (people should be able to continue typing characters, narrowing the search).

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

[1] Should be when I find some matches, but alas.


Solution

  • This should work:

    Go with QCompleter solution.
    Create class which inherits this QCompleter and re-implement 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 crate data model that contains all cities (words with diacritics) and under some custom role number return string which is diacritics free (sub-classing QStringListModel could be the easiest way, just re implement data to specially treat 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 connect this model with QCompleter set this special role value to completionRole and everything should work perfectly.

    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've tested it works perfectly. Note in practice I've pasted here complete code (just omitted some obvious things), so solution is quite simple.