How to Be Cute on All Desktops with Qt

Qt always has been about cross-platform. By providing a rich API that isn't tied to a specific platform, Qt can be both intuitive to use and innovative.
Accessing Drives

What we've discussed so far affects only the visuals. You can try all of this from within Qt Designer or QtCreator without writing a single line of source code (not counting the stylesheets). But, cross-platform programming is more than just look and feel. For instance, how do you traverse a filesystem on multiple platforms without providing unique source code for each platform?

Qt provides classes for this. For example, the following short snippet shows the directories contained in the root directory of each drive of a given computer. On a Windows machine, it lists the drives one by one, while on UNIX machines, it lists only the root drive / (note that foreach is a Qt-supplied C++ macro):

foreach( QFileInfo drv, QDir::drives() ) {
  qDebug( "%s contains", qPrintable(drv.absolutePath()) );
  foreach( QString name,
           drv.absoluteDir().entryList( QDir::Dirs ) ) {
    qDebug( " %s", qPrintable(name) );

By using the QDir class to access the filesystem, you can do so in a platform-independent manner. The class contains static functions for common entry points, such as drives, the user's home directory, the current directory, as well as the system's directory for temporary files.

Handling Text

Another common source of cross-platform problems occurs at a much more basic level—the encoding of text and data. Qt provides a custom class for handling text strings called QString. It provides Unicode representation across all platforms. The string class itself can convert to and from UTF-8, ASCII and Latin1. It also can convert to and from most other string representations using text codecs. Qt comes with a variety of codecs, but it also is possible to create custom codecs to handle special cases.

When reading and writing text to and from files, the encoding is respected by using the QTextStream class. This class provides a stream interface based on the << and >> operators. It usually autodetects the encoding, but you can use the setCodec function to force it to a specific setting. To illustrate, the following short snippet of code reads a line from a text file encoded as UTF-32 on a big-endian system:

QTextStream stream( &file );
stream.setCodec( QTextCodec::codecForName("UTF-32BE") );
QString myString = stream.readLine();

Which End Is First?

Speaking of endianness, this is often an issue that occurs when dealing with cross-platform code. The issue with endianness is that when you write binary data, such as a 32-bit value (four bytes), you can choose to write the bytes in two different directions: left to right or right to left, aka big endian and little endian.

The default order for writing bytes depends on the endianness of the system on which the program is running. Some architectures, such as IA32 and the VAX, use little endian. Others, such as PowerPC, ColdFire and SPARC, use big endian. Others still, such as ARM, MIPS, IA64 and Sparc V9, are able to do either (although which one is used often has to be hard-wired into the system when the hardware is built). Systems based on most of these architectures are commonly targeted by Qt.

To ensure cross-platform compatibility for binary data, you need to specify the order explicitly when writing and again when reading. By using a QDataStream to handle binary file formats, endianness no longer is an issue. You simply specify the byte order to use and then use the stream operators, and it just works.

The snippet of code below shows this. It also contains the setVersion function, letting you specify which version of Qt's encoding of complex data types you want to use. For instance, if the internal representation of colors changed between version 2 and 4 of Qt, by specifying an older version, you still can read and write data in the old format using the same stream class. This is something that comes in handy when having to handle old legacy file formats from modern code:

QDataStream stream( &file );
stream.setByteOrder( QDataStream::BigEndian );
stream.setVersion( QDataStream::Qt_4_0 );
int value;
stream >> value;

Storing Preferences

When handing user preferences, Windows has the registry. UNIX systems usually rely on hidden directories, one for each application. OS X has an XML format for preferences. This works fine for users. They usually do not rely on being able to move their preferences between their computers, especially if they do not use the same operating system. From a software developer's perspective, the situation is different.

To resolve this, Qt provides the QSettings class. It provides access to each platform's preferred method. It also can be used to create and read INI files outside the platform's system that can be moved between platforms by the users.

The QSettings class relies on the name of the application and the application provider. Then, you simply use the setValue and value functions to write and read. The returned value is of the type QVariant. This type can be used to hold any type of data. The basic types, such as integers, are handled directly. More complex types, such as QColor, rely on the data stream operators:

QSettings settings( "The App Company", "The App" );
int v = settings.value("myInt").toInt();
QColor c = settings.value("myColour").value<QColor>();

Many more issues arise when moving code between platforms. Qt's solution is to provide a Qt API. This API removes almost all traces of specific platforms, while trying to support all functionality on each of the platforms involved. More complex cases than the ones shown here involve multithreading, database access, networking and so on.


Johan Thelin is a consultant working with Qt, embedded and free software. On-line, he is known as e8johan.