Development/Tutorials/KDE2/Extending the KDE Panel
Introduction
The KDE panel (below referred to as "kicker") has been rewritten from scratch for KDE 2.0 and one of the main goals of the rewrite was to increase extensibility. For this reason with KDE 2.0 an API for panel applets has been introduced followed by an API for panel extension introduced with the KDE 2.1 release. Panel applets and extensions are simply referred to as "plugins" in the following paragraphs.
Panel applets are small applications living inside the panel. Available applets range from desktop pagers and task bars to little games or other toys. In contrast to applets the panel extension API is an interface for extensions living outside of the panel, in the window manager dock area (as defined in the freedesktop.org window manager specifications and are managed by the panel. Examples for panel extensions are the external task bar and the dock application bar which adds support for Window Maker applets and other applications using the standard X11 docking mechanism to kicker.
Starting with a short technology overview this tutorial discusses the implementation of a simple panel applet. Both the applet and extension APIs are simple. This qualifies writing a panel applet as a suitable task for an introduction to KDE programming. This tutorial presumes that the reader has some basic C++ and Qt knowledge. You can find a basic Qt tutorial at: http://doc.trolltech.com/tutorial.html
Overview
Panel plugins are implemented as DSO s (Dynamic Shared Objects). The panel locates available applets by searching for applet description files in (ALL_KDEDIRS)/share/apps/kicker/applets Every plugin must install a description file to be recognized by the panel. Available panel extensions are located by searching (A_KDEDIR)/share/apps/kicker/extensions for similar description files.
Implementing a panel applet is as easy as inheriting from the base class KPanelApplet (declared in kdelibs/kdeui/kpanelapplet.h), providing a factory function and installing the description file mentioned above. For extensions the respective base class is KPanelExtension , declared in kdelibs/kdeui/kpanelextension.h.
While plugins are implemented as shared libraries and thus loaded into the panel's address space, for security and stability reasons kicker also implements loading of plugins via external proxy processes called appletproxy and extensionproxy. The loading behavior is user configurable via kcontrol, with the obvious benefit that users may configure their panel to load untrusted third party plugins via the proxies to avoid buggy plugins taking the whole panel down with them.
While as an plugin author you don't have to be concerned about these implementation details it is good to know what happens behind the scenes. Therefore I might mention here that the panel communicates with the proxies via the DCOP protocol (see kdelibs/dcop) and makes use of QXEmbed (see kdelibs/kdeui/qxembed.h) to embed external (loaded via one of the two proxies) plugins into the panel.
A hello world panel applet
We start with a hello world panel applet that can be implemented in only a few lines of code and is a good base upon which to build a more complex applet. As already mentioned in the overview section all that needs to be done is inheriting from KPanelApplet , providing a factory function and a .desktop description file.
Our little project consists of four files: helloworldapplet.h containing the class declaration, helloworldapplet.cpp containing the class implementation and the factory function, helloworldapplet.desktop the description file and Makefile.am as input for the automake/autoconf KDE build system.
HelloWorldApplet class declaration
class HelloWorldApplet : public KPanelApplet
{
Q_OBJECT
public:
HelloWorldApplet( const QString& configFile, Type t = Stretch,
int actions = 0, QWidget *parent = 0,
const char *name = 0 );
int widthForHeight( int height ) const;
int heightForWidth( int width ) const;
};
Having a look at kpanelapplet.h see that all we do is re-implement the constant virtual functions int widthForHeight(int) and heightForWidth(int) Playing around with kicker you may notice that you can arrange the panel either horizontally or vertically at the desktop borders. You will also notice that there is a fixed set of panel sizes (representing panel height for horizontal panels and width for vertical panels) to choose from.
The concept of panel applets is that one size component is dictated by the panel while the other one is free for the applet to choose. On horizontal panels the applet's height is fixed according to the panel height while the applet is free to choose its width. Respectively on vertical panels the applet's height is fixed while it is free to choose its height.
Every panel applet should be prepared to be used both on horizontal and vertical panels. The panel uses the two functions we are re-implementing for the HelloWorldApplet to query the applet's preferred size. A horizontal panel will call widthForHeight() while a vertical panel will call heightForWidth() to query the free size component of the applet. Applets are guaranteed to be resized according to the size they request.
HelloWorldApplet class implementation
HelloWorldApplet::HelloWorldApplet( const QString& configFile,
Type type, int actions,
QWidget *parent, const char *name )
: KPanelApplet( configFile, type, actions, parent, name )
{
setBackgroundColor( blue );
setFrameStyle( StyledPanel | Sunken );
}
int HelloWorldApplet::widthForHeight( int h ) const
{
return h; // we want to be quadratic
}
int HelloWorldApplet::heightForWidth( int w ) const
{
return w; // we want to be quadratic
}
Our constructor simply passes its default arguments to the KPanelApplet constructor, sets the frame style to StyledPanel Sunken (see qframe.h) and sets the background color of the applet to blue so we recognize it later running in the panel. Both widthForHeight() and heightForHeight() are implemented to choose a quadratic geometry for the applet. Thus we expect our applet to show up as a sunken blue square on the panel when run.
Screenshot of 'Hello, World!' applet
The factory function
extern "C"
{
KPanelApplet* init( QWidget *parent, const QString& configFile )
{
KGlobal::locale()->insertCatalogue( "helloworldapplet");
return new HelloWorldApplet( configFile, KPanelApplet::Normal,
0, parent, "helloworldapplet");
}
}
The factory function is mostly copy&paste work where you would replace "helloworldapplet" with "myapplet" to adjust it to your custom applet.
The description file: helloworldapplet.desktop
[Desktop Entry]
Name = Hello World
Comment = Hello World Applet
X-KDE-Library = libhelloworldapplet
X-KDE-UniqueApplet = true
Besides standard .desktop file keys like "Name", "Comment" and "Icon" there are two panel applet specific keys:
X-KDE-Library is used by the panel to locate the applet DSO (Dynamic Shared Object)
Example: X-KDE-Library=libexampleapplet
X-KDE-UniqueApplet
Similar to KApplication and KUniqueApplication there are two types of panel applets. Use unique applets when it makes no sense to run more than one instance of an applet in the panel. A good example for unique applets is the taskbar applet. Use normal applets when you need instance specific configuration. An example is the koolclock applet where you might want to run two instances in your panel, one configured as analog clock, the other one as digital clock. X-KDE-UniqueApplet is a boolean key which defaults to "false".
Example: X-KDE-UniqueApplet=true
The following conventions are used for the applet
DSOs: Name libappletnameapplet.la LDFLAGS -module -no-undefined