Mr. Sweet teaches us how to write an application for the KDE desktop—for the experienced GUI programmer.
The stated goal of the K Desktop Environment, KDE, is to provide a free, user-friendly desktop for Linux/UNIX systems. The project’s participants began by providing a window manager (kwm/kpanel) and a file manager (kfm) and retrofitting popular X applications (e.g., Ghostview, xcalc, ezppp), giving them a common look and feel based on the Qt widget set from Troll Tech. As time has passed, the KDE libraries (which provide UI elements and file management services) have grown in functionality so that developing KDE compliant applications is not only simple, but enticing.
The program presented in this article, khello, should provide you with a good foundation for writing your own KDE application, or providing a K-UI for your favorite UNIX application. To compile the program, you need to install Qt 1.3 and KDE. For details, see http://www.kde.org/beta1.html. Although KDE has just been released in only beta form, it is quite stable and usable. It has, in fact, been the primary desktop on my home computer since well before the beta release (alpha releases and frequent development “snapshots” have been available for some time).
Like so many before me, I have chosen to present a program which says, “Hello World!” to its user. Nevertheless, the basic elements of a KDE application are present: a menu bar, a toolbar, and an “about” box, as well as the functionality provided by KApplication and KTopLevelWidget. The UI components are provided by classes in libkdeui; KApplication belongs to libkdecore.
KApplication is the base class from which KDE applications are derived. It handles simple session management, access to the application icon, the help file, and configuration and locale information. The KApplication constructor takes argc and argv as arguments and strips away anything used by X11 or by KApplication and updates argc so that you can go on to process your program’s options. The macro kapp is defined as KApplication::getApplication(). This returns a pointer to the KApplication object. You can use kapp anywhere within your program after you have created a KApplication. So, of course, you can only create one KApplication object. The GUI is started with app-exec() and returns when app->quit() is called. Notice that your instance of KApplication need not be told about your KTopLevelWidget. In our case, this means KHelloTW which is derived from KTopLevelWidget. There can be only one KTopLevelWidget, and it informs KApplication of itself.
The basis of the user interface is provided by KTopLevelWidget. It will manage a menu bar, multiple toolbars and a status bar, you don’t need to resize or position these objects; just create them and fill with the appropriate information. (Some session management is also handled by KTopLevelWidget, but this topic won’t be discussed in detail here.) You should derive a class from KTopLevelWidget, as KHelloTW is, and set up the UI (in the constructor, for example) before calling show(). This way, your window will not appear and then “flash” as its child widgets are created and/or rearranged. You must call show() for your KTopLevelWidget before calling app->exec(), since it isn’t shown automatically.
Listing 1. khellotw.cpp
#include <qkeycode.h> #include <kmsgbox.h> #include <kstdaccel.h> #include "khellotw.moc" KHelloTW::KHelloTW (void) { /** We'll get the standard KDE accelerator key combinations from this. */ KStdAccel *kkeys=new KStdAccel(kapp->getConfig()); /** Create the pulldown menus for the menubar. */ file = new QPopupMenu (); file->insertItem ("&Quit", this, SLOT (slotQuit()), kkeys->quit()); help=kapp->getHelpMenu(TRUE); help->insertSeparator(); help->insertItem ("&About KHello...", this, SLOT (slotAbout())); /** Set up the menubar. */ menubar = new KMenuBar (this); menubar->insertItem ("File",file); menubar->insertSeparator (); //This \ //pushes the Help menu to the far \ //right when KDE is in Motif widget mode menubar->insertItem ("Help",help); /** Set up the toolbar. */ toolbar = new KToolBar(this); QString buttonpicture; buttonpicture = kapp->kdedir()+"/share/toolbar/exclamation.xpm"; toolbar->insertButton (QPixmap (buttonpicture.data()),0, SIGNAL (released()), this, SLOT (slotHello()),TRUE, "Press this to say hello"); /** This label will fill the rest of our window (the part not occupied by the menubar or toolbar). */ label=new QLabel (this); label->setFont (QFont ("Helvetica",24)); label->setAlignment (AlignCenter); /** Tell KTopWidget about our menubar, toolbar, and label. */ setMenu (menubar); addToolBar (toolbar); setView (label); /** Drag and Drop. URL's can be dragged from KFM and dropped onto our QLabel. The URL will be displayed. */ dropzone = new KDNDDropZone (label, DndURL); connect (dropzone, SIGNAL (dropAction(KDNDDropZone *)), this, SLOT (slotDropped (KDNDDropZone *))); } void KHelloTW::slotQuit() { close(); } void KHelloTW::slotAbout() { KMsgBox::message (this, "About KHello", "KHello\nCopyright (C) 1997\nBy David Sweet\n\ dsweet@physics.umd.edu"); } void KHelloTW::slotHello() { label->setText ("Hello world!\n"); } void KHelloTW::slotDropped(KDNDDropZone *dz) { QString url; url = dz->getURLList().first(); label->setText (url.data()); } void KHelloTW::closeEvent (QCloseEvent *) { kapp->quit(); }
Let’s look at khellotw.cpp (Listing 1) to get an idea of how to work within KDE and Qt. First we create the file menu. We’ve declared “file” as a pointer to an object of type QPopupMenu and created the object with “new” so that the file menu will be available for the life of KHelloTW (which is, in this case, also the life of the program). The method insertItem places an item on the menu. In this case, we have only “Quit”. The ampersand in the argument &Quit tells Qt to underline the letter Q and, when this menu is visible, to allow the user to select “Quit” by pressing Q. The last argument kkeys->quit() says the user can press the key combination defined in KStdAccel::quit() (ctrl-Q) at any time to exit the program (this is called an “accelerator key”). The class KStdAccel contains various standard KDE accelerator keys. We use these definitions when creating menus to be consistent with the KDE look and feel. The other two arguments tell Qt which method (slotQuit()) to call when “Quit” is chosen and within which instance of the class to call that method. This system for getting messages from the GUI is called “Signals and Slots”. If we use the Qt precompiler (called moc for Meta-Object Compiler), we can use these signals and slots as if they were a natural part of C++.
Using moc, we get three new C++ keywords: signal, slot and emit. In khellowtw.h, several methods are declared under the heading “public slots:”. These can be called directly, like normal methods, but they can also be connected to signals. Signals are emitted whenever any GUI event occurs. All the slots which were connected to that signal are called at this point, and may even be called with arguments so that information can be passed between classes. (For details on how to use signals and slots, and for a definition of emit, see the Qt documentation.)
If we look at the toolbar->insertButton line in khellotw.cpp, which places a QPushButton on the toolbar, we see
SIGNAL ((released()), this, SLOT (slotHello()))
This tells KToolBar to connect the signal released(), which is emitted by QPushButton when the button is released after being pressed down, to the slot slotHello(), which puts the phrase “Hello world!” up in our window. Note that we chose the QPushButton signal released() over pressed() because the user expects the action to occur when he releases the button, not while holding it down. You could try changing this to see the difference in the feel of the interface.
Listing 2. main.cpp
#include "khellotw.h" int main (int argc, char *argv[]) { //KApplication *app = new KApplication (argc, //argv, "khello"); KApplication app (argc, argv, "khello"); KHelloTW hello; hello.show(); return app.exec(); }
The moc precompiler generates some C++ code from your header file and creates a .moc file. This .moc file should be included once and only once in one of the files that implements that class. Notice that khellotw.cpp includes khellotw.moc but main.cpp (Listing 2) includes khellotw.h (Listing 3).
Listing 3. khellotw.h
//#include <qpopmenu.h> //#include <qlabel.h> #include <kapp.h> #include <ktopwidget.h> #include <ktoolbar.h> #include <kmenubar.h> #include <drag.h> class KHelloTW : public KTopLevelWidget { Q_OBJECT; public: KHelloTW (void); void closeEvent (QCloseEvent *); private: QPopupMenu *file, *help; KToolBar *toolbar; KMenuBar *menubar; QLabel *label; KDNDDropZone *dropzone; public slots: void slotQuit(); void slotAbout(); void slotHello(); void slotDropped(KDNDDropZone *); };
To give KHello the look of a KDE application, we have added a KToolBar and used one of the pixmaps supplied by KDE for the button. To construct the path to the button pixmap, we’ve used kapp->kdedir() rather than hard coding the path, because users may install KDE in different places in their file systems. For the rest of the path, refer to the KDE File System Standard (or KFSSTD at http://www.kde.org/fsstnd.html). There is also a help menu on our menu bar. KApplication provides you with a base help menu in kapp->getHelpMenu(). This menu includes “About KDE…” and, optionally, “About Qt…” entries which tell the user about the underlying software, and a “Help” entry which will start the KDE help viewer (kdehelp) with the argument
kdedir()+ "/share/doc/default/khello"
which refers to the default KDE help file/directory. If you’d like to see this in action, simply create the directory, place an HTML file called khello.html in it, and choose “Help” from the khello Help menu. When invoking help this way, KApplication uses the string passed to its constructor as your application’s name (in this case, khello). So there is no need to tell KDE where your help file is; it is determined by the KFSSTD and is /share/doc/default/appname/appname.html.
Listing 4. Makefile
QINC = -I/usr/lib/qt/include KINC = -I/opt/kde/include INCS = $(QINC) $(KINC) LIBPATHS = -L/usr/X11R6/lib -L/opt/kde/lib QLIB = -lqt KLIB = -lkdecore -lkdeui -lkfm XLIB = -lX11 -lXext LIBS = $(LIBPATHS) $(KLIB) $(QLIB) $(XLIB) CC = c++ -DSTDC_HEADERS -DHAVE_UNISTD_H $(LIBS) CCo = c++ -c -DSTDC_HEADERS -DHAVE_UNISTD_H $(INCS) khello : main.o khellotw.o $(CC) -o khello main.o khellotw.o main.o : main.cpp khellotw.h $(CCo) main.cpp khellotw.o : khellotw.cpp khellotw.moc $(CCo) khellotw.cpp %.moc: %.h moc $< -o $@ clean: rm *.o *.moc
Similarly, you can give your application an icon (which will appear in the task bar next to your application) by placing an appname.xpm file in /share/icons/. We’ve added a separator and “About KHello…” to the bottom of the menu. Your “about” box should display at least the title, author and contact information (for bug reports, feature requests and general praise of your work). KHello’s simple “about” box is a KMsgBox. This is a modal dialog box, meaning that it is the only window of your program which will respond to user input during the time it is shown. When the user selects “OK” the box disappears, the function call returns and the program continues. It is not necessary to create an instance of KMsgBox for simple dialogs. The method KMsgBox::message() is static and can be called directly as is done in KHelloTW::slotAbout().Application communication via drag-and-drop is an integral part of a cohesive desktop. If your application works with data files, it should accept URLs dragged from kfm (the KDE file manager) and process them accordingly. This is simple to do and khello demonstrates this by displaying any URL that is dropped on it. To accept URL drop events, declare a widget a “drop zone” by creating a KDNDDropZone object and connecting its dropAction() signal to a slot which will process the event. The KDNDDropZone method called getURLList() will return a QStrList (a Qt utility class which manages a list of strings) containing one or more URLs which were dropped in a single drop event. The function slotDropped() sets the text of “label” to the first URL in the list.
There are several ways to shut down an application. The user could choose “Close” from the system menu (a right-click on the title bar brings this up) or click on the close button (an “X” on the title bar), or one’s application may be closed by the window manager when the X session is terminated. All of these call the closeEvent() member function of your KTopLevelWidget. So this is where you should ask the all-important question, “Would you like to save changes to ______?” Then call kapp->quit(), which tells your KApplication you are done and the app->exec() call in main() returns. To keep your program organized, you should force a closeEvent() call if you offer the user an alternative way of exiting your program (like choosing “Exit” from the “File” menu). This way, all pre-termination code is in that one place (closeEvent()). You can force a closeEvent() by calling close(), which is a member of KTopWidget. (In fact, it is a member of QWidget from which KTopWidget is derived.) This is done with KHelloTW::slotExit().
I encourage you to experiment with KHello and alter the code to learn about other KDE classes. A good place to start is KConfig. This class allows you to read and write from an application-specific configuration file stored in ~/.kde/config/. You should save the default program options in closeEvent(), then reread and set them upon construction of your KTopWidget.
KLocale is another important class for KDE applications. It helps you to translate your application into other languages by reading string literals (like “File”, “Help” or “Hello world!”) in from a file. KLocale will choose the appropriate strings based on the user’s locale settings. You need only provide the text. Since KDE is developed and used by people all over the world, it is a good idea to translate your application; the widespread interest makes it easier to find someone to help with the translation.
For further information on programming for KDE, I recommend going through the Qt tutorial at http://www.troll.no/qt/tutorial.html and visiting the KDE developer’s center at http://www.ph.unimelb.edu.au/~ssk/kde/devel/. Here you’ll find helpful tips on KDE programming as well as documentation for the KDE libraries and ideas for new KDE applications. It is in the spirit of the KDE project to announce your intention to create an application on the KDE mailing list so that effort is not duplicated. You’ll also find out how interested others are in using your proposed application and, most likely, receive lots of suggestions for program features. Good luck with your KDE programming.