Programming with Qt, Part 3

In this third and final installment on introductory Qt programming, let's take a look at two graphical development tools that are part of the Qt framework: the GUI editor Qt Designer and the foreign language translation tool Qt Linguist.

In this third and final installment on introductory Qt programming, let’s take a look at two graphical development tools that are part of the Qt framework: the GUI editor Qt Designer and the foreign language translation tool Qt Linguist.

Visual GUI Design Using Qt Designer

Qt Designer, shown in Figure One, is a visual graphical user interface editor, meaning that it allows you to design dialog boxes, wizards, and other interface elements in a WYSIWYG (what-you-see-is-what-you-get) fashion. It opens up to the standard two-panel view, familiar from most integrated development environments (IDEs), with three browser windows on the left-hand side and a workspace on the right. The three browsers show (from top to bottom) the file hierarchy, the parent/child containment hierarchy of widgets, and finally the properties of the currently active widget.

FIGURE ONE: Qt Designer, a visual graphical user interface builder

Besides all of the standard widgets, Qt’s layout managers are also available in the top toolbar. Layout managers provide functionality for the spatial arrangement of widgets, including proper relative positioning and resizing behavior.

Furthermore, the designer provides facilities to handle signal/slot management graphically. Selecting the red and blue “connection tool” in the toolbar and dragging from one widget to another opens the “Edit connections” dialog, also shown in Figure One. In the example, the clicked() signal of the Quit-Button is being connected to the quit() slot of the SliderDialog dialog box being created. Similarly, the valueChanged() signal on the QSlider widget can be connected to the display() slot on the QLCDNumber display directly within designer using only the mouse.

