Overriding QCompleter popup position

882 views Asked by At

There have been similar questions asked about overriding the QCompleter popup position but i'll still not found a working solution. I simply want to move the popup down around 5px (I have some specific styling requirements)

I've tried subclassing a QListView and using that as my popup using setPopup(). I then override the showEvent and move the popup down in Y. I also do this on the resizeEvent since I believe this is triggered when items are filtered and the popup resizes. However this doesn't work.. I then used a singleshot timer to trigger the move after 1ms. This does kind of work but it seems quite inconsistent - the first time it shows is different to subsequent times or resizing.

Below is my latest attempt (trying to hack it by counting the number of popups..), hopefully someone can show me what i'm doing wrong or a better solution

import sys
import os
from PySide2 import QtCore, QtWidgets, QtGui

class QPopup(QtWidgets.QListView):
    def __init__(self, parent=None):
        super(QPopup, self).__init__(parent)

        self.popups = 0

    def offset(self):
        y = 3 if self.popups < 2 else 7
        print('y: {}'.format(y))
        self.move(self.pos().x(), self.pos().y() + y)

        self.popups += 1

    def showEvent(self, event):
        print('show')
        # self.offset()
        QtCore.QTimer.singleShot(1, self.offset)

    def resizeEvent(self, event):
        print('resize')
        # self.offset()
        QtCore.QTimer.singleShot(1, self.offset)

class MyDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(MyDialog, self).__init__(parent)

        self.create_widgets()
        self.create_layout()
        self.create_connections()

    def create_widgets(self):
        self.le = QtWidgets.QLineEdit('')

        self.completer = QtWidgets.QCompleter(self)
        self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
        self.completer.setMaxVisibleItems(10)
        self.completer.setFilterMode(QtCore.Qt.MatchContains)
        self.completer.setPopup(QPopup())

        popup = QPopup(self)
        self.completer.setPopup(popup)

        self.model = QtCore.QStringListModel()
        self.completer.setModel(self.model)

        self.le.setCompleter(self.completer)

        self.completer.model().setStringList(['one','two','three'])

    def create_layout(self):
        main_layout = QtWidgets.QVBoxLayout(self)

        main_layout.addWidget(self.le)

    def create_connections(self):
        pass

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    my_dialog = MyDialog()

    my_dialog.show()  # Show the UI
    sys.exit(app.exec_())
1

There are 1 answers

0
Stephan Schlecht On BEST ANSWER

One solution could be to make a subclass of QLineEdit and override keyPressEvent to display the popup with an offset:

PySide2.QtWidgets.QCompleter.complete([rect=QRect()])

For PopupCompletion and QCompletion::UnfilteredPopupCompletion modes, calling this function displays the popup displaying the current completions. By default, if rect is not specified, the popup is displayed on the bottom of the widget() . If rect is specified the popup is displayed on the left edge of the rectangle.

see doc.qt.io -> QCompleter.complete.

Complete, self-contained example

The rect is calculated based on the y-position of the cursor rect. The height of the popup window is not changed. The width is adjusted to the width of the ZLineEdit widget.

rect = QtCore.QRect(0,
                    self.cursorRect().y() + 4,
                    self.width(),
                    self.completer().widget().height())

Your code, slightly modified using the points mentioned above, could look like this:

import sys
from PySide2 import QtCore, QtWidgets
from PySide2.QtWidgets import QLineEdit, QDialog, QCompleter


class ZLineEdit(QLineEdit):
    def __init__(self, string, parent=None):
        super().__init__(string, parent)

    def keyPressEvent(self, event):
        super().keyPressEvent(event)
        if len(self.text()) > 0:
            rect = QtCore.QRect(0,
                                self.cursorRect().y() + 4,
                                self.width(),
                                self.completer().widget().height())
            self.completer().complete(rect)


class MyDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.le = ZLineEdit('')
        autoList = ['one', 'two', 'three']
        self.completer = QCompleter(autoList, self)

        self.setup_widgets()
        self.create_layout()
        self.create_connections()

    def setup_widgets(self):
        self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
        self.completer.setMaxVisibleItems(10)
        self.completer.setFilterMode(QtCore.Qt.MatchContains)
        self.le.setCompleter(self.completer)

    def create_layout(self):
        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.addWidget(self.le)

    def create_connections(self):
        pass


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    my_dialog = MyDialog()
    my_dialog.show()
    sys.exit(app.exec_())

Test

On the left side you see the default behavior. On the right side the popup is moved down 4px:

offset