Development/Tutorials/Plasma4/UsingExtenders: Difference between revisions

From KDE TechBase
No edit summary
 
(17 intermediate revisions by 5 users not shown)
Line 1: Line 1:
'''Under Construction'''
==Abstract==
==Abstract==
This tutorial needs KDE 4.2 (trunk) to build. For correct behavior you should also build ExtenderApplet in playground (I really should be moving this to kdebase soon). And finally, you'll need to build the kuiserver dataengine (also in playground).
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.  
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==
==About extenders==
Extenders allow applets to, with only very little extra lines of code, use relocatable widgets. These {{class|Plasma::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.
Extenders allow applets to, with only very little extra lines of code, use relocatable widgets. These {{class|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 {{class|Plasma::Extender}}.
The class that shows and manages these extender items is {{class|Plasma::Extender}}.


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


* Instatiate an {{class|Extender}}.
===Instatiate an {{class|Extender}}===
* Add {{class|ExtenderItem}} objects to this 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().
* ''optional:'' Implement a initExtenderItem(ExtenderItem*) function in your applet, if you want to make your extender items persistent. Use this function to create a widget to display in your ExtenderItem.
* ''optional:'' Add some extra actions to your ExtenderItems.


To don't have to border with your applet resizing correctly when the extender changes size, and to have your applet automatically inconified when in a panel, I'd strongy advice you to use {{class|PopupApplet}} as a base class for your applets if you wish to use extenders. It will spare you a lot of hassle.
===Add {{class|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==
==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 a remove ExtenderItem action is added, so a 'delete' icon is displayed in the items titlebar.
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. Yes, it isn't very usable, but this is only an example.
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===
===The header===
<code cppqt>
<syntaxhighlight lang="cpp-qt">
#ifndef EXTENDERTUTORIAL_H
#ifndef EXTENDERTUTORIAL_H
#define EXTENDERTUTORIAL_H
#define EXTENDERTUTORIAL_H
Line 47: Line 48:


     protected:
     protected:
        //We implement graphicsWidget to let PopupApplet know we want to use this applet's extender
         //Implement this function to make ExtenderItems persistent.
        //as popup.
//This function will get called on plasma start for every  
        QGraphicsWidget *graphicsWidget();
//ExtenderItem that belonged to this applet, and is still
 
//around. Instantiate the widget to be wrapped in the  
         //Implement this function to make ExtenderItems persistent. This function will get called on
//ExtenderItem here.
        //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);
         void initExtenderItem(Plasma::ExtenderItem *item);


     public slots:
     public slots:
         //We want to add a new ExtenderItem everytime a new job is started.
         //We want to add a new ExtenderItem everytime a new job  
//is started.
         void sourceAdded(const QString &source);
         void sourceAdded(const QString &source);
};
};
Line 64: Line 64:


#endif
#endif
</code>
</syntaxhighlight>


This header should be mostly self explanetory. We use PopupApplet to have a nice basis for this kind of applet, which requires us to implement graphicsWidget().
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===
===The implementation===
<code cppqt>
<syntaxhighlight lang="cpp-qt">
#include "extendertutorial.h"
#include "extendertutorial.h"


Line 81: Line 81:
#include <Plasma/Meter>
#include <Plasma/Meter>


ExtenderTutorial::ExtenderTutorial(QObject *parent, const QVariantList &args)
ExtenderTutorial::ExtenderTutorial(QObject *parent,  
  const QVariantList &args)
     : Plasma::PopupApplet(parent, 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");
}
}


Line 92: Line 97:
void ExtenderTutorial::init()
void ExtenderTutorial::init()
{
{
     //An init() function like this is usefull for most applets that use extenders.
     //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.


    //Always instatiate an extender in init, and not earlier, since extenders need to access
     //The message to be shown when there are no ExtenderItems in
    //configuration, and a corona is needed for that.
    //this extender.
    new Plasma::Extender(this);
     //The message to be shown when there are no ExtenderItems in this extender.
     extender()->setEmptyExtenderMessage(i18n("no running jobs..."));
     extender()->setEmptyExtenderMessage(i18n("no running jobs..."));
    //A sane size policy: a fixed verticle size feels natural for vertical lists.
    extender()->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
    //Create a layout, and add the extender to it.
    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(this);
    setLayout(layout);
    layout->addItem(extender());


    //Monitor the geometry of the Extender, so popupapplet can resize the applet when necesarry.
    //I'm working on making this no longer necesarry in the future. Actually, I'll put it next on my todo
    //list, just ignore this line for now.
    connect(extender(), SIGNAL(geometryChanged()), this, SLOT(widgetGeometryChanged()));
     //Notify ourself whenever a new job is created.
     //Notify ourself whenever a new job is created.
     connect(dataEngine("kuiserver"), SIGNAL(sourceAdded(const QString &)),
     connect(dataEngine("kuiserver"),  
    SIGNAL(sourceAdded(const QString &)),
             this, SLOT(sourceAdded(const QString &)));
             this, SLOT(sourceAdded(const QString &)));
}
QGraphicsWidget *ExtenderTutorial::graphicsWidget()
{
    //We want to have the Extender as 'popup widget'
    return extender();
}
}


Line 128: Line 118:


     meter->setMeterType(Plasma::Meter::BarMeterHorizontal);
     meter->setMeterType(Plasma::Meter::BarMeterHorizontal);
     meter->setSvg("extendertutorial/progress-bar");
     meter->setSvg("widgets/bar_meter_horizontal");
     meter->setMaximum(100);
     meter->setMaximum(100);
     meter->setValue(0);
     meter->setValue(0);
Line 134: Line 124:
     meter->setMinimumSize(QSizeF(250, 45));
     meter->setMinimumSize(QSizeF(250, 45));
     meter->setPreferredSize(QSizeF(250, 45));
     meter->setPreferredSize(QSizeF(250, 45));
    meter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);


    //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);
     item->setWidget(meter);


    //Load the title of the ExtenderItem from it's config and set it.
     //Job names are not unique across plasma restarts (kuiserver
    item->setTitle(item->config().readEntry("title", ""));
    //engine just starts with Job1 again), so avoid problems and
 
    //just don't give reinstantiated items a name.
     //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("");
     item->setName("");


     //Add a 'remove' action.
     //Show a close button.
     QAction *removeAction = new QAction(item);
     item->showCloseButton();
    removeAction->setIcon(KIcon("edit-delete"));
    removeAction->setEnabled(true);
    removeAction->setVisible(true);
    item->addAction("remove", removeAction);
    connect(removeAction, SIGNAL(triggered()), item, SLOT(destroy()));
}
}


