Abstract

This tutorial needs KDE 4.2 to build.

In this tutorial I will show how to create a simple applet that uses an extender. This applet is basically a very stripped down version of the kuiserver applet. Stripped down to the point where it isn't very useful anymore, but that allows us to focus on extenders.

About extenders

Extenders allow applets to, with only very little extra lines of code, use relocatable widgets. These ExtenderItem objects, can be dragged around by the user, dropped anywhere, and can even be persistent between sessions. They can also keep running, even when the applet that created them, isn't.
The class that shows and manages these extender items is Plasma::Extender.

How to approach extenders

If you want to use extenders in your applet you'll usually have to do a couple of things:

This is quite easy: calling extender() from your applet will instantiate an extender for you if your applet doesn't have an extender yet. Note that an applet cannot have more then one extender. Also note that you should always instantiate an extender in your applet's init() function. An extender requires a scene, which isn't available before the call to init().

Just instantiating an ExtenderItem with your extender as parameter to the constructor will add a new ExtenderItem to this extender. In this tutorial we will add an ExtenderItem whenever a new kuiserver job is created. After instantiating an ExtenderItem you'll usually want to set a name, title, icon, and probably even other information which can be stored by accessing the item's config(). Once you've got an ExtenderItem set up, you'll need to call initExtenderItem(ExtenderItem*) to actually set the QGraphicsWidget that will be wrapped inside this ExtenderItem.

Implement a initExtenderItem(ExtenderItem*) function in your applet

This function will be used to recreate detached ExtenderItems after plasma has been restarted. It will not only be called by your applet after instantiating a new ExtenderItem, it will also be called whenever plasma is started and detached ExtenderItems are restored from configuration. In this function you'll need to extract the required information from the item (name, or other information you have stored in the item's config()), and use this information to create the desired widget to be wrapped inside this item. Then call setWidget on this item to actually wrap this widget inside the ExtenderItem.

The code

