[Completer] Highlight popup rows without rewriting the line edit (#7502)

* [Completer] Highlight popup rows without rewriting the line edit

Selecting a popup row with ClearAndSelect emits selectionChanged, which
QCompleter turns into highlighted() and QLineEdit turns into setText().
Since be2c16eca5 (#7466) that rewrites the typed text on every completion
reset / arrow-key press (e.g. typing "A" becomes "Axe" and cannot be
edited), regressing the 514014dc00 (#7447) fix that used NoUpdate.

Add selectWithoutRewrite(), which blocks the selection-model signals while
applying ClearAndSelect so the highlight stays visible without touching
the line edit, then syncs the preview manually. Route both the
above-popup auto-select and Up/Down navigation through it, and handle
Up/Down for below-popups too instead of falling through to Qt's
rewriting default. Enter/click acceptance via activated() is unchanged.

* [Completer] Repaint popup highlight after silent selection change

Blocking the selection model's signals also suppresses the view's own
selectionChanged/currentChanged repaints, so force a viewport update
after restoring signals. Use EnsureVisible to match Qt's normal scroll
behaviour.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-10-16 19:01:38 +02:00 • committed by GitHub
parent 456d9d9e70
commit bbe438b09a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 14 deletions

View file

@ -133,29 +133,33 @@ bool CardCompleterStyler::handlePopupKeyPress(QKeyEvent *event)
return false;
}
if (!above) {
return false;
}
auto *popup = completer->popup();
const int rowCount = completer->completionModel()->rowCount();
if (rowCount == 0) {
return true;
}
// Handle navigation ourselves so arrow keys move the highlight without
// rewriting the line edit. QCompleter's default handling selects with a
// selection change that QLineEdit turns into setText(), which fights the
// user while typing (e.g. typing "A" becomes "Axe" and cannot be edited).
// With the popup above the text edit the rows are shown in reverse order and
// the closest match is in the bottom-most row. The up arrow then advances
// through the list (towards its end at the top of the popup).
const int currentRow = popup->currentIndex().row();
const int step = (key == Qt::Key_Up) ? -1 : 1;
const int newRow = qBound(0, currentRow + step, rowCount - 1);
if (newRow != currentRow) {
const QModelIndex newIndex = completer->completionModel()->index(newRow, completer->completionColumn());
popup->selectionModel()->setCurrentIndex(newIndex,
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
const QModelIndex current = popup->currentIndex();
int newRow;
if (!current.isValid()) {
newRow = (key == Qt::Key_Down) ? 0 : rowCount - 1;
} else {
const int step = (key == Qt::Key_Up) ? -1 : 1;
newRow = qBound(0, current.row() + step, rowCount - 1);
if (newRow == current.row()) {
return true;
}
}
selectWithoutRewrite(completer->completionModel()->index(newRow, completer->completionColumn()));
return true;
}
@ -218,8 +222,37 @@ void CardCompleterStyler::ensureClosestSelected()
const int closestRow = rowCount - 1;
if (currentRow != closestRow) {
popup->selectionModel()->setCurrentIndex(completionModel->index(closestRow, completer->completionColumn()),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
selectWithoutRewrite(completionModel->index(closestRow, completer->completionColumn()));
}
}
// ---------------------------------------------------------------------------
void CardCompleterStyler::selectWithoutRewrite(const QModelIndex &index)
{
auto *popup = completer->popup();
auto *selectionModel = popup->selectionModel();
// Changing the popup selection emits selectionChanged, which QCompleter
// turns into highlighted() and QLineEdit turns into setText(). That
// rewrites what the user typed (e.g. "A" becomes "Axe") and fights further
// typing. Block the signals so the highlight and preview move without
// touching the line edit; Enter/click still activates via activated().
const bool wasBlocked = selectionModel->blockSignals(true);
if (!index.isValid()) {
selectionModel->clear();
} else {
selectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
selectionModel->blockSignals(wasBlocked);
popup->viewport()->update();
// currentRowChanged was suppressed above, so sync the preview manually.
updatePreview(index);
if (index.isValid()) {
popup->scrollTo(index, QAbstractItemView::EnsureVisible);
} else {
popup->scrollToTop();
}
}

View file

@ -55,6 +55,7 @@ private:
void applyPopupWidth();
void updateOrientation();
void ensureClosestSelected();
void selectWithoutRewrite(const QModelIndex &index);
bool handlePopupKeyPress(QKeyEvent *event);
bool isPopupAboveWidget() const;