Line 157: Line 142:
{
{
     //Add a new ExtenderItem
     //Add a new ExtenderItem
     Plasma::ExtenderItem *item = new Plasma::ExtenderItem(extender());
     Plasma::ExtenderItem *item =  
new Plasma::ExtenderItem(extender());
     initExtenderItem(item);
     initExtenderItem(item);


     //We give this item a name, which we don't use in this example, but allows us to look up
     //We give this item a name, which we don't use in this
     //extenderItems by calling extenderItem(name). That function is useful to avoid duplicating
    //example, but allows us to look up extenderItems by calling
     //detached ExtenderItems between session, because you can check if a certain item allready
     //extenderItem(name). That function is useful to avoid  
    //exists.
     //duplicating detached ExtenderItems between session, because  
    //you can check if a certain item already exists.
     item->setName(source);
     item->setName(source);


     //And we give this item a title. It's stupid, but it shows how to make your items persistent
     //And we give this item a title. Titles, along with icons and
     //between sessions.
     //names are persistent between sessions.
     item->setTitle(source);
     item->setTitle(source);
    item->config().writeEntry("title", source);


     //Connect a dataengine. If this applet would display data where datasources would have unique
     //Connect a dataengine. If this applet would display data where  
    //names, even between sessions, you would want to do this in initExtenderItem, so that after a
    //datasources would have unique names, even between sessions,  
     //plasma restart, datasources would still get connected to the appropriate sources. Kuiserver
    //you should do this in initExtenderItem, so that after a plasma
    //jobs are not persistent however, so we connect them here.
     //restart, datasources would still get connected to the  
     dataEngine("kuiserver")->connectSource(source, item->widget(), 200);
    //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.
     //Show the popup for 5 seconds if in panel, so the user notices
    //that there's a new job running.
     showPopup(5000);
     showPopup(5000);
}
}


#include "extendertutorial.moc"
#include "extendertutorial.moc"
</code>
</syntaxhighlight>


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


==The rest==
==The rest==
Finally you'll need a cmakelist, a desktop file, and a svg for the meter widget. You can download those here:
Finally you'll need a cmakelist and a .desktop file.
 
The .desktop file:
<syntaxhighlight lang="ini">
[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-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
</syntaxhighlight>
 
The CMakeList.txt:
<syntaxhighlight lang="bash">
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})


TODO: upload files here.
install(TARGETS plasma_applet_extendertutorial DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES plasma-applet-extendertutorial.desktop DESTINATION ${SERVICES_INSTALL_DIR})
</syntaxhighlight>


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 {{class|Extender}} and {{class|ExtenderItem}}. I'm looking forward to see your extender using applet, be creative! :)
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 {{class|Extender}} and {{class|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! :)

Latest revision as of 23:27, 11 September 2014

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! :)