Interestingly, QtDesigner does not generate the source code for the developed user interface elements itself. Rather, it saves the information about the widgets used, the layout, and the signal/slot connections made in XML files, which are commonly given the extension .ui. A separate command-line tool, uic (User Interface Compiler), generates the actual C++ source code. Fortunately, the Makefile generated by qmake (for more information about qmake, see last month’s column, available online in March 2004 at http://www.linux-mag.com/2003-12/compile_01.html) already contains all the proper targets to automate the required calls to uic, making them almost invisible to the developer.

uic is a code generator. Experienced developers are likely familiar with a common problem when using code generators: how to make changes and additions to the generated code, while ensuring that edits are not lost if the code generator runs again. A popular solution in object-oriented projects is the “re-use through inheritance” idiom: the generated class is immediately subclassed and all customizations and additions are made to the subclass only. When the code for the superclass is re-generated, the code for the subclass is left untouched and will continue to work with the newly generated code, provided that the interface of the superclass has not changed. The base classes are never instantiated and the application uses only the derived classes. Developers rely on some naming convention (such as appending _Base to the names of generated classes) to distinguish generated superclasses from handwritten subclasses.

Qt has always supported this idiom and continues to do so, but offers another approach that utilizes the C++ pre-processor. Basically, an additional, empty source file with the extension .ui.h is written by the Designer. All programmer customizations are made to this file, which is then #included into the .cpp file generated by uic.

This sounds complicated, but is actually a very simple idea: the source code for the class corresponding to the designed GUI element is split into two files, one generated by uic, while the other is edited by the actual developer. The C++ pre-processor is then used to bring both of these files together before the actual compiler sees them. This approach avoids the proliferation of classes that result from the “re-use through inheritance” idiom, and prevents the associated hassles of keeping the classes’ interfaces in synch. This comes at the price of reliance on the C++ pre-processor with its very primitive file inclusion mechanism.

In any case, the fact that the .ui.h file is written together with the .ui file by Designer implies that developers can make changes to the source code while working in Designer. This makes it possible, for instance, to add new signals and slots to GUI elements as they are being laid out. The “Edit Connections” dialog has an “Edit Slots” button, which allows the developer to create a new slot and connect a signal to it at the same time.

In Figure One, you can also see the code for the .ui.h. file with the new quit() slot that was added manually.

As a visual GUI editor, Qt Designer is a very useful tool, and it has come a long way since the previous (1.0) version. Nevertheless, it does not seem quite as polished as the rest of the Qt development system.

For instance, the wizards to create a new widget or dialog box do not properly prompt for the name of the new class that is being created — the developer has to edit the corresponding property in the property editor manually. This information is then not propagated to the name of the corresponding .ui.h file. Since the #include statements for this file are generated by the uic, one can easily end up in a situation where the project will not compile because of file naming conflicts.

Teach your Application a Foreign Language with Qt Linguist

One problem most programmers would rather ignore is how to make an application ready for the international marketplace. For an application with a graphical user interface, internationalization means translating all user-visible text — all buttons, menu entries, captions, labels, and more — into one or more additional languages. Moreover, keyboard shortcuts must be “translated” as well: in another language, another letter may be more appropriate as the shortcut then the one in the original design.

Qt’s QString class, which is used throughout the framework, uses Unicode internally and therefore supports most character sets, as well as bi-directional writing (left-to-right text, as well as right-to-left). The QTextCodec class can be used to convert Unicode to any desired local character encoding. Therefore, the technical infrastructure exists to support (almost) any language on the planet.

But how to actually perform the translation on the fly? Qt ships with a set of tools to support the creation of foreign language versions of an application.

* lupdate is a command-line tool that extracts all user-visible text from the source code.

* linguist is a graphical application that displays the extracted text (if necessary together with some suitable “context”), allowing a foreign language expert to enter the proper translation without needing access to the source code.

* lrelease is another command-line tool and it transforms the translated text file into a highly compressed file format, optimized for quick lookups of the translated text at runtime of the application.

To improve both productivity as well as consistency of the translation process, linguist also maintains a phrase book of text snippets and their translations for easy re-use.

For example, to create a German version of the “Hello, World” application created in Part One of this series (code is available from http://www.linux-mag.com/downloads/2003-11/compile), we’d use the code shown in Listing One.

LISTING ONE: An internationalized “Hello, World”

#include <qapplication.h>
#include <qvgroupbox.h>
#include <qlabel.h>
#include <qpushbutton.h>

int main( int argc, char *argv[] ) {
QApplication app( argc, argv );

QTranslator trs( NULL );
trs.load( “tt_de”, “.” );
app.installTranslator( &trs );

QVGroupBox dialog;
QLabel text( QLabel::tr(“Hello, World!”, “Greeting text”), &dialog );
QPushButton button( QPushButton::tr(“Quit”), &dialog );

QObject::connect( &button, SIGNAL( clicked() ), &app, SLOT( quit() ) );

dialog.move( 200, 100 );

app.setMainWidget( &dialog );
return app.exec();

Two things are new: first, we create an instance of QTranslator, register it with the QApplication object, and load a file (tt_de) that contains the German translations from the current (“.”) directory. (Rather then hardcoding the name of the file, we could construct it based on, say, the current locale, which is available through QTextCodec::locale().)

Furthermore, all user-visible text is now filtered through calls to the tr() function. All classes derived from QObject — that is, any class that participates in Qt’s signal and slot mechanism, including all widgets — provide a tr() function. tr() performs a lookup for the translation of the text entered as its argument, or returns the original text if no translation is found. The second parameter to tr() is optional, but is displayed as a hint to the translator in linguist.

A word on the ugly syntax used to call tr() in the code snippet above: since main() is a standalone function, you have to specify which class’s tr() function to use. This is not necessary when calling tr() in a member function of a class derived from QObject, because then tr() is called through the implied this pointer. In most cases, the code simply becomes something like QLabel text( tr(“Hello, World!”), parent );.

We need to specify the translation files we want to generate, by adding a line similar to the following to the project (simple.pro) file generated by qmake:


Now, the following command line…

% $QTDIR/bin/lupdate simple.pro

… creates the file tt_de.ts. This is an XML file containing all user visible text from the source code, which can then be edited using linguist, as shown in Figure Two. Finally, run…

% $QTDIR/bin/lrelease simple.pro

… to create the binary tt_de.qm file. If you now restart the “Hello, World” application, it’s become a “Hallo, Welt!” program. Notice that recompilation wasn’t needed to make the translations become active. The lookup of the new text is performed at run-time.

FIGURE TWO: Translations can be edited in linguist

Qt in Perspective

To conclude this three-part series on programming with Qt, here are some reflections on Qt as a development framework.

The variety and selection of GUI widgets is remarkably rich — especially when you consider that Qt is also portable among Unix, Windows, and the Macintosh. The documentation is also complete and remarkably well written.

At a higher level, Qt is attractive because it tries to offer a complete system or framework to the developer, not a mere library. Tools such as qmake help get a project off the ground quickly and abstracts away most of the Qt-specific tasks without forcing you to adopt the library vendor’s entire development process.

The addition of non-GUI features to the Qt libraries is also attractive. Threading, XML handling, regular expressions, network, and file and database access are now all part of the framework (at least in the Enterprise and Free Editions), promising a “one-stop shopping” experience for almost all C++ projects. Time will tell whether the consistent design principles that have contributed to Qt’s success will survive the addition of these new areas of functionality. The next version of Qt, Qt 4, which is expected in 2004, breaks the library up into different components, more cleanly separating its GUI element parts from the “enterprise application” features.

Finally, Qt’s liberal use of code generators to add non-trivial extensions to the basic language is an example of the ongoing shift in the perception of what a “compiler” can do. Like Aspect-Oriented Programming and some of the more creative uses of the C++ templating system, Qt’s language extensions demonstrate a trend towards a new phase in software development where an additional processing step is introduced between the actual implementation and the final compilation, adding value to the application and reducing the burden on the developer.

Of course, we could cover only a few highlights of programming with Qt in this series. There are a few books available on Qt programming, the most well-known being “Programming with Qt, Second Edition” by Matthias Kalle Dalheimer. Qt comes with full documentation in HTML format, which includes the full API reference, a number of tutorials, and several in-depth overviews on special topics (such as internationalization and the signal/slot mechanism). Finally, Trolltech publishes a quarterly newsletter on Qt-related topics. Details can be found on Trolltech’s web site at http://www.trolltech.com.

Philipp K. Janert, Ph.D. is a server programmer and project manager. He maintains the beyondCode.org website and his writings have appeared on the O’Reilly Network, IBM developerWorks, and in IEEE Software. Contact him at janert@ieee.org.

Comments are closed.