Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally change color of files in QListView connected to QFileSystemModel

I'm having a QListView with a QFileSystemModel. Based on a selection in a QTreeView, the QListView shows the content of the folder.
Now I need to change the color of the filenames depending on some condition.
The initial idea would be to iterate over the items in the QListView and set the color for each item depending on whether the condition is fulfilled. However this seems to be impossible, since the setData() method of QFileSystemModel only accepts changes to the EditRole, ignoring something like [see this]

self.FileModel.setData(index, QtGui.QBrush(QtCore.Qt.red), role=QtCore.Qt.ForegroundRole)

This has also been pointed out here and the suggestion in the latter was to subclass QItemDelegate for the purpose of colorizing items in the QListView.

I therefore subclassed QStyledItemDelegate and reimplemented its paint() method to show the filename in green, if the condition is fulfilled - which works fine. However it now looks kind of ugly: File icons are lost and the "mouse_over" effect is not working anymore.

While this subclassing is anyway a messy work-around, my top-level question would be

  • Is there a way to colorize items in a QListView connected to a QFileSystemModel based on a condition?

Now provided that this might not be the case and sticking to the subclassing of QItemDelegate,

  • Is there a way to get the original behaviour with nice selections and icons back?
  • Does anyone know which ItemDelegate is originally used for QFileSystemModel in a QListView and how to use it?
  • Is it possible to get its source code and copy the paint method from there ?

Here is a minimal code that uses subclassing and shows the descibed behaviour. It uses a QLineEdit where one can type in a string, such that all files containing that string are highlighted in green.

import sys
from PyQt4 import QtGui, QtCore


class MyFileViewDelegate(QtGui.QStyledItemDelegate ):
    def __init__(self, parent=None, *args, **kwargs):
        QtGui.QItemDelegate.__init__(self, parent, *args)

        self.condition = None
        self.isMatch = False

        self.brush_active =         QtGui.QBrush(QtGui.QColor("#79b9ed"))
        self.brush_active_matched = QtGui.QBrush(QtGui.QColor("#58cd1c"))
        self.pen =                  QtGui.QPen(QtGui.QColor("#414141") )
        self.pen_matched =          QtGui.QPen(QtGui.QColor("#39c819") )
        self.pen_active =           QtGui.QPen(QtGui.QColor("#eef2fd") )
        self.pen_active_matched =   QtGui.QPen(QtGui.QColor("#e7fade") )


    def paint(self, painter, option, index):
        text = index.data(QtCore.Qt.DisplayRole)
        self.matchText(text)

        painter.save()
        ######## set background 
        painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
        if option.state & QtGui.QStyle.State_Selected:
            if self.isMatch:
                painter.setBrush(self.brush_active_matched)
            else:
                painter.setBrush(self.brush_active)

        painter.drawRect(option.rect)
        ######## set font color
        if option.state & QtGui.QStyle.State_Selected:
            if self.isMatch:
                painter.setPen(self.pen_active_matched)
            else:
                painter.setPen(self.pen_active)
        else:
            if self.isMatch:
                painter.setPen(self.pen_matched) 
            else:
                painter.setPen(self.pen) 

        painter.drawText(option.rect, QtCore.Qt.AlignLeft, text)

        painter.restore()

    def matchText(self, filename):
        # testing condition. In the real case this is much more complicated
        if (self.condition != None) and (self.condition != "") and (self.condition in filename):
            self.isMatch = True
        else:
            self.isMatch = False

    def setCondition(self, condition):
        self.condition = condition


class MainWidget(QtGui.QWidget):
    def __init__(self, parent=None, useDelegate = False):
        super(MainWidget, self).__init__(parent)
        self.setLayout(QtGui.QVBoxLayout())

        self.FolderModel = QtGui.QFileSystemModel()
        self.FolderModel.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.AllDirs)
        self.FolderModel.setRootPath("")

        self.FolderView = QtGui.QTreeView(parent=self)
        self.FolderView.setModel(self.FolderModel)

        self.FolderView.setHeaderHidden(True)
        self.FolderView.hideColumn(1)
        self.FolderView.hideColumn(2)
        self.FolderView.hideColumn(3)
        self.FolderView.expanded.connect(self.FolderView.scrollTo)
        self.FolderView.clicked[QtCore.QModelIndex].connect(self.browserClicked)

        self.FileModel = QtGui.QFileSystemModel()
        self.FileModel.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Files)

        self.FileView = QtGui.QListView(parent=self)
        self.FileView.setModel(self.FileModel)

        self.FileViewDelegate = None
        if useDelegate:
            self.FileViewDelegate = MyFileViewDelegate()
            self.FileView.setItemDelegate(self.FileViewDelegate)

        self.FileView.setSelectionMode(  QtGui.QAbstractItemView.ExtendedSelection  ) 

        self.LineEdit = QtGui.QLineEdit()
        self.LineEdit.textChanged.connect(self.changeCondition)

        # Add Widgets to layout
        self.layout().addWidget(self.FolderView)
        self.layout().addWidget(self.FileView)
        self.layout().addWidget(self.LineEdit)

    def changeCondition(self, text):
        if self.FileViewDelegate:
            self.FileViewDelegate.setCondition(text)

    def browserClicked(self, index):
        # the signal passes the index of the clicked item
        # set the FileView's root_index to the clicked index
        dir_path =  self.FileModel.filePath(index) 
        root_index = self.FileModel.setRootPath(dir_path)
        self.FileView.setRootIndex(root_index)


class App(QtGui.QMainWindow):
    def __init__(self, parent=None, useDelegate=False):
        super(App, self).__init__(parent)
        self.central = MainWidget(parent =self, useDelegate=useDelegate)
        self.setCentralWidget(self.central)

if __name__=='__main__':
    app = QtGui.QApplication(sys.argv)
    thisapp = App(None, True) # set False to view App without custom FileViewDelegate
    thisapp.show()
    sys.exit(app.exec_())

This is the comparison of how it looks with and without subclassing QItemDelegate:

enter image description here

just to mention, another problem with this code is, that once the condition is changed, one needs to move the mouse into the QFileView to initiate the repainting. I wonder which slot I could use to connect to the LineEdit.textChange signal to do that directly.

like image 385
ImportanceOfBeingErnest Avatar asked Oct 31 '16 09:10

ImportanceOfBeingErnest


1 Answers

There's no need for an item-delegate. It can be achieved much more simply by reimplementing the data method of the QFileSystemModel:

class FileSystemModel(QtGui.QFileSystemModel):
    def __init__(self, *args, **kwargs):
        super(FileSystemModel, self).__init__(*args, **kwargs)
        self.condition = None

    def setCondition(self, condition):
        self.condition = condition
        self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if self.condition and role == QtCore.Qt.TextColorRole:
            text = index.data(QtCore.Qt.DisplayRole)
            if self.condition in text:
                return QtGui.QColor("#58cd1c")
        return super(FileSystemModel, self).data(index, role)

class MainWidget(QtGui.QWidget):
    def __init__(self, parent=None, useDelegate = False):
        super(MainWidget, self).__init__(parent)
        ...
        self.FileModel = FileSystemModel(self)
        ...

    def changeCondition(self, text):
        self.FileModel.setCondition(text)
like image 129
ekhumoro Avatar answered Sep 17 '22 21:09

ekhumoro