0

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.
Code Your Dream
  • 347
  • 3
  • 11

2 Answers2

1
    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

bartolo-otrit
  • 2,396
  • 3
  • 32
  • 50
  • Thanks, but this doesn't put `..` always to the top, and that's my main goal. And by the way if there's a folder with a name like `!something` it gets above `..` even after sorting. Also if I sort by name dirs aren't listed first, and that's another goal. – Code Your Dream Feb 16 '21 at 11:56
  • Adding this to SortingModel code helps bringing `..` to the top, but doesn't help keeping it if sorting changed (which is expected): `if file_info1.fileName() == "..":` `return True` Maybe I have to override `sort()` function? – Code Your Dream Feb 16 '21 at 12:03
  • Found and posted the working solution, thanks for your help. – Code Your Dream Feb 16 '21 at 12:42
  • @CodeYourDream You can mark your answer as accepted. – bartolo-otrit Feb 17 '21 at 07:34
  • No I can't... yet. Have to wait, SO allows accepting own answers only after some time afaik. – Code Your Dream Feb 17 '21 at 11:04
1

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)
Code Your Dream
  • 347
  • 3
  • 11