pythonpyqtmime-typesqfiledialog

How do I combine MimeTypeFilters and NameFilters for a QFileDialog using PyQt6 or PyQt5?


Using PyQt6, I am investigating using QFileDialog directly without the use of one of the static functions (i.e. don't use QFileDialog.getOpenFileName). The issue that I am running into is creating a filter list that uses a combination of MIME types and named types.

For example, say you want to set a filter for *.css and *.qss files. At there core, they are essentially the same file type, however MIME doesn't recognize *.qss. I really like the idea of using MIME types because it ensures that the many extension options of a file type are included (i.e. [*.jpg, *.jpeg, *.jpe] or [*.md, *.mkd, *.markdown]), but I also need to work with the files that are not included in MIME.

The code snippet that I am working with is as follows:

file_dialog = QFileDialog()
file_dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
file_dialog.setMimeTypeFilters(["text/css",
                                "application/octet-stream"])
file_dialog.setNameFilter("Qt Style Sheet (*.qss)")

if file_dialog.exec():
    print(file_dialog.selectedFiles())

When this code executes, the .setNameFilter function completely overwrites the filters set with the .setMimeTypeFilters function. If I reverse the order of the filter setting, the same thing happens just in reverse.

I have also tried adding the name filter to the MIME type list, but the name filter is just ignored.

file_dialog.setMimeTypeFilters(["text/css",
                                "Qt Style Sheet (*.qss)",
                                "application/octet-stream"])

Anyone know of a way to have both filters without having to explicitly set all options and nix using .setMimeTypeFilters?


Solution

  • Combining mime-filters and name-filters is quite easy to achieve using QMimeDatabase. Doing things this way will allow you to merge glob patterns (e.g. *.qss with the css defaults), as well as getting full control over the final ordering of the filters. This won't normally be possible when using the QFileDialog methods.

    Below is a simple function that demonstrates the idea. The function takes a dict with mime-type/filter-name keys and glob-list values. If the function detects a mime-type, the glob-list will be merged with the built-in defaults. The return value is a list that can be passed to setNameFilters. The code logic is essentially the same as what QFileDialog itself uses, but tweaked slightly to allow greater flexibility:

    def combine_filters(filters):
        result = []
        md = QMimeDatabase()
        for name, patterns in filters.items():
            if (mt := md.mimeTypeForName(name)).isValid():
                if mt.isDefault():
                    result.append('All Files (*)')
                    continue
                name = mt.comment()
                patterns = sorted(mt.globPatterns() + list(patterns or ()))
            result.append(f'{name} ({" ".join(patterns)})')
        return result
    

    Example with merge:

    filters = combine_filters({
        'text/css': ['*.qss'],
        'application/octet-stream': None,
        })
    
    print('\n'.join(filters))
    
    # CSS stylesheet (*.css *.qss)
    # All Files (*)
    

    Example without merge:

    filters = combine_filters({
        'text/css': None,
        'Qt Style Sheet': ['*.qss'],
        'application/octet-stream': None,
        })
    
    print('\n'.join(filters))
    
    # CSS stylesheet (*.css)
    # Qt Style Sheet (*.qss)
    # All Files (*)