Custom file browser implementation on PySide6

1.6k Views Asked by At

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.
2

There are 2 best solutions below

0
Code Your Dream On BEST ANSWER

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)
5
bartolo-otrit On
    import PySide2
    from PySide2 import QtWidgets
    from PySide2.QtWidgets import QFileSystemModel
    from PySide2.QtWidgets import QMainWindow, QWidget
    from PySide2.QtWidgets import QTreeView
    from PySide2.QtWidgets import QVBoxLayout
    from PySide2.QtCore import QDir
    from PySide2.QtCore import QSortFilterProxyModel
    from PySide2.QtCore import Qt

    class View(QMainWindow):
        def __init__(self):
            super().__init__()

            self._w_main = QWidget()
            self.setCentralWidget(self._w_main)
            self.tree_view = QTreeView(self._w_main)

            self._layout = QVBoxLayout()
            self._layout.addWidget(self.tree_view)
            self._w_main.setLayout(self._layout)

    class SortingModel(QSortFilterProxyModel):
        def lessThan(
                self,
                source_left: PySide2.QtCore.QModelIndex,
                source_right: PySide2.QtCore.QModelIndex
        ):
            file_info1 = self.sourceModel().fileInfo(source_left)
            file_info2 = self.sourceModel().fileInfo(source_right)

            if file_info1.isDir() and file_info2.isDir():
                return super().lessThan(source_left, source_right)
            return file_info1.isDir()

    app = QtWidgets.QApplication([])
    view = View()
    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)
    view.showMaximized()
    return sys.exit(app.exec_())

QSortFilterProxyModel puts .. to the top of the list by default in PySide2 5.14.1 on my machine.

mapFromSource is used for index mapping for setRootIndex