Difference between revisions of "Development/Tutorials/KDE2/Extending the KDE Panel"

Jump to: navigation, search
(some fixes. <code cppqt> links still wrong)
m (Text replace - "<code cppqt n>" to "<syntaxhighlight lang="cpp-qt" line>")
Line 23: Line 23:
  
 
=== HelloWorldApplet class declaration ===
 
=== HelloWorldApplet class declaration ===
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
class HelloWorldApplet : public KPanelApplet
 
class HelloWorldApplet : public KPanelApplet
 
{
 
{
Line 48: Line 48:
  
 
=== HelloWorldApplet class implementation ===
 
=== HelloWorldApplet class implementation ===
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
HelloWorldApplet::HelloWorldApplet( const QString& configFile,
 
HelloWorldApplet::HelloWorldApplet( const QString& configFile,
 
                                   Type type, int actions,
 
                                   Type type, int actions,
Line 77: Line 77:
  
 
=== The factory function ===
 
=== The factory function ===
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
extern "C"  
 
extern "C"  
 
{
 
{
Line 166: Line 166:
  
 
{{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 :
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
class FifteenApplet : public KPanelApplet  
 
class FifteenApplet : public KPanelApplet  
 
{
 
{
Line 195: Line 195:
  
 
The second class declared in {{path|fifteenapplet.h}} is our game board inheriting from QTableView :
 
The second class declared in {{path|fifteenapplet.h}} is our game board inheriting from QTableView :
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
class PiecesTable : public QTableView  
 
class PiecesTable : public QTableView  
 
{
 
{
Line 241: Line 241:
  
 
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:
 
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:
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
extern "C"  
 
extern "C"  
 
{
 
{
Line 254: Line 254:
 
</code>
 
</code>
 
The implementation of the FifteenApplet class is very short as the game board class will handle all the drawing and game logic.
 
The implementation of the FifteenApplet class is very short as the game board class will handle all the drawing and game logic.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
FifteenApplet::FifteenApplet(const QString& configFile, Type type, int actions,
 
FifteenApplet::FifteenApplet(const QString& configFile, Type type, int actions,
 
                           QWidget *parent, const char *name)
 
                           QWidget *parent, const char *name)
Line 274: Line 274:
 
</code>
 
</code>
 
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.
 
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.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
int FifteenApplet::widthForHeight(int h) const  
 
int FifteenApplet::widthForHeight(int h) const  
 
{
 
{
Line 286: Line 286:
 
</code>
 
</code>
 
Similar to the hello world applet the fifteen pieces applet will have a quadratic shape.
 
Similar to the hello world applet the fifteen pieces applet will have a quadratic shape.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void FifteenApplet::about()  
 
void FifteenApplet::about()  
 
{
 
{
Line 311: Line 311:
  
 
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 http://doc.trolltech.com/qtableview.html#details]
 
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 http://doc.trolltech.com/qtableview.html#details]
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
PiecesTable::PiecesTable(QWidget* parent, const char* name )
 
PiecesTable::PiecesTable(QWidget* parent, const char* name )
 
   : QTableView(parent, name), _menu(0), _activeRow(-1),  
 
   : QTableView(parent, name), _menu(0), _activeRow(-1),  
Line 337: Line 337:
 
</code>
 
</code>
 
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.
 
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.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::initMap()  
 
void PiecesTable::initMap()  
 
{
 
{
Line 349: Line 349:
 
</code>
 
</code>
 
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.
 
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.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::initColors()  
 
void PiecesTable::initColors()  
 
{
 
{
Line 360: Line 360:
 
</code>
 
</code>
 
initColors() is similar but calculates different color values for each piece to increase the eye candy effect of our applet.
 
initColors() is similar but calculates different color values for each piece to increase the eye candy effect of our applet.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::paintCell(QPainter *p, int row, int col)
 
void PiecesTable::paintCell(QPainter *p, int row, int col)
 
{
 
{
Line 418: Line 418:
 
</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 {{qt2|QPainter}} and {{qt2|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>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::resizeEvent(QResizeEvent *e)  
 
void PiecesTable::resizeEvent(QResizeEvent *e)  
 
{
 
{
Line 450: Line 450:
  
 
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:
 
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:
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::mouseMoveEvent(QMouseEvent* e)  
 
void PiecesTable::mouseMoveEvent(QMouseEvent* e)  
 
{
 
{
Line 489: Line 489:
  
 
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:
 
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:
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::randomizeMap()  
 
void PiecesTable::randomizeMap()  
 
{
 
{
Line 517: Line 517:
 
</code>
 
</code>
 
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.
 
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.
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::checkwin()  
 
void PiecesTable::checkwin()  
 
{
 
{
Line 536: Line 536:
 
</code>
 
</code>
 
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:
 
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:
<code cppqt n>
+
<syntaxhighlight lang="cpp-qt" line>
 
void PiecesTable::mousePressEvent(QMouseEvent* e)
 
void PiecesTable::mousePressEvent(QMouseEvent* e)
 
{
 
{

Revision as of 21:32, 29 June 2011

Contents

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

  1. class HelloWorldApplet : public KPanelApplet
  2. {
  3.   Q_OBJECT
  4.  
  5. public:
  6.  
  7.   HelloWorldApplet( const QString& configFile, Type t = Stretch, 
  8.                     int actions = 0, QWidget *parent = 0, 
  9.                     const char *name = 0 );
  10.  
  11.   int widthForHeight( int height ) const;
  12.  
  13.   int heightForWidth( int width ) const;
  14.  
  15. };
  16. </code>
  17.  
  18. Having a look at {{path|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.
  19.  
  20. 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.
  21.  
  22. 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.
  23.  
  24. === HelloWorldApplet class implementation ===
  25. <syntaxhighlight lang="cpp-qt" line>
  26. HelloWorldApplet::HelloWorldApplet( const QString& configFile,
  27.                                   Type type, int actions,
  28.                                   QWidget *parent, const char *name )
  29.  
  30.   : KPanelApplet( configFile, type, actions, parent, name )
  31.  
  32. {
  33.   setBackgroundColor( blue );
  34.  
  35.   setFrameStyle( StyledPanel | Sunken );
  36. }
  37.  
  38. int HelloWorldApplet::widthForHeight( int h ) const
  39. {
  40.   return h; // we want to be quadratic
  41. }
  42.  
  43. int HelloWorldApplet::heightForWidth( int w ) const
  44. {
  45.   return w; // we want to be quadratic
  46. }
  47. </code>
  48. Our constructor simply passes its default arguments to the KPanelApplet constructor, sets the frame style to StyledPanel Sunken (see {{path|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.
  49.  
  50. === Screenshot of 'Hello, World!' applet ===
  51. [[Image:helloworld_applet.png]]
  52.  
  53. === The factory function ===
  54. <syntaxhighlight lang="cpp-qt" line>
  55. extern "C" 
  56. {
  57.   KPanelApplet* init( QWidget *parent, const QString& configFile )
  58.   {
  59.       KGlobal::locale()->insertCatalogue( "helloworldapplet");
  60.  
  61.       return new HelloWorldApplet( configFile, KPanelApplet::Normal,
  62.                                    0, parent, "helloworldapplet");
  63.   }
  64. }
  65. </code>
  66.  
  67. 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.}}
  68.  
  69. === The description file: helloworldapplet.desktop ===
  70. <code ini>
  71. [Desktop Entry] 
  72. Name = Hello World
  73. Comment = Hello World Applet
  74. X-KDE-Library = libhelloworldapplet
  75. X-KDE-UniqueApplet = true
  76. </code>
  77.  
  78. Besides standard {{path|.desktop}} file keys like "Name", "Comment" and "Icon" there are two panel applet specific keys:
  79.  
  80. '''X-KDE-Library'''<br>
  81. is used by the panel to locate the applet DSO (Dynamic Shared Object) 
  82.  
  83. Example: X-KDE-Library=libexampleapplet 
  84.  
  85. '''X-KDE-UniqueApplet'''<br>
  86. 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". 
  87.  
  88. Example: X-KDE-UniqueApplet=true
  89.  
  90. The following conventions are used for the applet
  91.  
  92. ''DSOs'':<br>
  93. '''Name'''<br>
  94. libappletnameapplet.la<br>
  95. '''LDFLAGS'''<br>
  96. -module -no-undefined
  97.  
  98. === The automake input file: Makefile.am ===
  99. <code>
  100. INCLUDES = $(all_includes) 
  101.  
  102. lib_LTLIBRARIES = libhelloworldapplet.la
  103.  
  104. libhelloworldapplet_la_SOURCES = helloworldapplet.cpp
  105.  
  106. METASOURCES = AUTO
  107.  
  108. noinst_HEADERS = helloworldapplet.h
  109.  
  110. lnkdir = $(kde_datadir)/kicker/applets
  111.  
  112. lnk_DATA = helloworldapplet.desktop
  113.  
  114. EXTRA_DIST = $(lnk_DATA)
  115.  
  116. libhelloworldapplet_la_LDFLAGS = $(all_libraries) -version-info 1:0:0 -module \
  117.                                -no-undefined
  118.  
  119. libhelloworldapplet_la_LIBADD = $(LIB_KDEUI)
  120.  
  121. messages:
  122.       $(XGETTEXT) *.cpp *.h -o $(podir)/helloworldapplet.pot
  123. </code>
  124. 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".
  125.  
  126. === Building and installing ===
  127. You can download a complete helloworldapplet.tar.gz tarball here: [http://developer.kde.org/documentation/tutorials/dot/khelloworldapplet.tar.gz 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:
  128. <code>
  129. ./configure -prefix=<your-kde-dir> 
  130. make
  131. su -c 'make install'
  132. </code>
  133.  
  134. === Hello world! ===
  135. 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.
  136.  
  137. == Fifteen pieces panel applet ==
  138. 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.
  139.  
  140. 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.
  141.  
  142. {{path|fifteenapplet.h}} contains the declaration of FifteenApplet, the class inheriting from KPanelApplet :
  143. <syntaxhighlight lang="cpp-qt" line>
  144. class FifteenApplet : public KPanelApplet 
  145. {
  146.   Q_OBJECT
  147.  
  148. public:
  149.  
  150.   FifteenApplet(const QString& configFile, Type t = Stretch,
  151.                 int actions = 0,
  152.                 QWidget *parent = 0, const char *name = 0);
  153.  
  154.   int widthForHeight(int height) const;
  155.  
  156.   int heightForWidth(int width) const;
  157.  
  158.   void about();
  159.  
  160. private:
  161.  
  162.   PiecesTable *_table;
  163.   KAboutData  *_aboutData;
  164.  
  165. };
  166. </code>
  167. While it is very similar to the declaration of HelloWorldApplet, we have added a reimplementation of void about() (as defined in {{path|kpanelapplet.h}}) and private pointers for our game board class and a KAboutData object (see {{path|kdelibs/kdecore/kaboutdata.h}}) used to build an about dialog from.
  168.  
  169. 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.
  170.  
  171. The second class declared in {{path|fifteenapplet.h}} is our game board inheriting from QTableView :
  172. <syntaxhighlight lang="cpp-qt" line>
  173. class PiecesTable : public QTableView 
  174. {
  175.   Q_OBJECT
  176.  
  177. public:
  178.  
  179.   PiecesTable(QWidget* parent = 0, const char* name = 0);
  180.  
  181. protected:
  182.  
  183.   void resizeEvent(QResizeEvent*);
  184.  
  185.   void mousePressEvent(QMouseEvent*);
  186.  
  187.   void mouseMoveEvent(QMouseEvent*);
  188.  
  189.   void paintCell(QPainter *, int row, int col);
  190.  
  191.   void initMap();
  192.  
  193.   void initColors();
  194.  
  195.   void randomizeMap();
  196.  
  197.   void checkwin();
  198.  
  199. private:
  200.  
  201.   QArray<int>     _map;
  202.  
  203.   QArray<QColor>  _colors;
  204.  
  205.   QPopupMenu     *_menu;
  206.  
  207.   int             _activeRow, _activeCol;
  208.  
  209.   bool            _randomized;
  210.  
  211.   enum MenuOp { mRandomize = 1, mReset = 2 };
  212.  
  213. };
  214. </code>
  215. I'm going to explain the details of this class below by means of the class implementation.
  216.  
  217. 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:
  218. <syntaxhighlight lang="cpp-qt" line>
  219. extern "C" 
  220. {
  221.   KPanelApplet* init(QWidget *parent, const QString& configFile)
  222.   {
  223.       KGlobal::locale()->insertCatalogue("kfifteenapplet");
  224.  
  225.       return new FifteenApplet(configFile, KPanelApplet::Normal,
  226.                                KPanelApplet::About, parent, "kfifteenapplet");
  227.   }
  228. }
  229. </code>
  230. The implementation of the FifteenApplet class is very short as the game board class will handle all the drawing and game logic.
  231. <syntaxhighlight lang="cpp-qt" line>
  232. FifteenApplet::FifteenApplet(const QString& configFile, Type type, int actions,
  233.                            QWidget *parent, const char *name)
  234.  
  235.   : KPanelApplet(configFile, type, actions, parent, name), _aboutData(0)
  236. {
  237.   // setup table
  238.  
  239.   _table = new PiecesTable(this);
  240.  
  241.   // setup layout
  242.  
  243.   QHBoxLayout *_layout = new QHBoxLayout(this);
  244.  
  245.   _layout->add(_table);
  246.  
  247.   srand(time(0));
  248. }
  249. </code>
  250. 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.
  251. <syntaxhighlight lang="cpp-qt" line>
  252. int FifteenApplet::widthForHeight(int h) const 
  253. {
  254.   return h; // we want to be quadratic
  255. }
  256.  
  257. int FifteenApplet::heightForWidth(int w) const
  258. {
  259.   return w; // we want to be quadratic
  260. }
  261. </code>
  262. Similar to the hello world applet the fifteen pieces applet will have a quadratic shape.
  263. <syntaxhighlight lang="cpp-qt" line>
  264. void FifteenApplet::about() 
  265. {
  266.   if(!_aboutData) {
  267.  
  268.       _aboutData = new KAboutData("kfifteenapplet", I18N_NOOP("KFifteenApplet"), 
  269.               "1.0", I18N_NOOP("Fifteen pieces applet.\n\n"
  270.               "The goal is to put the sliding pieces into numerical order.\n"
  271.               "Select \"Randomize Pieces\" from the RMB menu to start a game."),
  272.               KAboutData::License_BSD, "(c) 2001, Matthias Elter");
  273.  
  274.       _aboutData->addAuthor("Matthias Elter", 0, "elter@kde.org");
  275.   }
  276.  
  277.   KAboutApplication dialog(_aboutData);
  278.  
  279.   dialog.show();
  280. }
  281. </code>
  282. The implementation of the void about() action handler we reimplement from KPanelApplet creates a KAboutData object (see {{path|kdelibs/kdecore/kaboutdata.h}}) and makes use of KAboutApplication (see {{path|kdelibs/kdeui.kaboutapplication.h}}) to display a nice about dialog when the user selects "About" from the applet's context menu.
  283.  
  284. == Screenshot of fifteenpieces 'about' dialog ==
  285. [[Image:fifteenpieces_about.png]]
  286.  
  287. 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 http://doc.trolltech.com/qtableview.html#details]
  288. <syntaxhighlight lang="cpp-qt" line>
  289. PiecesTable::PiecesTable(QWidget* parent, const char* name )
  290.   : QTableView(parent, name), _menu(0), _activeRow(-1), 
  291.     _activeCol(-1), _randomized(false)
  292.  
  293. {
  294.   // setup table view
  295.  
  296.   setFrameStyle(StyledPanel | Sunken);
  297.  
  298.   setBackgroundMode(NoBackground);
  299.  
  300.   setMouseTracking(true);
  301.  
  302.   setNumRows(4);
  303.  
  304.   setNumCols(4);
  305.  
  306.   // init arrays
  307.  
  308.   initMap();
  309.  
  310.   initColors();
  311. }
  312. </code>
  313. 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.
  314. <syntaxhighlight lang="cpp-qt" line>
  315. void PiecesTable::initMap() 
  316. {
  317.   _map.resize(16);
  318.  
  319.   for (unsigned int i = 0; i < 16; i++)
  320.       _map[i] = i;
  321.  
  322.   _randomized = false;
  323. }
  324. </code>
  325. 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.
  326. <syntaxhighlight lang="cpp-qt" line>
  327. void PiecesTable::initColors() 
  328. {
  329.   _colors.resize(numRows() * numCols());
  330.  
  331.   for (int r = 0; r < numRows(); r++)
  332.       for (int c = 0; c < numCols(); c++)
  333.           _colors[c + r *numCols()] = QColor(255 - 70 * c,255 - 70 * r, 150);
  334. }
  335. </code>
  336. initColors() is similar but calculates different color values for each piece to increase the eye candy effect of our applet.
  337. <syntaxhighlight lang="cpp-qt" line>
  338. void PiecesTable::paintCell(QPainter *p, int row, int col)
  339. {
  340.   int w = cellWidth();
  341.   int h = cellHeight();
  342.  
  343.   int x2 = w - 1;
  344.   int y2 = h - 1;
  345.  
  346.   int number = _map[col + row * numCols()] + 1;
  347.  
  348.   bool active = (row == _activeRow && col == _activeCol);
  349.  
  350.   // draw cell background
  351.  
  352.   if(number == 16)
  353.       p->setBrush(colorGroup().background());
  354.  
  355.   else
  356.       p->setBrush(_colors[number-1]);
  357.  
  358.   p->setPen(NoPen);
  359.  
  360.   p->drawRect(0, 0, w, h);
  361.  
  362.   // draw borders
  363.  
  364.   if (height() > 40) {
  365.  
  366.       p->setPen(colorGroup().text());
  367.  
  368.       if(col < numCols()-1)
  369.  
  370.           p->drawLine(x2, 0, x2, y2); // right border line
  371.  
  372.       if(row < numRows()-1)
  373.  
  374.           p->drawLine(0, y2, x2, y2); // bottom border line
  375.  
  376.   }
  377.  
  378.   // draw number
  379.  
  380.   if (number == 16) return;
  381.  
  382.   if(active)
  383.  
  384.       p->setPen(white);
  385.  
  386.   else
  387.  
  388.       p->setPen(black);
  389.  
  390.   p->drawText(0, 0, x2, y2, AlignHCenter | AlignVCenter, 
  391.               QString::number(number));
  392. }
  393. </code>
  394. 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}}
  395. <syntaxhighlight lang="cpp-qt" line>
  396. void PiecesTable::resizeEvent(QResizeEvent *e) 
  397. {
  398.   QTableView::resizeEvent(e);
  399.  
  400.   // set font
  401.  
  402.   QFont f = font();
  403.  
  404.   if (height() > 50)
  405.       f.setPixelSize(8);
  406.  
  407.   else if (height() > 40)
  408.       f.setPixelSize(7);
  409.  
  410.   else if (height() > 24)
  411.       f.setPixelSize(5);
  412.  
  413.   else
  414.       f.setPixelSize(3);
  415.  
  416.   setFont(f);
  417.  
  418.   setCellWidth(contentsRect().width()/ numRows());
  419.  
  420.   setCellHeight(contentsRect().height() / numCols());
  421.  
  422. }
  423. </code>
  424. 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.
  425.  
  426. 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:
  427. <syntaxhighlight lang="cpp-qt" line>
  428. void PiecesTable::mouseMoveEvent(QMouseEvent* e) 
  429. {
  430.   QTableView::mouseMoveEvent(e);
  431.  
  432.   // highlight on mouse over
  433.  
  434.   int row = findRow(e->y());
  435.   int col = findCol(e->x());
  436.  
  437.   int oldrow = _activeRow;
  438.   int oldcol = _activeCol;
  439.  
  440.   if(row >= numRows()
  441.      || col >= numCols()
  442.      || row < 0
  443.      || col < 0) {
  444.  
  445.       _activeRow = -1;
  446.       _activeCol = -1;
  447.  
  448.   }
  449.  
  450.   else {
  451.  
  452.       _activeRow = row;
  453.       _activeCol = col;
  454.   }
  455.  
  456.   updateCell(oldrow, oldcol, false);
  457.   updateCell(row, col, false);
  458. }
  459. </code>
  460. 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.
  461.  
  462. == Screenshot of fifteenpieces applet ==
  463. [[Image:fifteenpieces_applet.png]]
  464.  
  465. 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:
  466. <syntaxhighlight lang="cpp-qt" line>
  467. void PiecesTable::randomizeMap() 
  468. {
  469.   QArray<int> positions;
  470.  
  471.   positions.fill(0, 16);
  472.  
  473.   for (unsigned int i = 0; i < 16; i++) {
  474.  
  475.       while(1) {
  476.  
  477.           int r = (int) (((double)rand() / RAND_MAX) * 16);
  478.  
  479.           if(positions[r] == 0) {
  480.  
  481.               _map[i] = r;
  482.               positions[r] = 1;
  483.               break;
  484.           }
  485.       }
  486.   }
  487.  
  488.   repaint();
  489.  
  490.   _randomized = true;
  491. }
  492. </code>
  493. 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.
  494. <syntaxhighlight lang="cpp-qt" line>
  495. void PiecesTable::checkwin() 
  496. {
  497.   if(!_randomized) return;
  498.  
  499.   int i;
  500.  
  501.   for (i = 0; i < 16; i++)
  502.       if(i != _map[i])
  503.           break;
  504.  
  505.   if (i == 16)
  506.       KMessageBox::information(this, 
  507.                                i18n("Congratulations!\nYou win the game!"),
  508.                                i18n("Fifteen Pieces"));
  509.  
  510. }
  511. </code>
  512. 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:
  513. <syntaxhighlight lang="cpp-qt" line>
  514. void PiecesTable::mousePressEvent(QMouseEvent* e)
  515. {
  516.   QTableView::mousePressEvent(e);
  517.  
  518.   if (e->button() == RightButton) {
  519.  
  520.       // setup RMB pupup menu
  521.  
  522.       if(!_menu) {
  523.  
  524.           _menu = new QPopupMenu(this);
  525.           _menu->insertItem(i18n("R&andomize Pieces"), mRandomize);
  526.           _menu->insertItem(i18n("&Reset Pieces"), mReset);
  527.           _menu->adjustSize();
  528.       }
  529.  
  530.  
  531.       // execute RMB popup and check result
  532.  
  533.       switch(_menu->exec(mapToGlobal(e->pos()))) {
  534.  
  535.           case mRandomize:
  536.               randomizeMap();
  537.               break;
  538.  
  539.  
  540.           case mReset:
  541.               initMap();
  542.               repaint();
  543.               break;
  544.  
  545.           default:
  546.               break;
  547.       }
  548.   }
  549.  
  550.   else {
  551.  
  552.       // GAME LOGIC
  553.  
  554.       // find the free position
  555.  
  556.       int pos = _map.find(15);
  557.  
  558.       if(pos < 0) return;
  559.  
  560.       int frow = pos / numCols();
  561.  
  562.       int fcol = pos - frow * numCols();
  563.  
  564.       // find click position
  565.  
  566.       int row = findRow(e->y());
  567.  
  568.       int col = findCol(e->x());
  569.  
  570.       // sanity check
  571.  
  572.       if (row < 0 || row >= numRows()) return;
  573.  
  574.       if (col < 0 || col >= numCols()) return;
  575.  
  576.       // valid move?
  577.  
  578.       if(row != frow && col != fcol) return;
  579.  
  580.       // rows match -> shift pieces
  581.  
  582.       if(row == frow) {
  583.  
  584.           if (col < fcol) {
  585.  
  586.               for(int c = fcol; c > col; c-) {
  587.  
  588.                   _map[c + row * numCols()] = _map[ c-1 + row *numCols()];
  589.  
  590.                   updateCell(row, c, false);
  591.  
  592.               }
  593.  
  594.           }
  595.  
  596.           else if (col > fcol) {
  597.  
  598.               for(int c = fcol; c < col; c++) {
  599.  
  600.                   _map[c + row * numCols()] = _map[ c+1 + row *numCols()];
  601.  
  602.                   updateCell(row, c, false);
  603.               }
  604.           }
  605.       }
  606.  
  607.       // cols match -> shift pieces
  608.  
  609.       else if (col == fcol) {
  610.  
  611.           if (row < frow) {
  612.  
  613.               for(int r = frow; r > row; r-) {
  614.  
  615.                   _map[col + r * numCols()] = _map[ col + (r-1) *numCols()];
  616.  
  617.                   updateCell(r, col, false);
  618.               }
  619.           }
  620.  
  621.           else if (row > frow) {
  622.  
  623.               for(int r = frow; r < row; r++) {
  624.  
  625.                   _map[col + r * numCols()] = _map[ col + (r+1) *numCols()];
  626.  
  627.                   updateCell(r, col, false);
  628.               }
  629.           }
  630.       }
  631.  
  632.       // move free cell to click position
  633.  
  634.       _map[col + row * numCols()] = 15;
  635.  
  636.       updateCell(row, col, false);
  637.  
  638.       // check if the player wins with this move
  639.  
  640.       checkwin();
  641.   }
  642. }
  643. </code>
  644. Ok, we are done, now either download and modify modify [http://developer.kde.org/documentation/tutorials/dot/khelloworldapplet.tar.gz khelloworldapplet.tar.gz] (308 kB) as described above or download [http://developer.kde.org/documentation/tutorials/dot/kfifteenapplet-1.0.tar.gz 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. ;-)
  645.  
  646. == Screenshot of popup menu above applet ==
  647.  
  648. 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.
  649.  
  650. == Applets of variable size ==
  651. 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 {{path|kpanelapplet.h}}) and kicker will relayout the panel and requery widthForHeight() (horizontal panel) or heightForWidth() 
  652. Session management 
  653. 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 {{path|$KDEDIR/share/config/appletidrc}} with "appletid" being the library name as defined in the {{path|.desktop}} file with X-KDE-Library For non-unique applets the name of the config file will contain a unique session id.
  654. == Stretch applets ==
  655. 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.
  656. == General action dispatcher ==
  657. 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.
  658. == Requesting keyboard focus ==
  659. 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.
  660. == Applet GUI layout ==
  661. 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.
  662. == Tips 'n' tricks ==
  663. 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 ({{path|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.
  664.  
  665. 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.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal