Search code examples
pythonqtreeviewqfilesystemmodelpyside6

Custom file browser implementation on PySide6


I would like to implement the file browser on PySide6, and my goals are:

  1. Display files and folders with .. always at the top (regardless of the sorting), so that the user could double click it and go up one level.
  2. After .. I'd like to display folders and then files (just like Windows explorer does) regardless of the sorting.
  3. Have an alternative display mode which shows a certain set of files (they can be on different drives, in different folders, etc).

I'm currently using the following code to initialize the model and the view:

self.model = QFileSystemModel()
self.model.setRootPath(path)
self.model.setFilter(QDir.NoDot | QDir.AllEntries)
self.model.sort(0,Qt.SortOrder.AscendingOrder)
self.ui.treeView.setModel(self.model)
self.ui.treeView.setRootIndex(self.model.index(path))
self.ui.treeView.header().setSortIndicator(0, Qt.AscendingOrder)
self.ui.treeView.setSortingEnabled(True)

Instead of QFileSystemModel() I'm actually using my customized QFileSystemModel with an additional column.

The problems I'm experiencing are that:

  • .. gets sorted together with other contents and doesn't appear at the top
  • directories don't stay at the top after sorting

I don't understand what's the best approach for the problems I'm solving.

I see the following options:

  • use QSortFilterProxyModel and somehow force .. to be always on top regardless the sorting (not sure if it's possible) and also keep directories first (there's a related question), I could also potentially use it for the point 3 above to display the files by certain criteria
  • use a completely different approach, maybe QFileSystemWatcher or QTreeWidget that I'll fill in manually (keeping .. always on top seems to cause troubles in any case).
  • somehow add .. at the top of QTreeView after it's loaded or sorted

I tried implementing QSortFilterProxyModel, but ran into another problem: I don't understand how I should modify treeView.setRootIndex() call.

So my specific questions are:

  1. Can I use QSortFilterProxyModel to solve all of the problems mentioned above? If yes, please provide a sample implementation.
  2. If you think there's a better approach for this problem please describe it.

Solution

  • The following solution works:

    class SortingModel(QSortFilterProxyModel):
        def lessThan(self, source_left: QModelIndex, source_right: QModelIndex):
            file_info1 = self.sourceModel().fileInfo(source_left)
            file_info2 = self.sourceModel().fileInfo(source_right)       
            
            if file_info1.fileName() == "..":
                return self.sortOrder() == Qt.SortOrder.AscendingOrder
    
            if file_info2.fileName() == "..":
                return self.sortOrder() == Qt.SortOrder.DescendingOrder
                    
            if (file_info1.isDir() and file_info2.isDir()) or (file_info1.isFile() and file_info2.isFile()):
                return super().lessThan(source_left, source_right)
    
            return file_info1.isDir() and self.sortOrder() == Qt.SortOrder.AscendingOrder
    

    The code for initializing view and model are the same as in @bartolo-otrit answer:

        model = QFileSystemModel()
        model.setRootPath('.')
        model.setFilter(QDir.NoDot | QDir.AllEntries)
        model.sort(0, Qt.SortOrder.AscendingOrder)
        sorting_model = SortingModel()
        sorting_model.setSourceModel(model)
        view.tree_view.setModel(sorting_model)
        view.tree_view.setRootIndex(sorting_model.mapFromSource(model.index('.')))
        view.tree_view.header().setSortIndicator(0, Qt.AscendingOrder)
        view.tree_view.setSortingEnabled(True)