Writing Kate Application Plugins

Introduction

This tutorial is for KDE4.

First at all, why writing plugins for an editor ? Good question, and I hope I have a good answer: Because we want the base app be small and all extended features not all users need should go into plugins (like CVS suppport, project managment, coffee cooking ;) Therefore Kate provides a quite fullfeatured plugin interface and interfaces to all important stuff in the kate application (the documents, views, mainwindows, sidebar ...).

This tutorial is for people knowing the Qt/KDE libs (and how to code regular KDE apps) and will not describe how to compile, make ... a kde/qt program/lib in detail.

The "helloworld" plugin which is here described can be found in the "kdesdk" package of kde (located in kdesdk/kate/plugins/helloworld). A detailed description of the Kate API is available here..

Coding example: A little Hello World plugin ;)

Like in each programming language learning book I start with an easy "Hello World!" plugin, too ;) This plugin only implements a menuitem and inserts the string "Hello World!" into the current document. The example should show only the basic stuff you need to get your first plugin running, extensions won't be too hard because some existing plugins in kdesdk/kate/plugins are available to steal some code and the API is quiet self-explaining (I hope ;)

Needed files for a plugin

Each plugin consists of at least 3 files which are needed:

  • a desktop file with some information about the plugin (correct syntax described later - named like kate"pluginname".desktop
  • a XmlGui rc file named ui.rc
  • a lib containing the plugin (named like kate"pluginname"plugin)

About the desktop file

The desktop file needs the syntax shown here and must be located in kde services directory with the name kate"yourpluginname".desktop,

Example: katehelloworld.desktop

[Desktop Entry]
Type=Service
ServiceTypes=Kate/Plugin
X-KDE-Library=katehelloworldplugin
X-Kate-Version=3.0
Name=My first Kate Plugin
Comment=Your short description about the plugin goes here

The "X-Kate-Version=3.0" line is important! Kate 3.0 and up won't load your plugin unless the property is similar to the version with which you want to use the plugin!

To install this desktop file to KDE's services directory, we'll later add the following line to the CMakeLists.txt file:

install( FILES katehelloworld.desktop  DESTINATION  ${SERVICES_INSTALL_DIR} )

About the xmlGUI rc file

The xmlgui resource file describes how the menu and toolbar actions appear in the Kate application. For more detailed examples look into other plugins. The ui.rc file for the katehelloworldplugin looks like this:

<!DOCTYPE kpartgui>
<kpartplugin name="katehelloworld" library="libkatehelloworldplugin" version="1">
<MenuBar>
 <Menu name="edit"><Text>&Edit</Text>
    <Action name="edit_insert_helloworld" />
 </Menu>
</MenuBar>
</kpartplugin>

To automatically install the ui.rc file into the right directory later we add the following line to CMakeLists.txt:

install( FILES ui.rc  DESTINATION  ${DATA_INSTALL_DIR}/kate/plugins/katehelloworld )

About plugin lib

Each kate plugin is a library which Kate will dynamically load on demand via KLibLoader. The name of the library is kate"yourpluginname"plugin.so and should be installed into KDE's plugin directory.

The complete result for the file CMakeLists.txt looks like this:

set(katehelloworldplugin_PART_SRCS plugin_katehelloworld.cpp )
kde4_add_plugin(katehelloworldplugin ${katehelloworldplugin_PART_SRCS})
target_link_libraries(katehelloworldplugin  ${KDE4_KDEUI_LIBS} kateinterfaces )

########### install files ###############
install(TARGETS katehelloworldplugin  DESTINATION ${PLUGIN_INSTALL_DIR} )
install( FILES ui.rc  DESTINATION  ${DATA_INSTALL_DIR}/kate/plugins/katehelloworld )
install( FILES katehelloworld.desktop  DESTINATION  ${SERVICES_INSTALL_DIR} )

Start the real coding ;)

Now we can start with the real coding, The Hello World! plugin consists of 2 files, plugin_katehelloworld.cpp and .h.

The plugin header file - plugin_katehelloworld.h

