pythonpyqtpyqt4qitemdelegateqidentityproxymodel

How to use QitemDelegate with multiple proxy models stacked?


This question is related to this previous question: how to use QSortFilterProxyModel for filter a 2d array?

i have been trying to stack several proxy model to display a 2d array of data into a qtableview. @eyllanesc provided a really cool solution to my question but it doesn't seem to be compatible with qitemdelegate. When i add it to his example the delegate doesnt display as expected. Without the proxy3 it does show correctly.

import random
import math
from PyQt4 import QtCore, QtGui


class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data, columns, parent=None):
        super(TableModel, self).__init__(parent)
        self._columns = columns
        self._data = data[:]

    def rowCount(self, parent=QtCore.QModelIndex()): 
        if parent.isValid() or self._columns == 0:
            return 0
        return math.ceil(len(self._data )*1.0/self._columns)

    def columnCount(self, parent=QtCore.QModelIndex()): 
        if parent.isValid():
            return 0
        return self._columns

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid(): 
            return
        if role == QtCore.Qt.DisplayRole: 
            try:
                value = self._data[ index.row() * self._columns + index.column() ]
                return value
            except:
                pass

class Table2ListProxyModel(QtGui.QIdentityProxyModel):
    def columnCount(self, parent=QtCore.QModelIndex()):
        return 1

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return self.sourceModel().rowCount() * self.sourceModel().columnCount()

    def mapFromSource(self, sourceIndex):
        if sourceIndex.isValid() and sourceIndex.column() == 0 \
                and sourceIndex.row() < self.rowCount():
            r = sourceIndex.row()
            c = sourceIndex.column()
            row = c * sourceIndex.model().columnCount() + r
            return self.index(row, 0)
        return QtCore.QModelIndex()

    def mapToSource(self, proxyIndex):
        r = proxyIndex.row() / self.sourceModel().columnCount()
        c = proxyIndex.row() % self.sourceModel().columnCount()
        return self.sourceModel().index(r, c)

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


class ListFilterProxyModel(QtGui.QSortFilterProxyModel):
    def setThreshold(self, value):
        setattr(self, "threshold", value)
        self.invalidateFilter()

    def filterAcceptsRow(self, row, parent):
        if hasattr(self, "threshold"):
            ix = self.sourceModel().index(row, 0)
            val = ix.data()
            if val is None:
                return False
            return int(val.toString()) < getattr(self, "threshold")
        return True


class List2TableProxyModel(QtGui.QIdentityProxyModel):
    def __init__(self, columns=1, parent=None):
        super(List2TableProxyModel, self).__init__(parent)
        self._columns = columns

    def columnCount(self, parent=QtCore.QModelIndex()):
        return self._columns

    def rowCount(self, parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return math.ceil(self.sourceModel().rowCount()/self._columns)

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

    def data(self, index, role=QtCore.Qt.DisplayRole):
        r = index.row()
        c = index.column()
        row = r * self.columnCount() + c
        if row < self.sourceModel().rowCount():
            return super(List2TableProxyModel, self).data(index, role)

    def mapFromSource(self, sourceIndex):
        r = math.ceil(sourceIndex.row() / self.columnCount())
        c = sourceIndex.row() % self.columnCount()
        return self.index(r, c)

    def mapToSource(self, proxyIndex):
        if proxyIndex.isValid():
            r = proxyIndex.row()
            c = proxyIndex.column()
            row = r * self.columnCount() + c
            return self.sourceModel().index(row, 0)
        return QtCore.QModelIndex()




class Delegate(QtGui.QItemDelegate):
    def __init__(self, parent = None):
        QtGui.QItemDelegate.__init__(self, parent)

    def paint(self, painter, option, index):
        number = str(index.data(QtCore.Qt.DisplayRole).toString())
        widget  = QtGui.QWidget()
        layout = QtGui.QVBoxLayout()
        widget.setLayout( layout )
        title = QtGui.QLabel("<font color='red'>"+number+"</font>")
        layout.addWidget( title )

        if not self.parent().tvf.indexWidget(index):
            self.parent().tvf.setIndexWidget(
                index, 
                widget
            )
        
        QtGui.QItemDelegate.paint(self, painter, option, index)



class Widget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        data = [random.choice(range(10)) for i in range(20)]

        l = QtGui.QHBoxLayout(self)
        splitter = QtGui.QSplitter()
        l.addWidget(splitter)

        tv = QtGui.QTableView()
        lv = QtGui.QListView()
        lvf = QtGui.QListView()
        self.tvf = QtGui.QTableView()

        delegate = Delegate(self)
        self.tvf.setItemDelegate(delegate)

        model = TableModel(data, 3, self)
        proxy1 = Table2ListProxyModel(self)
        proxy1.setSourceModel(model)
        proxy2 = ListFilterProxyModel(self)
        proxy2.setSourceModel(proxy1)
        proxy2.setThreshold(5)
        proxy3 = List2TableProxyModel(3, self)
        proxy3.setSourceModel(proxy2)

        tv.setModel(model)
        lv.setModel(proxy1)
        lvf.setModel(proxy2)
        self.tvf.setModel(proxy3)

        splitter.addWidget(tv)
        splitter.addWidget(lv)
        splitter.addWidget(lvf)
        splitter.addWidget(self.tvf )


if __name__=="__main__":
    import sys
    a=QtGui.QApplication(sys.argv)
    w=Widget()
    w.show()
    sys.exit(a.exec_())

Here is the expected result I am looking for:

enter image description here

and this is what it look like when i bypass the proxy model (the numbers are red). replacing : self.tvf.setModel(proxy3) to self.tvf.setModel(model) enter image description here


Solution

  • You are incorrectly using the delegate that works sometimes if and sometimes not. The concepts of delegate and IndexWidget are 2 alternatives, the first one is a low level painting but also low cost, the second instead a widget is embedded in the item task simple but expensive since a widget is much more than a simple painted , also has a logic.

    In this case the solution is to use a QStyledItemDelegate and overwrite the initStyleOption() method by modifying the palette.

    class Delegate(QtGui.QStyledItemDelegate):
        def initStyleOption(self, option, index):
            super(Delegate, self).initStyleOption(option, index)
            option.palette.setBrush(QtGui.QPalette.Text, QtGui.QColor("red"))
    

    enter image description here

    Note: Note: it is not necessary to write

    def __init__(self, parent = None):
        QtGui.QItemDelegate.__init__(self, parent)
    

    since it is not modified.