Development/Tutorials/KDE2/Extending the KDE Panel: Difference between revisions

From KDE TechBase
(some fixes. <code cppqt> links still wrong)
Line 6: Line 6:
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  [http://www.freedesktop.org/standards/wm-spec.html 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 [http://www.windowmaker.org Window Maker] applets and other applications using the standard X11 docking mechanism to kicker.
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  [http://www.freedesktop.org/standards/wm-spec.html 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 [http://www.windowmaker.org 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 http://doc.trolltech.com/tutorial.html]
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 also start with [http://doc.trolltech.com/2.3/tutorial.html a basic Qt2 tutorial].


== Overview ==
== Overview ==
Line 163: Line 163:
Searching for a panel applet suitable for this tutorial I stumbled over a screenshot of an applet implementation of the old fifteen pieces game for another desktop environment. While it's more a toy than of any real use it is simple and fast to implement and thus perfect for a tutorial. I'm sure you know the fifteen pieces game with the goal to put fifteen sliding pieces into numerical oder. The idea of the game is that on a 4x4 cell quadratic game board fifteen numbered (1 to 15) quadratic pieces must be put into numerical order while pieces can only be moved horizontally or vertically and with only one free cell to perform move operations on.
Searching for a panel applet suitable for this tutorial I stumbled over a screenshot of an applet implementation of the old fifteen pieces game for another desktop environment. While it's more a toy than of any real use it is simple and fast to implement and thus perfect for a tutorial. I'm sure you know the fifteen pieces game with the goal to put fifteen sliding pieces into numerical oder. The idea of the game is that on a 4x4 cell quadratic game board fifteen numbered (1 to 15) quadratic pieces must be put into numerical order while pieces can only be moved horizontally or vertically and with only one free cell to perform move operations on.


We are going to use a customized {{qt|QTableView}} widget for the game board. Similar to the hello world applet our little project will consist of three files, {{path|fiftenapplet.cpp}}, {{path|fifteenapplet.h}}, {{path|fifteenapplet.desktop}} and the {{path|Makefile.am}} file.
We are going to use a customized {{qt2|QTableView}} widget for the game board. Similar to the hello world applet our little project will consist of three files, {{path|fiftenapplet.cpp}}, {{path|fifteenapplet.h}}, {{path|fifteenapplet.desktop}} and the {{path|Makefile.am}} file.


{{path|fifteenapplet.h}} contains the declaration of FifteenApplet, the class inheriting from KPanelApplet :
{{path|fifteenapplet.h}} contains the declaration of FifteenApplet, the class inheriting from KPanelApplet :
Line 417: Line 417:
}
}
</code>
</code>
We have reimplemented void paintCell() from QTableView to actually paint the table cells, which on our game board represent sliding pieces, when the widgets receives a paint event. The function is a bit to long to explain in all detail but is quite easy to understand looking up calls you are unsure about in the Qt documentation for {{qt3|QPainter}} and {{qt3|QTableView}}
We have reimplemented void paintCell() from QTableView to actually paint the table cells, which on our game board represent sliding pieces, when the widgets receives a paint event. The function is a bit to long to explain in all detail but is quite easy to understand looking up calls you are unsure about in the Qt documentation for {{qt2|QPainter}} and {{qt2|QTableView}}
<code cppqt n>
<code cppqt n>
void PiecesTable::resizeEvent(QResizeEvent *e)  
void PiecesTable::resizeEvent(QResizeEvent *e)  

Revision as of 20:24, 11 July 2007

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 also start with a basic Qt2 tutorial.

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.

Note
Don't change the factory function's signature or the panel will fail to load your applet. After adding the factory function, we are done with the code part and only the description file and the Makefile.am remain to be written.


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

The automake input file: Makefile.am

INCLUDES = $(all_includes)

lib_LTLIBRARIES = libhelloworldapplet.la

libhelloworldapplet_la_SOURCES = helloworldapplet.cpp

METASOURCES = AUTO

noinst_HEADERS = helloworldapplet.h

lnkdir = $(kde_datadir)/kicker/applets

lnk_DATA = helloworldapplet.desktop

EXTRA_DIST = $(lnk_DATA)

libhelloworldapplet_la_LDFLAGS = $(all_libraries) -version-info 1:0:0 -module \

                              -no-undefined

libhelloworldapplet_la_LIBADD = $(LIB_KDEUI)

messages:

     $(XGETTEXT) *.cpp *.h -o $(podir)/helloworldapplet.pot

Explaining the details of this particular automake input file and the KDE build system in general is not the goal of this tutorial. So to adjust it to your applet project simply replace all occurrences of "helloworldapplet" with "myapplet".

Building and installing

You can download a complete helloworldapplet.tar.gz tarball here: khelloworldapplet.tar.gz (308 kB), which in addition to the four files mentioned above contains the automake/autoconf magic of the KDE build system. It's convenient to base your own panel applets on as the build system magic is already in place. Untar it, change into the untarred directory and build and install it with: ./configure -prefix=<your-kde-dir> make su -c 'make install'

Hello world!

Now that your applet is installed and the description file in place, a "Hello World" entry will show up in the Add-Applet sub menu of the panel menu. Select the "Hello World" entry and you will see our blue sunken quadratic panel applet show up in the panel. You will also notice a small grey handle on the left side of the applet. Use the right mouse button context menu of the applet handle to move or remove the applet. You can also move applets by dragging the handle.

Fifteen pieces panel applet

Searching for a panel applet suitable for this tutorial I stumbled over a screenshot of an applet implementation of the old fifteen pieces game for another desktop environment. While it's more a toy than of any real use it is simple and fast to implement and thus perfect for a tutorial. I'm sure you know the fifteen pieces game with the goal to put fifteen sliding pieces into numerical oder. The idea of the game is that on a 4x4 cell quadratic game board fifteen numbered (1 to 15) quadratic pieces must be put into numerical order while pieces can only be moved horizontally or vertically and with only one free cell to perform move operations on.

We are going to use a customized QTableView widget for the game board. Similar to the hello world applet our little project will consist of three files, fiftenapplet.cpp, fifteenapplet.h, fifteenapplet.desktop and the Makefile.am file.

fifteenapplet.h contains the declaration of FifteenApplet, the class inheriting from KPanelApplet : class FifteenApplet : public KPanelApplet {

 Q_OBJECT

public:

 FifteenApplet(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;
 void about();

private:

 PiecesTable *_table;
 KAboutData  *_aboutData;

}; While it is very similar to the declaration of HelloWorldApplet, we have added a reimplementation of void about() (as defined in kpanelapplet.h) and private pointers for our game board class and a KAboutData object (see kdelibs/kdecore/kaboutdata.h) used to build an about dialog from.

Searching for void about() in the KPanelApplet class declaration you will find two similar protected functions void help() and void preferences() In the right mouse button context menu of some applets you find in addition to the already mentioned "Move" and "Remove" entries, entries called "About", "Help" and "Preferences". The three protected functions are action handlers called when a user selects "About", "Help" or "Preferences" from the applets context menu. You have to reimplement them in your applet class to handle them. Not every applet implements for example the "Preferences" action because it might not have any preferences to configure. To avoid unused menu actions in the applet's context menu, kicker will only display those that you configure it to do by passing the according actions you reimplement or'ed together as the third parameter of the applet's class constructor.

The second class declared in fifteenapplet.h is our game board inheriting from QTableView : class PiecesTable : public QTableView {

 Q_OBJECT

public:

 PiecesTable(QWidget* parent = 0, const char* name = 0);

protected:

 void resizeEvent(QResizeEvent*);
 void mousePressEvent(QMouseEvent*);
 void mouseMoveEvent(QMouseEvent*);
 void paintCell(QPainter *, int row, int col);
 void initMap();
 void initColors();
 void randomizeMap();
 void checkwin();

private:

 QArray<int>     _map;
 QArray<QColor>  _colors;
 QPopupMenu     *_menu;
 int             _activeRow, _activeCol;
 bool            _randomized;
 enum MenuOp { mRandomize = 1, mReset = 2 };

}; I'm going to explain the details of this class below by means of the class implementation.

The factory function is very similar to the one of the helloworld applet with all occurrences of "helloworldapplet" replaced with "fifteenapplet". The second difference you might notice is that we pass KPanelApplet::About as the third parameter of the FifteenApplet constructor to make sure the "About" context menu entry will be there: extern "C" {

 KPanelApplet* init(QWidget *parent, const QString& configFile)
 {
     KGlobal::locale()->insertCatalogue("kfifteenapplet");
     return new FifteenApplet(configFile, KPanelApplet::Normal,
                              KPanelApplet::About, parent, "kfifteenapplet");
 }

} The implementation of the FifteenApplet class is very short as the game board class will handle all the drawing and game logic. FifteenApplet::FifteenApplet(const QString& configFile, Type type, int actions,

                          QWidget *parent, const char *name)
 : KPanelApplet(configFile, type, actions, parent, name), _aboutData(0)

{

 // setup table
 _table = new PiecesTable(this);
 // setup layout
 QHBoxLayout *_layout = new QHBoxLayout(this);
 _layout->add(_table);
 srand(time(0));

} The constructor creates an instance of our game board class and places it into a simple layout object to resize it to the full applet size every time the applet itself is resized. srandom is used to initialize the random number generator with the current time in seconds (see the man pages of srandom and random) as seed. int FifteenApplet::widthForHeight(int h) const {

 return h; // we want to be quadratic

}

int FifteenApplet::heightForWidth(int w) const {

 return w; // we want to be quadratic

} Similar to the hello world applet the fifteen pieces applet will have a quadratic shape. void FifteenApplet::about() {

 if(!_aboutData) {
     _aboutData = new KAboutData("kfifteenapplet", I18N_NOOP("KFifteenApplet"), 
             "1.0", I18N_NOOP("Fifteen pieces applet.\n\n"
             "The goal is to put the sliding pieces into numerical order.\n"
             "Select \"Randomize Pieces\" from the RMB menu to start a game."),
             KAboutData::License_BSD, "(c) 2001, Matthias Elter");
     _aboutData->addAuthor("Matthias Elter", 0, "[email protected]");
 }
 KAboutApplication dialog(_aboutData);
 dialog.show();

} The implementation of the void about() action handler we reimplement from KPanelApplet creates a KAboutData object (see kdelibs/kdecore/kaboutdata.h) and makes use of KAboutApplication (see kdelibs/kdeui.kaboutapplication.h) to display a nice about dialog when the user selects "About" from the applet's context menu.

Screenshot of fifteenpieces 'about' dialog

Now let's have a look at the implementation of the game board class called PiecesTable As it inherits from QTableView you might want to have a look at the description of this abstract base class for tables in the excellent Qt documentation: http://doc.trolltech.com/qtableview.html#details PiecesTable::PiecesTable(QWidget* parent, const char* name )

 : QTableView(parent, name), _menu(0), _activeRow(-1), 
   _activeCol(-1), _randomized(false)

{

 // setup table view
 setFrameStyle(StyledPanel | Sunken);
 setBackgroundMode(NoBackground);
 setMouseTracking(true);
 setNumRows(4);
 setNumCols(4);
 // init arrays
 initMap();
 initColors();

} The constructor initializes a few member variables used by the game board, sets up the frame style of the table ( QTableView inherits from QFrame ), sets the background mode to NoBackground (we do this to avoid flicker), enables mouse tracking for the widget and configures the number of rows and columns of the table. We also call two protected initialization functions we have defined in PiecesTable to set up two arrays, one (array of ints) used to represent the game board and another (array of QColor ) where we store color values for the 15 pieces. void PiecesTable::initMap() {

 _map.resize(16);
 for (unsigned int i = 0; i < 16; i++)
     _map[i] = i;
 _randomized = false;

} initMap() resizes the QArray<int> we use to represent the game board to 16 (4x4 fields on the board) and initializes it with the values one to fifteen. void PiecesTable::initColors() {

 _colors.resize(numRows() * numCols());
 for (int r = 0; r < numRows(); r++)
     for (int c = 0; c < numCols(); c++)
         _colors[c + r *numCols()] = QColor(255 - 70 * c,255 - 70 * r, 150);

} initColors() is similar but calculates different color values for each piece to increase the eye candy effect of our applet. void PiecesTable::paintCell(QPainter *p, int row, int col) {

 int w = cellWidth();
 int h = cellHeight();
 int x2 = w - 1;
 int y2 = h - 1;
 int number = _map[col + row * numCols()] + 1;
 bool active = (row == _activeRow && col == _activeCol);
 // draw cell background
 if(number == 16)
     p->setBrush(colorGroup().background());
 else
     p->setBrush(_colors[number-1]);
 p->setPen(NoPen);
 p->drawRect(0, 0, w, h);
 // draw borders
 if (height() > 40) {
     p->setPen(colorGroup().text());
     if(col < numCols()-1)
         p->drawLine(x2, 0, x2, y2); // right border line
     if(row < numRows()-1)
         p->drawLine(0, y2, x2, y2); // bottom border line
 }
 // draw number
 if (number == 16) return;
 if(active)
     p->setPen(white);
 else
     p->setPen(black);
 p->drawText(0, 0, x2, y2, AlignHCenter | AlignVCenter, 
             QString::number(number));

} We have reimplemented void paintCell() from QTableView to actually paint the table cells, which on our game board represent sliding pieces, when the widgets receives a paint event. The function is a bit to long to explain in all detail but is quite easy to understand looking up calls you are unsure about in the Qt documentation for QPainter and QTableView void PiecesTable::resizeEvent(QResizeEvent *e) {

 QTableView::resizeEvent(e);
 // set font
 QFont f = font();
 if (height() > 50)
     f.setPixelSize(8);
 else if (height() > 40)
     f.setPixelSize(7);
 else if (height() > 24)
     f.setPixelSize(5);
 else
     f.setPixelSize(3);
 setFont(f);
 setCellWidth(contentsRect().width()/ numRows());
 setCellHeight(contentsRect().height() / numCols());

} We also reimplement the resizeEvent() action handler of the QTableView widgets to adjust the game board according to the geometry we are resized to by the panel. You remember that the panel and thus also the applets can have different sizes and what we do in the resizeEvent is to adjust the font size used to draw on the widget (and thus used in the paintEvent to draw the piece numbers) to the widget size to increase readability on small panels. We furthermore set the cell width and height according to the widget geometry. The algorithm is simple as all cells (pieces) on our game board have the same size.

For some extra eye candy we reimplement the mouseMoveEvent() action handler of the game board widget to highlight the cell (piece) under the mouse cursor if the mouse is moved over the board: void PiecesTable::mouseMoveEvent(QMouseEvent* e) {

 QTableView::mouseMoveEvent(e);
 // highlight on mouse over
 int row = findRow(e->y());
 int col = findCol(e->x());
 int oldrow = _activeRow;
 int oldcol = _activeCol;
 if(row >= numRows()
    || col >= numCols()
    || row < 0
    || col < 0) {
     _activeRow = -1;
     _activeCol = -1;
 }
 else {
     _activeRow = row;
     _activeCol = col;
 }
 updateCell(oldrow, oldcol, false);
 updateCell(row, col, false);

} We are done now with drawing the game board and adjusting its size to game board geometry. While the applet already looks nice it is not of much use without the game logic for moving pieces around in place.

Screenshot of fifteenpieces applet

As the idea of the game is to put randomized pieces in numerical order, we first implement a helper function used to randomize the arrangement of the pieces on the game board: void PiecesTable::randomizeMap() {

 QArray<int> positions;
 positions.fill(0, 16);
 for (unsigned int i = 0; i < 16; i++) {
     while(1) {
         int r = (int) (((double)rand() / RAND_MAX) * 16);
         if(positions[r] == 0) {
             _map[i] = r;
             positions[r] = 1;
             break;
         }
     }
 }
 repaint();
 _randomized = true;

} We need a second helper function to be called after each move operation to check whether the player won with the previous move and to display a message box in case of victory. You may have notice the _randomized member variable in the functions above and from the code in checkwin() you see that it is used to make sure that we trigger the "You win!" message box only if the player actually did randomize the game board before putting it into numerical order. void PiecesTable::checkwin() {

 if(!_randomized) return;
 int i;
 for (i = 0; i < 16; i++)
     if(i != _map[i])
         break;
 if (i == 16)
     KMessageBox::information(this, 
                              i18n("Congratulations!\nYou win the game!"),
                              i18n("Fifteen Pieces"));

} Move operations are triggered by the player clicking on one of the sliding pieces. We reimplement the mousePressEvent() handler of the game board widgets to handle mouse clicks on the board. Right mouse button clicks trigger an additional context menu with actions to randomize or reset the game board. A left mouse button click triggers the game logic which is pretty simple if you take 5 minutes to think about it. You should be able to understand the details reading the numerous comments in the code: void PiecesTable::mousePressEvent(QMouseEvent* e) {

 QTableView::mousePressEvent(e);
 if (e->button() == RightButton) {
     // setup RMB pupup menu
     if(!_menu) {
         _menu = new QPopupMenu(this);
         _menu->insertItem(i18n("R&andomize Pieces"), mRandomize);
         _menu->insertItem(i18n("&Reset Pieces"), mReset);
         _menu->adjustSize();
     }


     // execute RMB popup and check result
     switch(_menu->exec(mapToGlobal(e->pos()))) {
         case mRandomize:
             randomizeMap();
             break;


         case mReset:
             initMap();
             repaint();
             break;
         default:
             break;
     }
 }
 else {
     // GAME LOGIC
     // find the free position
     int pos = _map.find(15);
     if(pos < 0) return;
     int frow = pos / numCols();
     int fcol = pos - frow * numCols();
     // find click position
     int row = findRow(e->y());
     int col = findCol(e->x());
     // sanity check
     if (row < 0 || row >= numRows()) return;
     if (col < 0 || col >= numCols()) return;
     // valid move?
     if(row != frow && col != fcol) return;
     // rows match -> shift pieces
     if(row == frow) {
         if (col < fcol) {
             for(int c = fcol; c > col; c-) {
                 _map[c + row * numCols()] = _map[ c-1 + row *numCols()];
                 updateCell(row, c, false);
             }
         }
         else if (col > fcol) {
             for(int c = fcol; c < col; c++) {
                 _map[c + row * numCols()] = _map[ c+1 + row *numCols()];
                 updateCell(row, c, false);
             }
         }
     }
     // cols match -> shift pieces
     else if (col == fcol) {
         if (row < frow) {
             for(int r = frow; r > row; r-) {
                 _map[col + r * numCols()] = _map[ col + (r-1) *numCols()];
                 updateCell(r, col, false);
             }
         }
         else if (row > frow) {
             for(int r = frow; r < row; r++) {
                 _map[col + r * numCols()] = _map[ col + (r+1) *numCols()];
                 updateCell(r, col, false);
             }
         }
     }
     // move free cell to click position
     _map[col + row * numCols()] = 15;
     updateCell(row, col, false);
     // check if the player wins with this move
     checkwin();
 }

} Ok, we are done, now either download and modify modify khelloworldapplet.tar.gz (308 kB) as described above or download kfifteenapplet-1.0.tar.gz (311 kB) to try the applet. I'm quite sure that you will agree that while the fifteen pieces applet is more or less useless it's a nifty toy to waste your time playing around with. You are good if you manage to figure out the basic move patterns used to win basically every game in less than 10min. ;-)

Screenshot of popup menu above applet

Now that we have implemented a demo applet I'm going to discuss the "advanced" API features we did not make use of for the simple fifteen pieces applet.

Applets of variable size

Some panel applets like the pager applet need variable sizes depending on their state (for example the number of desktops for the pager applet). To change the width or height of your applet simply emit the signal void updateLayout() (see kpanelapplet.h) and kicker will relayout the panel and requery widthForHeight() (horizontal panel) or heightForWidth() Session management Unlike normal KDE applications panel applets are not managed by ksmserver, the KDE session manager, but saved and restored by the panel. Session management is transparent to the applet. All that needs to be done to implement functional session management for your applet is to ensure you _always_ use the KConfig object returned by KPanelApplet::config() to save and read applet configuration settings. For unique applets ( X-KDE-UniqueApplet=true ) this config object will write settings to $KDEDIR/share/config/appletidrc with "appletid" being the library name as defined in the .desktop file with X-KDE-Library For non-unique applets the name of the config file will contain a unique session id.

Stretch applets

Both the hello world and fifteen pieces applet we have discussed are of type KPanelApplet::Normal as passed in the applet constructor to the KPanelApplet constructor. However there is a second type KPanelApplet::Stretch that is used for stretch applets like the taskbar applet that comes with kicker. For stretch applets the values returned by widthForHeight() and heightForWidth() are interpreted as minimal size values by kicker. This means a stretch applet will never be smaller than the size value you choose, but will be stretched by kicker to take up all the available space to the next applet on the panel. Move around the taskbar applet on the panel to see this effect.

General action dispatcher

Looking at the class declaration of KPanelApplet you may have noticed that there is one value ( ReportBug ) in the Action enumeration which does not have a virtual action handler function. The reason for this is ReportBug was added after the release of KDE 2.0 and it is not possible to add virtual functions without breaking binary compatibility of the KDE 2 platform. For this reason to handle the ReportBug action you must reimplement the general action dispatcher function void action(Action) It is called when the user selects any of the actions from the applet's context menu.

Requesting keyboard focus

The panel normally does not accept keyboard focus. For this reason applets that need keyboard focus (for example to input text) have to emit the void requestFocus() signal (for example as reaction to a mouse click event on the applet) to retrieve keyboard focus.

Applet GUI layout

As mentioned earlier the panel can have different orientations (horizontal or vertical) and can be placed on different borders of the desktop. Thus you might want to adjust the GUI layout of your applet depending on the panel orientation. There are two protected functions declared in KPanelApplet for applets to query the panel orientation and the direction in which popup menus should be positioned depending on the panel position. The functions are Orientation orientation() and Direction popupDirection() There are also two virtual handlers ( void orientationChange() and void popupDirectionChange() ) an applet can reimplement to react on orientation or direction changes.

Tips 'n' tricks

Never call show() in the applet's constructor. There is absolutely no reason to call show() in a widget constructor. The reason why this is important for panel applets is that QXEmbed (kdelibs/kdecore/qxembed.h), the class used to embed external applets (those run via the proxy) into kicker, often suffers from race conditions when a window is visible before it is reparented.

As panel applets provide but a very small GUI it is often easier to do the layouting by hand in the resizeEvent() instead of using QLayout.