filterpyqtpyqt5filenamesqfilesystemmodel

How to filter by no extension in QFileSystemModel


The following code runs (after importing necessary libraries):

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('MainWindow')
        self.layout = QHBoxLayout()
        self.file_system_widget = FileBrowser()
        self.layout.addWidget(self.file_system_widget)
        widget = QWidget()
        widget.setLayout(self.layout)
        self.setCentralWidget(widget)
   
class FileBrowser(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    
    def initUI(self):
        layout = QVBoxLayout()
        self.model = QFileSystemModel()
        self.model.setRootPath(self.model.myComputer())
        self.model.setNameFilters(['*.*'])
        self.model.setNameFilterDisables(1)
        self.tree = QTreeView()
        self.tree.setModel(self.model)
        self.tree.setAnimated(False)
        self.tree.setSortingEnabled(True)
        layout.addWidget(self.tree)
        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    try:
        os.mkdir('Imports')
    except:
        pass
    main = MainWindow()
    main.show()
    app.exec()

It gives the following result on my C drive:

enter image description here

(Some of these files are provided here https://drive.google.com/drive/folders/1ejY0CjfEwS6SGS2qe_uRX2JvlruMKvPX).

My objective is to modify the line self.model.setNameFilters(['*.*']) such that, in the tree view, it only shows files with dcm extension and also files without extension. That is, the part I draw red gets removed.

enter image description here

How do I achieve such a goal? For keeping dcm files, I can write lines like self.model.setNameFilters(['*.dcm']) to keep them and remove the others. But I am not sure how to deal with files without extension or how to deal with the two requirements at the same time .


Solution

  • The QFileSystemModel class only supports basic wildcard filtering, so you will need to use a QSortFilterProxyModel to get fully customisable filtering. This will allow you to use a regular expression to do the filtering, which achieves most of what you want quite simply. However, reproducing the behaviour of setNameFilterDisables will require a reimplemention of the flags method of the proxy model, and the sorting will also need some adjustment.

    Below is a simple demo based on your example that implements all of that:

    screenshot

    import sys
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    
    class FilterProxy(QSortFilterProxyModel):
        def __init__(self, disables=False, parent=None):
            super().__init__(parent)
            self._disables = bool(disables)
    
        def filterAcceptsRow(self, row, parent):
            index = self.sourceModel().index(row, 0, parent)
            if not self._disables:
                return self.matchIndex(index)
            return index.isValid()
    
        def matchIndex(self, index):
            return (self.sourceModel().isDir(index) or
                    super().filterAcceptsRow(index.row(), index.parent()))
    
        def flags(self, index):
            flags = super().flags(index)
            if (self._disables and
                not self.matchIndex(self.mapToSource(index))):
                flags &= ~Qt.ItemIsEnabled
            return flags
    
    class FileBrowser(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()
    
        def initUI(self):
            layout = QVBoxLayout()
            self.model = QFileSystemModel()
            self.model.setFilter(
                QDir.AllDirs | QDir.AllEntries | QDir.NoDotAndDotDot)
            self.proxy = FilterProxy(True, self)
            self.proxy.setFilterRegularExpression(r'^(.*\.dcm|[^.]+)$')
            self.proxy.setSourceModel(self.model)
    
            self.tree = QTreeView()
            self.tree.setModel(self.proxy)
            self.tree.setAnimated(False)
    
            header = self.tree.header()
            header.setSectionsClickable(True)
            header.setSortIndicatorShown(True)
            header.setSortIndicator(0, Qt.AscendingOrder)
            header.sortIndicatorChanged.connect(self.model.sort)
    
            self.model.setRootPath(self.model.myComputer())
            root = self.model.index(self.model.rootPath())
            self.tree.setRootIndex(self.proxy.mapFromSource(root))
    
            layout.addWidget(self.tree)
            self.setLayout(layout)
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            self.setWindowTitle('MainWindow')
            self.layout = QHBoxLayout()
            self.file_system_widget = FileBrowser()
            self.layout.addWidget(self.file_system_widget)
            widget = QWidget()
            widget.setLayout(self.layout)
            self.setCentralWidget(widget)
    
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
        main = MainWindow()
        main.show()
        app.exec()