Here I will show you our very simple stripped down kuiserver applet. It will show every new job in an ExtenderItem using a Plasma::Meter to display the jobs progress. In the title bar of the ExtenderItem the name of the datasource will be displayed, and the icon of the application that created the job will be shown.
Finally, the items will also be sort of persistent. When plasma is restarted, all detached items will get shown again, but will only show the name of the original datasource it was connected to as title. Kuiserver datasources are not persistent between sessions (yet?) so the meter won't show progress anymore.
As basis for this applet we will use {{class|PopupApplet}. This means that the applet will automatically collapse into an icon when put into a panel. PopupApplet will also take care of setting the correct appearence on your extender, depending of where it's located (top of screen, bottom of screen, desktop). PopupApplet is a convenient base class for all applets that use extenders and I'd strongly advice you to use it.

The header

#ifndef EXTENDERTUTORIAL_H#define EXTENDERTUTORIAL_H#include <plasma/popupapplet.h>#include <plasma/dataengine.h>namespacePlasma{classExtenderItem;}// namespace PlasmaclassExtenderTutorial:publicPlasma::PopupApplet{Q_OBJECTpublic:ExtenderTutorial(QObject*parent,constQVariantList&args);~ExtenderTutorial();voidinit();protected://Implement this function to make ExtenderItems persistent.//This function will get called on plasma start for every //ExtenderItem that belonged to this applet, and is still//around. Instantiate the widget to be wrapped in the //ExtenderItem here.voidinitExtenderItem(Plasma::ExtenderItem*item);publicslots://We want to add a new ExtenderItem everytime a new job //is started.voidsourceAdded(constQString&source);};K_EXPORT_PLASMA_APPLET(extendertutorial,ExtenderTutorial)#endif</code>Thisheadershouldbemostlyselfexplanetory.WeusePopupApplettohaveanicebasisforthiskindofapplet.ThedefaultimplementationofPopupApplet'sgraphicsWidget()returnsit'sextenderifithasone.Thisisexactlywhatwewant(displaytheextenderinthepopup),sowedon'tneedtoimplementgraphicsWidget().===Theimplementation===<syntaxhighlightlang="cpp-qt">#include "extendertutorial.h"#include <QAction>#include <QGraphicsLinearLayout>#include <Plasma/Containment>#include <Plasma/DataEngine>#include <Plasma/Extender>#include <Plasma/ExtenderItem>#include <Plasma/Meter>ExtenderTutorial::ExtenderTutorial(QObject*parent,constQVariantList&args):Plasma::PopupApplet(parent,args){//We want to collapse into an icon when put into a panel.//If you don't call this function, you can display another //widget, or draw something yourself.setPopupIcon("extendertutorial");}ExtenderTutorial::~ExtenderTutorial(){}voidExtenderTutorial::init(){//Calling extender() instantiates an extender for you if you//haven't already done so. Never instantiate an extender //before init() since Extender needs access to applet->config()//to work.//The message to be shown when there are no ExtenderItems in//this extender.extender()->setEmptyExtenderMessage(i18n("no running jobs..."));//Notify ourself whenever a new job is created.connect(dataEngine("kuiserver"),SIGNAL(sourceAdded(constQString&)),this,SLOT(sourceAdded(constQString&)));}voidExtenderTutorial::initExtenderItem(Plasma::ExtenderItem*item){//Create a Meter widget and wrap it in the ExtenderItemPlasma::Meter*meter=newPlasma::Meter(item);meter->setMeterType(Plasma::Meter::BarMeterHorizontal);meter->setSvg("widgets/bar_meter_horizontal");meter->setMaximum(100);meter->setValue(0);meter->setMinimumSize(QSizeF(250,45));meter->setPreferredSize(QSizeF(250,45));//often, you'll want to connect dataengines or set properties//depending on information contained in item->config().//In this situation that won't be necessary though. item->setWidget(meter);//Job names are not unique across plasma restarts (kuiserver//engine just starts with Job1 again), so avoid problems and//just don't give reinstantiated items a name.item->setName("");//Show a close button.item->showCloseButton();}voidExtenderTutorial::sourceAdded(constQString&source){//Add a new ExtenderItemPlasma::ExtenderItem*item=newPlasma::ExtenderItem(extender());initExtenderItem(item);//We give this item a name, which we don't use in this//example, but allows us to look up extenderItems by calling//extenderItem(name). That function is useful to avoid //duplicating detached ExtenderItems between session, because //you can check if a certain item already exists.item->setName(source);//And we give this item a title. Titles, along with icons and//names are persistent between sessions.item->setTitle(source);//Connect a dataengine. If this applet would display data where //datasources would have unique names, even between sessions, //you should do this in initExtenderItem, so that after a plasma //restart, datasources would still get connected to the //appropriate sources. Kuiserver jobs are not persistent however, //so we connect them here.dataEngine("kuiserver")->connectSource(source,dynamic_cast<QObject*>(item->widget()),200);//Show the popup for 5 seconds if in panel, so the user notices//that there's a new job running.showPopup(5000);}#include "extendertutorial.moc"</code>ThispieceofcodeiswelldocumentedsoIwon'texplainthisanyfurther.==Therest==Finallyyou'llneedacmakelistanda.desktopfile.The.desktopfile:<syntaxhighlightlang="ini">[DesktopEntry]Name=ExtenderTutorialType=ServiceX-KDE-ServiceTypes=Plasma/AppletX-KDE-Library=plasma_applet_extendertutorialX-KDE-PluginInfo-Author=ThePlasmaTeamX-KDE-PluginInfo-Email=plasma-devel@kde.orgX-KDE-PluginInfo-Name=extendertutorialX-KDE-PluginInfo-Version=pre0.1X-KDE-PluginInfo-Website=http://plasma.kde.org/X-KDE-PluginInfo-Category=X-KDE-PluginInfo-Depends=X-KDE-PluginInfo-License=GPLX-KDE-PluginInfo-EnabledByDefault=true</code>TheCMakeList.txt:<codebash>set(extendertutorial_SRCSextendertutorial.cpp)kde4_add_plugin(plasma_applet_extendertutorial${extendertutorial_SRCS})target_link_libraries(plasma_applet_extendertutorial${KDE4_PLASMA_LIBS}${KDE4_KIO_LIBS}${KDE4_KDEUI_LIBS})install(TARGETSplasma_applet_extendertutorialDESTINATION${PLUGIN_INSTALL_DIR})install(FILESplasma-applet-extendertutorial.desktopDESTINATION${SERVICES_INSTALL_DIR})</code>Sothereitis,allittakestomakeasimpleappletthatusesanextender.ForamorecompleteexampleI'dliketopointyouatthekuiserverappletwhichisinplaygroundrightnow.AndI'dencourageyoutoreadtoapidocumentationof{{class|Extender}}and{{class|ExtenderItem}}.EspeciallyExtenderItemhasalotoffeaturesthatarenotusedinthisexamplebutcanbereallyusefullikebeingabletosetatimeoutandaddcustomactionstotheitem,thatareshowintheitem'sdraghandle.I'mlookingforwardtoseeyourappletusingextender,becreative!:)