title: How to format the list items of QCompleter's popup list properly?
body: <p>I want to investigate how to make a small user interface in which a user can type some letters and gets some suggestions based on a given data source (list here) which makes searches easier. For this purpose i am using Qt's <code>QCompleter</code> class.</p>
  
  <p>In the matching elements the typed letters shall be highlighted with HTML like the example in the code below: <code>Au&lt;b&gt;st&lt;/b&gt;ria</code>.
  Finally i merged some SO answers (see <a href="https://stackoverflow.com/q/1956542/1504082">How to make item view render rich (html) text in Qt</a>) and tutorials to a small standalone module:</p>
  
  <pre class="lang-python prettyprint-override"><code>from PySide import QtCore, QtGui
  
  class HTMLDelegate(QtGui.QStyledItemDelegate):
      """ From: https://stackoverflow.com/a/5443112/1504082 """
  
      def paint(self, painter, option, index):
          options = QtGui.QStyleOptionViewItemV4(option)
          self.initStyleOption(options, index)
          if options.widget is None:
              style = QtGui.QApplication.style()
          else:
              style = options.widget.style()
  
          doc = QtGui.QTextDocument()
          doc.setHtml(options.text)
          doc.setTextWidth(option.rect.width())
  
          options.text = ""
          style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter)
  
          ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
  
          # Highlighting text if item is selected
          # if options.state &amp; QtGui.QStyle.State_Selected:
          #     ctx.palette.setColor(QtGui.QPalette.Text,
          #                          options.palette.color(QtGui.QPalette.Active,
          #                                                QtGui.QPalette.HighlightedText))
  
          textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText,
                                          options)
          painter.save()
          painter.translate(textRect.topLeft())
          painter.setClipRect(textRect.translated(-textRect.topLeft()))
          doc.documentLayout().draw(painter, ctx)
          painter.restore()
  
      def sizeHint(self, option, index):
          options = QtGui.QStyleOptionViewItemV4(option)
          self.initStyleOption(options, index)
          doc = QtGui.QTextDocument()
          doc.setHtml(options.text)
          doc.setTextWidth(options.rect.width())
          return QtCore.QSize(doc.size().width(), doc.size().height())
  
  
  class CustomQCompleter(QtGui.QCompleter):
      """ Implement "contains" filter mode as the filter mode "contains" is not
      available in Qt &lt; 5.2
      From: https://stackoverflow.com/a/7767999/1504082 """
  
      def __init__(self, parent=None):
          super(CustomQCompleter, self).__init__(parent)
          self.local_completion_prefix = ""
          self.source_model = None
          self.delegate = HTMLDelegate()
  
      def setModel(self, model):
          self.source_model = model
          super(CustomQCompleter, self).setModel(self.source_model)
  
      def updateModel(self):
          local_completion_prefix = self.local_completion_prefix
  
          # see: http://doc.qt.io/qt-4.8/model-view-programming.html#proxy-models
          class InnerProxyModel(QtGui.QSortFilterProxyModel):
              def filterAcceptsRow(self, sourceRow, sourceParent):
                  # model index mapping by row, 1d model =&gt; column is always 0
                  index = self.sourceModel().index(sourceRow, 0, sourceParent)
                  source_data = self.sourceModel().data(index, QtCore.Qt.DisplayRole)
                  # performs case insensitive matching
                  # return True if item shall stay in th returned filtered data
                  # return False to reject an item
                  return local_completion_prefix.lower() in source_data.lower()
  
          proxy_model = InnerProxyModel()
          proxy_model.setSourceModel(self.source_model)
          super(CustomQCompleter, self).setModel(proxy_model)
          # @todo: Why to be set here again?
          self.popup().setItemDelegate(self.delegate)
  
      def splitPath(self, path):
          self.local_completion_prefix = path
          self.updateModel()
          return ""
  
  
  class AutoCompleteEdit(QtGui.QLineEdit):
      """ Basically from:
      http://doc.qt.io/qt-5/qtwidgets-tools-customcompleter-example.html
      """
  
      def __init__(self, list_data, separator=' ', addSpaceAfterCompleting=True):
          super(AutoCompleteEdit, self).__init__()
          # settings
          self._separator = separator
          self._addSpaceAfterCompleting = addSpaceAfterCompleting
          # completer
          self._completer = CustomQCompleter(self)
          self._completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
          self._completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
  
          self.model = QtGui.QStringListModel(list_data)
          self._completer.setModel(self.model)
  
          # connect the completer to the line edit
          self._completer.setWidget(self)
          # trigger insertion of the selected completion when its activated
          self.connect(self._completer,
                       QtCore.SIGNAL('activated(QString)'),
                       self._insertCompletion)
  
          self._ignored_keys = [QtCore.Qt.Key_Enter,
                                QtCore.Qt.Key_Return,
                                QtCore.Qt.Key_Escape,
                                QtCore.Qt.Key_Tab]
  
      def _insertCompletion(self, completion):
          """
          This is the event handler for the QCompleter.activated(QString) signal,
          it is called when the user selects an item in the completer popup.
          It will remove the already typed string with the one of the completion.
          """
          stripped_text = self.text()[:-len(self._completer.completionPrefix())]
  
          extra_text = completion  # [-extra:]
          if self._addSpaceAfterCompleting:
              extra_text += ' '
          self.setText(stripped_text + extra_text)
  
      def textUnderCursor(self):
          text = self.text()
          textUnderCursor = ''
          i = self.cursorPosition() - 1
          while i &gt;= 0 and text[i] != self._separator:
              textUnderCursor = text[i] + textUnderCursor
              i -= 1
          return textUnderCursor
  
      def keyPressEvent(self, event):
          if self._completer.popup().isVisible():
              if event.key() in self._ignored_keys:
                  event.ignore()
                  return
          super(AutoCompleteEdit, self).keyPressEvent(event)
          completionPrefix = self.textUnderCursor()
          if completionPrefix != self._completer.completionPrefix():
              self._updateCompleterPopupItems(completionPrefix)
          if len(event.text()) &gt; 0 and len(completionPrefix) &gt; 0:
              self._completer.complete()
          if len(completionPrefix) == 0:
              self._completer.popup().hide()
  
      def _updateCompleterPopupItems(self, completionPrefix):
          """
          Filters the completer's popup items to only show items
          with the given prefix.
          """
          self._completer.setCompletionPrefix(completionPrefix)
          # self._completer.popup().setCurrentIndex(
          #     self._completer.completionModel().index(0, 0))
  
  
  if __name__ == '__main__':
      def demo():
          import sys
          app = QtGui.QApplication(sys.argv)
          values = ['Germany',
                    'Au&lt;b&gt;st&lt;/b&gt;ria',
                    'Switzerland',
                    'Hungary',
                    'The United Kingdom of Great Britain and Northern Ireland']
          editor = AutoCompleteEdit(values)
          window = QtGui.QWidget()
          hbox = QtGui.QHBoxLayout()
          hbox.addWidget(editor)
          window.setLayout(hbox)
          window.show()
  
          sys.exit(app.exec_())
  
      demo()
  </code></pre>
  
  <p>My problem is the suggestion of user Timo in the answer <a href="https://stackoverflow.com/a/5443112/1504082">https://stackoverflow.com/a/5443112/1504082</a>: </p>
  
  <blockquote>
    <p>After line: 'doc.setHtml(options.text)', you need to set also doc.setTextWidth(option.rect.width()), otherwise the delegate wont render longer content correctly in respect to target drawing area. For example does not wrap words in QListView.</p>
  </blockquote>
  
  <p>So i did this to avoid cropping of long text in the completer's popup. But i get the following output:
  <a href="https://i.stack.imgur.com/MFnj8.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/MFnj8.png" alt="Where does this vertical margin come from?"></a></p>
  
  <p>Where does this additional vertical margin come from?</p>
  
  <p>I investigated this a bit and i see that the <code>sizeHint</code> method of <code>HTMLDelegate</code> is sometimes called with an <code>options</code> parameter which contains a rectangle with attributes <code>(0, 0, 0, 0)</code>. And the display behaviour finally changes after the call of <code>doc.setTextWidth(options.rect.width())</code>. But i couldnt finally find out who calls it with this parameter and how i could properly fix this.</p>
  
  <p>Can somebody explain where this comes from and how i can fix this porperly? </p>