Selection issues when using a Qt proxy model to reparent items under virtual rows

89 views Asked by At

I'm attempting to use a proxy model to add virtual rows that will group items in the source model. I almost have something "working", but I can't select the item that is reparented to the virtual row.

Below is the code for the simple test case I've been using to prototype this functionality. This is based on several weeks of scouring the internet for clues, and even AI suggestions, but it's still confusing what's actually going on here.

#!/usr/bin/env python
from Qt import QtCore, QtGui, QtWidgets


class VirtualRowProxyModel(QtCore.QSortFilterProxyModel):

    # Some internal id indicating when its a virtual row
    IS_VIRTUAL = 5

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

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def mapToSource(self, proxyIndex):
        if self._isVirtualRow(proxyIndex):
            return QtCore.QModelIndex()
        return super(VirtualRowProxyModel, self).mapToSource(proxyIndex)

    def mapFromSource(self, sourceIndex):
        row = sourceIndex.row()
        column = sourceIndex.column()
        parent = sourceIndex.parent()

        if row == 0 and not parent.isValid():
            return self.index(0, 0).child(row, column)

        return super(VirtualRowProxyModel, self).mapFromSource(sourceIndex)

    def hasChildren(self, parent=QtCore.QModelIndex()):
        if self._isVirtualRow(parent):
            return True
        return super(VirtualRowProxyModel, self).hasChildren(parent)

    def index(self, row, column, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            return self.createIndex(row, column, self.IS_VIRTUAL)

        return super(VirtualRowProxyModel, self).index(row, column, parent)

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()
        if self._isVirtualRow(index):
            return QtCore.QModelIndex()
        sourceIndex = self.mapToSource(index)

        if not sourceIndex.parent().isValid():
            return self.index(0, 0)

        return super(VirtualRowProxyModel, self).parent(index)

    def data(self, proxyIndex, role=QtCore.Qt.DisplayRole):
        # if role == QtCore.Qt.DisplayRole:
        #     sourceIndex = self.mapToSource(proxyIndex)
        #     return str([sourceIndex.row(), sourceIndex.data(),
        #                 proxyIndex.isValid(), proxyIndex.parent().isValid(), sourceIndex.parent().isValid()])
        if self._isVirtualRow(proxyIndex):
            if role == QtCore.Qt.DisplayRole:
                sourceIndex = self.mapToSource(proxyIndex)
                return "-VIRTUAL ROW-"
            if role == QtCore.Qt.UserRole:
                return True
            return None
        return super(VirtualRowProxyModel, self).data(proxyIndex, role)

    def rowCount(self, parent=QtCore.QModelIndex()):
        if not parent.isValid():
            return 3
        if self._isVirtualRow(parent):
            if parent.row() == 0:
                return 1
            return 0
        return self.sourceModel().rowCount(self.mapToSource(parent))

    def _isVirtualRow(self, index):
        return index.isValid() and index.internalId() == self.IS_VIRTUAL


def itemRow(name):
    return [QtGui.QStandardItem(name)]


class Ctrl(QtCore.QObject):
    def __init__(self, parent=None):
        super(Ctrl, self).__init__(parent)

        self.window = QtWidgets.QFrame()
        layout = QtWidgets.QHBoxLayout(self.window)

        self.view = QtWidgets.QTreeView()
        self.view.setAlternatingRowColors(True)

        model = QtGui.QStandardItemModel()
        row1 = itemRow("A")
        model.appendRow(row1)
        row2 = itemRow("B")
        row1[0].appendRow(row2)
        row3 = itemRow("C")
        row2[0].appendRow(row3)

        self.proxy = VirtualRowProxyModel()
        self.proxy.setSourceModel(model)

        self.view.setModel(self.proxy)
        self.view.expandAll()

        layout.addWidget(self.view)


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    app.setStyle("fusion")

    ctrl = Ctrl()
    window = ctrl.window

    window.resize(600, 300)
    window.show()
    window.raise_()

    app.exec_()

If you run this, item "A" is reparented under a virtual row, but it is unselectable. However, B and C are selectable.

I'm unsure if this general approach is correct, so if you have a better suggestion for this, let me know. I've found some C++ examples of proxy models that do groupings of item models, but they are very large and confusing to me as well. Thanks!

0

There are 0 answers