Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyQt4 checkable directory view

Tags:

qt

pyqt

pyqt4

In an attempt to make a checkable directory view, I wrote the following code. but in the CheckableDirModel every time one checks a folder, it has to traverse to all the sub-folders to check them and this is very slow. I was hoping someone could help me solve this issue.

This is how this one looks right now. but it's slow especially if one clicks a big folder.

enter image description here

The code is executable...

from PyQt4 import QtGui, QtCore


class CheckableDirModel(QtGui.QDirModel):
    def __init__(self, parent=None):
        QtGui.QDirModel.__init__(self, None)
        self.checks = {}

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role != QtCore.Qt.CheckStateRole:
            return QtGui.QDirModel.data(self, index, role)
        else:
            if index.column() == 0:
                return self.checkState(index)

    def flags(self, index):
        return QtGui.QDirModel.flags(self, index) | QtCore.Qt.ItemIsUserCheckable

    def checkState(self, index):
        if index in self.checks:
            return self.checks[index]
        else:
            return QtCore.Qt.Unchecked

    def setData(self, index, value, role):
        if (role == QtCore.Qt.CheckStateRole and index.column() == 0):
            self.checks[index] = value
            for i in range(self.rowCount(index)):
                self.setData(index.child(i,0),value,role)
            return True 

        return QtGui.QDirModel.setData(self, index, value, role)

    def exportChecked(self, acceptedSuffix=['jpg', 'png', 'bmp']):
        selection=[]
        for c in self.checks.keys():
            if self.checks[c]==QtCore.Qt.Checked and self.fileInfo(c).completeSuffix().toLower() in acceptedSuffix:
                try:

                    selection.append(self.filePath(c).toUtf8())
                except:
                    pass
        return selection   

if __name__ == '__main__':
    import sys

    app = QtGui.QApplication(sys.argv)

    model = QtGui.QDirModel()
    tree = QtGui.QTreeView()
    tree.setModel(CheckableDirModel())

    tree.setAnimated(False)
    tree.setIndentation(20)
    tree.setSortingEnabled(True)

    tree.setWindowTitle("Dir View")
    tree.resize(640, 480)
    tree.show()

    sys.exit(app.exec_())  
like image 677
Kiarash Avatar asked Jun 08 '13 08:06

Kiarash


1 Answers

You can't store checkbox state for each file separately. There can be too many of them. I suggest you to do the following:

You keep a list of checkbox values for indexes user has actually clicked. When user clicks something, you add an entry to the list (or update it if it is already present), then remove all entries for children indexes present in the list. You need to emit a signal about the data of the parent index and all children index has been changed.

When a checkbox value is requested (by calling data() of the model), you search for the requested index in the list and return its value. If the index is not present in the list, you search for the closest parent index and return its value.

Note that aside from slow executing, there is another issue in your code. When file tree has too many levels, the 'maximum recursion depth exceeded' exception happens. When implementing my suggestion, do not use recursion in such way. File tree depth is almost unlimited.

Here is the implementation:

from collections import deque

def are_parent_and_child(parent, child):
    while child.isValid():
        if child == parent:
            return True
        child = child.parent()
    return False


class CheckableDirModel(QtGui.QDirModel):
    def __init__(self, parent=None):
        QtGui.QDirModel.__init__(self, None)
        self.checks = {}

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.CheckStateRole and index.column() == 0:
            return self.checkState(index)
        return QtGui.QDirModel.data(self, index, role)

    def flags(self, index):
        return QtGui.QDirModel.flags(self, index) | QtCore.Qt.ItemIsUserCheckable

    def checkState(self, index):
        while index.isValid():
            if index in self.checks:
                return self.checks[index]
            index = index.parent()
        return QtCore.Qt.Unchecked

    def setData(self, index, value, role):
        if role == QtCore.Qt.CheckStateRole and index.column() == 0:
            self.layoutAboutToBeChanged.emit()
            for i, v in self.checks.items():
                if are_parent_and_child(index, i):
                    self.checks.pop(i)
            self.checks[index] = value
            self.layoutChanged.emit()
            return True 

        return QtGui.QDirModel.setData(self, index, value, role)

    def exportChecked(self, acceptedSuffix=['jpg', 'png', 'bmp']):
        selection=set()
        for index in self.checks.keys():
            if self.checks[index] == QtCore.Qt.Checked:
                for path, dirs, files in os.walk(unicode(self.filePath(index))):
                    for filename in files:
                        if QtCore.QFileInfo(filename).completeSuffix().toLower() in acceptedSuffix:
                            if self.checkState(self.index(os.path.join(path, filename))) == QtCore.Qt.Checked:
                                try:
                                    selection.add(os.path.join(path, filename))
                                except:
                                    pass
    return selection  

I didn't find a way to use dataChanged signal to notify view that data of all child indexes has been changed. We don't know which indexes are currently shown, and we can't notify about every child index because it can be slow. So I used layoutAboutToBeChanged and layoutChanged to force view to update all data. It seems that this method is fast enough.

exportChecked is a bit complicated. It's not optimized, sometimes an index is processed many times. I've used set() to filter duplicates. Maybe it can be optimized somehow if it will work too slow. However, if user have checked some massive directory with many files and subdirectories, any implementation of this function will be slow. So there is no point in optimization, just try to not call this function often.

like image 125
Pavel Strakhov Avatar answered Nov 03 '22 10:11

Pavel Strakhov