Difference between revisions of "Development/Tutorials/Write a Flake Plugin"

Jump to: navigation, search
(Copy paste from koffice wiki.)
 
m (Text replace - "</code>" to "</syntaxhighlight>")
 
(13 intermediate revisions by 6 users not shown)
Line 1: Line 1:
This tutorial will guide you step by step through the creation of a flake shape. At the end you will be able to write a shape that is loadable by any KOffice application.
+
This tutorial will guide you step by step through the creation of a [http://wiki.koffice.org/index.php?title=Flake Flake] shape. At the end you will be able to write a shape that is loadable by any KOffice application.
 +
 
 +
For an introduction of KOffice plugins, see [[Development/Tutorials/KOffice Overview]] and for a technical introduction to plugins in KOffice see [[Development/Tutorials/Generic KOffice Plugin Creation|Generic KOffice Plugin Creation]]
  
 
=Do the groundwork - create a shape=
 
=Do the groundwork - create a shape=
Line 6: Line 8:
 
The only method you '''have''' to reimplement is the [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#027c57ec3dc4360294c5bb7330c768d7 paint()] method which is responsible for painting your shape.
 
The only method you '''have''' to reimplement is the [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#027c57ec3dc4360294c5bb7330c768d7 paint()] method which is responsible for painting your shape.
  
You should also implement the [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#8df944b51caa617049593bfa2ff4de49 boundingRect()] method to return the rectangle around your shape. What you might be interested in is the [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#09a45358ef20c372675790531989d5b6 resize()] and [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#5bc408a6f4d7f0bd0ea832002b96ac83 size()] method which set the available size to the shape. Some shapes define the size they need on their own so they reimplement size() to return the size they have.
+
What you might be interested in is the [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#09a45358ef20c372675790531989d5b6 resize()] and [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#5bc408a6f4d7f0bd0ea832002b96ac83 size()] method which set the available size to the shape. Some shapes define the size they need on their own so they reimplement size() to return the size they have.  
 
+
Note from TZ: the boundingRect does not have to be reimplemented
+
if the shape properly returns a size().
+
  
 
An example for this is the KoFormulaShape. A formula has a fixed size due to its contents and so KoFormulaShape reimplements the size() method.
 
An example for this is the KoFormulaShape. A formula has a fixed size due to its contents and so KoFormulaShape reimplements the size() method.
 
If your shape has a special outline, reimplement [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#71ac11dbac93b5bff2f91fb768fa17e5 outline()] to return your shape's outline correctly.
 
If your shape has a special outline, reimplement [http://koffice.org/developer/apidocs/libs-flake/classKoShape.html#71ac11dbac93b5bff2f91fb768fa17e5 outline()] to return your shape's outline correctly.
  
The rotation, scaling and scewing is done through a matrix and so you don't need to care about it.
+
The rotation, scaling and skewing is done through a matrix and so you don't need to care about it.
  
 
So here is an example how your code might look like:
 
So here is an example how your code might look like:
<pre>
+
<syntaxhighlight lang="cpp-qt">
 
#include <KoShape.h>
 
#include <KoShape.h>
  
Line 26: Line 25:
  
 
     // absolutly necessary:
 
     // absolutly necessary:
     void paint( QPainter &painter, const KoViewConverter &converter );
+
     void paint( QPainter &painter,
 
+
                const KoViewConverter &converter );
     // not strictly necessary:
+
     virtual bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context);
    void resize( const QSizeF &size );
+
     virtual void saveOdf(KoShapeSavingContext & context) const;
     QSizeF size();
+
    QRectF boundingRect() const;
+
    const QPainterPath outline();
+
  
 
private:
 
private:
 
     SomeDataClass* m_dataClass;
 
     SomeDataClass* m_dataClass;
 
};
 
};
</pre>
+
</syntaxhighlight>
  
 
=Make your shape loadable - create a factory and a plugin=
 
=Make your shape loadable - create a factory and a plugin=
  
Now when you have created your shape class and implemented all the necessary things you can think about the loading of your shape.
+
Now when you have created your shape class and implemented all the necessary things to make it at least compile you can think about the loading of your shape.
  
 
The KOffice apps use [http://koffice.org/developer/apidocs/libs-flake/classKoShapeFactory.html KoShapeFactory] to get instances of shapes in a generic way. This way of obtaining shape instances is designed after [http://en.wikipedia.org/wiki/Abstract_factory_pattern the factory pattern]. So you should also implement a KoShapeFactory derived class that makes creating new instances of your shape possible. The factory class has to implement two methods:  
 
The KOffice apps use [http://koffice.org/developer/apidocs/libs-flake/classKoShapeFactory.html KoShapeFactory] to get instances of shapes in a generic way. This way of obtaining shape instances is designed after [http://en.wikipedia.org/wiki/Abstract_factory_pattern the factory pattern]. So you should also implement a KoShapeFactory derived class that makes creating new instances of your shape possible. The factory class has to implement two methods:  
  
* KoShape* createDefaultShape();
+
* KoShape* createDefaultShape() const;
 
* KoShape* createShape( const KoProperties* params ) const;
 
* KoShape* createShape( const KoProperties* params ) const;
  
 
An example factory class definition:
 
An example factory class definition:
<pre>
+
<syntaxhighlight lang="cpp-qt">
 
class FooShapeFactory : public KoShapeFactory {
 
class FooShapeFactory : public KoShapeFactory {
 
public:
 
public:
 
     FooShapeFactory( QObject *parent );
 
     FooShapeFactory( QObject *parent );
    ~FooShapeFactory();
 
  
 
     KoShape* createDefaultShape() const;
 
     KoShape* createDefaultShape() const;
     KoShape* createShape( const KoProperties* params ) const;
+
     KoShape* createShape(const KoProperties* params) const;
 
};
 
};
</pre>
+
</syntaxhighlight>
 
The according implementation:
 
The according implementation:
<pre>
+
<syntaxhighlight lang="cpp-qt">
FooShapeFactory::FooShapeFactory( QObject* parent)  
+
FooShapeFactory::FooShapeFactory(QObject* parent)  
   : KoShapeFactory( parent, "FooShape", i18n("Foo Shape") )
+
   : KoShapeFactory( parent, "FooShape",
 +
                    i18n("Foo Shape") )
 
{
 
{
 
     setToolTip( i18n("A foo shape") );
 
     setToolTip( i18n("A foo shape") );
Line 70: Line 66:
 
{
 
{
 
     KoFooShape* fooShape = new KoFooShape();
 
     KoFooShape* fooShape = new KoFooShape();
     // set the defaults
+
     // set defaults
 
     return fooShape;
 
     return fooShape;
 
}
 
}
  
KoShape* FooShapeFactory::createShape( const KoProperties* params ) const
+
KoShape* FooShapeFactory::createShape(
 +
                            const KoProperties* params ) const
 
{
 
{
 +
    KoFooShape* fooShape = new KoFooShape();
 
     // use the params
 
     // use the params
     return new KoFooShape();
+
     return fooShape;
 
}
 
}
</pre>
+
</syntaxhighlight>
  
With the factory there is now a generic way to obtain an instance of your shape. But somehow you have to publish your shape as a plugin to let the KOffice application know that there is a plugin to load. Therefore the flake library provides the [http://koffice.org/developer/apidocs/libs-flake/classKoShapeRegistry.html KoShapeRegistry] class. Each application has access to the registry and to let the app know about your shape you have to register it within the registry.
+
With the factory there is now a generic way to obtain an instance of your shape. But somehow you have to publish your shape as a plugin to let the KOffice application know that there is a plugin to load. Therefore the flake library provides the [http://koffice.org/developer/apidocs/libs-flake/classKoShapeRegistry.html KoShapeRegistry] class. Each application has access to the registry and to let the application know about your shape you have to register it within the registry.
  
 
To register we would have to make a call like this:
 
To register we would have to make a call like this:
KoShapeRegistry::instance()->add( new FooShapeFactory( parent ) );
+
<syntaxhighlight lang="cpp-qt">
In order to make that call, we will create a plugin class which is special in that it will be automatically loaded and created by KOffice when an application starts.  This means that the constructor of our plugin class will be the perfect place to actually register our shape.
+
KoShapeRegistry::instance()->add( new FooShapeFactory( parent ) );
 +
</syntaxhighlight>
 +
In order to make that call, we will create a plugin class which is special in that it will be automatically loaded and created by KOffice when an application starts.  This means that the constructor of our plugin class will be the perfect place to actually register our shape using the above line.
  
 
The registration is done within the constructor of the FooShapePlugin class. This is a very simple class that represents the plugin and does registration.
 
The registration is done within the constructor of the FooShapePlugin class. This is a very simple class that represents the plugin and does registration.
  
 
Example plugin class definition:
 
Example plugin class definition:
<pre>
+
<syntaxhighlight lang="cpp-qt">
 
#include <QObject>
 
#include <QObject>
  
Line 96: Line 96:
 
     Q_OBJECT
 
     Q_OBJECT
 
public:
 
public:
     FooShapePlugin(QObject *parent, const QStringList & );
+
     FooShapePlugin(QObject *parent, const QStringList&);
    ~FooShapePlugin() {}
+
 
};
 
};
</pre>
+
</syntaxhighlight>
  
 
Example plugin class implementation:
 
Example plugin class implementation:
<pre>
+
<syntaxhighlight lang="cpp-qt">
 
#include "FooShapePlugin.h"
 
#include "FooShapePlugin.h"
 
#include <kgenericfactory.h>
 
#include <kgenericfactory.h>
  
K_EXPORT_COMPONENT_FACTORY(fooshapelibrary, KGenericFactory<FooShapePlugin>( "FooPlugin" ) )
+
K_EXPORT_COMPONENT_FACTORY(fooshapelibrary,
 +
    KGenericFactory<FooShapePlugin>( "FooPlugin" ) )
  
FooShapePlugin::FooShapePlugin( QObject *parent, const QStringList& )  
+
FooShapePlugin::FooShapePlugin(QObject *parent, const QStringList&)
  : QObject(parent)
+
    : QObject(parent)
 
{
 
{
 
     // register the shape's factory
 
     // register the shape's factory
     KoShapeRegistry::instance()->add( new KoFooShapeFactory( parent ) );
+
     KoShapeRegistry::instance()->add(
 +
        new KoFooShapeFactory( parent ) );
 
     // we could register more things here in this same plugin.
 
     // we could register more things here in this same plugin.
 
}
 
}
 
#include "FooShapePlugin.moc"
 
#include "FooShapePlugin.moc"
</pre>
+
</syntaxhighlight>
This demonstrates how for the plugin related tasks KDE provides services which are dynamic loaded libraries. The secret ingredient that makes this class the plugin of that library is the call to the K_EXPORT_COMPONENT_FACTORY macro defined in kgenericfactory.h
+
This demonstrates how for the plugin related tasks KDE provides services which are dynamic loaded libraries. The secret ingredient that makes this class the plugin of that library is the call to the K_EXPORT_COMPONENT_FACTORY macro defined in {{path|kgenericfactory.h}}
  
We now have a way to create instances of your shape (FooShapeFactory), a way to register them for the apps (KoShapeRegistry) and a plugin that can dynamically be loaded (FooShapePlugin). The last step is to create a .desktop file that describes your plugin and makes it findable by KOffice. For "X-KDE-Library" you have to set the library name you have already specified within K_EXPORT_COMPONENT_FACTORY().
+
We now have a way to create instances of your shape (FooShapeFactory), a way to register them for the apps (KoShapeRegistry) and a plugin that can dynamically be loaded (FooShapePlugin). The last step is to create a {{path|.desktop}} file that describes your plugin and makes it findable by KOffice. For "X-KDE-Library" you have to set the library name you have already specified within K_EXPORT_COMPONENT_FACTORY().
  
Example fooshape.desktop file:
+
Example {{path|fooshape.desktop}} file:
<pre>
+
<syntaxhighlight lang="ini">
 
[Desktop Entry]
 
[Desktop Entry]
 
Encoding=UTF-8
 
Encoding=UTF-8
Line 130: Line 131:
 
X-KDE-Library=fooshapelibrary
 
X-KDE-Library=fooshapelibrary
 
X-Flake-Version=1
 
X-Flake-Version=1
</pre>
+
</syntaxhighlight>
  
After installing that file in the kde services directory and installing your plugin where the application can open it, your plugin is system wide known and can be loaded by KOffice.
+
After installing that file in the KDE services directory and installing your plugin where the application can open it, your plugin is system wide known and can be loaded by KOffice.
  
 
TODO; add example CMake file.
 
TODO; add example CMake file.
Line 139: Line 140:
  
 
To edit your shape in the GUI the user wants to select a tool and alter your shape. Therefore you have to provide a KoTool derived class. This class implements all edit actions that can be done on your shape but it is also possible to have more than one tool per shape.
 
To edit your shape in the GUI the user wants to select a tool and alter your shape. Therefore you have to provide a KoTool derived class. This class implements all edit actions that can be done on your shape but it is also possible to have more than one tool per shape.
 
Note from TZ:  I suggest creating a koffice/shapes/example dir in svn
 
which you can refer to from this tutorial.
 

Latest revision as of 21:56, 29 June 2011

This tutorial will guide you step by step through the creation of a Flake shape. At the end you will be able to write a shape that is loadable by any KOffice application.

For an introduction of KOffice plugins, see Development/Tutorials/KOffice Overview and for a technical introduction to plugins in KOffice see Generic KOffice Plugin Creation

[edit] Do the groundwork - create a shape

First of all you need a class derived from the KoShape class. This will be the actual shape class so you have to ensure that all the data you need for painting is accessable for this KoShape derived class. The only method you have to reimplement is the paint() method which is responsible for painting your shape.

What you might be interested in is the resize() and size() method which set the available size to the shape. Some shapes define the size they need on their own so they reimplement size() to return the size they have.

An example for this is the KoFormulaShape. A formula has a fixed size due to its contents and so KoFormulaShape reimplements the size() method. If your shape has a special outline, reimplement outline() to return your shape's outline correctly.

The rotation, scaling and skewing is done through a matrix and so you don't need to care about it.

So here is an example how your code might look like:

#include <KoShape.h>
 
class KoFooShape : public KoShape {
public:
    KoFooShape();
    ~KoFooShape();
 
    // absolutly necessary:
    void paint( QPainter &painter,
                const KoViewConverter &converter );
    virtual bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context);
    virtual void saveOdf(KoShapeSavingContext & context) const;
 
private:
    SomeDataClass* m_dataClass;
};

[edit] Make your shape loadable - create a factory and a plugin

Now when you have created your shape class and implemented all the necessary things to make it at least compile you can think about the loading of your shape.

The KOffice apps use KoShapeFactory to get instances of shapes in a generic way. This way of obtaining shape instances is designed after the factory pattern. So you should also implement a KoShapeFactory derived class that makes creating new instances of your shape possible. The factory class has to implement two methods:

  • KoShape* createDefaultShape() const;
  • KoShape* createShape( const KoProperties* params ) const;

An example factory class definition:

class FooShapeFactory : public KoShapeFactory {
public:
    FooShapeFactory( QObject *parent );
 
    KoShape* createDefaultShape() const;
    KoShape* createShape(const KoProperties* params) const;
};

The according implementation:

FooShapeFactory::FooShapeFactory(QObject* parent) 
   : KoShapeFactory( parent, "FooShape",
                     i18n("Foo Shape") )
{
    setToolTip( i18n("A foo shape") );
}
 
KoShape* FooShapeFactory::createDefaultShape() const
{
    KoFooShape* fooShape = new KoFooShape();
    // set defaults
    return fooShape;
}
 
KoShape* FooShapeFactory::createShape(
                            const KoProperties* params ) const
{
    KoFooShape* fooShape = new KoFooShape();
    // use the params
    return fooShape;
}

With the factory there is now a generic way to obtain an instance of your shape. But somehow you have to publish your shape as a plugin to let the KOffice application know that there is a plugin to load. Therefore the flake library provides the KoShapeRegistry class. Each application has access to the registry and to let the application know about your shape you have to register it within the registry.

To register we would have to make a call like this:

KoShapeRegistry::instance()->add( new FooShapeFactory( parent ) );

In order to make that call, we will create a plugin class which is special in that it will be automatically loaded and created by KOffice when an application starts. This means that the constructor of our plugin class will be the perfect place to actually register our shape using the above line.

The registration is done within the constructor of the FooShapePlugin class. This is a very simple class that represents the plugin and does registration.

Example plugin class definition:

#include <QObject>
 
class FooShapePlugin : public QObject {
    Q_OBJECT
public:
    FooShapePlugin(QObject *parent, const QStringList&);
};

Example plugin class implementation:

#include "FooShapePlugin.h"
#include <kgenericfactory.h>
 
K_EXPORT_COMPONENT_FACTORY(fooshapelibrary,
    KGenericFactory<FooShapePlugin>( "FooPlugin" ) )
 
FooShapePlugin::FooShapePlugin(QObject *parent, const QStringList&)
    : QObject(parent)
{
    // register the shape's factory
    KoShapeRegistry::instance()->add(
        new KoFooShapeFactory( parent ) );
    // we could register more things here in this same plugin.
}
#include "FooShapePlugin.moc"

This demonstrates how for the plugin related tasks KDE provides services which are dynamic loaded libraries. The secret ingredient that makes this class the plugin of that library is the call to the K_EXPORT_COMPONENT_FACTORY macro defined in kgenericfactory.h

We now have a way to create instances of your shape (FooShapeFactory), a way to register them for the apps (KoShapeRegistry) and a plugin that can dynamically be loaded (FooShapePlugin). The last step is to create a .desktop file that describes your plugin and makes it findable by KOffice. For "X-KDE-Library" you have to set the library name you have already specified within K_EXPORT_COMPONENT_FACTORY().

Example fooshape.desktop file:

[Desktop Entry]
Encoding=UTF-8
Name=Foo Shape
ServiceTypes=KOffice/Flake
Type=Service
X-KDE-Library=fooshapelibrary
X-Flake-Version=1

After installing that file in the KDE services directory and installing your plugin where the application can open it, your plugin is system wide known and can be loaded by KOffice.

TODO; add example CMake file.

[edit] Make your shape editable - create a tool

To edit your shape in the GUI the user wants to select a tool and alter your shape. Therefore you have to provide a KoTool derived class. This class implements all edit actions that can be done on your shape but it is also possible to have more than one tool per shape.


This page was last modified on 29 June 2011, at 21:56. This page has been accessed 12,742 times. Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal