Andras
Mantia
amantia@kde.org
Michal
Rudolf
mrudolf@kdewebdev.org
Extending &kommander;
Creating &kommander; Widgets
With &kommander; you can create new widgets based on non-&kommander; widgets
fairly easily.
There are two ways of adding new widgets to &kommander;: by creating
plugins or by adding it directly to the &kommander; source.
Create the widget class
The first step is to create the widget class. The approach is to derive your new &kommander; widget class from the
&Qt;/&kde; widget which you wish to integrate with &kommander;, and then also from the
KommanderWidget class. Overriding methods from this class gives the &kommander;
widget its functionality.
Most of the code of a &kommander; widget is just template code.
Therefore, you can use the KDevelop &kommander; plugin template to generate
most the &kommander; widget code for you. To do so run KDevelop (3.5 is recommended),
select Project->New Project, tick the Show all project templates checkbox,
select the C++/&kommander;/KommanderPlugin template. Give a name for your plugin and
follow the instructions in the wizard.
All you have to do is fill in the
important parts relating to your widget like any state information, widget text
etc.
Let's say we want to create a new line edit widget for &kommander;,
based on the KDE widget KLineEdit. Using the &kommander; widget generator
dialog, we get something like this in the generated header file:
#include <kommanderwidget.h>
class QShowEvent;
class KomLineEdit : public KLineEdit, public KommanderWidget
{
Q_OBJECT
Q_PROPERTY(QString populationText READ populationText WRITE setPopulationText DESIGNABLE false)
Q_PROPERTY(QStringList associations READ associatedText WRITE setAssociatedText DESIGNABLE false)
Q_PROPERTY(bool KommanderWidget READ isKommanderWidget)
public:
KomLineEdit(QWidget *a_parent, const char *a_name);
~KomLineEdit();
virtual QString widgetText() const;
virtual bool isKommanderWidget() const;
virtual void setAssociatedText(const QStringList&);
virtual QStringList associatedText() const;
virtual QString currentState() const;
virtual QString populationText() const;
virtual void setPopulationText(const QString&);
public slots:
virtual void setWidgetText(const QString &);
virtual void populate();
protected:
void showEvent( QShowEvent *e );
signals:
void widgetOpened();
void widgetTextChanged(const QString &);
};
Most of this is just template code that you don't need to worry about.
The only two things you need to take notice of are that the kommanderwidget.h
file is included at the top, and that the class is derived first from the
widget we wish to integrate with &kommander;, and secondly from KommanderWidget.
There are a few parts in the cpp file that are important to each particular widget.
KomLineEdit::KomLineEdit(QWidget *a_parent, const char *a_name)
: KLineEdit(a_parent, a_name), KommanderWidget(this)
{
QStringList states;
states << "default";
setStates(states);
setDisplayStates(states);
}
In the constructor, we set the states this widget may have.
Our line edit doesn't have any kind of state, so we just
give it one state default. If you were creating a widget
that had different kinds of states, such as a check box, you might
set three states unchecked, semichecked and checked here.
QString KomLineEdit::currentState() const
{
return QString("default");
}
We set the states in the constructor above, and this just
returns the current state of the widget. For our widget
it will always be default, but you should put code here
that checks what state your widget is currently in and
return the appropriate string here.
QString KomLineEdit::widgetText() const
{
return KLineEdit::text();
}
void KomLineEdit::setWidgetText(const QString &a_text)
{
KLineEdit::setText(a_text);
emit widgetTextChanged(a_text);
}
These are the two most important methods, where the bulk of the
functional code goes.
QString KomLineEdit::widgetText() const method returns the widget text of the
widget (the text that the @widgetText special is expanded to in text
associations). For our widget, the widget text is simply the text inside
the line edit, so we just return that. Similarly when setting the widget text,
we just set the text inside the line edit. We emit the widgetTextChanged()
signal after setting the widget text so other widgets can recognize the fact
that this widget was updated.
In order to add functionality to the widget, you need to register some function and add code to handle them. Here is the code to be used to register, put it in the beginning of the cpp file, above the constructor:
#include <klocale.h> //for i18n
#include "kommanderplugin.h"
#include "specials.h"
enum Functions {
FirstFunction = 1159,
Function1,
Function2,
LastFunction
};
KomLineEdit::KomLineEdit(QWidget *a_parent, const char *a_name)
: KLineEdit(a_parent, a_name), KommanderWidget(this)
{
... //code like described above
KommanderPlugin::setDefaultGroup(Group::DCOP);
KommanderPlugin::registerFunction(Function1, "function1(QString widget, QString arg1, int arg2)", i18n("Call function1 with two arguments, second is optional."), 2, 3);
KommanderPlugin::registerFunction(function2, "function2(QString widget)", i18n("Get a QString as a result of function2."), 1);
}
This registers two functions: function1 and function2. The number assigned to the functions (here 1160 and 1161) must be unique, not used in any other plugin or
inside &kommander;. function1 takes two arguments, one is optional, function2 takes no argument and returns a string. The QString widget argument in the signatures notes that this functions work on a widget, like: KomLineEdit.function1("foo", 1).
To teach &kommander; that the widget supports these functions, add a method like this:
bool KomLineEdit::isFunctionSupported(int f)
{
return (f > FirstFunction && f < LastFunction) || f == DCOP::text;
}
This means that KomLineEdit supports the above functions and the standard text
function.
The function code should be handled inside the handleDCOP method:
QString KomLineEdit::handleDCOP(int function, const QStringList& args)
{
switch (function)
{
case function1:
handleFunction1(arg[0], arg[1].toInt()); //call your function1 handler
break;
case function2:
return handleFunction2(); //call function2
break;
case DCOP::text:
return text(); //call the standard KLineEdit::text() method
break;
default:
return KommanderWidget::handleDCOP(function, args);
}
return QString::null;
}
There are cases when the widget should appear differently in the editor than in
the executor, like the case of ScriptObjects, about dialog, etc. The usual solution is to show a QLabel instead of the widget. For this, your widget must
derive from QLabel, and use this in the constructor:
if (KommanderWidget::inEditor)
{
setPixmap(KGlobal::iconLoader()->loadIcon("iconname", KIcon::NoGroup, KIcon::SizeMedium));
setFrameStyle(QFrame::Box | QFrame::Plain);
setLineWidth(1);
setFixedSize(pixmap()->size());
}
else
setHidden(true);
You can create the widget itself (if you need a widget at all, maybe your
"widget" provides only functionality to access e.g databases) in one of your
functions, like in the execute function. Here is an example from the AboutDialog widget:
QString AboutDialog::handleDCOP(int function, const QStringList& args)
{
switch (function) {
...
case DCOP::execute:
{
if (m_aboutData)
{
KAboutApplication dialog(m_aboutData, this);
dialog.exec();
}
break;
}
...
}
You now have a complete &kommander; widget. All that's left
to do is make it available to the &kommander; system via plugins.
Create the &kommander; plugin
All of the widgets in &kommander; are provided by plugins.
The standard widgets are loaded as widget plugins, but the &kommander; editor
is also linked against this library because certain mechanisms in the editor
are tied specifically to the standard widgets.
A plugin in &kommander; is simply a shared library that has the symbol
'kommander_plugin'. This symbol is a function returning a pointer
to an instance of a KommanderPlugin class.
&kommander; makes it easy to create a plugin for you widgets, so you don't
need to worry about this low level stuff. The basic idea is to derive
a new plugin class for your widgets from the KommanderPlugin base class
and implement a few specific details. A template code is generated by the above described KDevelop project template.
The following code continues on our example of creating a Kommander line edit
widget.
#include <kommanderplugin.h>
/* WIDGET INCLUDES */
#include "komlineedit.h"
First thing we do is include kommanderplugin.h. This contains the definition
of the KommanderPlugin class. We also include all header files of the widgets
this plugin provides - only komlineedit.h in this case.
class MyKomPlugin : public KommanderPlugin
{
public:
MyKomPlugin();
virtual QWidget *create( const QString &className, QWidget *parent = 0, const char *name = 0 );
};
We then create a KommanderPlugin sub-class called MyKomPlugin.
This class simply has a constructor and an overridden create method.
MyKomPlugin::MyKomPlugin()
{
addWidget( "KomLineEdit", "My Widget Group", i18n("A Kommander line edit widget") new QIconSet(KGlobal::iconLoader()->loadIcon("iconname", KIcon::NoGroup, KIcon::SizeMedium)));
//add my other widgets here
}
In the constructor of the plugin, we call addWidget() for each widget we wish
to provide in this plugin. addWidget() takes 6 arguments but only the first 4
are required. In order, the arguments are the widget's class name, group,
tool tip, an iconset for the icon used in the editor toolbar, what's this information, and a bool indicating whether the widget
is a container for other widgets or not. This information is used
by the editor when grouping your widget in menus, providing help information
etc.
Regarding the icon, the above example loads a medium sized icon called iconname from the standard &kde; icon location.
QWidget *MyKomPlugin::create( const QString &className, QWidget *parent, const char *name )
{
if( className == "KomLineEdit" )
return new KomLineEdit( parent, name );
//create my other widgets here
return 0;
}
create() is where instances of our widgets actually get created.
Whenever &kommander; wants an instance of one of the classes provided
by our plugin, it will call create() with the name of the class it wants,
and the parent and name that should be used.
If the className matches any widget we know about, we return a new instance
of that class but otherwise we return 0.
Finally, we export our plugin. This just provides an entry point to our
plugin so the &kommander; system can get access to it. Without this,
&kommander; will not recognize your library as a &kommander; plugin.
KOMMANDER_EXPORT_PLUGIN(MyKomPlugin)
To compile your new &kommander; extension, you should compile all files
as a shared library, linking against the kommanderplugin, kommanderwidget
and any appropriate KDE libraries.
With the line edit example, if we had komlineedit.h, komlineedit.cpp and
mykomplugin.cpp, compiling and installing your plugin would involve
something similar to the following commands:
libtool --mode=compile g++ -$KDEDIR/include -IQTDIR/include \
-I. -fPIC -c komlineedit.cpp
libtool --mode=compile g++ -$KDEDIR/include -IQTDIR/include \
-I. -fPIC -c mykomplugin.cpp
libtool --mode=link g++ -shared -L$KDEDIR/lib -lkdeui -lkommanderwidget \
-lkommanderplugin komlineedit.cppkomlineedit.o mykomplugin.o
-o libmykomplugin.so
If you want to install new plugin system-wide, root, use:
su -c "cp libmykomplugin.so $KDEDIR/lib"
If you use the KDevelop project generator, you will not need to do the above, but instead adapt the Makefile.am to link against extra libraries. By default, it will link to &Qt; and &kde; libraries and generate all the needed object files. Just run make to build, and su -c make install to install.
Configure the installed plugins
Now that the plugin is installed, run the kmdr-plugins program or choose Settings->Configure Plugins from the Editor. The list in this program displays the
plugins that are currently loaded by &kommander;. Add the new plugin to the
list by clicking the Add button in the toolbar and choosing your plugin.
Closing the program saves changes.
If you now restart the &kommander; editor, the widgets your new plugin
provides should be available in the menus and toolbars. You can
now use your new widgets in &kommander; dialogs.
Add the widget directly to &kommander;
This section is for &kommander; developers and describes how to add a new widget directly to &kommander;.
Ironically, this one is more complicated, especially if the widget needs
extra editing methods.
First you create the widget like above. After that you need to register the
widget to the editor and the executor.
To register it inside the editor, add it to editor/widgetdatabase.cpp:
...
#include "mywidget.h"
...
void WidgetDatabase::setupDataBase( int id )
{
...
r = new WidgetDatabaseRecord;
r->name = "MyWidgetName";
r->iconName = "icon.png";
r->group = widgetGroup( "Kommander" );
r->toolTip = i18n("My new widget");
append(r);
...
}
You need to add to the editor/widgetfactory.cpp as well:
...
#include "mywidget.h"
...
QWidget *WidgetFactory::createWidget( const QString &className, QWidget *parent, const char *name, bool init,
const QRect *r, Qt::Orientation orient )
{
...
else if (className == "MyWidgetName")
return new MyWidget(parent, name);
...
}
To register to the executor (actually to the plugin system), add this to
widgets/plugin.cpp:
KomStdPlugin::KomStdPlugin()
{
...
addWidget("MyWidgetName", group, "", new QIconSet(KGlobal::iconLoader()->loadIcon("iconname", KIcon::NoGroup, KIcon::SizeMedium)));
...
}
This is similar to how the widget is registered via the plugin system in the
first case.