Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to workaround change in Qt 5 handling of QListView.mousePressEvent and QStyledItemDelegate.editorEvent?



  • Between Qt 5.12.2 and 5.12.7 (I'm unsure precisely which release), Qt 5 behavior changed in the following scenario:

    • A class inheriting QListView is created and selection mode is set to QAbstractItemView.ExtendedSelection. Let's call this subclass ThumbnailView.
    • mousePressEvent is initiated after a left mouse button click
    • The ThumbnailView.mousePressEvent determines the left click was in a particular region of a QListView cell (e.g. over a QStyleOptionButton), and does not call the parent class mousePressEvent(event)

    In Qt 5.12.2, the delegate editorEvent is called regardless of whether the subclass called the parent class mousePressEvent. In Qt 5.12.7, mousePressEvent is called only if the the parent class mousePressEvent was called. It appears as if some versions of Qt are patched to mimic the behavior in Qt 5.12.7, e.g. in openSUSE Leap 15.1.

    Why this is important? When the user clicks a thumbnail checkbox the action of marking / unmarking is applied to all the selected thumbnails, not just the single checkbox in a single thumbnail:
    alt text
    With default Qt 5 behavior, marking the checkbox deselects all other listview items and selects only the listview item containing the checkbox, which is the behavior I want to override.
    My PyQt5 code to do this is (irrelevant class members removed):

    class ThumbnailView(QListView):
    
        def __init__(self, parent: QWidget) -> None:
            style = """QAbstractScrollArea { background-color: %s;}""" % ThumbnailBackgroundName
            super().__init__(parent)
            self.rapidApp = parent
            self.setViewMode(QListView.IconMode)
            self.setResizeMode(QListView.Adjust)
            self.setStyleSheet(style)
            self.setUniformItemSizes(True)
            self.setSpacing(8)
            self.setSelectionMode(QAbstractItemView.ExtendedSelection)
    
            self.selectionToRestore = self.selectedIndexesToRestore = None
    
            try:
                major, minor, patch = [int(v) for v in QT_VERSION_STR.split('.')]
            except ValueError:
                logging.error("Could not determine Qt version using %s", QT_VERSION_STR)
                self.editorEvent_always_triggered = False
            else:
                self.editorEvent_always_triggered = minor <= 12 and patch < 7
                if self.editorEvent_always_triggered:
                    logging.info('Disabling editorEvent workaround with Qt %s', QT_VERSION_STR)
                else:
                    logging.info('Applying editorEvent workaround')
    
        @pyqtSlot(QMouseEvent)
        def mousePressEvent(self, event: QMouseEvent) -> None:
            """
            Filter selection changes when click is on a thumbnail checkbox.
    
            When the user has selected multiple items (thumbnails), and
            then clicks one of the checkboxes, Qt's default behaviour is to
            treat that click as selecting the single item, because it doesn't
            know about our checkboxes. Therefore if the user is in fact
            clicking on a checkbox, we need to filter that event.
    
            On some versions of Qt 5 (to be determined), no matter what we do here,
            the delegate's editorEvent will still be triggered.
    
            :param event: the mouse click event
            """
    
            right_button_pressed = event.button() == Qt.RightButton
            if right_button_pressed:
                super().mousePressEvent(event)
    
            else:
                checkbox_clicked = False
                index = self.indexAt(event.pos())
                row = index.row()
                if row >= 0:
                    rect = self.visualRect(index)  # type: QRect
                    delegate = self.itemDelegate(index)  # type: ThumbnailDelegate
                    checkboxRect = delegate.getCheckBoxRect(rect)
                    checkbox_clicked = checkboxRect.contains(event.pos())
                    if checkbox_clicked:
                        status = index.data(Roles.download_status)  # type: DownloadStatus
                        checkbox_clicked = status not in Downloaded
    
                if not checkbox_clicked:
                    if self.rapidApp.prefs.auto_scroll and row >= 0:
                        self._scrollTemporalProximity(row=row)
                    super().mousePressEvent(event)
                elif not self.editorEvent_always_triggered:
                    if self.selectionModel().selection().contains(index):
                        if len(self.selectionModel().selectedIndexes()) > 1:
                            super().keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_Space, Qt.NoModifier, ' '))
                    else:
                        super().mousePressEvent(event)
    

    The code is hopefully self-explanatory. The line containing delegate.getCheckBoxRect(rect) simply returns the QRect of the checkbox. The code from which it is taken is available here.

    My workaround with Qt >= 5.12.7 when multiple items are selected and the user clicks a checkbox in one of the selected items is to convert the button press into a spacebar press, which does not affect the selection. It works well enough — except for when older versions of Qt are patched by a Linux distro!

    What can I do differently to make this code more robust?


  • Lifetime Qt Champion

    @DamonLynch said in How to workaround change in Qt 5 handling of QListView.mousePressEvent and QStyledItemDelegate.editorEvent?:

    What can I do differently to make this code more robust?

    Provide a proper simple testcase (in c++), create a bugreport and let us know :)



  • @Christian-Ehrlicher said in How to workaround change in Qt 5 handling of QListView.mousePressEvent and QStyledItemDelegate.editorEvent?:

    @DamonLynch said in How to workaround change in Qt 5 handling of QListView.mousePressEvent and QStyledItemDelegate.editorEvent?:

    What can I do differently to make this code more robust?

    Provide a proper simple testcase (in c++), create a bugreport and let us know :)

    @Christian-Ehrlicher sorry, but that would be rather difficult for me. It has been more than 20 years since I last touched C++ code. If the bug report example code really has to be in C++, I would be extremely grateful if someone could produce a C++ testcase.



  • In case anyone comes across this in a search, I was able to solve the problem by manipulating the selection in the class member function selectionChanged():

    https://bazaar.launchpad.net/~dlynch3/rapid/zeromq_pyqt/revision/1178#raphodo/thumbnaildisplay.py

    Perhaps that was always the best way to do it.