#ifndef _PLUGIN_KATE_HELLOWORLD_H_
#define _PLUGIN_KATE_HELLOWORLD_H_

#include <kate/mainwindow.h>
#include <kate/plugin.h>

#include <kxmlguiclient.h>

class KatePluginHelloWorld : public Kate::Plugin
{
  Q_OBJECT

  public:
    explicit KatePluginHelloWorld( QObject* parent = 0, const QList& = QList() );
    virtual ~KatePluginHelloWorld();

    Kate::PluginView *createView( Kate::MainWindow *mainWindow );
};

class KatePluginHelloWorldView : public Kate::PluginView, public KXMLGUIClient
{
    Q_OBJECT

  public:
    KatePluginHelloWorldView( Kate::MainWindow *mainWindow );
    ~KatePluginHelloWorldView();

    virtual void readSessionConfig( KConfigBase* config, const QString& groupPrefix );
    virtual void writeSessionConfig( KConfigBase* config, const QString& groupPrefix );

  public slots:
    void slotInsertHello();
};

#endif

The plugin header file - line by line

#ifndef _PLUGIN_KATE_HELLOWORLD_H_
#define _PLUGIN_KATE_HELLOWORLD_H_

This are the include guards, that avoid the file to be included more than once.

#include <kate/mainwindow.h>
#include <kate/plugin.h>

#include <kxmlguiclient.h>

This part includes the Kate Plugin Interface headers and some other needed kde headers.

class KatePluginHelloWorld : public Kate::Plugin
{
  Q_OBJECT

  public:
    explicit KatePluginHelloWorld( QObject* parent = 0, const QList& = QList() );
    virtual ~KatePluginHelloWorld();

    Kate::PluginView *createView( Kate::MainWindow *mainWindow );
};

We derive a class from Kate::Plugin. The Kate application will create exactly one instance of this class. To hook into Kate's mainwindows, we have to implement the createView() fuction.

class KatePluginHelloWorldView : public Kate::PluginView, public KXMLGUIClient
{
    Q_OBJECT

  public:
    KatePluginHelloWorldView( Kate::MainWindow *mainWindow );
    ~KatePluginHelloWorldView();

    virtual void readSessionConfig( KConfigBase* config, const QString& groupPrefix );
    virtual void writeSessionConfig( KConfigBase* config, const QString& groupPrefix );

  public slots:
    void slotInsertHello();
};

This class delivers the content for every mainwindow. Our class has one slot called slotInsertHello(). This will be called later when the menu item "Insert Hello World" is activated.

The plugin source file - plugin_katehelloworld.cpp

#include "plugin_katehelloworld.h"
#include "plugin_katehelloworld.moc"

#include <kate/application.h>
#include <ktexteditor/view.h>

#include <kaction.h>
#include <kactioncollection.h>
#include <klocale.h>
#include <kpluginfactory.h>
#include <kpluginloader.h>
#include <kaboutdata.h>

K_PLUGIN_FACTORY(KatePluginHelloWorldFactory, registerPlugin();)
K_EXPORT_PLUGIN(KatePluginHelloWorldFactory(KAboutData("katehelloworld","katehelloworld",ki18n("Hello World"), "0.1", ki18n("Example kate plugin"))) )

KatePluginHelloWorld::KatePluginHelloWorld( QObject* parent, const QList& )
    : Kate::Plugin( (Kate::Application*)parent, "kate-hello-world-plugin" )
{
}

KatePluginHelloWorld::~KatePluginHelloWorld()
{
}

Kate::PluginView *KatePluginHelloWorld::createView( Kate::MainWindow *mainWindow )
{
  return new KatePluginHelloWorldView( mainWindow );
}

KatePluginHelloWorldView::KatePluginHelloWorldView( Kate::MainWindow *mainWin )
    : Kate::PluginView( mainWin )
{
  setComponentData( KatePluginHelloWorldFactory::componentData() );
  setXMLFile( "plugins/katehelloworld/ui.rc" );

  KAction *a = actionCollection()->addAction( "edit_insert_helloworld" );
  a->setText( i18n("Insert Hello World") );
  connect( a, SIGNAL( triggered(bool) ), this, SLOT( slotInsertHello() ) );

  mainWindow()->guiFactory()->addClient( this );
}

