Francais Deutsch Chinese

KDE technology : KPart components

by Philippe Fremy

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:

Corba makes it very hard to meet these requirements, while makes it easy. Corba is a very good technology but it is definitely not suited for gui components. Read what follows to see how KPart is a success in this area.

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:

Remember, this list means that you can embed any of these components in an application with 8 lines of code. And with the 2.2 release coming, there will be more of them!

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
KWord screenshot KSpread screenshot

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
KWord component screenshot KSpread Component screenshot
The same KSpread document, in KSpread
kspread screenshot

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 screenshot

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


Copyright (c) 2001 Philippe Fremy (phil at freehackers.org)
Last modification : $Date: 2005/07/02 17:45:27 $ - $Author: philippe $