Coding between Mouse and Keyboard, Part II

Version 3.0 of the Qt toolkit promises to make GUI programming even easier. In the previous issue we created the GUI of a tiny text editor using the Qt Designer. This time we add missing functionality and translate the application into lanugages other than English.
Time for Coding

Basically, we won't do much more than substitute the qWarning()'s saying “not yet implemented” in editor.cpp with reasonable functionality, using our favourite text editor. If the Designer's built-in code editor suits your needs, you don't have to worry about subclassing, but compiling and debugging becomes more painful, so we decided against it.

Listing 1 shows all of editor.h; Listing 2 is an excerpt from editor.cpp. All listings are available from the Linux Journal FTP site [].

Listing 1. editor.h

Listing 2. editor.cpp

Apart from the four slots left to implement, we re-implement the closeEvent(), a function that's called when a widget is closed, because we want the user to confirm the closing of an editor window so as not to unexpectedly lose data. For clarity reasons, the relevant user dialog is implemented in a separate function, saveAndContinue().

Also, we introduce two class variables: fileName, to store the filename of the currently edited file, and editField, to hold a copy of the QTextEdit widget. Providing these variables with initial values is the only task for the Editor constructor.

Another easy task is to implement the fileNew() slot. It creates a new Editor window and shows it. This is why we don't make the first editor window the application's main widget: if we did, closing the first editor window would make all other windows close too.

But what happens when the user closes a window or the entire application? The re-implemented closeEvent() calls saveAndContinue() with one argument: the message that should be displayed to users when they decide to abort the closing process (line 159). As with all text strings, we embrace it with tr() to make localization possible. If saveAndContinue() returns a “yes, continue”, the close event is accepted; otherwise the event is dismissed.

If the user has chosen a filename for the editor content or entered some text into the QTextEdit widget, it is safe to assume that he or she might want to keep the work. In this case saveAndContinue() brings up a message box using the filename as the window caption that asks: Save filename? Three reply buttons are provided: Yes, No and Cancel. The slightly copious notation with the %1 placeholder for the content of fileName is important for internationalization: other languages order words differently, and the translator must have a chance to place the filename elsewhere, say the beginning of the phrase.

If the Yes button was pressed (line 181) the editor content is saved under the given name. If no filename has been set, fileSaveAs() asks for a filename before storing. If the answer is No, all unsaved changes are lost. The user is informed about this in the status bar for 2,000 milliseconds (line 187). The Cancel button shows the abort message in the status bar, and saveAndContinue() returns a “no, don't continue” (line 194). In the case where no filename and no editor content are present, no message box appears. The return value then is a TRUE, “do continue anyway” (line 201).

saveAndContinue also is called from the fileOpen() slot. If the editor window contains text or if a filename was defined previously, the user has the chance to save it prior to opening another file in this window.

Whether saved or not, clearing the editor content and resetting the window caption makes the user aware that the old editor content is gone. With the help of a file dialog presenting the content of the current (.) directory, the user gets the chance to choose a new file.

All over the World

With the tr() functions around each text string, we're able to release the program in other languages. All this step requires are some modifications of the main() function and the actual translation. The latter is easily accomplished using Qt Linguist (Figure 2). But before you or another translator gets started, we have to obtain the text strings to be translated.

First we add another variable, TRANSLATIONS, to the *.pro file:

TRANSLATIONS = ljedit_de.ts \

This example states that the application is bound for translation into German (ljedit_de.ts) and Norwegian (ljedit_no.ts). It is quite important that the base name of the file containing the translation ends with a locale abbreviation like “de” or “no”.

Once you have selected the desired translation language(s) for the application, typing lupdate creates two XML files that can be loaded into linguist. The translator now translates each string and switches the yellow question mark to a green tick when completed. The task has been fulfilled as soon as the Scope window (on the left-hand side of Figure 2) reveals that all of the classes have been translated fully.

Figure 2. Translator's Friend: Qt Linguist

A professional translator will compile phrase books of common phrase-translation pairs before actually starting with the translation of a program. As Linguist currently doesn't let users copy translated strings from a program directly into phrase books, the effort of compiling a phrase book is too high for those who do not translate frequently. This is a pity because phrase books support consistent usage of phrases within an application or even throughout an entire range of programs. However, even without phrase books, Linguist offers quality control switches. Validation®Accelerators—if on—ensures that accelerator keys (marked with an &) are not forgotten under translation. Validation®Ending Punctuation, on the other hand, checks whether the translated string ends with the same punctuation mark as the original.

When the last translated phrase is saved, File®Release... compiles the translations into a *.qm binary file that can be used by the program. To release an entire set of translated *.ts files, lrelease can be used instead. If the code is changed afterward, a fresh lupdate run integrates the relevant changes with the *.ts files listed in the project file, and lrelease updates the binary version.

Listing 3 shows the internationalized main(). New is the inclusion of qtranslator.h and qtextcodec.h. Depending on the locale used (as defined by the environment variable LANG), the base name of the translation file is compiled (line 13). If LANG is set to, for example, de or de_DE, the application looks for a file named ljedit_de or ljedit_de.qm in /local/lib. If it can't be found there, the original language version is used. Unfortunately, there is no simple way to search multiple directories and/or avoid hard-coded directory names.

Listing 3. ljedit.cpp (Internationalized Version)

If a translation file is found, the QTranslator object loads it, and it is installed to serve the application (line 15). A German version of ljedit is shown in Figure 3.

Figure 3. ljedit.cpp (Internationalized Version—German)

Patricia Jung ( has been a system administrator, technical writer and editor, and as such, is happy to have the privilege of dealing with Linux/UNIX exclusively.