KatePluginHelloWorldView::~KatePluginHelloWorldView()
{
  mainWindow()->guiFactory()->removeClient( this );
}

void KatePluginHelloWorldView::slotInsertHello()
{
  if (!mainWindow()) {
    return;
  }

  KTextEditor::View *kv = mainWindow()->activeView();

  if (kv) {
    kv->insertText( "Hello World" );
  }
}

void KatePluginHelloWorldView::readSessionConfig( KConfigBase* config, const QString& groupPrefix )
{
  // If you have session-dependant settings, load them here.
  // If you have application wide settings, you have to read your own KConfig,
  // see the Kate::Plugin docs for more information.
  Q_UNUSED( config );
  Q_UNUSED( groupPrefix );
}

void KatePluginHelloWorldView::writeSessionConfig( KConfigBase* config, const QString& groupPrefix )
{
  // If you have session-dependant settings, save them here.
  // If you have application wide settings, you have to create your own KConfig,
  // see the Kate::Plugin docs for more information.
  Q_UNUSED( config );
  Q_UNUSED( groupPrefix );
}

The plugin source file - line by line

#include "plugin_katehelloworld.h"
#include "plugin_katehelloworld.moc"

#include <kate/application.h>
#include <ktexteditor/view.h>

#include <kaction.h>
#include <kactioncollection.h>
#include <klocale.h>
#include <kpluginfactory.h>
#include <kpluginloader.h>
#include <kaboutdata.h>

Include the header and moc file of the plugin, and some KDE includes needed by the plugin.

K_PLUGIN_FACTORY(KatePluginHelloWorldFactory, registerPlugin();)
K_EXPORT_PLUGIN(KatePluginHelloWorldFactory(KAboutData("katehelloworld","katehelloworld",ki18n("Hello World"), "0.1", ki18n("Example kate plugin"))) )

This macros expands to functions that will create an instance of KatePluginHelloWorld.

KatePluginHelloWorld::KatePluginHelloWorld( QObject* parent, const QList& )
    : Kate::Plugin( (Kate::Application*)parent, "kate-hello-world-plugin" )
{
}

KatePluginHelloWorld::~KatePluginHelloWorld()
{
}

Constructor/Destructor of our example plugin class. This is usually empty. The only thing to do in the constructor is to pass the arguments to Kate::Plugin.

Kate::PluginView *KatePluginHelloWorld::createView( Kate::MainWindow *mainWindow )
{
  return new KatePluginHelloWorldView( mainWindow );
}

As already mentioned, createView() is called for every Kate mainwindow. This means here we have to return an instance of our KatePluginHelloWorldView for the mainwindow.

KatePluginHelloWorldView::KatePluginHelloWorldView( Kate::MainWindow *mainWin )
    : Kate::PluginView( mainWin )
{
  setComponentData( KatePluginHelloWorldFactory::componentData() );
  setXMLFile( "plugins/katehelloworld/ui.rc" );

  KAction *a = actionCollection()->addAction( "edit_insert_helloworld" );
  a->setText( i18n("Insert Hello World") );
  connect( a, SIGNAL( triggered(bool) ), this, SLOT( slotInsertHello() ) );

  mainWindow()->guiFactory()->addClient( this );
}

KatePluginHelloWorldView::~KatePluginHelloWorldView()
{
  mainWindow()->guiFactory()->removeClient( this );
}

Creating an instance of KatePluginHelloWorldView will call the constructor. This is where we have to set the xmlgui file ui.rc and then setup the action we defined there. The action's signal is then connected to our slot slotInsertHello(). Finally we add ourselves to the xmlgui factory by calling addClient(). Note the call of removeClient() in the desturctor for a clean exit.

