Development/Tutorials/Games/Palapeli Slicers: Difference between revisions

From KDE TechBase
(new version for libpala is ready)
Line 1: Line 1:
{{TutorialBrowser
{{TutorialBrowser
|series=Programming with the Palapeli API
|series=Programming with the Palapeli API
|name=Creating a Palapeli pattern
|name=Creating a slicer plugin for Palapeli
|pre=[[Development/Tutorials/Programming_Tutorial_KDE_4|Introduction to KDE4 programming]]
|pre=[[Development/Tutorials/Programming_Tutorial_KDE_4|Introduction to KDE4 programming]]
|reading=API reference for [http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/index.html libpala]
|reading=API reference for [http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/index.html libpala]
}}
}}
{{Box|Warning|I'm currently updating this article to the new API. -- [[User:Majewsky|Majewsky]] 13:25, 27 September 2009 (UTC)}}


== Abstract ==
== Abstract ==
Line 20: Line 18:
Slicer writers do not have to bother with the changes that occur in the Palapeli application every now and then. Slicer plugins are not linked against Palapeli, but against ''libpala'', a light-weight library that is designed for the sole purpose of slicing management. To Palapeli, it serves as an interface to talk with arbitrary slicer plugins. To slicer plugins, it provides an API to get and perform slicing jobs.
Slicer writers do not have to bother with the changes that occur in the Palapeli application every now and then. Slicer plugins are not linked against Palapeli, but against ''libpala'', a light-weight library that is designed for the sole purpose of slicing management. To Palapeli, it serves as an interface to talk with arbitrary slicer plugins. To slicer plugins, it provides an API to get and perform slicing jobs.


== Structure ==
A slicer plugin needs to define a subclass of [http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/classPala_1_1Slicer.html <tt>Pala::Slicer</tt>]. Of course, you can also define more classes, but libpala will only talk to the single <tt>Pala::Slicer</tt> subclass.
 
A slicer plugin needs to define a subclass of [http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/classPala_1_1Slicer.html Pala::Slicer]. Of course, you can also define more classes, but libpala will only talk to the single Pala::Slicer subclass.
 
<hr />
Below this line follows everything that has not been edited yet.
<hr />
 
A pattern plugin consists of two classes. The first one (derived from [http://api.kde.org/playground-api/games-apidocs/palapeli/lib/html/classPalapeli_1_1PatternConfiguration.html Palapeli::PatternConfiguration]) tells Palapeli what features this pattern plugin has and which configuration values it needs. The second one (derived from [http://api.kde.org/playground-api/games-apidocs/palapeli/lib/html/classPalapeli_1_1Pattern.html Palapeli::Pattern]) does the actual slicing.


== The code: mypattern.h ==
== The code: myslicer.h ==


<code cppqt n>
<code cppqt n>
#ifndef MYPATTERN_H
#ifndef MYSLICER_H
#define MYPATTERN_H
#define MYSLICER_H


#include <Palapeli/Pattern>
#include <Pala/Slicer>
#include <Palapeli/PatternConfiguration>
#include <Pala/SlicerJob>
#include <Pala/SlicerProperty>


class MyPattern : public Palapeli::Pattern
class MySlicer : public Pala::Slicer
{
{
    public:
Q_OBJECT
        MyPattern(const QString& type);
public:
        virtual ~MyPattern() {}
MySlicer(QObject* parent = 0, const QVariantList& args = QVariantList());
 
virtual bool run(Pala::SlicerJob* job);
        virtual int estimatePieceCount() const;
    protected:
        virtual void doSlice(const QImage& image);
    private:
        QString m_type;
};
};


class MyPatternConfiguration : public Palapeli::PatternConfiguration
#endif // MYSLICER_H
{
    public:
        MyPatternConfiguration(QObject* parent = 0, const QVariantList& args = QVariantList());
        virtual ~MyPatternConfiguration() {}
        virtual Palapeli::Pattern* createPattern() const;
};
 
#endif // MYPATTERN_H
</code>
</code>


As described above, we have declared two classes deriving from the base classes [http://api.kde.org/playground-api/games-apidocs/palapeli/lib/html/classPalapeli_1_1Pattern.html Palapeli::Pattern] and [http://api.kde.org/playground-api/games-apidocs/palapeli/lib/html/classPalapeli_1_1PatternConfiguration.html Palapeli::PatternConfiguration]. For this simple example, we do only reimplement constructors, destructors and some pure virtual functions.
As described above, we need to create a subclass of <tt>Pala::Slicer</tt>. (We also include the other two classes from libpala, <tt>Pala::SlicerJob</tt> and <tt>Pala::SlicerProperty</tt>, which we'll be using in the code.) For this example, we only need to implement the minimum of two functions:
* The constructor of the <tt>Pala::Slicer</tt> subclass needs to have '''exactly that signature''', because this constructor is called by the <tt>KPluginLoader</tt> in this way. The arguments need to be passed to the <tt>Pala::Slicer</tt> constructor, which might want to handle them. (You as a slicer developer will never have to bother with them.)
* <tt>Pala::Slicer</tt> has one pure virtual method <tt>run()</tt>, which does the actual work.


== The code: mypattern.cpp ==
== The code: myslicer.cpp ==


<code cppqt n>
<code cppqt n>
#include "mypattern.h"
#include "myslicer.h"


#include <QImage>
#include <KLocalizedString>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KPluginFactory>
#include <KPluginLoader>
#include <KPluginLoader>


K_PLUGIN_FACTORY(MyPatternFactory, registerPlugin<MyPatternConfiguration>();)
K_PLUGIN_FACTORY(MySlicerFactory, registerPlugin<MySlicer>();)
K_EXPORT_PLUGIN(MyPatternFactory("mypattern"))
K_EXPORT_PLUGIN(MySlicerFactory("myslicer"))


MyPattern::MyPattern(const QString& type)
MySlicer::MySlicer(QObject* parent, const QVariantList& args)
    : Palapeli::Pattern()
: Pala::Slicer(parent, args)
    , m_type(type)
{
{
Pala::SlicerProperty* prop;
prop = new Pala::SlicerProperty(Pala::SlicerProperty::Integer, i18n("Piece count in horizontal direction"));
prop->setRange(3, 100);
prop->setDefaultValue(10);
addProperty("XCount", prop);
prop = new Pala::SlicerProperty(Pala::SlicerProperty::Integer, i18n("Piece count in vertical direction"));
prop->setRange(3, 100);
prop->setDefaultValue(10);
addProperty("YCount", prop);
}
}


int MyPattern::estimatePieceCount() const
bool MySlicer::run(Pala::SlicerJob* job)
{
{
    return 2;
//read job
const int xCount = job->argument("XCount").toInt();
const int yCount = job->argument("YCount").toInt();
const QImage image = job->image();
//calculate some metrics
const int pieceWidth = image.width() / xCount;
const int pieceHeight = image.height() / yCount;
const QSize pieceSize(pieceWidth, pieceHeight);
//create pieces
for (int x = 0; x < xCount; ++x)
{
for (int y = 0; y < yCount; ++y)
{
//calculate more metrics
const QPoint offset(x * pieceWidth, y * pieceHeight);
const QRect pieceBounds(offset, pieceSize);
//copy image part to piece
const QImage pieceImage = image.copy(pieceBounds);
job->addPiece(x + y * xCount, pieceImage, offset);
}
}
//create relations
for (int x = 0; x < xCount; ++x)
{
for (int y = 0; y < yCount; ++y)
{
//along X axis (pointing left)
if (x != 0)
job->addRelation(x + y * xCount, (x - 1) + y * xCount);
//along Y axis (pointing up)
if (y != 0)
job->addRelation(x + y * xCount, x + (y - 1) * xCount);
}
}
return true;
}
}


void MyPattern::doSlice(const QImage& image)
#include "myslicer.moc"
{
</code>
    int pieceWidth, pieceHeight;
    QImage leftPiece, rightPiece;
    if (m_type == i18n("Vertical"))
    {
        pieceWidth = image.width() / 2;
        pieceHeight = image.height();
        //create piece images
        leftPiece = image.copy(QRect(0, 0, pieceWidth, pieceHeight));
        rightPiece = image.copy(QRect(pieceWidth, 0, pieceWidth, pieceHeight));
        //add the pieces to the puzzle scene
        addPiece(leftPiece, QRectF(0, 0, pieceWidth, pieceHeight));
        addPiece(rightPiece, QRectF(pieceWidth, 0, pieceWidth, pieceHeight));
    }
    else //m_type == i18n("Horizontal")
    {
        //like in the other branch, only with other metrics
        pieceWidth = image.width();
        pieceHeight = image.height() / 2;
        leftPiece = image.copy(QRect(0, 0, pieceWidth, pieceHeight));
        rightPiece = image.copy(QRect(0, pieceHeight, pieceWidth, pieceHeight));
        addPiece(leftPiece, QRectF(0, 0, pieceWidth, pieceHeight));
        addPiece(rightPiece, QRectF(0, pieceHeight, pieceWidth, pieceHeight));
    }
    //define a neighbor relation between the pieces
    addRelation(0, 1); //0 and 1 are the consecutive indices of the pieces
}
 
MyPatternConfiguration::MyPatternConfiguration(QObject* parent, const QVariantList& args)
    : Palapeli::PatternConfiguration(parent, args)
{
    Q_UNUSED(parent)
    Q_UNUSED(args)
    addProperty("splitting", Palapeli::PatternConfiguration::String, i18n("Splitting direction:"));
    QVariantList options; options << i18n("Horizontal") << i18n("Vertical");
    addPropertyParameters("splitting", options);
}


Palapeli::Pattern* MyPatternConfiguration::createPattern() const
The two <tt>KPlugin</tt> headers are necessary for the plugin integration, which happens in line 7 and 8. Note that the string constant in line 8 (<tt>"myslicer"</tt>) needs to match your library name.
{
    return new MyPattern(property("splitting").toString());
}
</code>


We will start with the <tt>MyPatternConfiguration</tt> class, because this is the logical entry point. The pattern configuration class is created when Palapeli is started. (The constructor arguments are necessary for the plugin loader, you will not need to bother with them as long as you pass them to the base class constructor.) Its job is to manage the pattern's settings.
The constructor of a <tt>Pala::Slicer</tt> has two tasks: It needs to pass its arguments to the base class constructor, and define the properties of the slicer. Properties make it possible for the user to configure the slicer. In our case, we let the user choose how much pieces are generated. See the <a href="http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/classPala_1_1SlicerProperty.html"><tt>Pala::SlicerProperty</tt></a> documentation for more details on what types of properties can be defined. (''Warning:'' libpala's slicer properties have nothing to do with QObject's meta properties.)


In this case, one is able to configure whether the image should be splitted in horizontal or in vertical direction. A configuration option is described by a <tt>property</tt>. (These have nothing to do with QObject's property system.) Properties are defined with a data type. Note that property data is always passed as QVariant, the data type does only determine which widgets should be used in the configuration interface for the pattern. Legal data types include String (represented by a <tt>KLineEdit</tt>) or Integer (through <tt>KIntSpinBox</tt>). The creation of those widgets is done in the <tt>Palapeli::PatternConfiguration</tt> class, you do only need to define the properties correctly.
We do not save pointers to the <tt>Pala::SlicerProperty</tt> instances that we add to the slicer. The properties are only passed internally to the application, which will construct an appropriate interface, and allow the user to enter values for the properties. When the user has configured everything, both the source image and the selected property values are packed into a <tt>Pala::SlicerJob</tt> object. The <tt>MySlicer::run</tt> method is then called for this job.


When we define our "splitting" property, we assign the data type <tt>String</tt> to it (from the <tt>Palapeli::PatternConfiguration::DataType</tt> enumeration). Also, we give some parameters to this property. It depends on the data type how these parameters are used. In this case, the parameters define a list of valid inputs to this property. Therefore, not a line edit is used in the configuration, but a combo box, which limits the user input to the given strings.
In the <tt>run</tt> method, we read both the image and the property values from the job. After having calculated some metrics, we start to split the image into pieces. The process is straight-forward because all pieces are perfectly rectangular. When piece images are ready, we use the <tt>Pala::SlicerJob::addPiece</tt> method to add them to the result mass. Note that we need to define piece IDs for each piece (the first parameter of the <tt>addPiece</tt> call in line 44). These IDs can be arbitrary non-negative numbers, but have to be unique among all pieces.


When the user has configured its game (and therefore the pattern), the Palapeli game engine will call the <tt>createPattern</tt> method to create a [http://api.kde.org/playground-api/games-apidocs/palapeli/lib/html/classPalapeli_1_1Pattern.html Palapeli::Pattern] object. If you have added properties, read them and pass them to the constructor of your pattern object.
After we have added all pieces, we need to define neighbor relations between pieces. Neighbor relations are used to snap pieces together when they're near enough. Consider three pieces in a row: If piece 1 is near piece 3, nothing should snap because they do not have a common edge. If piece 1 is moved near piece 2, these should snap together. Therefore, we define a relation between piece 1 and piece 2 (and piece 2 and piece 3, respectively). The relation is added through the <tt>Pala::SlicerJob::addRelation</tt> method, which takes the IDs of two neighboring pieces. (The neighbor relation only needs to be defined in one direction: If piece 1 is a neighbor of piece 2, then piece 2 is also a neighbor of piece 1.)


Now we have a [http://api.kde.org/playground-api/games-apidocs/palapeli/lib/html/classPalapeli_1_1Pattern.html Palapeli::Pattern] instance. Its main purpose is the <tt>doSlice</tt> method which takes a method. In this implementation, we copy the left and the right part (or the upper and the lower part) of the image to get two pieces. To add these pieces to Palapeli's puzzle scene, use the base class' <tt>addPiece</tt> method. It will properly report it to Palapeli.
We see that the <tt>Pala::SlicerJob</tt> object is used as a two-way communication channel between the slicer plugin and the application: Palapeli places the source image and the property values in it; the slicer reads these and writes the pieces and the relations into it; and in the end, Palapeli reads the pieces and the relations.


After we have added all pieces, we need to define neighbor relations between pieces. Neighbor relations are used to snap pieces together when they're near enough. Consider three pieces in a row: If piece 1 is near piece 3, nothing should snap because they do not have a common edge. If piece 1 is moved near piece 2, these should snap together. Therefore, we define a relation between piece 1 and piece 2 (and piece 2 and piece 3, respectively). The relation is added through the <tt>addRelation</tt> function. In our case (line 29), we define a relation between the first piece (index 0) and the second piece (index 1). The piece indices are based on the order in which you added the pieces to the scene, and start at zero.
This simple implementation of a <tt>Pala::Slicer::run</tt> method always returns ''true''. You can return ''false'' if something went wrong during the slicing (e.&nbsp;g. because some external resources could not be located).


== Integrate into Palapeli: mypattern.desktop ==
== Integrate into Palapeli: myslicer.desktop ==


<code>
<code>
[Desktop Entry]
[Desktop Entry]
X-KDE-Library=mypattern
Name=My very first slicer
X-KDE-PluginInfo-Author=The best KDE hacker
Name[de]=Mein erstes Schnittmuster
X-KDE-PluginInfo-Email=kdehacker@kde-hackers.example.org
Comment=It's quite simple, actually.
X-KDE-PluginInfo-Name=mypattern
Comment[de]=Eigentlich ist das ganz einfach.
Type=Service
Icon=myslicer
X-KDE-Library=myslicer
X-KDE-ServiceTypes=Libpala/SlicerPlugin
X-KDE-PluginInfo-Author=Kandalf
X-KDE-PluginInfo-Email=kandalf@kde-hackers.example.org
X-KDE-PluginInfo-Name=myslicer
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=http://kde-hackers.example.org/palapelipatterns
X-KDE-PluginInfo-Website=http://kde-hackers.example.org/myslicer
X-KDE-PluginInfo-Category=
X-KDE-PluginInfo-Category=
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-ParentApp=libpalapelipattern
X-KDE-ServiceTypes=Palapeli/PatternPlugin
Type=Service
Icon=palapeli-pattern-mypattern
PatternIdentifier=mypattern
Name=My pattern
Name[de]=Mein Schnittmuster
Comment=The best pattern in the world
Comment[de]=Das beste Schnittmuster auf der ganzen Welt
</code>
</code>


We need to tell the Palapeli library that there is a new plugin out there. We need a desktop entry file like shown here. You can mostly copy everything you see here; as long as you keep the fields <tt>X-KDE-Library</tt> and <tt>X-KDE-PluginInfo-Name</tt> in sync with the library name (see next section). You should not change the <tt>PatternIdentifier</tt> field because that will invalidate saved games created with previous versions. Note also that the name is used in the "New puzzle" dialog to identify this pattern. (It can be translated; for example, a German translation has been added in the above example.)
We need to tell the Palapeli library that there is a new plugin out there. We need a desktop entry file like shown here. You can mostly copy everything you see here; the most important thing is to keep the fields <tt>X-KDE-Library</tt> and <tt>X-KDE-PluginInfo-Name</tt> in sync with the library name (see next section). Note that the name is used in the "New puzzle" dialog to identify this pattern. (It can be translated; for example, a German translation has been added in the above example.)


== Build everything: CMakeLists.txt ==
== Build everything: CMakeLists.txt ==


<code>
<code>
project(mypattern)
project(myslicer)
 
find_package(KDE4 REQUIRED)
find_package(KDE4 REQUIRED)
find_package(Palapeli REQUIRED)
find_package(LibPala REQUIRED)


add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
add_definitions(${LIBPALA_DEFINITIONS})
include_directories(${KDE4_INCLUDES} ${PALAPELI_INCLUDE_DIR})
include_directories(${LIBPALA_INCLUDES})


set(mypattern_SRCS
kde4_add_plugin(myslicer myslicer.cpp)
mypattern.cpp
target_link_libraries(myslicer ${LIBPALA_LIBRARIES})
)


kde4_add_plugin(mypattern ${mypattern_SRCS})
install(TARGETS myslicer DESTINATION ${PLUGIN_INSTALL_DIR})
target_link_libraries(mypattern ${KDE4_KDEUI_LIBS} palapelipattern)
install(FILES myslicer.desktop DESTINATION ${SERVICES_INSTALL_DIR})
</code>


install(TARGETS mypattern DESTINATION ${PLUGIN_INSTALL_DIR})
Everything of this is pretty straightforward if you have previous experience with CMake (and this is what I assume). Make sure you compile everything as a plugin, and install it into the plugin directory. The find script for libpala sets some convenience variables that shorten the code in lines 5, 6 and 9. After having installed the plugin, run <tt>kbuildsycoca4</tt>. This will locate your plugin, and enable Palapeli to use it. Here is how it should look like then:
install(FILES mypattern.desktop DESTINATION ${SERVICES_INSTALL_DIR})
</code>


Everything of this is pretty straightforward if you have previous experience with CMake (and this is what I assume). Make sure you compile everything as a plugin, and install it into the plugin directory. After having installed the plugin, run <tt>kbuildsycoca4</tt>. This will locate your plugin, and enable Palapeli to use it. Here is how it should look like then:
{{Box|Warning|This image is outdated. It will be replaced by a current image, when the new version of Palapeli gains an interface for creating new puzzles.}}


[[Image:Palapeli Pattern Tutorial1.png]]
[[Image:Palapeli Pattern Tutorial1.png]]

Revision as of 14:50, 27 September 2009

Creating a slicer plugin for Palapeli
Tutorial Series   Programming with the Palapeli API
Previous   Introduction to KDE4 programming
What's Next   n/a
Further Reading   API reference for libpala

Abstract

This tutorial shows you how to create a slicer for Palapeli, that is: a plugin for Palapeli that splits an image into pieces.

As an example for a very basic slicer, we will discuss the structure of the rectangle slicer, which splits the image into a configurable number of evenly-sized pieces.

Technical overview

Overview of the Palapeli infrastructure
Overview of the Palapeli infrastructure

Slicer writers do not have to bother with the changes that occur in the Palapeli application every now and then. Slicer plugins are not linked against Palapeli, but against libpala, a light-weight library that is designed for the sole purpose of slicing management. To Palapeli, it serves as an interface to talk with arbitrary slicer plugins. To slicer plugins, it provides an API to get and perform slicing jobs.

A slicer plugin needs to define a subclass of Pala::Slicer. Of course, you can also define more classes, but libpala will only talk to the single Pala::Slicer subclass.

The code: myslicer.h

  1. ifndef MYSLICER_H
  2. define MYSLICER_H
  1. include <Pala/Slicer>
  2. include <Pala/SlicerJob>
  3. include <Pala/SlicerProperty>

class MySlicer : public Pala::Slicer { Q_OBJECT public: MySlicer(QObject* parent = 0, const QVariantList& args = QVariantList()); virtual bool run(Pala::SlicerJob* job); };

  1. endif // MYSLICER_H

As described above, we need to create a subclass of Pala::Slicer. (We also include the other two classes from libpala, Pala::SlicerJob and Pala::SlicerProperty, which we'll be using in the code.) For this example, we only need to implement the minimum of two functions:

  • The constructor of the Pala::Slicer subclass needs to have exactly that signature, because this constructor is called by the KPluginLoader in this way. The arguments need to be passed to the Pala::Slicer constructor, which might want to handle them. (You as a slicer developer will never have to bother with them.)
  • Pala::Slicer has one pure virtual method run(), which does the actual work.

The code: myslicer.cpp

  1. include "myslicer.h"
  1. include <KLocalizedString>
  2. include <KPluginFactory>
  3. include <KPluginLoader>

K_PLUGIN_FACTORY(MySlicerFactory, registerPlugin<MySlicer>();) K_EXPORT_PLUGIN(MySlicerFactory("myslicer"))

MySlicer::MySlicer(QObject* parent, const QVariantList& args) : Pala::Slicer(parent, args) { Pala::SlicerProperty* prop; prop = new Pala::SlicerProperty(Pala::SlicerProperty::Integer, i18n("Piece count in horizontal direction")); prop->setRange(3, 100); prop->setDefaultValue(10); addProperty("XCount", prop); prop = new Pala::SlicerProperty(Pala::SlicerProperty::Integer, i18n("Piece count in vertical direction")); prop->setRange(3, 100); prop->setDefaultValue(10); addProperty("YCount", prop); }

bool MySlicer::run(Pala::SlicerJob* job) { //read job const int xCount = job->argument("XCount").toInt(); const int yCount = job->argument("YCount").toInt(); const QImage image = job->image(); //calculate some metrics const int pieceWidth = image.width() / xCount; const int pieceHeight = image.height() / yCount; const QSize pieceSize(pieceWidth, pieceHeight); //create pieces for (int x = 0; x < xCount; ++x) { for (int y = 0; y < yCount; ++y) { //calculate more metrics const QPoint offset(x * pieceWidth, y * pieceHeight); const QRect pieceBounds(offset, pieceSize); //copy image part to piece const QImage pieceImage = image.copy(pieceBounds); job->addPiece(x + y * xCount, pieceImage, offset); } } //create relations for (int x = 0; x < xCount; ++x) { for (int y = 0; y < yCount; ++y) { //along X axis (pointing left) if (x != 0) job->addRelation(x + y * xCount, (x - 1) + y * xCount); //along Y axis (pointing up) if (y != 0) job->addRelation(x + y * xCount, x + (y - 1) * xCount); } } return true; }

  1. include "myslicer.moc"

The two KPlugin headers are necessary for the plugin integration, which happens in line 7 and 8. Note that the string constant in line 8 ("myslicer") needs to match your library name.

The constructor of a Pala::Slicer has two tasks: It needs to pass its arguments to the base class constructor, and define the properties of the slicer. Properties make it possible for the user to configure the slicer. In our case, we let the user choose how much pieces are generated. See the <a href="http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/classPala_1_1SlicerProperty.html">Pala::SlicerProperty</a> documentation for more details on what types of properties can be defined. (Warning: libpala's slicer properties have nothing to do with QObject's meta properties.)

We do not save pointers to the Pala::SlicerProperty instances that we add to the slicer. The properties are only passed internally to the application, which will construct an appropriate interface, and allow the user to enter values for the properties. When the user has configured everything, both the source image and the selected property values are packed into a Pala::SlicerJob object. The MySlicer::run method is then called for this job.

In the run method, we read both the image and the property values from the job. After having calculated some metrics, we start to split the image into pieces. The process is straight-forward because all pieces are perfectly rectangular. When piece images are ready, we use the Pala::SlicerJob::addPiece method to add them to the result mass. Note that we need to define piece IDs for each piece (the first parameter of the addPiece call in line 44). These IDs can be arbitrary non-negative numbers, but have to be unique among all pieces.

After we have added all pieces, we need to define neighbor relations between pieces. Neighbor relations are used to snap pieces together when they're near enough. Consider three pieces in a row: If piece 1 is near piece 3, nothing should snap because they do not have a common edge. If piece 1 is moved near piece 2, these should snap together. Therefore, we define a relation between piece 1 and piece 2 (and piece 2 and piece 3, respectively). The relation is added through the Pala::SlicerJob::addRelation method, which takes the IDs of two neighboring pieces. (The neighbor relation only needs to be defined in one direction: If piece 1 is a neighbor of piece 2, then piece 2 is also a neighbor of piece 1.)

We see that the Pala::SlicerJob object is used as a two-way communication channel between the slicer plugin and the application: Palapeli places the source image and the property values in it; the slicer reads these and writes the pieces and the relations into it; and in the end, Palapeli reads the pieces and the relations.

This simple implementation of a Pala::Slicer::run method always returns true. You can return false if something went wrong during the slicing (e. g. because some external resources could not be located).

Integrate into Palapeli: myslicer.desktop

[Desktop Entry] Name=My very first slicer Name[de]=Mein erstes Schnittmuster Comment=It's quite simple, actually. Comment[de]=Eigentlich ist das ganz einfach. Type=Service Icon=myslicer X-KDE-Library=myslicer X-KDE-ServiceTypes=Libpala/SlicerPlugin X-KDE-PluginInfo-Author=Kandalf [email protected] X-KDE-PluginInfo-Name=myslicer X-KDE-PluginInfo-Version=1.0 X-KDE-PluginInfo-Website=http://kde-hackers.example.org/myslicer X-KDE-PluginInfo-Category= X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true

We need to tell the Palapeli library that there is a new plugin out there. We need a desktop entry file like shown here. You can mostly copy everything you see here; the most important thing is to keep the fields X-KDE-Library and X-KDE-PluginInfo-Name in sync with the library name (see next section). Note that the name is used in the "New puzzle" dialog to identify this pattern. (It can be translated; for example, a German translation has been added in the above example.)

Build everything: CMakeLists.txt

project(myslicer) find_package(KDE4 REQUIRED) find_package(LibPala REQUIRED)

add_definitions(${LIBPALA_DEFINITIONS}) include_directories(${LIBPALA_INCLUDES})

kde4_add_plugin(myslicer myslicer.cpp) target_link_libraries(myslicer ${LIBPALA_LIBRARIES})

install(TARGETS myslicer DESTINATION ${PLUGIN_INSTALL_DIR}) install(FILES myslicer.desktop DESTINATION ${SERVICES_INSTALL_DIR})

Everything of this is pretty straightforward if you have previous experience with CMake (and this is what I assume). Make sure you compile everything as a plugin, and install it into the plugin directory. The find script for libpala sets some convenience variables that shorten the code in lines 5, 6 and 9. After having installed the plugin, run kbuildsycoca4. This will locate your plugin, and enable Palapeli to use it. Here is how it should look like then:

This image is outdated. It will be replaced by a current image, when the new version of Palapeli gains an interface for creating new puzzles.
Warning