Components are a very powerful technology. Basically, a component allow 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 component technology has the following characteristics:
KPart is based on Shared Libraries. This makes the component appears directely as a C++ object. There is no need to warp its features with an IDL language, everything is directey accessible. 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 the menu of an application easily extensible, it is defined in a XML file.
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 allow 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 assiociated 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.
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.
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. :-)
Now you see why I said it is easy to create a component.
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:
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 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 componenent. All the good points of KPart apply to KOffice: easiness to write and use the component, quick and leightweight 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 exactely 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 instant, 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 like if you were editing the sheet directely in KSpread, as you can see with the third screenshot.
| KWord document containing a KSpread frame | KSpread document | ||
|---|---|---|---|
| KWord activated | KSpread activated | ||
![]() |
![]() |
![]() |
|
| Empty KOffice Workspace | |
|---|---|
![]() |
|
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 suit 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 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.
Previous: Introduction | Next: KDE IPC/RPC |