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 -ltdeui -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.