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.

In Part I of this series [LJ, September 2002] we created the GUI of a tiny text editor using Qt Designer. Now, we add missing functionality and translate the application into other languages by using our favourite editor along with make and the g++ C++ compiler, and use Qt Linguist as an attractive working environment during translation.

We use the new qmake utility to write the Makefile for our Qt application; qmake outdates tmake, a Perl tool widely used with older Qt versions.

Our editor GUI already closes windows and the entire application, but we still lack a user dialog that asks whether the old data should be saved or discarded or whether the user wants to stay with the old file. The New, Save and Save As actions do nothing right now, but everything else was completed. The GUI already supports copy, cut and paste, undo and redo. It can switch font characteristics to italic, bold, underlined and any combination of the three. These QTextEdit actions already can be tested in the Qt Designer preview. The About entry in the Help menu will be fully functional as soon as we compile the GUI into a C++ program.

We could write the remaining functions using Qt Designer's built-in code editor. As there are no means to compile the project within the Designer, however, using one's favourite text editor is faster.

Rolling out a Project

Now that we have the user interface ready in a ui file for the interface description, and a ui.h file containing the code already written, it's time to implement the remaining functionality. First we have to convert the XML to C++ with the User Interface Compiler, uic, but using qmake we don't need to worry about this detail. Let's feed it the project file generated by the Designer:

qmake -o Makefile

A Makefile is created with a subdirectory named .ui that is supposed to store the C++ code generated from the ljeditor.ui and ljeditor.ui.h files. If this fails and you are asked to set the QMAKEPATH, set this variable to the mkspecs subdirectory of your Qt installation, which describes your operating system and compiler. For example:

Depending on where you have installed Qt, make sure that the search path contains the $QTDIR/bin directory of the used Qt version.

To generate the C++ files simply type:

make .ui/ljeditor.h
make .ui/ljeditor.cpp

If problems arise, check the environment variables QTDIR, PATH and LD_LIBRARY_PATH. The first one should point to the directory parenting the Qt 3.0 subdirectories lib and include. The directories where uic, qmake and designer live should be included in the path, and $QTDIR/lib should be added to the linker path.

Editing the two generated files means the changes are lost when the ui file is moved to the Designer and a new conversion round becomes necessary. So, we derive a subclass from ljeditor and add our changes to it instead of to ljeditor.

uic offers the command-line switches -subdecl classname and -subimpl classname to build the appropriate code skeletons. With

uic -o editor.h -subdecl Editor .ui/ljeditor.h \

we obtain editor.h, the header file for the new Editor subclass. On the other hand (mind the argument header file), the following line creates the implementation skeleton in editor.cpp:

uic -o editor.cpp -subimpl Editor editor.h \
These new files need to be added to the project file by adding two lines:
HEADERS += editor.h
SOURCES += editor.cpp
to Or you could start Qt Designer, open the project file and add them via Project®Add File. Remember to set the File type to C++ Files, otherwise the file dialog won't find them). If you like the text editor included in the Designer, you even might edit them there.

The subclass code generated by uic always includes skeletons for all functions present in the parent class. Thus, it's a good idea to delete the declarations and function skeletons of all functions that you don't plan to re-implement in the subclass. Remove the lines:

void fileExit();
void helpAbout();
void fileClose();

from editor.h and the relevant code skeletons from editor.cpp, which look like:

void Editor::fileExit()
   qWarning( "Editor::fileExit() "
             "not yet implemented!" );
For a complete program we still need a main() function. We may write it by hand, but Qt Designer can help you a little. Choose File®New®C++ Main-File (main.cpp) from the menu and the subsequent dialog.

The dialog shown in Figure 1 asks us to name the main file (we choose ljedit.cpp) and the main widget. Designer does not provide the Editor subclass here; thus we don't really have a choice and choose ljeditor.

Figure 1. Configuring the main File

Choosing this name means we have to correct the generated code. Instead of ljeditor.h, we include editor.h, and instead of creating a new object of the ljeditor class, we need an Editor one. Now our ljedit.cpp should look like this:

#include <qapplication.h>
#include "editor.h"
int main( int argc, char ** argv )
    QApplication a( argc, argv );
    Editor *w = new Editor;
    a.connect( &a, SIGNAL( lastWindowClosed() ),
               &a, SLOT( quit() ) );
    return a.exec();

As in every usual Qt main(), we create a QApplication object, hand it possible command-line arguments (argv) and create the Editor widget w. Then we show it to the world and enter the application loop with a.exec().

You may notice that a a.setMainWidget( w ); line is missing that defines the main widget of the application (we'll explain this later). However, without a main widget, the application will not quit when the last window is closed. So, we have to connect the application object's signal, lastWindowClosed(), to its quit() slot.

make and a subsequent ./lj-article in the project directory should result in a running, yet not fully functional text editor. If you wish to call the binary by something other than the project file's base name, add the line TARGET = ljedit to