Elentok's Blog

About me

Autocomplete textbox for multiple keywords/tags entry in PySide

Last month I started writing QTodoTxt, a PySide (Python Qt bindings) GUI for the todo.txt concept.

While using it for my own todo list I noticed several features that were missing (I intend to implement them all in due time), one of these features was auto-completion for projects and contexts when editing or creating a new task, something like this:

After some googling I found the built-in QCompleter component that can be easily attached to any QLineEdit control and allow easy auto-completion:

lineEdit = QtGui.QLineEdit()
completer = QtGui.QCompleter(['one', 'two', 'three', 'four'])
lineEdit.setCompleter(lineEdit)

However, this only allows auto-completing for the first word and I wanted to auto-completion for every word in the text. So I went back to google and found a post in the Qt developers forum that shows a simple implementation of this in C++.

Instead of using the QLineEdit's setCompleter method (which wasn't available for QTextEdit he was using), his implementation handles opening the completer manually and just attaches the completer to the QLineEdit using the QCompleter.setWidget method.

To create my own auto-complete control I implemented his C++ control in python and added some extra features of my own. At first I created the AutoCompleteEdit class that inherits from QLineEdit and initializes the QCompleter:

from PySide import QtCore, QtGui

class AutoCompleteEdit(QtGui.QLineEdit):
  def __init__(self, model, separator = ' ', \
      addSpaceAfterCompleting = True):
    super(AutoCompleteEdit, self).__init__()
    self._separator = separator
    self._addSpaceAfterCompleting = \
        addSpaceAfterCompleting
    self._completer = QtGui.QCompleter(model)
    self._completer.setWidget(self)
    self.connect(
        self._completer,
        QtCore.SIGNAL('activated(QString)'),
        self._insertCompletion)
    self._keysToIgnore = [QtCore.Qt.Key_Enter,
                          QtCore.Qt.Key_Return,
                          QtCore.Qt.Key_Escape,
                          QtCore.Qt.Key_Tab]</pre>

I overrode the keyPressEvent method of QLineEdit to handle the completion manually:

def keyPressEvent(self, event):
    if self._completer.popup().isVisible():
      if event.key() in self._keysToIgnore:
        event.ignore()
        return
    super(AutoCompleteEdit, self).keyPressEvent(event)
    completionPrefix = self.textUnderCursor()
    if completionPrefix != self._completer.completionPrefix():
      self._updateCompleterPopupItems(completionPrefix)
    if len(event.text()) > 0 and len(completionPrefix) > 0:
      self._completer.complete()
    if len(completionPrefix) == 0:
      self._completer.popup().hide()

This method performs the following tasks:

  • If the user pressed Enter, Escape or Tab the method ignores them and returns.
  • Every other character is forwarded to the base method.
  • Filters the items displayed in the completer popup to only show the items that start with the text the user started to write (the completion prefix).

Thank you for reading,

You can see the full code in my Bitbucket repository

Please feel free to leave comments,

David.

Next:Tip: Removing a changset from a Mercurial repository