Search code examples
pythonautocompletepyqt5pyside2

PySide2 AutoComplete: Customize the Result


I am using QCompleter to implement auto-completion on a QLineEdit widget:

from PySide2 import QtGui
from PySide2.QtCore import Qt
from PySide2.QtGui import QStandardItem
from PySide2.QtWidgets import QCompleter, QWidget, QLineEdit, QFormLayout, QApplication


class SuggestionPlaceModel(QtGui.QStandardItemModel):

    def __init__(self, parent=None):
        super(SuggestionPlaceModel, self).__init__(parent)

    def search(self, text):
        self.clear()
        data = [{'text': f"{text} {i}"} for i in range(10)]
        for i, row in enumerate(data):
            item = QStandardItem(row['text'])
            self.appendRow(item)


class Completer(QCompleter):

    def splitPath(self, path):
        self.model().search(path)
        return super(Completer, self).splitPath(path)


class Widget(QWidget):

    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self._model = SuggestionPlaceModel(self)
        completer = Completer(self)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        completer.setModel(self._model)
        lineedit = QLineEdit()
        lineedit.setCompleter(completer)
        lay = QFormLayout(self)
        lay.addRow("Location: ", lineedit)


if __name__ == '__main__':

    import sys

    app = QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

Here is a result:

enter image description here

QUESTION: How can I customize the SuggestionPlaceModel class so that the search result can include icons, horizontal separaters, different fonts, different font sizes, etc like this? enter image description here


Solution

  • A possible solution is to use a custom delegate where the icon is set, in the case of html you can use a QLabel that supports rich text.

    import random
    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    class Delegate(QtWidgets.QStyledItemDelegate):
        def initStyleOption(self, option, index):
            super(Delegate, self).initStyleOption(option, index)
            option.text = ""
    
        def paint(self, painter, option, index):
            if isinstance(option.widget, QtWidgets.QAbstractItemView):
                option.widget.openPersistentEditor(index)
            super(Delegate, self).paint(painter, option, index)
    
        def createEditor(self, parent, option, index):
            editor = QtWidgets.QLabel(index.data(), parent)
            editor.setContentsMargins(0, 0, 0, 0)
            editor.setText(index.data(QtCore.Qt.UserRole))
            return editor
    
    
    class SuggestionPlaceModel(QtGui.QStandardItemModel):
        def search(self, text):
            self.clear()
            for i in range(10):
                html = f"{text}-{i} <b>Stack</b> <i>Overflow</i>"
                plain = QtGui.QTextDocumentFragment.fromHtml(html).toPlainText()
                pixmap = QtGui.QPixmap(128, 128)
                pixmap.fill(QtGui.QColor(*random.sample(range(255), 4)))
                item = QtGui.QStandardItem(plain)
                item.setData(html, QtCore.Qt.UserRole)
                item.setIcon(QtGui.QIcon(pixmap))
                self.appendRow(item)
    
    
    class Completer(QtWidgets.QCompleter):
        def splitPath(self, path):
            self.model().search(path)
            return super(Completer, self).splitPath(path)
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Widget, self).__init__(parent)
            self._model = SuggestionPlaceModel(self)
            completer = Completer(self)
            completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
            completer.setModel(self._model)
            delegate = Delegate(completer.popup())
            completer.popup().setItemDelegate(delegate)
    
            lineedit = QtWidgets.QLineEdit()
            lineedit.setCompleter(completer)
            lay = QtWidgets.QFormLayout(self)
            lay.addRow("Location: ", lineedit)
    
    
    if __name__ == "__main__":
    
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Widget()
        w.show()
        sys.exit(app.exec_())