Konqueror • Technical • Embedded tutorial • Part 4

Embedded Components Tutorial - Page 4

Add Boilerplate KParts Code

The Plan

In this step, you will convert aKtion into an embedded component using "standard" boilerplate code. After this step, when you click on an animation file, it will embed aKtion instead of spawning it off in a separate process. Since no aKtion specific code is used in this step, it won't actually play the animation -- that's reserved for step 3.

The code in this step is completely generic. You can drop it into any application with no modifications!

You will do this in three baby steps:

  1. Add the aktion_part.cpp and aktion_part.h files
  2. Modify the Makefile.am to reflect the new files
  3. Modify the aktion.desktop file to indicate that it is an embedded component

Add KParts Files

Add the following two files to your project with no modifications.

aktion_part.h
#ifndef __aktion_part_h__
#define __aktion_part_h__

#include <kparts/browserextension.h>
#include <klibloader.h>

class KInstance;
class AktionBrowserExtension;
class QLabel;

class AktionFactory : public KLibFactory
{
    Q_OBJECT
public:
    AktionFactory();
    virtual ~AktionFactory();

    virtual QObject* create(QObject* parent = 0, const char* name = 0,
                            const char* classname = "QObject",
                            const QStringList &args = QStringList());

    static KInstance *instance();

private:
    static KInstance *s_instance;
};

class AktionPart: public KParts::ReadOnlyPart
{
    Q_OBJECT
public:
    AktionPart(QWidget *parent, const char *name);
    virtual ~AktionPart();

protected:
    virtual bool openFile();

private:
    QLabel *widget;
    AktionBrowserExtension *m_extension;
};

class AktionBrowserExtension : public KParts::BrowserExtension
{
    Q_OBJECT
    friend class AktionPart;
public:
    AktionBrowserExtension(AktionPart *parent);
    virtual ~AktionBrowserExtension();
};

#endif
aktion_part.cpp
#include "aktion_part.h"

#include <kinstance.h>
#include <klocale.h>
#include <kaboutdata.h>

#include <qlabel.h>

extern "C"
{
    /**
     * This function is the 'main' function of this part.  It takes
     * the form 'void *init_lib<library name>()  It always returns a
     * new factory object
     */
    void *init_libaktion()
    {
        return new AktionFactory;
    }
};

/**
 * We need one static instance of the factory for our C 'main'
 * function
 */
KInstance *AktionFactory::s_instance = 0L;

AktionFactory::AktionFactory()
{
}

AktionFactory::~AktionFactory()
{
    if (s_instance)
        delete s_instance;

    s_instance = 0;
}

QObject *AktionFactory::create(QObject *parent, const char *name, const char*,
                               const QStringList& )
{
    QObject *obj = new AktionPart((QWidget*)parent, name);
    emit objectCreated(obj);
    return obj;
}

KInstance *AktionFactory::instance()
{
    if ( !s_instance )
    {
        KAboutData about("aktion", I18N_NOOP("aKtion"), "1.99");
        s_instance = new KInstance(&about);
    }
    return s_instance;
}

AktionPart::AktionPart(QWidget *parent, const char *name)
    : KParts::ReadOnlyPart(parent, name)
{
    setInstance(AktionFactory::instance());

    // create a canvas to insert our widget
    QWidget *canvas = new QWidget(parent);
    canvas->setFocusPolicy(QWidget::ClickFocus);
    setWidget(canvas);

    m_extension = new AktionBrowserExtension(this);

    // as an example, display a blank white widget
    widget = new QLabel(canvas);
    widget->setText("aKtion!");
    widget->setAutoResize(true);
    widget->show();
}

AktionPart::~AktionPart()
{
    closeURL();
}

bool AktionPart::openFile()
{
    widget->setText(m_file);

    return true;
}

bool AktionPart::closeURL()
{
    return true;
}

AktionBrowserExtension::AktionBrowserExtension(AktionPart *parent)
    : KParts::BrowserExtension(parent, "AktionBrowserExtension")
{
}

AktionBrowserExtension::~AktionBrowserExtension()
{
}

Line By Line: Factory

All KParts components need to construct two classes: A factory class derived from KLibFactory and a view class derived from KParts::ReadOnlyPart. In this tutorial, they are:


#include <kparts/browserextension.h>
#include <klibloader.h>
class AktionFactory : public KLibFactory
class AktionPart : public KParts::ReadOnlyPart

The factory object is responsible for instantiating the components and returning a pointer to them. This is how Konqueror gets a reference to your component.

The entire process start here:


    void *init_libaktion()
    {
        return new AktionFactory;
    }

This is the first function that is called in the loading process. The form of the name is void *init_libyourapp() Keep the part after init_ in mind -- that is the string you will use in the X-KDE-Library entry later on. This function always returns a new instance of your factory object

You also need to overload two functions in the factory class: create() and instance().


QObject *AktionFactory::create(QObject *parent, const char *name, const char*,
                               const QStringList& )
{
    QObject *obj = new AktionPart((QWidget*)parent, name);
    emit objectCreated(obj);
    return obj;
}

The create() function is called each time your component is needed. It is responsible for instantiating a new view object and returning it.


KInstance *AktionFactory::instance()
{
    if ( !s_instance )
    {
        KAboutData about("aktion", I18N_NOOP("aKtion"), "1.99");
        s_instance = new KInstance(&about);
    }
    return s_instance;
}

The instance() function returns an instance of type KInstance. The above code is very standard. Just use it.

