Introduction
KDE 2.0 was released on september 2000. It was a great enhancement over KDE 1.X. The desktop was almost entirely rewritten and takes advantage of powerful new technologies: components, RPC/IPC, network transparency, sound deamon, ...One can imagine that these new technologies are going to make the writing an applciation a tedious task. But this is not the case. Those technologies are damn cool and damn easy to use. This is what I am going to show in this article.
All the code examples here were written and tested with KDE 2.1 . The code examples come from various places : cvs examples, kde book, mails, doc, tutorials, applications. You can find almost all this on KDE's developer site: developer.kde.org
Note that these technology are now very mature. They have gone through three stable releases (KDE 2.0 , 2.1 and 2.2 ) with almost no change. And KDE relies heavily on them. So this is something you can trust to be rock-stable.
KDE Gui Component technology
Gui components are a very powerful technology. Basically, a component allows an application to provide its features and its interface not only as an application but also as an embedded frame into any other application of the desktop. Each component can be improved separately, and you can assign preferred components for certain tasks. Typical examples: a mail reader requests a Text editor component to compose a new mail; an IDE requests a Text editor to edit the code (not necessarily the same one), a spreadsheet requests a Chart builder to analyse its data; an application requests a html browser for its need, etc.
Components increase code reuse and modularity, thus should be used as much as possible. To turn components into a reality, KDE made them very simple to write and use.
In Gnome, the component technology is provided by Bonobo, which is built upon Corba. KDE also used Corba in the past, but eventually dumped it for an home-made technology: KPart. This choice has been very criticized although almost nobody understood the ground and the consequences of it. It was a good choice and I'll write an article one day, to explain why.
In short, a good Gui Component technology has the following characteristics:
- it is easy to enable an application as a component
- it is easy to use a component in another application
- the component is activated quickly (almost instantaneously).
KPart
When the KDE core-developers realised that Corba was becoming an unmanagable nightmare, they wrote in a few days a lightweight and efficient component technology to replace it: KPart.KPart is based on Shared Libraries. This makes the component appears directly as a C++ object. There is no need to wrap its features with an IDL language, everything is accessible without extra effort. So coding a componenent is very close to coding a C++ Object, which you must do anyway for your application. Shared libraries are also very quick to activate or unload . You don't even have to issue a fork, the code runs inside your application. 95% of the component specific work is done by the KPart API. so writing and using component is very easy, as I show in the examples. Reading the small documentation and the tutorial tells you everything you need to know for that.
There are two kinds of KPart: components and plugins. Components provide a widget that you can display, and may extend the application's menu to add the component's specific actions. A plugin has no widget. This is usually one menu entry that provides one new feature. To make this possible, the menus of an application are defined in a XML file.
Example 1: request a html browser
Using a part is very simple. Here is an example of the code you can use to embed a html browser in your application. This code is extracted from the application template generated by kapptemplate (the application template generator written by Kurt Granroth).
KTrader::OfferList offers = KTrader::self()->query("text/html",
"'KParts/ReadOnlyPart' in ServiceTypes");
KLibFactory *factory = 0;
// in theory, we only care about the first one.. but let's try all
// offers just in case the first can't be loaded for some reason
KTrader::OfferList::Iterator it(offers.begin());
for( ; it != offers.end(); ++it) {
KService::Ptr ptr = (*it);
// we now know that our offer can handle HTML and is a part.
// since it is a part, it must also have a library... let's try to
// load that now
factory = KLibLoader::self()->factory( ptr->library() );
if (factory) {
m_html = static_cast<KParts::ReadOnlyPart *>(factory->create(this,
ptr->name(), "KParts::ReadOnlyPart"));
break;
}
}
|
All available components are indexed and stored in a database. You make queries with KTrader, which allows you to specify the characteristics of the components you want: the component name, the mimetype it can handles, or many other parameters. Here, we ask for a component that can display text/html for read only. KTrader returns a list of available components (KService::Ptr), sorted by preference. These KService::Ptr have an associated library, that you load using KLibLoader. The library has a factory, which is able to create a widget. The widget is the component you have requested.
It may sound complicated the first time, but it will never get more complicated nor different. Find the service, load its library, get the library factory and let it create our widget. That's it. In our case, the preferred html renderer of the user will be activated (probably khtml, but gecko is also available, via kmozilla). To request a different component, just change text/html to another mimetype.
Example 2: Request the preferred text editor
Using the same approach, you can request a text editor. The code is more compact but nothing has changed. This code is extracted from the KTextEditor interface documentation
KTrader::OfferList offers = KTrader::self()->query( "KTextEditor/Document" ); ASSERT( offers.count() >= 1 ); KService::Ptr service = *offers.begin(); KLibFactory *factory = KLibLoader::self()->factory( service->library() ); ASSERT( factory ); m_part = static_cast<KTextEditor::Document *>( factory->create( this, 0, "KTextEditor::Document" ) ); ASSERT( m_part ); QWidget * view = m_part->createView( my_parent_widget, 0 ); |
8 lines of code, including 3 asserts. You see that it is really easy to use a part in an application, I wasn't lying.
Example 3: how to provide a component
Now, Imagine that we have written a cool application and want to make it available to other apps, as a kpart component. Here is the necessary code, extracted from the KPart tutorial written by Kurt Granroth. This KPart is for an application called aKtion.Basically, you have to provide a factory which is used when the library gets loaded. The factory's role is only to provide a kpart object. The KPart installs the new menu entries and returns a widget, which should contain your application.
| aktion_part.h | aktion_part.cpp |
|---|---|
#ifndef __aktion_part_h__
#define __aktion_part_h__
#include "kparts/browserextension.h"
#include "klibloader.h"
class KAboutData;
class KInstance;
class AktionBrowserExtension;
class QLabel;
class AktionFactory : public KLibFactory
{
Q_OBJECT
public:
AktionFactory() {}
virtual ~AktionFactory();
virtual QObject* create(QObject* parent = 0,
const char* name = 0,
const char* classname = "QObject",
const QStringList &args = QStringList());
static KInstance *instance();
static KAboutData *aboutData();
private:
static KInstance *s_instance;
};
class AktionPart: public KParts::ReadOnlyPart
{
Q_OBJECT
public:
AktionPart(QWidget *parent, const char *name);
virtual ~AktionPart() { closeURL(); }
bool closeURL() { return true; }
protected:
virtual bool openFile()
{ widget->setText(m_file); return true; }
QLabel *widget;
};
#endif
|
#include "aktion_part.h"
#include "kinstance.h"
#include "klocale.h"
#include "kaboutdata.h"
#include "qlabel.h"
extern "C"
{
void *init_libaktion()
{
return new AktionFactory;
}
};
/**
* We need one static instance of the factory for our C 'main'
* function
*/
KInstance *AktionFactory::s_instance = 0L;
AktionFactory::~AktionFactory()
{
if (s_instance) {
delete s_instance->aboutData();
delete s_instance;
}
s_instance = 0;
}
QObject *AktionFactory::create(QObject *parent,
const char *name, const char*,
const QStringList & )
{
QObject *obj = new AktionPart((QWidget*)parent, name);
emit objectCreated(obj);
return obj;
}
KInstance *AktionFactory::instance()
{
if ( !s_instance ) s_instance = new KInstance( aboutData() );
return s_instance;
}
KAboutdata *AktionFactory::aboutData()
{
KAboutData *about = new KAboutData("aktion",
I18N_NOOP("aKtion"), "1.99");
return about;
}
AktionPart::AktionPart(QWidget *parent, const char *name)
: KParts::ReadOnlyPart(parent, name)
{
setInstance(AktionFactory::instance());
// create a canvas to insert our widget
QWidget *canvas = new QWidget(parent);
canvas->setFocusPolicy(QWidget::ClickFocus);
setWidget(canvas);
// as an example, display a blank white widget
widget = new QLabel(canvas);
widget->setText("aKtion!");
widget->setAutoResize(true);
widget->show();
}
|
There are 80 lines of useful code here, 90% of which is generic and reusable for another component. Only the last 15 lines are specific to the aktion part. Of course, if you use kapptemplate or kdevelop, the "pain" of writing these 80 lines is spared because it is generated automatically for you. :-)
Example 4: Konqueror
Konqueror is known as the KDE 2 Web Browser. But it is not a web browser, it is just a Shell that requests data using Kio slaves (another KDE technology) and embeds view on this data using KPart. This is the most striking example of the components use in KDE.
Here is a non-complete list of all applications that provide a KPart component and can therefore be embedded inside Konqueror:
- konqueror: filemanagement, iconview, multicolumnview, dirtree_view, treeview, detailedlistview, textview
- khtml
- khelpcenter
- kwrite_component
- kdvi
- kghostview
- kview (image viewer)
- aktion (xanim component)
- kfax
- konsolepart (terminal emulator)
- kmid
- kchart
- kword
- kspread
- krayon
- killustrator
- kformula
- kpresenter
- korganizer
- kivio
Example 5: KOffice
KDE is well known for its office suite. It features a word processor (kword), a spread-sheet (kspread), a presenter application (kpresenter), a vector application (killustrator) and a chart application (kchart), etc.
What is less known about KOffice is that each application is available as a component. The class which represents a KOffice document is KoDocument, which inherits from KParts::ReadWritePart. Thus, every KOffice document can be embedded and viewed just like a KPart component. All the good points of KPart apply to KOffice: easiness to write and use the component, quick and lightweight loading, etc. KOffice components technology add more activation subtilities, because they can be nested. But functionally, they are a KPart.
KOffice embedding is used in Konqueror to preview KOffice documents inside the browser. But the main use is to allow for instance KWord to embed a spread-sheet, a formula and a graph inside a word document. When you are working on the text frame, the KWord component is activated. When you are working on the spread-sheet frame, the KSpread component is activated (screenshot below).
When you launch KWord or KSpread, you get a dialog to create a new document or open one. If you look at the window behind the dialog (see screenshots), you notice that the toolbar is almost empty and that this window is exactly the same for KWord and KSpread. This window is a generic KOffice document shell and contains nothing specific to KWord or KSpread. It will turn into KWord if a KWord component is activated, or into KSpread if a KSpread component is activated. The only thing that distinguish KWord from KSpread when you launch them is the mimetypes that the opening dialog is able to handle.
| KWord openting a document | KSpread opening a document |
|---|---|
|
|
Now create a KWord document in which you embed a KSpread document. When you are working on the KWord document, all the menu and toolbar are those of KWord. The KSpread frame contains only a static array (see screenshots below). But if you click on the KSpread frame, you will see all the KWord menu and toolbar disappear, and all the KSpread menu and toolbar appear. What just happened is a component deactivation/activation. The kword component has been deactivated. All its menu and toolbar entries have been removed from the Shell window. During a slight instant, there was an empty Koffice window (the same one that is behind the opening dialogs). The next moment, the KSpread component has been activated. All the KSpread menu and toolbars have been added. The array has been turned into a KSpread sheet which you can manipulate. Since all the features of KSpread are provided by its component, it is exactely as if you were editing the sheet directly in KSpread, as you can see with the third screenshot.
| KWord document containing a KSpread frame | |
|---|---|
| KWord activated | KSpread activated |
![]() |
![]() |
| The same KSpread document, in KSpread | |
|---|---|
![]() |
|
KOffice workspace
There is a program that highlights the true component nature of KOffice. It is called Koffice workspace and you can launch it by typing "koshell". You'll notice that the main window is the generic shell I have been talking about. You can activate any of the Koffice component by clicking on their icon in the left frame. Then your generic Koffice Shell will turn into KWord, KSpread or whatever KOffice application you have requested.
| Empty KOffice Workspace | |
|---|---|
![]() |
|
KOffice code figures
As opposed to OpenOffice, Gnu Spread or Abiword, which existed before Gnome, KOffice was developped from scratch, along with KDE. It was possible to get KDE's Office suite written quickly because the architecture and the development framework of KDE and KOffice are very powerful and very simple.
For example, the KOffice Workspace which we have just seen is programmed in 600 lines of code. This represents something like 3 days of work for an average programmer.
philippe@werewindle /usr/src/kde-cvs/koffice/koshell $ ls
AUTHORS Makefile.am dummy.cc koshell_shell.cc
CVS/ Makefile.in koshell.desktop koshell_shell.h
Makefile TODO koshell_main.cc
philippe@werewindle /usr/src/kde-cvs/koffice/koshell $ wc -l *.h *.cc
121 koshell_shell.h
1 dummy.cc
49 koshell_main.cc
434 koshell_shell.cc
605 total
|
If you want to start a new KOffice application, the main thing you have to do is to create a new class that inherits from KoDocument. The example provided by KOffice has 430 lines of code.
philippe@werewindle /usr/src/kde-cvs/koffice/example $ ls
CVS/ README example_factory.cc example_view.cc
Makefile configure.in.in example_factory.h example_view.h
Makefile.am example.desktop example_part.cc main.cc
Makefile.in example.rc example_part.h x-example.desktop
philippe@werewindle /usr/src/kde-cvs/koffice/example $ wc -l *.h *.cc
47 example_factory.h
42 example_part.h
48 example_view.h
106 example_factory.cc
70 example_part.cc
67 example_view.cc
49 main.cc
429 total
|
All you have to do to turn this example into a real full-featured KOffice document is to rewrite three functions: ExamplePart::loadXML(), ExamplePart::saveXML() and ExamplePart::paintContent(), which will respectively load your document data from an XML file, save your document data into a XML file and paint your document's content.
Conclusions
As you have now understood, KDE has a powerful component architecture, that makes it possible to use components widely. KPart is indeed used everywhere inside KDE. Bonobo is far from providing this ease of use and I have understood that Gnome 2.0 won't be bonoboized. Gnome still has a long way to catch-up with KDE.In my next article, I'll talk about IPC/RPC withing KDE, which is provided by DCOP.
References
- developer.kde.org for everything concerning development under KDE
- The KDE 2.0 development book
- KPart
- Kpart tutorial
Copyright (c) 2001 Philippe Fremy (phil at freehackers.org)
Last modification : $Date: 2005/07/02 17:45:27 $ - $Author: philippe $





