Development/Tutorials/Generic Calligra Plugin Creation
Parts to be reviewed:
Port to KF5Writing a plugin for Calligra technically means that you create the following things;
- A plugin class should be created as an entry point to all the others below. This is really a plugin-loader object and it will be created in order to allow the plugin to register itself with the specific Calligra registries.
- An implementation of one of the baseclasses (interfaces) from the Calligra base-libraries. For functionality implemented unique to the plugin. Examples of these interfaces are KoShape, KoTool and KoColorSpace.
- A factory class should be created. The factory design pattern allows the plugin to create as many instances of the plugin as required by the user. For example when a new view is opened Calligra asks flake-plugins to create a new tool for that view. Each type of Calligra-plugin type has its own Factory object that should be inherited from.
- A library file is installed that contains all classes that make up the plugin.
- A desktop file is installed with the information about the plugin, including the name of the library.
The concept of a plugin.
The concept is really based on what Object Orientated design is saying, it means that you can create a programmer-interface and encapsulate that in a class without being very clear as to what that implementation is suppost to do. You can then supply several classes that actually contains the implementation of the programmer-interface, each in their own way. The actual way that the class responds to the same environment is thus the implementation and not the programmer-interface. Plugins are a way to provide a different implementation for a known interface.
Calligra has defined several classes as base classes and has made the programmer interface of those base classes generic enough so a wide range of functionality can be accomplished by providing a plugin with a new implementation for such a base class.
An example; one plugin type that Calligra supports is a docker widget. A docker widget is really a widget that can be shown on screen like a button or a text field are widgets as well. The plugin can ship a new implementation of the QDockWidget class by inheriting from that QDockWidget and providing text/buttons and functionality using the full API of the Qt and KDE/Calligra libraries to do their work. This means I can provide a plugin that ships a docker. And the docker shows the time of day, for example. By installing the plugin according to the instructions for that type of Calligra-plugin the Calligra application are able to find it and the docker will be created and shown on screen.
One unique property of a plugin is that it is self contained. Which means that the end result of the plugin is one library which exports just the 'plugin object'. This means that whatever code and classes is changed in one plugin can not possibly affect code in another plugin or even in the Calligra applications. This has the benefit that development becomes more separated and changes in one place will not give problems in another. It is therefor suggested to use any number of classes that fit your design and programming style, and you are free to avoid using namespaces or class name prefixes, as you wish. In other words; your plugin is walled off, nobody will be bothered by what you do, or how do you it.
Components
The first component is the class that inherits from the interface this plugin represents. Each plugin type will go into details on this as each interface has different features and requirements.
The factory component is also unique per plugin, but quite generic in setup and idea. The reason for the existance of a factory is that it makes it possible to create any number of the plugin objects at the time that the host applications want to do this, probably at the request of the user. The workflow in general is this;
- A plugin is loaded and a factory is created. (see the plugin-object component)
- This factory is registered in a Registry.
- Applications query the registry to ask about all plugins and use, for example, the name and an icon from the factory to show buttons on screen.
- The user clicks on a button, or in another way selects the plugin factory.
- The plugin factory is then asked to create a new instance of the thing it is meant to create.
- That new shape or widget or other thing is used in the document or gui. All depending on the plugin type.
This means that our main advertisement to the outside world is the factory. As the factory is used to show the user what kind of things this plugin can do. Each plugin type has its own factory, with at minimum a create() like method which then returns a new instance. The factories typically have a large set of user interface components you can register on them. Like a name, or an icon etc. It is wise to fill as many of them with relevant information as possible.
The next component is the Plugin component. This is a QObject inheriting class that has a specific constructor signature and, interrestingly, will be deleted immediately after it is created.
The trick therefor is to do all the work of creating and registring the factories in the constructor.
The code for this class is pretty simple, and you copy paste it with just a few modifications;
in the header file:
#include <QObject>
class MyPlugin : public QObject {
Q_OBJECT
public:
MyPlugin(QObject * parent, const QStringList &list);
};
in the cpp file;
#include "MyPlugin.h"
#include <kgenericfactory.h>
#include <KoShapeRegistry.h>
#include <KoToolRegistry.h>
K_EXPORT_COMPONENT_FACTORY(libraryname, KGenericFactory<MyPlugin>("MyPlugin"))
MyPlugin::MyPlugin(QObject * parent, const QStringList &)
: QObject(parent)
{
KoToolRegistry::instance()->add(new MyToolFactory(parent));
KoShapeRegistry::instance()->add(new MyShapeFactory(parent));
}
#include <MyPlugin.moc>
This example registeres 2 factories, but you can register any number you want to ship in 1 plugin. Note how only the factory is created, and nothing else is done. This makes loading of the plugin as fast as possible.
If you copy paste this code make sure you update the 'libraryname' to be the (all lowercase) name of your plugin-library. Which is also repeated in the desktop file. Note that the name is without any extentions. In the
K_EXPORT_COMPONENT_FACTORY
macro the 'MyPlugin' is repeated twice. Its easiest to just replace that with the class name you choose for your project.
The library is what will contain all the compiled classes of your plugin.
A plugin is technically implemented as a library, at least on Linux and other unixes. It is loaded as a library by Calligra near the start of the application start. As the plugin is a library all development software can create them using a linker. It is worth noting that it is possible for your plugin to depend on external shared libraries, apart from the Calligra ones. But you should install that shared library as well as your plugin. In cmake the creation of a library goes like this;
########### Flake Plugin library ############### project( myPlugin) include_directories( ${KOMAIN_INCLUDES} ${FLAKE_INCLUDES} )
SET ( myplugin_SRCS MyPlugin.cpp # other cpp files )
kde4_add_plugin(textshape ${myplugin_SRCS}) target_link_libraries(myplugin komain flake) install(TARGETS myplugin DESTINATION ${PLUGIN_INSTALL_DIR}) install( FILES myplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR})
And last is the desktop-file component after which the plugin will be found and used by Calligra.
A desktop file is basically a database record for the KDE database of what is installed. In this case the component is a 'Service'.
For our running example here is myplugin.desktop
[Desktop Entry]
Encoding=UTF-8
Name=My Shape
ServiceTypes=Calligra/Flake
Type=Service
X-KDE-Library=myplugin
X-Flake-Version=1
A couple of things are important;
- The encoding has to match the encoding used. So make sure you use an editor that understands and writes utf-8 when editing this file.
- The name is used for the plugin-manager.
- The ServiceTypes is used to specify which registry should try to load us. One plugin can contain any number of factories, and some would say, it can contain any number of plugins. It typically is useful to combine a shape and a tool into one plugin, for example. Now, when an application looks for all shape plugins, our plugin should be found, naturally. So we can choose to use the ServiceTypes 'Calligra/Shape'. But when the application looks for tools we should also find the plugin. So we might need to have 'Calligra/Tool' instead.
This gets confusing and we don't want one plugin to be loaded twice, so instead we take the more generic 'Calligra/Flake' which will be searched for when either a shape or a tool is requested. Each plugin type will show the best matching ServiceType, but if you are doubtful you can always use 'Calligra/Flake'. - The X-KDE-Library field has to contain the actual (lowercase) name of the library we created in the previous step. Without any extention.