Things To Remember:
From a practical point of view, all of the factory code is cut and paste. Change the references to aktion and Aktion to your own naming convention and you are set (e.g., s/aktion/yourapp, s/Aktion/YourApp)

Line By Line: View

There are six methods that you need to overload for your view class... but a lot of the code is very cut and paste. The code in bold (related to QLabel) is the only application specific code in the view class! Here is what each function is doing:


AktionPart::AktionPart(QWidget *parent, const char *name)
    : KParts::ReadOnlyPart(parent, name)
{
    setInstance(AktionFactory::instance());

    // create a canvas to insert our widget
    QWidget *canvas = new QWidget(parent);
    canvas->setFocusPolicy(QWidget::ClickFocus);
    setWidget(canvas);

    m_extension = new AktionBrowserExtension(this);

    // as an example, display a blank white widget
    widget = new QLabel(this);
    widget->setText("aKtion!");
    widget->setAutoResize(true);
    widget->show();
}

The constructor is responsible for initializing your internal variables as well as your "workhorse" object. Typically, you do not do much processing inside of your KParts derived class. Rather, you have an existing class that has all of the functionality and you just use that inside of your view class. In this example, a QLabel label is used just so there is something displayed when you run it.

Notice that a blank "canvas" is the main widget. This is because Konqueror will resize the component to the full width of the view. In many cases, you will want to control the size of your component; both the QLabel and the actual aKtion component are examples of this. In this case, you set a blank widget as the resized widget and just use that as the parent of your "real" class.


bool AktionPart::openFile()
{
    widget->setText(m_file);

    return true;
}

The openFile method is arguably the most important method in this class. It is what is called when Konqueror wants your application to display a file. There is a lot going on behind the scenes of this function, though. The KParts framework itself handles all file downloads. That means that by the time openFile is called, the file to play is already local.

Note that if your application can handle remote URLs already, then you can overload the openURL function instead of openFile


    widget->setText(m_file);

The m_file variable is set by the KParts framework. It is a QString with a path (not a URL) to a local file. If the original URL was a local file, then it will be a path to the actual file. If it the original URL was remote, then the path will likely be in the /tmp directory.

Things To Remember:
Most of the parts code is cut and paste, also. Just rename the references to "aktion" and "Aktion" and insert your own widget in the place of widget and you're set.

Building the Component

Makefile.am
# since the "real" aktion is a library, we start constructing the
# stuff for it
bin_PROGRAMS    = aktion
lib_LTLIBRARIES = libaktion.la

libaktion_la_SOURCES = main.cpp aktionConf.cpp capture.cpp  \
                       aktionVm.cpp kxanim.cpp principal.cpp \
                       aktion_part.cpp
libaktion_la_LDFLAGS = $(all_libraries) -version-info 1:0:0 -module
libaktion_la_LIBADD  = $(LIB_KFILE) $(LIBVM) -lkparts

aktion_SOURCES = main.cpp
aktion_LDADD   = libaktion.la

# these are the headers for your project
noinst_HEADERS = principal.h aktionConf.h kxanim.h capture.h aktionVm.h \
                 aktion_part.h

The two parts files that you just added to the project need to be added to the Makefile just like any other file.. and are done so above. You also need to link to the KParts library since we make extensive use of it now.

Things To Remember:
To build an embedded component, you need only to add:
1. Your "parts" files to _SOURCES and _HEADERS
2. The -lkparts library to _LIBADD

Letting "The System" Know of the Component

aktion.desktop
[Desktop Entry]
Name=aKtion!
Exec=aktion %i %m -caption "%c"
Icon=aktion
MimeType=video/mpeg;video/x-msvideo;video/quicktime;video/x-flic;
Type=Application
DocPath=aktion/aktion.html
Comment=Video Player
Comment[es]=Reproductor de videos
Terminal=0
ServiceTypes=Browser/View
X-KDE-Library=libaktion

So far, you've made all the changes necessary to make aKtion an embedded component (albeit one that doesn't do much).. but there is no way (yet) for Konqueror to know that when it clicks on a Quicktime file, it should embed the component instead of spawning an external viewer.

This is where the aktion.desktop file comes in to play. When you install the desktop file, the KDE mimetypes system becomes aware of all of its fields (if it doesn't do it immediately, you can force a reload by running the kbuildsycoca command). The three that matter in this case or the MimeType, ServiceTypes, and X-KDE-Library entries.


MimeType=video/mpeg;video/x-msvideo;video/quicktime;video/x-flic;
ServiceTypes=Browser/View
X-KDE-Library=libaktion

These lines tell the mimetypes system that aKtion can handle the mimetypes specified in MimeType; it implements an embedded component of type Browser/View; and the name of the component is libaktion.

When Konqueror is asked to execute a Quicktime file, it asks the mimetype system for a corresponding app that can handle video/quicktime. It also asks if the app can handle "Browser/View" embedding. When a pointer to aKtion is returned, it uses X-KDE-Library to tell it that the component it needs to load is 'libaktion'.

Things To Remember:
To "enable" your embedded component, you need to have a .desktop file with:
1. A MimeType entry with your supported mimetypes
2. A ServiceTypes=Browser/View entry (exactly like that)
3. A X-KDE-Library=libyourapp entry

Visible Result

Step 2

Practical Matters

If your own application uses a standard automake like the one above, then converting it to a shared library uses exactly the same steps (and code). There is little specific to aKtion in this procedure.

[ Edit ]

Global navigation links