Mastering Qt File Selectors

Mastering Qt File Selectors

By Jeff Tranter

Sometimes an application needs to use different assets, such as graphics images or QML files, depending on the platform that the application is running on.

One way to do this is with conditional code at compile time using the C++ pre-processor (i.e. #ifdefs). It can also be done with conditional code that loads different files at run-time, depending on the platform.

Qt provides a nice facility to handle this in a more elegant way: file selectors. In this blog post. I'll look at what Qt provides, for both C++ and QML, and show a simple but complete program example.

QFileSelector

At the C++ level, the QFileSelector (1) class (part of Qt Core) provides a convenient way of selecting files based on the platform or device.

After creating an instance of a QFileSelector object, you can call its select() method, specifying a file path or URL, and it will select the appropriate file based on the available selectors. You do that by organizing the files in directories prefixed with a "+" and the selector name.

As a concrete example, we could have image files in the following directory structure:

  image.png
  +linux/image.png
  +mac/image.png
  +windows/image.png

Requesting the file image.png would return the appropriate image based on the platform the application is running on for either Linux, Mac OS or Windows. The first image would be used when not running on any of these three platforms.

The default selectors include the platform being run on, the locale and the Linux distribution (if applicable).

The locale selector can be useful when you need to load different resources, like images, depending on the locale. In general, the best practice for localization is to make all images independent of the locale (not putting text on them, for example), but in some cases this is unavoidable.

As you might expect, the path or URL can specify a file or it can be a Qt resource that is stored in the application itself.

You can add additional custom selectors programmatically or via an environment variable. This could be used, for example, to load different sized image files depending on whether the application is running on a smart phone or a tablet.

QQmlFileSelector

When using QML, the C++ class QQmlFileSelector (2) creates a file selector that can be applied to a QML engine. The engine will then use the file selector facility when specifying QML files and asset paths (e.g. for Image elements).

When you instantiate a QQmlFileSelector, you pass a QQmlEngine object to its constructor. You can also add custom selectors, in the same way as with QFileSelector.

QQmlApplicationEngine

If you use the QQmlApplicationEngine (3) class for instantiating QML components in your application, it is even easier to use file selectors because it automatically creates a QQmlFileSelector and any URLs in your QML code will make use of file selectors.

If you use Qt Creator's New Project wizard to create a Qt Quick application, the code it generates will use a QQmlApplicationEngine, so you can use the file selector facility without making any additional changes to your code.

Code Example

While it may not immediately be clear from reading the Qt documentation, it is very easy to use the file selector facility. I wrote a short example program (4) to illustrate using it in QML.

I used Qt Creator to generate my Qt Quick application, so the code it produced creates a QQmlApplicationEngine instance.

My example needed no changes to the main.cpp it created, but I added a couple of lines to create a QFileSelector and display the default file selectors just for illustrative purposes.

Here is the complete listing for main.cpp:

#include <QDebug>
#include <QFileSelector>
#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    QFileSelector sel;
    qDebug() << sel.allSelectors();

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

The QML program, main.qml, uses the file selector mechanism and loads one of four files based on the platform in use. They are stored as resources in the following directory structure:

  main.qml
  +linux/main.qml
  +mac/main.qml
  +windows/main.qml

For the example, each file is similar but indicates the platform it is on and loads an image, which also makes use of the file selector mechanism. Here is the +linux/main.qml file:

import QtQuick 2.3
import QtQuick.Window 2.2

Window {
    visible: true

    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }

    Column {
        Text {
            text: "This is the Linux QML file."
        }

        Image {
            source: "qrc:/image.png"
        }
    }
}


The other main.qml files are similar and only differ in the text that is displayed.

The image files are stored as resources in the following structure so that a platform-specific image is loaded:

  image.png
  +linux/image.png
  +mac/image.png
  +windows/image.png

When run on my Ubuntu Linux desktop system, the application displays the default selectors, which correspond to the locale and several different selectors for the platform:

  ("en_CA", "unix", "linux", "ubuntu")

On a Windows 10 system it displayed:

  ("en_CA", "windows")

The application shows text and an image that indicates that the platform-specific QML and image files were loaded. Screen shots of the example running on Linux and Windows are shown below:

     

Note that the example can be changed to use files rather than Qt resources, simply by removing the qrc:/ prefix in the URLs.

Note also that if you run the main.qml program using the qmlscene program, internally it does not create a QQmlApplicationEngine, so the file selector mechanism will not work in this case.

Summary

QML doesn't have a conditional compilation facility like the C++ pre-processor. I haven't yet heard a compelling argument for a real use case that requires this feature. One example might be in order to write code that could use either the Qt Quick Controls 1 (for desktop) or Qt Quick Controls 2 (for embedded) from the same QML source file, which would require using different import statements.

For many purposes, such as conditionally loading images and other resources, the file selector mechanism provides a clean solution, both for C++ and QML programmers.

References

  1. QFileSelector, Qt documentation website, last accessed 6 Nov 2015, http://doc.qt.io/qt-5/qfileselector.html
  2. QQmlFileSelector, Qt documentation website, last accessed 6 Nov 2015, http://doc.qt.io/qt-5/qqmlfileselector.html
  3. QQmlApplicationEngine, Qt documentation website, last accessed 6 Nov 2015, http://doc.qt.io/qt-5/qqmlapplicationengine.html
  4. Source code for example application, last accessed 6 Nov 2015, ftp://ftp.ics.com/pub/pickup/FileSelector.zip