Development/Tutorials/Services/Plugins: Difference between revisions
(Should've been getting the interface because it was overriding the existing KTextEdit and resulting in a constant null pointer) |
|||
(9 intermediate revisions by 8 users not shown) | |||
Line 1: | Line 1: | ||
{{TutorialBrowser| | {{TutorialBrowser| | ||
Line 10: | Line 8: | ||
}} | }} | ||
{{Review|Port to KF5}} | |||
== Abstract == | == Abstract == | ||
Before starting with the code let's see which advantages we can undertake | A plugin is a library of code that can be added to the program at run-time in a modular fashion. Before starting with the code let's see which advantages we can undertake by developing a plugin structured application. Yes, first benefit is that it sounds super cool. But the developer should think about the real advantages of this kind of development. For instance, imagine an archive manager application: the main program can delegate the creation/extraction/editing of each type of archive to specific plugins. This allows the application to support brand new archive types in the future that simply consist in writing the specific plugin instead of the whole application. If the application decides to support new experimental format, it can disable the plugin by default. Using plugins also decreases build time of a large application, instead of reconstructing a single massive binary, a change in one part only requires that the specific plugin be rebuilt. There are many other benefits on top of this, and as you'll see the structure is fairly easy, so let's get to it! | ||
developing a plugin structured application. | |||
== The Text Editor Example == | == The Text Editor Example == | ||
Line 24: | Line 22: | ||
This class will be inherited by every plugin and should give access to the key resources of the main application. In this example the main plugin class will inherit a KXMLGUIClient since every plugin will give access to its features through a gui element (a KAction in this case). | This class will be inherited by every plugin and should give access to the key resources of the main application. In this example the main plugin class will inherit a KXMLGUIClient since every plugin will give access to its features through a gui element (a KAction in this case). | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
#include <kdemacros.h> | #include <kdemacros.h> | ||
#include <kxmlguiclient.h> | #include <kxmlguiclient.h> | ||
Line 55: | Line 53: | ||
}; | }; | ||
</ | </syntaxhighlight> | ||
This base class will give the useful pointers needed by each plugin in order to manipulate the main application data. In this case we only give access to the main editor interface through editorInterface(). | This base class will give the useful pointers needed by each plugin in order to manipulate the main application data. In this case we only give access to the main editor interface through editorInterface(). | ||
Line 61: | Line 59: | ||
Here follows the implementation: | Here follows the implementation: | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
#include "plugin.h" | #include "plugin.h" | ||
#include <KTextEdit> | #include <KTextEdit> | ||
Line 81: | Line 79: | ||
Plugin::~Plugin() | Plugin::~Plugin() | ||
{} | { | ||
delete d; | |||
} | |||
KTextEdit* Plugin::editorInterface() | KTextEdit* Plugin::editorInterface() | ||
Line 93: | Line 93: | ||
} | } | ||
</ | </syntaxhighlight> | ||
Where plugin.h is the header file seen before. | Where plugin.h is the header file seen before. | ||
Line 102: | Line 102: | ||
So we better put this macro in our own myplugin_macros.h as follows. | So we better put this macro in our own myplugin_macros.h as follows. | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
#include <kdemacros.h> | #include <kdemacros.h> | ||
#include <KPluginFactory> | #include <KPluginFactory> | ||
Line 110: | Line 110: | ||
K_PLUGIN_FACTORY( TextEditorFactory, registerPlugin< c >(); ) \ | K_PLUGIN_FACTORY( TextEditorFactory, registerPlugin< c >(); ) \ | ||
K_EXPORT_PLUGIN( TextEditorFactory("c") ) | K_EXPORT_PLUGIN( TextEditorFactory("c") ) | ||
</ | </syntaxhighlight> | ||
This macro simply unifies two KDE macros that allow us the exportation of the plugin. | This macro simply unifies two KDE macros that allow us the exportation of the plugin. | ||
== | == Loading Plugins == | ||
Now let's get prepared to make our application find and load every compatible plugin. We'll make use of a simple helper that uses standard KDE methods in order to locate and load plugins. Here follows header and implementation | Now let's get prepared to make our application find and load every compatible plugin. We'll make use of a simple helper that uses standard KDE methods in order to locate and load plugins. Here follows header and implementation | ||
PLUGINLOADER.H | === PLUGINLOADER.H === | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
#include <QObject> | #include <QObject> | ||
Line 138: | Line 138: | ||
void pluginLoaded(Plugin * plugin); | void pluginLoaded(Plugin * plugin); | ||
}; | }; | ||
</ | </syntaxhighlight> | ||
PLUGINLOADER.CPP | === PLUGINLOADER.CPP === | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
#include "pluginloader.h" | #include "pluginloader.h" | ||
Line 174: | Line 174: | ||
if (!factory) | if (!factory) | ||
{ | { | ||
//KMessageBox::error(0, i18n("<html><p>KPluginFactory could not load the plugin:<br/><i>%1</i></p></html>", | //KMessageBox::error(0, i18n("<html><p>KPluginFactory could not load the plugin:<br /><i>%1</i></p></html>", | ||
// service->library())); | // service->library())); | ||
kError(5001) << "KPluginFactory could not load the plugin:" << service->library(); | kError(5001) << "KPluginFactory could not load the plugin:" << service->library(); | ||
Line 190: | Line 190: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
The code shown simply queries SyCoCa for a plugin for the TextEditor. When creating a plugin a .desktop file is provided with the Service Type the plugin is designed for. We'll see later how to set the proper Service Type for our custom plugin. We emit the pluginLoaded signal when the plugin is properly loaded so that the application will integrate it in the GUI. | The code shown simply queries SyCoCa for a plugin for the TextEditor. When creating a plugin a .desktop file is provided with the Service Type the plugin is designed for. We'll see later how to set the proper Service Type for our custom plugin. We emit the pluginLoaded signal when the plugin is properly loaded so that the application will integrate it in the GUI. | ||
Line 198: | Line 198: | ||
Let's assume we have a pointer to our KTextEdit simply called editor. | Let's assume we have a pointer to our KTextEdit simply called editor. | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
void MyMainWindow::loadPlugins() | void MyMainWindow::loadPlugins() | ||
{ | { | ||
PluginLoader *loader = new PluginLoader(this); | PluginLoader *loader = new PluginLoader(this); | ||
connect(loader, SIGNAL(pluginLoaded(Plugin*)), this, SLOT(addPlugin(Plugin*))); | connect(loader, SIGNAL(pluginLoaded(Plugin*)), this, SLOT(addPlugin(Plugin*))); | ||
loader->loadAllPlugins(); | |||
} | } | ||
</ | </syntaxhighlight> | ||
So with this piece of code we ask PluginLoader to load all plugins and call the slot pluginLoaded we'll see now to implement the plugins in the interface. | So with this piece of code we ask PluginLoader to load all plugins and call the slot pluginLoaded we'll see now to implement the plugins in the interface. | ||
The loadPlugins method is supposed to be called at the beginning of the application in order to load everything before the user can start using it. Of course you can decide what to do with your plugins and ignore this suggestion :). | The loadPlugins method is supposed to be called at the beginning of the application in order to load everything before the user can start using it. Of course you can decide what to do with your plugins and ignore this suggestion :). | ||
< | <syntaxhighlight lang="cpp-qt"> | ||
void MyMainWindow::addPlugin(Plugin *plugin) | void MyMainWindow::addPlugin(Plugin *plugin) | ||
{ | { | ||
Line 216: | Line 216: | ||
guiFactory()->addClient(plugin); | guiFactory()->addClient(plugin); | ||
} | } | ||
</ | </syntaxhighlight> | ||
Since our plugin inherits KXMLGuiClient we can provide any kind of gui client supported and KDE will automagically load it into our application. So this code snippet simply integrates our plugin with the main window. | Since our plugin inherits KXMLGuiClient we can provide any kind of gui client supported and KDE will automagically load it into our application. So this code snippet simply integrates our plugin with the main window. | ||
== A | == The plugin .desktop file == | ||
If you want to define your own kind of plugins, (instead of "TextEditor/Plugin") you will need to create a .desktop file like this one: | |||
<syntaxhighlight lang="ini"> | |||
[Desktop Entry] | |||
Type=ServiceType | |||
X-KDE-ServiceType=MyApp/Plugin | |||
X-KDE-Derived=KPluginInfo | |||
Name=My App Plugin | |||
</syntaxhighlight> | |||
And install it as follows (cmake syntax): | |||
<syntaxhighlight lang="cmake"> | |||
install(FILES myapp_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) | |||
</syntaxhighlight> | |||
== A simple plugin example == | |||
You can now create as many classes that inherit from the plugin interface we have previously defined (class Plugin) as you want, and each class will be found by the plugin loader and instantiated in your main application. For each of these, you will need to add a .desktop file describing it and a CMakeLists.txt that installs everything in the correct place: | |||
myplugin.desktop: | |||
<syntaxhighlight lang="ini"> | |||
[Desktop Entry] | |||
Encoding=UTF-8 | |||
X-KDE-Library=myplugin | |||
X-KDE-PluginInfo-Author=Konqui the Dragon | |||
X-KDE-PluginInfo-Name=ktexteditortimedate | |||
X-KDE-PluginInfo-Version=0.1 | |||
X-KDE-PluginInfo-Website=http://mywebsite.org | |||
X-KDE-PluginInfo-License=GPL | |||
ServiceTypes=MyApp/Plugin | |||
Type=Service | |||
Icon=korganizer | |||
Name=My plugin | |||
Comment=A sample plugin | |||
</syntaxhighlight> | |||
There are important parts on this file: | |||
* '''X-KDE-Library''': will define the library that provides this plugin. It is _really_ important that this variable matches exactly the same with the actual name of the library being built. | |||
* '''ServiceTypes''': this have to match your plugin type. | |||
* '''Type''': in order to load it properly this is needed as "Service". | |||
CMakeLists.txt: | |||
<syntaxhighlight lang="cmake"> | |||
find_package(KDE4 REQUIRED) | |||
include (KDE4Defaults) | |||
include_directories(${KDE4_INCLUDES}) | |||
set(myplugin_SRCS | |||
myplugin.cpp | |||
plugininterface.cpp | |||
) | |||
kde4_add_plugin(myplugin ${myplugin_SRCS}) | |||
target_link_libraries(myplugin | |||
${KDE4_KDECORE_LIBS} | |||
) | |||
install(TARGETS myplugin DESTINATION ${PLUGIN_INSTALL_DIR} ) | |||
install(FILES myplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) | |||
</syntaxhighlight> | |||
Remember you will also need to call the TEXTEDITOR_PLUGIN_EXPORT macro from your .cpp file. |
Latest revision as of 05:09, 15 September 2020
Tutorial Series | Services |
Previous | Traders: Querying the System |
What's Next | n/a |
Further Reading | n/a |
Parts to be reviewed:
Port to KF5Abstract
A plugin is a library of code that can be added to the program at run-time in a modular fashion. Before starting with the code let's see which advantages we can undertake by developing a plugin structured application. Yes, first benefit is that it sounds super cool. But the developer should think about the real advantages of this kind of development. For instance, imagine an archive manager application: the main program can delegate the creation/extraction/editing of each type of archive to specific plugins. This allows the application to support brand new archive types in the future that simply consist in writing the specific plugin instead of the whole application. If the application decides to support new experimental format, it can disable the plugin by default. Using plugins also decreases build time of a large application, instead of reconstructing a single massive binary, a change in one part only requires that the specific plugin be rebuilt. There are many other benefits on top of this, and as you'll see the structure is fairly easy, so let's get to it!
The Text Editor Example
So, even if an archiving application would be nicer to look at, here we will examine a simple text editing application capable of plugin loading. Each plugin will simply give a specific text editing feature.
Creating a Plugin Interface Class
The main step in order to create a plugin structure is to define its base class. This class will be inherited by every plugin and should give access to the key resources of the main application. In this example the main plugin class will inherit a KXMLGUIClient since every plugin will give access to its features through a gui element (a KAction in this case).
#include <kdemacros.h>
#include <kxmlguiclient.h>
#include <QObject>
class KTextEdit;
class KDE_EXPORT Plugin : public QObject , public KXMLGUIClient
{
Q_OBJECT
public:
Plugin(QObject *parent);
virtual ~Plugin();
/**
* @return KTextEdit pointing to the main application editor widget
*/
KTextEdit* editorInterface();
/**
* @internal Used by the main application to correctly set the editor.
* Do not call from within the reimplementation.
*/
void setEditorInterface(KTextEdit *);
private:
class PluginPrivate;
PluginPrivate *d;
};
This base class will give the useful pointers needed by each plugin in order to manipulate the main application data. In this case we only give access to the main editor interface through editorInterface().
Here follows the implementation:
#include "plugin.h"
#include <KTextEdit>
class Plugin::PluginPrivate {
public:
PluginPrivate(Plugin *q):
q(q),
m_editor(0){}
Plugin *q;
KTextEdit *m_editor;
};
Plugin::Plugin(QObject *parent) : QObject(parent),
d(new PluginPrivate(this))
{}
Plugin::~Plugin()
{
delete d;
}
KTextEdit* Plugin::editorInterface()
{
return d->m_editor;
}
void Plugin::setEditorInterface(KTextEdit *editor)
{
d->m_editor = editor;
}
Where plugin.h is the header file seen before. The setEditorInterface allows the main application to set the pointer to the main KTextEditor. This way each programmer will be able to access the main text editing interface and interact with it as well.
The Plugin Factory Macro
Every plugin will be made "visible and available" through the use of a macro. So we better put this macro in our own myplugin_macros.h as follows.
#include <kdemacros.h>
#include <KPluginFactory>
#include <KPluginLoader>
#define TEXTEDITOR_PLUGIN_EXPORT( c ) \
K_PLUGIN_FACTORY( TextEditorFactory, registerPlugin< c >(); ) \
K_EXPORT_PLUGIN( TextEditorFactory("c") )
This macro simply unifies two KDE macros that allow us the exportation of the plugin.
Loading Plugins
Now let's get prepared to make our application find and load every compatible plugin. We'll make use of a simple helper that uses standard KDE methods in order to locate and load plugins. Here follows header and implementation
PLUGINLOADER.H
#include <QObject>
#include "plugintest_macros.h"
class Plugin;
class PluginLoader : public QObject
{
Q_OBJECT
public:
PluginLoader(QObject * parent);
virtual ~PluginLoader();
void loadAllPlugins();
signals:
void pluginLoaded(Plugin * plugin);
};
PLUGINLOADER.CPP
#include "pluginloader.h"
#include "plugin.h"
#include <KServiceTypeTrader>
#include <KDebug>
PluginLoader::PluginLoader(QObject * parent)
: QObject(parent)
{
}
PluginLoader::~PluginLoader()
{
}
void PluginLoader::loadAllPlugins()
{
kDebug() << "Load all plugins";
KService::List offers = KServiceTypeTrader::self()->query("TextEditor/Plugin");
KService::List::const_iterator iter;
for(iter = offers.begin(); iter < offers.end(); ++iter)
{
QString error;
KService::Ptr service = *iter;
KPluginFactory *factory = KPluginLoader(service->library()).factory();
if (!factory)
{
//KMessageBox::error(0, i18n("<html><p>KPluginFactory could not load the plugin:<br /><i>%1</i></p></html>",
// service->library()));
kError(5001) << "KPluginFactory could not load the plugin:" << service->library();
continue;
}
Plugin *plugin = factory->create<Plugin>(this);
if (plugin) {
kDebug() << "Load plugin:" << service->name();
emit pluginLoaded(plugin);
} else {
kDebug() << error;
}
}
}
The code shown simply queries SyCoCa for a plugin for the TextEditor. When creating a plugin a .desktop file is provided with the Service Type the plugin is designed for. We'll see later how to set the proper Service Type for our custom plugin. We emit the pluginLoaded signal when the plugin is properly loaded so that the application will integrate it in the GUI.
Handling plugins by the Main Application
I'll just show pieces of code that handle plugin loading in the main window. Let's assume we have a pointer to our KTextEdit simply called editor.
void MyMainWindow::loadPlugins()
{
PluginLoader *loader = new PluginLoader(this);
connect(loader, SIGNAL(pluginLoaded(Plugin*)), this, SLOT(addPlugin(Plugin*)));
loader->loadAllPlugins();
}
So with this piece of code we ask PluginLoader to load all plugins and call the slot pluginLoaded we'll see now to implement the plugins in the interface. The loadPlugins method is supposed to be called at the beginning of the application in order to load everything before the user can start using it. Of course you can decide what to do with your plugins and ignore this suggestion :).
void MyMainWindow::addPlugin(Plugin *plugin)
{
editor = plugin->editorInterface();
guiFactory()->addClient(plugin);
}
Since our plugin inherits KXMLGuiClient we can provide any kind of gui client supported and KDE will automagically load it into our application. So this code snippet simply integrates our plugin with the main window.
The plugin .desktop file
If you want to define your own kind of plugins, (instead of "TextEditor/Plugin") you will need to create a .desktop file like this one:
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=MyApp/Plugin
X-KDE-Derived=KPluginInfo
Name=My App Plugin
And install it as follows (cmake syntax):
install(FILES myapp_plugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR})
A simple plugin example
You can now create as many classes that inherit from the plugin interface we have previously defined (class Plugin) as you want, and each class will be found by the plugin loader and instantiated in your main application. For each of these, you will need to add a .desktop file describing it and a CMakeLists.txt that installs everything in the correct place:
myplugin.desktop:
[Desktop Entry]
Encoding=UTF-8
X-KDE-Library=myplugin
X-KDE-PluginInfo-Author=Konqui the Dragon
X-KDE-PluginInfo-Email=[email protected]
X-KDE-PluginInfo-Name=ktexteditortimedate
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Website=http://mywebsite.org
X-KDE-PluginInfo-License=GPL
ServiceTypes=MyApp/Plugin
Type=Service
Icon=korganizer
Name=My plugin
Comment=A sample plugin
There are important parts on this file:
- X-KDE-Library: will define the library that provides this plugin. It is _really_ important that this variable matches exactly the same with the actual name of the library being built.
- ServiceTypes: this have to match your plugin type.
- Type: in order to load it properly this is needed as "Service".
CMakeLists.txt:
find_package(KDE4 REQUIRED)
include (KDE4Defaults)
include_directories(${KDE4_INCLUDES})
set(myplugin_SRCS
myplugin.cpp
plugininterface.cpp
)
kde4_add_plugin(myplugin ${myplugin_SRCS})
target_link_libraries(myplugin
${KDE4_KDECORE_LIBS}
)
install(TARGETS myplugin DESTINATION ${PLUGIN_INSTALL_DIR} )
install(FILES myplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
Remember you will also need to call the TEXTEDITOR_PLUGIN_EXPORT macro from your .cpp file.