void KatePluginHelloWorldView::slotInsertHello()
{
  if (!mainWindow()) {
    return;
  }

  KTextEditor::View *activeView = mainWindow()->activeView();

  if (activeView) {
    activeView->insertText( "Hello World" );
  }
}

Here the actions takes place: this slot is called if you hit the "Insert Hello World" menuitem which is created by the pluginview.
mainWindow()->activeView() returns the current active view of kate and activeView->insertText("Hello World") inserts the text at the current cursor position. That's it! :)

void KatePluginHelloWorldView::readSessionConfig( KConfigBase* config, const QString& groupPrefix )
{
  // If you have session-dependant settings, load them here.
  // If you have application wide settings, you have to read your own KConfig,
  // see the Kate::Plugin docs for more information.
  Q_UNUSED( config );
  Q_UNUSED( groupPrefix );
}

void KatePluginHelloWorldView::writeSessionConfig( KConfigBase* config, const QString& groupPrefix )
{
  // If you have session-dependant settings, save them here.
  // If you have application wide settings, you have to create your own KConfig,
  // see the Kate::Plugin docs for more information.
  Q_UNUSED( config );
  Q_UNUSED( groupPrefix );
}

Session related data can be stored and loaded for every mainwindow separately. The helloworld plugin does not need this however.

How does the Plugin look & work after install ?

You first have to go to the Settings -> Configure Kate -> Kate -> Plugins in Kate and activate the Hello World plugin, after that you have a "Insert Hello World" menuitem in your edit menu like on the screenshot. If you activate the menuitem the text "Hello World" will appear at the cursorposition in your current document.

More information ? Advanced plugin stuff ?

To get more information about the plugin interface and its advanced functions please look at the Kate API documentation and the KTextEditor API documentation or contact us on our mailing list!
The complete sources (with CMakeLists.txt) of this example can be found in KDE's source repository under kdesdk/kate/plugins/helloworld or via the web interface.

Comments

The example is wrong

After some hours of be trying to complile the example, i just found some mistakes in the code. In plugin_katehelloworld.h where in this line
explicit KatePluginHelloWorld( QObject* parent = 0, const QList& = QList() );

change it for

explicit KatePluginHelloWorld( QObject* parent = 0,const QList<QVariant>& = QList<QVariant>());

and in plugin_katehelloworld.cpp

replace

KPLUGINFACTORY(KatePluginHelloWorldFactory, registerPlugin();) KEXPORTPLUGIN(KatePluginHelloWorldFactory(KAboutData(“katehelloworld”,”katehelloworld”,ki18n(“Hello World”), “0.1”, ki18n(“Example kate plugin”))) )

KatePluginHelloWorld::KatePluginHelloWorld( QObject* parent, const QList& )

whit this

KPLUGINFACTORY(KatePluginHelloWorldFactory, registerPlugin<KatePluginHelloWorld>();) KEXPORTPLUGIN(KatePluginHelloWorldFactory(KAboutData(“katehelloworld”,”katehelloworld”,ki18n(“Hello World”), “0.1”, ki18n(“Example kate plugin”))) )

KatePluginHelloWorld::KatePluginHelloWorld( QObject* parent, const QList<QVariant>& )

Kind Regards

Mixel Adm

a plugin to write plugins

pate>http://paul.giannaros.org/pate/

Plugin to modify printed output?

I’d like to print from Kate, but have the printout highlight alternating lines of text so lines that wrap will be visually easier to see. Is that possible with the Kate plugins, or do I need to look to something else? (I’m using Kubuntu 7.10)

A Letter to Kate

Just as a sidenote you might be interested in this letter from a certain J. Appleseed. :-)

/Andreas

make

compiling is most difficult to newbies; more difficult than reading and understanding sources !!!!

how to compile

Makefile.am Should this be compiled with automake?

automake

automake: configure.ac' orconfigure.in’ is required

autoscan

autom4te: configure.ac: no such file or directory

??

How can I compile it with automake? How to compile this by directly invoking gcc (if automake is not installed)?

Broken links

The link to KDE API documentation and the link to the source are broken. Particularly, the source link should point to the SVN not CVS ;-)