Development/Tutorials/Services/Plugins: Difference between revisions

    From KDE TechBase
    (fixing memory leak)
    Line 205: Line 205:
         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*)));
         pluginLoader->loadAllPlugins();
         loader->loadAllPlugins();
    }
    }
    </code>
    </code>

    Revision as of 15:44, 6 February 2010


    Development/Tutorials/Services/Plugins


    Creating and Loading Plugins Using KService
    Tutorial Series   Services
    Previous   Traders: Querying the System
    What's Next   n/a
    Further Reading   n/a

    Abstract

    Before starting with the code let's see which advantages we can undertake developing a plugin structured application. First of all an application capable of managing plugins can extend its features with apparently no limits. Anyway, rather than developing a plugin structured application because "it's so coool!!" the developer should think about the real advantages of this kind of development. For instance we can think about an archive manager application: the main program can delegate the creation/extraction/editing of each type of archive to specific plugins. This way we give modularity to the application so that supporting brand new archive types in the future will simply consist in writing the specific plugin and not rewriting the whole application. Ok, then let's stop talking.. and start coding =).

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

    1. include <kdemacros.h>
    2. include <kxmlguiclient.h>
    3. 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:

    1. include "plugin.h"
    2. 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.

    1. include <kdemacros.h>
    2. include <KPluginFactory>
    3. include <KPluginLoader>
    1. 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.

    The plugin loading stuff

    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

    1. include <QObject>
    1. 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

    1. include "pluginloader.h"
    1. include "plugin.h"
    1. include <KServiceTypeTrader>
    2. 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>

    KPluginFactory could not load the plugin:
    %1

    </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.

    A Simple Plugin Example