diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/querycompleter.cpp | 67 | ||||
| -rw-r--r-- | src/querycompleter.h | 5 |
2 files changed, 66 insertions, 6 deletions
diff --git a/src/querycompleter.cpp b/src/querycompleter.cpp index 1a6f6de..b4968f5 100644 --- a/src/querycompleter.cpp +++ b/src/querycompleter.cpp @@ -23,6 +23,7 @@ #include <QCompleter> #include <QCoreApplication> #include <QFontMetrics> +#include <QKeyEvent> #include <QLabel> #include <QLineEdit> #include <QListView> @@ -350,9 +351,17 @@ QueryCompleter::QueryCompleter(QLineEdit *edit, const Config &config, // which silently drops the description column. m_popup->setItemDelegate(new CompletionDelegate(m_popup)); - m_edit->setCompleter(m_completer); - - connect(m_edit, &QLineEdit::textEdited, this, &QueryCompleter::updateContext); + // setWidget, NOT QLineEdit::setCompleter. setCompleter hands completion to + // the line edit, which then overwrites completionPrefix with the widget's + // ENTIRE text on every keystroke. The prefix must be the stem instead, so + // the whole-line prefix matches nothing and the popup stops appearing after + // the first token. setWidget still gives the completer the anchor it needs: + // complete() dereferences widget() unconditionally and crashes without one. + m_completer->setWidget(m_edit); + + // With the line edit no longer driving completion, every edit has to open + // the popup explicitly. + connect(m_edit, &QLineEdit::textEdited, this, &QueryCompleter::triggerCompletion); connect(m_edit, &QLineEdit::cursorPositionChanged, this, [this]() { updateContext(); }); @@ -361,8 +370,9 @@ QueryCompleter::QueryCompleter(QLineEdit *edit, const Config &config, acceptCompletion(index.data(Qt::DisplayRole).toString()); }); - if (m_config.completionOnFocus()) - m_edit->installEventFilter(this); + // Always filtered, not only for completion_on_focus: the popup is ours to + // drive now, so Tab, Enter, Escape and the arrows must be routed by hand. + m_edit->installEventFilter(this); } void QueryCompleter::triggerCompletion() @@ -386,12 +396,57 @@ bool QueryCompleter::eventFilter(QObject *watched, QEvent *event) // Only the empty-bar case: once there is text, ordinary typing has // already driven completion. if (watched == m_edit && event->type() == QEvent::FocusIn - && m_edit->text().isEmpty()) { + && m_config.completionOnFocus() && m_edit->text().isEmpty()) { triggerCompletion(); + return QObject::eventFilter(watched, event); + } + + if (event->type() != QEvent::KeyPress || !popupVisible()) + return QObject::eventFilter(watched, event); + + auto *keyEvent = static_cast<QKeyEvent *>(event); + switch (keyEvent->key()) { + case Qt::Key_Tab: + case Qt::Key_Enter: + case Qt::Key_Return: { + // Accept whatever the popup highlights. A freshly opened popup has no + // current row, so fall back to the first entry: the user sees it at the + // top of the list and expects Tab to take it. + QModelIndex index = m_popup->currentIndex(); + if (!index.isValid()) + index = m_popup->model()->index(0, 0); + if (!index.isValid()) + return QObject::eventFilter(watched, event); + + acceptCompletion(index.data(Qt::DisplayRole).toString()); + m_popup->hide(); + // Consume it. Tab would otherwise move focus to the next widget, and + // Return would run the half-typed query. + return true; } + case Qt::Key_Escape: + m_popup->hide(); + return true; + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + // Navigation belongs to the popup, which is not the focus widget while + // the user is typing in the bar. + QCoreApplication::sendEvent(m_popup, event); + return true; + default: + break; + } + return QObject::eventFilter(watched, event); } +bool QueryCompleter::popupVisible() const +{ + return m_popup && m_popup->isVisible(); +} + void QueryCompleter::acceptCompletion(const QString &value) { if (!m_edit) diff --git a/src/querycompleter.h b/src/querycompleter.h index 61b2555..350b21c 100644 --- a/src/querycompleter.h +++ b/src/querycompleter.h @@ -119,6 +119,11 @@ protected: bool eventFilter(QObject *watched, QEvent *event) override; private: + /// Whether the completion popup is on screen. Every key the filter claims + /// is claimed only while it is, so the bar types and tabs normally + /// otherwise. + bool popupVisible() const; + QList<CompletionEntry> entriesFor(const CompletionContext &context) const; void rebuildModel(const CompletionContext &context); |
