Search code examples
pythonqtpyqtsubstringqcompleter

How to achieve autocomplete on a substring of QLineEdit in PyQt6?


I'm very new to QT so please assume I know nothing. I want an auto-complete where it only matches against the text after the last comma.

e.g. if my word bank is ["alpha", "beta", "vector space"], and the user currently has typed "epsilon,dog,space" then it should match against "vector space" since "space" is a substring of "vector space".

I'm using PyQt6 and my current code looks something like this (adapted from a YouTube tutorial):

line_of_text = QLineEdit("")
word_bank = [name for name,value in names_dict.items()]
completer = QCompleter(word_bank)
completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
completer.setFilterMode(Qt.MatchContains)
line_of_text.setCompleter(completer)

So currently, if word_bank is ["alpha", "beta", "vector space"], then if line_of_text had the string "epsilon,dog,space" then it wouldn't match against anything because "epsilon,dog,space" isn't a substring of "alpha" nor "beta" nor "vector space".

How can I alter my code to achieve what I would like to achieve? -- I'm experienced with programming, just not with Qt.

PS: I have tried doing

line_of_text.textChanged[str].connect(my_function) 

where my_function takes only the substring of line_of_text after the last comma and feeds it to completer.setCompletionPrefix and then calls completer.complete(). This simply does not work. I assume the reason is that completer.complete() updates the completion prefix from line_of_text, causing the call to completer.setCompletionPrefix to be overwritten immediately after.


Solution

  • One solution is to use the textChanged signal to set the completion prefix, and bypass the built-in behaviour of the line-edit to allow greater control of when and how the completions happen.

    Below is a basic implementation that shows how to do that. By default, it only shows completions when the text after the last comma has more than single character - but that can be easily adjusted:

    from PyQt6 import QtCore, QtGui, QtWidgets
    
    class Window(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.edit = QtWidgets.QLineEdit()
            layout = QtWidgets.QVBoxLayout(self)
            layout.addWidget(self.edit)
            word_bank =  ['alpha', 'beta', 'vector space']
            self.completer = QtWidgets.QCompleter(word_bank)
            self.completer.setCaseSensitivity(
                QtCore.Qt.CaseSensitivity.CaseInsensitive)
            self.completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
            self.completer.setWidget(self.edit)
            self.completer.activated.connect(self.handleCompletion)
            self.edit.textChanged.connect(self.handleTextChanged)
            self._completing = False
    
        def handleTextChanged(self, text):
            if not self._completing:
                found = False
                prefix = text.rpartition(',')[-1]
                if len(prefix) > 1:
                    self.completer.setCompletionPrefix(prefix)
                    if self.completer.currentRow() >= 0:
                        found = True
                if found:
                    self.completer.complete()
                else:
                    self.completer.popup().hide()
    
        def handleCompletion(self, text):
            if not self._completing:
                self._completing = True
                prefix = self.completer.completionPrefix()
                self.edit.setText(self.edit.text()[:-len(prefix)] + text)
                self._completing = False
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(['Test'])
        window = Window()
        window.setGeometry(600, 100, 300, 50)
        window.show()
        app.exec()