2

In PyQt 5.11.2 (on Windows 10), QFileDialog with the DontUseNativeDialog option set has a bug when sorting by the 'Date Modified' column: it does not sort by the actual date - it sorts alphabetically by the non-zero-padded MM-DD-YYYY string, meaning that 9-12-2018 shows up as being more recent than 12-12-2018. enter image description here Is there a workaround or a fix?

The native dialog does sort by date correctly, but, the motivation for using the non-native dialog is that the native dialog does not respect fileDialog.setProxyModel (used to filter out certain files by more complex regex rules), described here and here, which I think is spelled out / alluded to by this line in the docs:

By default, a platform-native file dialog will be used if the platform has one. In that case, the widgets which would otherwise be used to construct the dialog will not be instantiated, so related accessors such as layout() and itemDelegate() will return null. You can set the DontUseNativeDialog option to ensure that the widget-based implementation will be used instead of the native dialog.

The code: the dialog is spawned by calling self.load() (no arguments)

def load(self,fileName=None):
    if not fileName:
        fileDialog=QFileDialog()
        fileDialog.setOption(QFileDialog.DontUseNativeDialog)
        fileDialog.setProxyModel(CSVFileSortFilterProxyModel(self))
        fileDialog.setNameFilter("CSV Radio Log Data Files (*.csv)")
        fileDialog.setDirectory(self.firstWorkingDir)
        if fileDialog.exec_():
            fileName=fileDialog.selectedFiles()[0]

... and the entire CSVFileSortFilterProxyModel class which only has one function:

# code for CSVFileSortFilterProxyModel partially taken from
#  https://github.com/ZhuangLab/storm-control/blob/master/steve/qtRegexFileDialog.py
class CSVFileSortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self,parent=None):
#       print("initializing CSVFileSortFilterProxyModel")
        super(CSVFileSortFilterProxyModel,self).__init__(parent)

    # filterAcceptsRow - return True if row should be included in the model, False otherwise
    #
    # do not list files named *_fleetsync.csv or *_clueLog.csv
    #  do a case-insensitive comparison just in case
    def filterAcceptsRow(self,source_row,source_parent):
#       print("CSV filterAcceptsRow called")
        source_model=self.sourceModel()
        index0=source_model.index(source_row,0,source_parent)
        # Always show directories
        if source_model.isDir(index0):
            return True
        # filter files
        filename=source_model.fileName(index0).lower()
#       filename=self.sourceModel().index(row,0,parent).data().lower()
#       print("testing lowercased filename:"+filename)
        # never show non- .csv files
        if filename.count(".csv")<1:
            return False
        if filename.count("_fleetsync.csv")+filename.count("_cluelog.csv")==0:
            return True
        else:
            return False
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Tom Grundy
  • 736
  • 5
  • 26

1 Answers1

2

The problem is caused by the custom QSortFilterProxyModel since in the lessThan method the values are strings, the solution is to convert to the appropriate type for the comparison:

class CSVFileSortFilterProxyModel(QtCore.QSortFilterProxyModel):
    def filterAcceptsRow(self,source_row,source_parent):
        source_model = self.sourceModel()
        index0=source_model.index(source_row,0,source_parent)
        if source_model.isDir(index0):
            return True
        filename = source_model.fileName(index0).lower()
        if filename.count(".csv")<1:
            return False
        return filename.count("_fleetsync.csv")+filename.count("_cluelog.csv") == 0

    def lessThan(self, left, right):
        source_model = self.sourceModel()

        if left.column() == right.column() == 1: 
            if source_model.isDir(left) and not source_model.isDir(right):
                return True
            return source_model.size(left) < source_model.size(right)

        if left.column() == right.column() == 2:
            return source_model.type(left) < source_model.type(right)

        if left.column() == right.column() == 3:
            return source_model.lastModified(left) < source_model.lastModified(right)
        return super(CSVFileSortFilterProxyModel, self).lessThan(left, right)

As indicated by @ekhumoro a simpler option is to overwrite the proxy sort:

class CSVFileSortFilterProxyModel(QtCore.QSortFilterProxyModel):
    def filterAcceptsRow(self,source_row,source_parent):
        source_model = self.sourceModel()
        index0=source_model.index(source_row,0,source_parent)
        if source_model.isDir(index0):
            return True
        filename = source_model.fileName(index0).lower()
        if filename.count(".csv")<1:
            return False
        return filename.count("_fleetsync.csv")+filename.count("_cluelog.csv") == 0

    def sort(self, column, order):
        self.sourceModel().sort(column, order)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • brilliant! Thanks, that works perfectly. It's interesting that the default lessThan doesn't account for that when the column name is 'date modified' or such. – Tom Grundy Dec 15 '18 at 21:52
  • @TomGrundy This is not the proxy problem but the sourcemodel, the sourcemodel is a QFileSystemModel that internally has a proxy but in this case it is not used because it used the Qt::DisplayRole role that returns strings. You have to verify if you add modifications. – eyllanesc Dec 15 '18 at 21:56
  • Makes sense. Thanks for your help. – Tom Grundy Dec 15 '18 at 21:57
  • 1
    This is overcomplicated. All you need to do is override `sort` in the proxy and call `self.sourceModel().sort(column, order)`. – ekhumoro Dec 15 '18 at 21:57
  • The view will automatically pass the necessary arguments. – ekhumoro Dec 15 '18 at 22:00
  • 1
    @ekhumoro I already understood you, instead of calling lessthan you must use the sourcemodel sort – eyllanesc Dec 15 '18 at 22:00