Development/Tutorials/Plasma4/UsingExtenders

    From KDE TechBase

    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:

    Instatiate an Extender

    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().

    Add ExtenderItem objects to this Extender

    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>
    
    namespace Plasma
    {
        class ExtenderItem;
    } // namespace Plasma
    
    class ExtenderTutorial : public Plasma::PopupApplet
    {
        Q_OBJECT
        public:
            ExtenderTutorial(QObject *parent, const QVariantList &args);
            ~ExtenderTutorial();
    
            void init();
    
        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.
            void initExtenderItem(Plasma::ExtenderItem *item);
    
        public slots:
            //We want to add a new ExtenderItem everytime a new job 
    	//is started.
            void sourceAdded(const QString &source);
    };
    
    K_EXPORT_PLASMA_APPLET(extendertutorial, ExtenderTutorial)
    
    #endif
    

    This header should be mostly self explanetory. We use PopupApplet to have a nice basis for this kind of applet. The default implementation of PopupApplet's graphicsWidget() returns it's extender if it has one. This is exactly what we want (display the extender in the popup), so we don't need to implement graphicsWidget().

    The implementation

    #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, 
    				   const QVariantList &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()
    {
    }
    
    void ExtenderTutorial::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(const QString &)),
                this, SLOT(sourceAdded(const QString &)));
    }
    
    void ExtenderTutorial::initExtenderItem(Plasma::ExtenderItem *item)
    {
        //Create a Meter widget and wrap it in the ExtenderItem
        Plasma::Meter *meter = new Plasma::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();
    }
    
    void ExtenderTutorial::sourceAdded(const QString &source)
    {
        //Add a new ExtenderItem
        Plasma::ExtenderItem *item = 
    	new Plasma::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"
    

    This piece of code is well documented so I won't explain this any further.

    The rest

    Finally you'll need a cmakelist and a .desktop file.

    The .desktop file:

    [Desktop Entry]
    Name=Extender Tutorial
    Type=Service
    X-KDE-ServiceTypes=Plasma/Applet
    
    X-KDE-Library=plasma_applet_extendertutorial
    X-KDE-PluginInfo-Author=The Plasma Team
    X-KDE-PluginInfo-Email=[email protected]
    X-KDE-PluginInfo-Name=extendertutorial
    X-KDE-PluginInfo-Version=pre0.1
    X-KDE-PluginInfo-Website=http://plasma.kde.org/
    X-KDE-PluginInfo-Category=
    X-KDE-PluginInfo-Depends=
    X-KDE-PluginInfo-License=GPL
    X-KDE-PluginInfo-EnabledByDefault=true
    

    The CMakeList.txt:

    set(extendertutorial_SRCS
        extendertutorial.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(TARGETS plasma_applet_extendertutorial DESTINATION ${PLUGIN_INSTALL_DIR})
    install(FILES plasma-applet-extendertutorial.desktop DESTINATION ${SERVICES_INSTALL_DIR})
    

    So there it is, all it takes to make a simple applet that uses an extender. For a more complete example I'd like to point you at the kuiserver applet which is in playground right now. And I'd encourage you to read to api documentation of Extender and ExtenderItem. Especially ExtenderItem has a lot of features that are not used in this example but can be really useful like being able to set a timeout and add custom actions to the item, that are show in the item's drag handle. I'm looking forward to see your applet using extender, be creative! :)