Justin
Justin

Reputation: 23

Inconsistent results when overriding filterAcceptsRow in QFileDialog

I'm trying to create a QFileDialog with filtering on the file extension and file name. The file extension can be filtered via setNameFilter(), but name filtering (as far as I know) can only be filtered by creating a custom dialog and proxy model and then overriding the filterAcceptsRow() function.

I should be returning True when I want a file to show up in the dialog and False when I don't. The trouble is that when I return based on the custom checking I get no results. But when I return True always (even when I execute the check and print out based on it), I see all of the expected files. If it weren't for the printout I'd think I'm creating the pattern improperly, but that's not the case. And each button that opens this dialog sends a different value for file_part so I can't hard-code the pattern.

I have replaced my first attempt at example code with a toy example that hopefully showcases the problem. In my test environment, C:\temp\ contains the following files:

import sys
import re

from PyQt5 import Qt, QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class FileFilterProxyModel(QtCore.QSortFilterProxyModel):
  def __init__(self, file_part, parent = None):
    super(FileFilterProxyModel, self).__init__(parent)
    self.__pattern = re.compile(f'0{file_part}_record2')

  def filterAcceptsRow(self, source_row, srcidx):
    model = self.sourceModel()
    index0 = model.index(source_row, 0, srcidx)
    file_name = model.fileName(index0)

    if model.isDir(srcidx):
      return True

    return self.__pattern.search(file_name.lower()) != None

class MyFilePicker(QFileDialog):
  def __init__(self, file_part):
    super().__init__()
    self.setWindowTitle('Filtered File Picker')
    self.setOption(QFileDialog.DontUseNativeDialog)
    self.setNameFilter('Record 2 Files (*.txt)')
    self.setDirectory('C:\\temp\\')
    self.setFileMode(QFileDialog.ExistingFile)
    self.setAcceptMode(QFileDialog.AcceptOpen)
    self.setProxyModel(FileFilterProxyModel(file_part, self))

if __name__ == '__main__':
  app = QApplication(sys.argv)
  fp = MyFilePicker('jjj')
  fp.show()
  fp.exec_()

Currently all files are passing the filter while I would expect only 0jjj_record2_part1.txt and 0jjj_record2_part2.txt to be displayed.

Upvotes: 0

Views: 228

Answers (2)

musicamante
musicamante

Reputation: 48270

The problem is that you're filtering everything, including the directory that is going to be shown. If the current selected path doesn't match the regex, then the index for that path won't be accepted, and the result would be an empty file dialog (since that path "doesn't exist").

A possible solution is to check that if the index is a directory:

    def filterAcceptsRow(self, source_row, srcidx):
        model = self.sourceModel()
        index0 = model.index(source_row, 0, srcidx)
        if model.isDir(index0):
            return True
        # ...

This will also solve the issue of folders inside the current directory, which wouldn't be visible, thus preventing the user to browse them.

If, for some reason, you don't want that, you can check whether the current directory of the dialog matches the parent of the index:

class FileFilterProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, file_part, dialog):
        super(FileFilterProxyModel, self).__init__(dialog)
        self.__pattern = re.compile(f'^0{file_part}_')
        self.dialog = dialog

    def filterAcceptsRow(self, source_row, srcidx):
        model = self.sourceModel()
        if self.dialog.directory().absolutePath() != model.filePath(srcidx):
            return True
        index0 = model.index(source_row, 0, srcidx)
        file_name = model.fileName(index0)
        return self.__pattern.search(file_name.lower()) != None

But consider that this would cause some issues: if the user tries to access the parent directory, the child directory won't be visible from there.

Upvotes: 0

eyllanesc
eyllanesc

Reputation: 243983

The problem is that if you are filtering the i-th file in the folder then srcidx will point to the directory so isDir() will always return True when filtering files. The solution is to change with index0:

def filterAcceptsRow(self, source_row, srcidx):
    model = self.sourceModel()
    index0 = model.index(source_row, 0, srcidx)
    file_name = model.fileName(index0)
    if model.isDir(index0):
        return True
    return self.__pattern.search(file_name.lower()) is not None

Upvotes: 2

Related Questions