The traditional way to share user interface elements is to place code in a shared library and link to this from the application binary. This approach allows easy embedding, but is inflexible.
A common requirement of an application is that it should be able to display, and perhaps allow editing of, some data. If the application does not have a built-in viewer/editor for the data type, it needs to have access to something that does. KDE 2 has the concept of ‘services’. A service is described in a single file that specifies which MIME types are provided, whether the service provides a viewer and/or an editor, and various details that help KDE to find and access the related ‘part’.
A database of services is maintained within KDE. This may be queried via simple functions. If an application wants to allow viewing of a mail message, it may query the database, asking for a component that handles data of type message/rfc822. To view the contents of a directory, it would ask for components handling type inode/directory. The database may return more than one offer where there are multiple services providing the necessary capability. The application is free to choose which it prefers though will usually accept the first offer in the list.
Once an acceptable service has been found in the offer list, loading and embedding the related part is simplicity itself. A factory is asked for a new part with the name of the service. This returns a new part, which may then be treated just like any other interface element.
Converting an existing widget to a kpart is very simple. If your object model is well designed, i.e. you have well defined external interfaces and loose coupling between classes, it is simply a matter of extracting the relevant classes and arranging for them to be built as a shared library. A simple wrapper class is then created to allow your class to be treated as a kpart, and you’re ready.
When embedding one part within another, it’s useful to be able to alter various parts of the containing interface to reflect the change. In a typical application with a menubar and toolbars, a contained ‘part’ will have certain ‘actions’ associated with it. For example, an editor part would provide actions such as ‘cut’, ‘copy’ and ‘save’. With a simplistic shared library approach, these actions will not be available from the container. A more suitable approach would be to allow these actions to be merged with the interface of the container.
When writing an application for KDE 2, the developer may elect to describe the content and layout of their menubar and toolbars by providing an XML file. Applications written to take advantage of this facility use less code and are therefore easier to maintain and adjust. In fact, as the XML contains a complete description of the menubar and toolbars, the interface can be changed without recompilation of, or even access to, the program code.
This new XML-based interface description framework has another trick up its sleeve. You can merge the interface of one application with another. This means that you can provide an XML file with your kpart and the interface described therein will be merged with that of the containing application.
This new approach to interface design only became possible due to another new feature of KDE 2, namely ‘actions’. When writing a KDE application, a relatively large amount of interface code is dedicated to management of the menubar and toolbars. Each menu item or toolbar button has various properties associated with it, such as a name (for display), an icon, and accelerator and shortcut keys.
Thanks to Qt’s typesafe dynamic callbacks, usually referred to with a less offputting phrase, ‘signals and slots’, connecting these menu items and buttons to internal code is made simple. Still, the interface elements which emit signals are more often than not created more than once. The functions of toolbar buttons are mirrored by menu items.
For example, many applications have a menu called ‘File’ and on its submenu, an entry called ‘Open’. Often the application creates a button on the main toolbar which performs the same function. There may be even more places in the interface where the same action may be triggered, for example in a right-mouse-button menu.
When writing for KDE 1, the programmer would have to write similar code for each of these interface elements and connect them all to the same internal ‘slot’. The correct icon would have to be found and loaded. The correct (translated) text would have to be assigned. The shortcut and accelerator keys would have to be given. All this, even though each interface element was performing the same function.
To make matters worse, the modern interface is dynamic. There may be times when the user should not be allowed to attempt to undo changes to their work, for example when they haven’t done any yet.
The interface must change to reflect to reflect the state of the system. This means that if the user should not be able to save a file (the file is read-only) or undo changes they have not done, the interface should not fool them into thinking that these operations are possible. If the menu and toolbar items were left enabled, then the user would be faced with an error message every time they did something that was impossible or illogical.
Writing for KDE 1, the programmer would have to disable each interface element that triggered the same action. This seemingly simple adjustment takes a surprising amount of code and therefore maintenance, plus involves extra communication between modules, which only serves to undermine the (hopefully) carefully designed internal object model.
You may ‘plug’ an action into a trigger, so for example you may plug it into a toolbar, where it will be triggered when the user selects the appropriate button. Actions have icons, text, keyboard shortcuts and accelerators associated with them. If you plug an action into a menu, the name and keyboard shortcut are shown. The ‘accelerator key’ will also become active (e.g. Ctrl+s.) You can disable an action and all associated interface elements will be disabled automatically.
KDE provides a set of standard actions. These may be plugged into interface elements to provide a consistent interface to the user. They also help the programmer, as no decisions have to be made about their name, icon, etc. Finally, they do not have to be mentioned in your XML interface description, so your standard interface remains standard while you manipulate the interesting parts.
kparts and XMLGUI provide an easy to use, sturdy, flexible, efficient and powerful framework for embedding user interface elements within others.
Converting an existing application to a kparts- and XML-based one is a simple task. The benefits in terms of system integration and flexibility are such that it would be impossible not to recommend this approach to all developers writing KDE applications.
One possibility for embedding is using X11 ‘swallowing’. This allows an interface component to be run in a separate process, in fact even on a remote server, but it also requires some communication mechanism, preferably one that allows for fixed (class) interface descriptions. The natural candidate for such a system is CORBA. Unfortunately, CORBA is by nature slow, cumbersome and unreliable; X11 swallowing is unreliable and a hack at best. Regardless of these disadvantages, this approach seemed to be the best candidate for implementing the ideas of the KDE team. A full implementation was completed during the initial KDE 2 design stage. This remains in place, so it is still possible to use the technology if required, but KDE has been converted completely to the new kparts approach, which has shown itself to be much more reliable, infinitely faster, just as flexible, and has reduced memory usage significantly.