I'm encountering an issue while attempting to create a custom searchable QComboBox. Specifically, I find that I need to click on a popup item twice before it activates the selection. This behavior seems to stem from my usage of QCompleter and QFilterProxyModel. While the default completer works as expected, I encounter this issue when applying custom filtering and sorting. Additionally, I've noticed that the hover highlighting on the completer popup is missing. Although I can set the stylesheet to address this, I suspect there might be an underlying issue causing its disappearance.

You can observe the issues in this video demonstration: https://streamable.com/j2vrg6.

I'd appreciate any insights or suggestions on resolving these issues.

from PySide6.QtCore import Qt, QSortFilterProxyModel
from PySide6.QtWidgets import QLineEdit, QComboBox, QApplication, QLineEdit, QWidget, QCompleter

class SubstringFilterProxyModel(QSortFilterProxyModel):
    def filterAcceptsRow(self, source_row, source_parent):
        model = self.sourceModel()
        if model is None:
            return False

        text_filter = self.filterRegularExpression().pattern().casefold()
        if text_filter == '':
            return True

        model_index = model.index(source_row, 0, source_parent)
        if not model_index.isValid():
            return False
        
        model_data = model.data(model_index, Qt.DisplayRole)

        for substring in text_filter.split(' '):
            if (substring not in model_data.casefold()):
                return False
        return True

    def lessThan(self, left, right):
        # get the data from the source model
        left_data = self.sourceModel().data(left)
        right_data = self.sourceModel().data(right)

        if left_data is None:
            return False
        if right_data is None:
            return True

        # find the index of the search string in each data
        text_filter = self.filterRegularExpression().pattern().casefold()

        left_index = left_data.find(text_filter)
        right_index = right_data.find(text_filter)

        # compare the indexes
        return left_index < right_index

class CustomQCompleter(QCompleter):
    def splitPath(self, path):
        self.model().invalidate() # Invalidates the current sorting and filtering.
        self.model().sort(0, Qt.AscendingOrder)
        return ''

class SelectAllLineEdit(QLineEdit):
    def __init__(self, parent=None):
        super(SelectAllLineEdit, self).__init__(parent)
        self.ready_to_edit = True

    def mousePressEvent(self, e):
        super(SelectAllLineEdit, self).mousePressEvent(e) # deselect on 2nd click
        if self.ready_to_edit:
            self.selectAll()
            self.ready_to_edit = False

    def focusOutEvent(self, e):
        super(SelectAllLineEdit, self).focusOutEvent(e) # remove cursor on focusOut
        self.deselect()
        self.ready_to_edit = True

class SearchableComboBox(QComboBox):
    def __init__(self, parent: QWidget | None = None ) -> None:
        super().__init__(parent)
        self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)

        self.setEditable(True)
        self.setLineEdit(SelectAllLineEdit())
        self.lineEdit().setEchoMode(QLineEdit.EchoMode.Normal)

        proxy_model = SubstringFilterProxyModel()
        proxy_model.setSourceModel(self.model())

        self.lineEdit().textChanged.connect(proxy_model.setFilterRegularExpression)
        self.lineEdit().editingFinished.connect(self.editing_finished)

        completer = CustomQCompleter()
        completer.setModel(proxy_model)
        completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
        # completer.popup().setStyleSheet("QListView::item:hover {background-color: rgb(55,134,209);}")

        completer.popup().clicked.connect(lambda: print("clicked"))
        # Set the completer for the combo box
        self.setCompleter(completer)

        # this works, but no custom filtering
        # self.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
        # self.completer().popup().setStyleSheet("QListView::item:hover {background-color: rgb(55,134,209);}")
        # self.completer().setModel(proxy_model) # this makes default completer act as described in the post
    
    def editing_finished(self):
        text = self.completer().currentCompletion()
        index = self.findText(text)
        self.setCurrentIndex(index)
        self.clearFocus()
        self.activated.emit(index)


if __name__ == "__main__":
    import sys
    from PySide6.QtWidgets import QApplication

    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    comboBox = SearchableComboBox()
    comboBox.addItems(["Apple", "Still filter apple", "Archer", "alchemy", "Orange apple", "banana", "pineapple", "pine apple", "pine apple but with more", "pine and apple"])
    comboBox.show()
    sys.exit(app.exec())
0

There are 0 best solutions below