QCompleter with a model where first item is disabled breaks keyboard navigation
-
Hi folks,
I'm trying to create categorized items in a QCompleter list, where some items are headings that shouldn't be selectable by the user.
When setting any item in the model that's hooked up to the QCompleter as disabled / unselectable other than the first one, this works fine. But when setting the first item as disabled / unselectable, keyboard navigation is broken. I can no longer up/down with the cursor keys to select an item.
I tried hooking up event filters to look at the key events of the QLineEdit, the QCompleter and the QListView to see where the down key is processed, but when the first item is disabled, none of these see a down key event.
-
In the createEditor method I do the following:
auto* lineEdit = new QLineEdit(parent); auto* completerListView = new QListView(parent); auto* completer = new QCompleter(parent); completer->setPopup(completerListView); auto* completerModel = new MyCompleterModel(); //Inherits QAbstractItemModel completer->setModel(completerModel); completer->setCompletionMode(QCompleter::CompletionMode::UnfilteredPopupCompletion); completer->setModelSorting(QCompleter::ModelSorting::UnsortedModel); lineEdit->setCompleter(completer); return lineEdit;
MyCompleterModel currently just has a list of hardcoded values that it returns, but the salient value in this case is in the flags method:
Qt::ItemFlags MyCompleterModel::flags(const QModelIndex &index) const { auto f = QAbstractItemModel::flags(index); if (index.row() == 0) f &= ~Qt::ItemIsSelectable; return f; }
-
Someone on Reddit reminded me of the selection model and the following solves my problem:
connect(completerListView->selectionModel(), &QItemSelectionModel::currentChanged, [completerModel, completerListView](const QModelIndex& current, const QModelIndex& previous) { int rowCount = completerModel->rowCount(); if (rowCount == 0 || current.row() == -1) // Nothing to do. return; int direction = current.row() == (previous.row() + 1) % rowCount ? 1 : -1; QModelIndex newIndex = current; while ((completerModel->flags(newIndex) & Qt::ItemIsSelectable) == 0) { if (newIndex.row() == 0 && direction == -1) { newIndex = completerModel->index(-1, -1); break; } newIndex = newIndex.sibling((newIndex.row() + direction) % rowCount, newIndex.column()); if (newIndex == current) // We've looped around and nothing could be selected. There's nothing we can do. break; } if (newIndex != current) { QTimer::singleShot(0, [completerListView, newIndex]() { completerListView->setCurrentIndex(newIndex); }); } }); ;
Edit: previous version I posted didn't handle upward movement. Also, QAbstractItemModel::flags returns NoItemFlags by default which was causing problems.
Just to be clear, I do still believe that QCompleter is broken when it comes to disabled / unselectable items.