Development/Architecture/KDE3/KParts
cleanup confusing sections and fix sections which contain a todo
The main idea behind components is reusability. Often, an application wants to use a functionality that another application provides. Of course, the way to do that is simply to create a shared library that both applications use. But without a standard framework for this, it means both applications are very much coupled to the library's API and will need to be changed if the applications decide to use another library instead. Furthermore, integrating the shared functionality has to be done manually by every application.
A framework for components enables an application to use a component it never heard of - and wasn't specifically adapted for - because both the application and the component comply to the framework and know what to expect from each other. An existing component can be replaced with a new implementation of the same functionality, without changing a single line of code in the application, because the interface remains the same.
The framework presented here concerns elaborate graphical components, such as an image viewer, a text editor, a mail composer, and so on. Simpler graphical components are usually widgets; I refine this distinction in the next section. Nongraphical components, such as a parser or a string manipulation class, are usually libraries with a specific Application Programming Interface (API).
Similar frameworks for graphical components exist for a different environment, such as IBM and Apple's OpenDoc, Microsoft's OLE, Gnome's Bonobo, and KDE's previous OpenParts.
The Difference Between Components and Widgets
A KDE component is called a part, and it encapsulates three things: a widget, the functionality that comes with it, and the user interface for this functionality.
The usual example is a text editor component. Its widget is a multiline text widget; its functionality might include Search And Replace, Copy, Cut, Paste, Undo, Redo, Spell Checking. To make it possible for the user to access this functionality, the component also provides the user interface for it: menu items and toolbar buttons.
An application using this component will get the widget embedded into a parent widget it provides, as well as the component's user interface merged into its own menubar and toolbars. This is like embedding a MS Excel document into MS Word, an example everybody knows, or when embedding a KSpread document into KWord, an example that will hopefully become very well known as well.
Another example of very useful component is an image viewer. When using KDE's file manager (Konqueror), clicking an image file opens the image viewer component from KDE's image viewer (KView) and shows it inside Konqueror's window. The part provides actions for zoom in, zoom out, rotate, reset to original size, and orientation.
So, when do you use a part and when do you use a widget?
Use a widget when all the functionality is in the widget itself and doesn't need additional user interface (menu items or toolbar buttons). A button is a widget, a multiline edit is a widget, but a text editor with all the functionality previously mentioned is a part. As you can see there is no problem choosing which one to use.
The KDE Component Framework
KParts is the framework for KDE parts, based on standard KDE/Qt objects, such as QWidget and KTMainWindow. It defines a very simple set of classes: part, plugin, mainwindow, and part manager.
A part, as previously described, is the name for a KDE component. To define a new part, you need to provide the widget, of course, but also the actions that give access to the part's functionality and an XML file that describes the layout of those actions in the user interface.
A pluginis a small piece of functionality that is not implemented by an embedded widget, but that defines some actions to be merged in the application's user interface, such as the calculator plugin for KSpread. It can be graphical, however, like a dialog box or a separate window popping up, or it can be an application-specific plugin and act on the application itself - a spell checker for a word processor, for example.
A KParts mainwindowis a special KTMainWindow whose user interface is described in XML and with actions so that it is able to embed parts. The reason it has to use XML is because merging user interfaces is implemented by merging XML documents.
A part manageris a more abstract object whose task is to handle the activation and the deactivation of the parts. Of course, this is useful only for mainwindows that embed more than one part, such as KOffice documents (where the main document is also a part), or Konqueror (where each view is a part). KWrite, which embeds only its own part, doesn't need a part manager.
In the following sections, you create a part for a simple text editor, a main window able to embed an existing PostScript-viewer part, a part manager to embed more than one part, and even a plug-in; thus, you will know everything about KParts.
Describing User Interface in XML
The XML file used by a part or a mainwindow provides only the layout of the actions in the user interface. The actions themselves are still implemented in the code, with slots, as usual.
More precisely, the XML file describes the layout of the menus and submenus in the menubar (only one menubar is always present) and the menu items within those menus, as well as the toolbars and the toolbar buttons. The menubar, menus, and toolbars are containers; menu items and toolbar buttons are the actions.
A sample XML file for a mainwindow looks like the one shown in Listing 13.1.
Listing 13.1 Excerpt of konqueror.rc: A User Interface Described in XML
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="Konqueror" version="1">
<MenuBar>
<Menu name="file"><Text>&File</Text>
<Action name="find"/>
<Separator/>
<Action name="print"/>
<Separator/>
<Action name="close"/>
</Menu>
<Menu name="edit"><Text>&Edit</Text>
<Action name="cut"/>
<Action name="copy"/>
<Action name="paste"/>
<Action name="trash"/>
<Action name="del"/>
<Separator/>
<Merge/>
<Separator/>
</Menu>
<Merge/>
</MenuBar>
<ToolBar fullWidth="true" name="mainToolBar"><Text>Main</Text>
<Action name="cut"/>
<Action name="copy"/>
<Action name="paste"/>
<Action name="print"/>
<Separator/>
<Merge/>
<Separator/>
<Action name="animated_logo"/>
</ToolBar>
<ToolBar name="locationToolBar"><Text>Location</Text>
<Action name="toolbar_url_combo"/>
</ToolBar>
</kpartgui>
</syntaxhighlight>
The DOCTYPE tag contains the name of the main element, which should be set to kpartgui. The top-level elements are MenuBar and ToolBar, as expected. In the MenuBar, the menus are described. Note that they have a name, used for merging later on, and a text, which is displayed in the user interface, possibly translated. Because this is XML, & has to be encoded as &. Inside a Menu tag, the actions, some separators, and possibly submenus are laid out. The action names are very important because they are used to match the actions created in the code.
The toolbars are then described. Note that the main toolbar has to be called mainToolBar because its settings can be different. KToolBar takes care of adding text under icons for this particular toolbar, if the user wants them. Actions are laid out in the toolbars the usual way. The text for a toolbar is used where the name of the toolbar is to be displayed to the user, possibly translated, such as the toolbar editor.
Another important tag is the Merge tag. This tag tells the framework where the actions of the active part - and the plug-ins - should be merged in a given container. As you can see, this XML file inserts the part's actions before a separator in the Edit menu, whereas it doesn't specify a position for items in the File menu. This means that if the part defines actions for the File menu, they will be appended to the File menu of the mainwindow.
The merging happens when a part simply uses the same menu name or toolbar name as the mainwindow.
If a Merge tag is specified as a child of the MenuBar tag, the merging happens at that position; otherwise, it takes place on the right of the existing menus. The toolbar allows merging of the part's actions as well, based on the same principle.
The Merge tag can also appear in a part's XML. It will be used for merging plug-ins or for more advanced uses; the merging engine can merge any number of "inputs" and it is possible to define specific inputs, such as the one Konqueror defines for its View menu.
Another advanced use of the Merge tag is to set a name attribute for it. For instance, if another XML file wants to embed a part and any other parts or plug-ins at different positions in a given menu, it can use two merge tags:
<Merge name="MyPart"/>
.
.
.
<Merge />
</syntaxhighlight>
Using the name attribute for the Merge tag allows you to control at which position each XML fragment is merged, but it is usually unnecessary.
Read-Only and Read/Write Parts
The framework defines three kind of parts. The generic class is Part and is the one that provides the basic functionality for a part: widget, XML, and actions.
Read-Only Parts
The class ReadOnlyPart provides a common framework for all parts that implement any kind of viewer. A text viewer, an image viewer, a PostScript viewer, and a Web browser are all viewers. What they have in common is that they all act on a URL, and in a read-only way. It has always been a design decision in KDE to provide network transparency wherever possible, which is why most KDE applications use URLs, not only filenames. The framework defines methods for opening a URL, closing a URL, and above all provides network transparency - by downloading the file, if remote, and emitting signals (started, progression, completed). The part itself has to provide only openFile(), which opens a local file.
This common framework for read-only parts enables applications to embed all viewers the same way and to better control those parts. For instance, when Konqueror uses a read-only part to display a file, it can make it open the file using openURL() and get all the progress information from the part. All this is not available in the generic Part class.
Read-Write Parts
The third kind of part is the ReadWritePart, which is an extension of the read-only one, to which it obviously adds the possibility to modify and save the document. This is the one used by a text editor part such as KWrite's, as well as all KOffice parts.
For read/write parts, the framework provides the other half of the network transparency - re-uploading the document when saving, for remote files. A read/write part must also know how to act read-only, in case it is used as a read-only part. This is what happens when embedding KWrite or KOffice into Konqueror to view a text file, without being allowed to edit the file. More generally, any editor can be and must know how to be a viewer, as well.
Creating a Part
In this section, you create a very simple part for a text editor. If you have closely followed the previous section, you know that the part should inherit KParts::ReadWritePart.
At this point, it is a very good idea to read kparts/part.h, directly or preferably after running kdoc on it (see Chapter 16, "Creating Documentation," for information about kdoc). This tells you that a read/write part implementation has to provide the methods openFile() and saveFile().
The task of openFile() is obviously to open a local file, which the framework has previously downloaded for us in case the URL that the user wants to open is a remote one. In this case, the file you open is a temporary local file.
In saveFile(), the part saves to the local file, and in case it's a temporary file, the framework takes care of uploading the new file.
You can now sketch the header file for your part, which is called NotepadPart (see Listing 13.2).
Listing 13.2 notepad_part.h: Header of the NotepadPart Class
- ifndef __notepad_h__
- define __notepad_h__
- include <kparts/part.h>
class QMultiLineEdit;
class NotepadPart : public KParts::ReadWritePart
{
Q_OBJECT
public:
NotepadPart( QWidget * parent, const char * name = 0L );
virtual ~NotepadPart() {}
virtual void setReadWrite( bool rw );
protected:
virtual bool openFile();
virtual bool saveFile();
protected slots:
void slotSelectAll();
protected:
QMultiLineEdit * m_edit;
KInstance *m_instance;
};
- endif
</syntaxhighlight>
The parent passed to the constructor is both the parent of the widget and the parent of the part itself, so that both get destroyed if the parent is destroyed. Note that having the same parent is not mandatory. If they have different parents, the framework deletes the widget if the part is destroyed and deletes the part if the widget is destroyed.
The class members are a QMultiLineEdit (the multiline widget from Qt), and a KInstance. An instance enables access to global KDE objects, which can be different from the ones of the application. The application's configuration file and the one of any other instance is different, as well as the search paths for locate(), and so on. In KParts, this is used to locate the XML file describing the part, which is usually installed into share/apps/instancename/.
In addition, you define a slot, slotSelectAll(), to be connected to the action your part provides.
The corresponding XML file for the part NotepadPart is listed in Listing 13.3 and defines its GUI by an action named selectall, to be inserted into the menu Edit in the menubar. Note that the text for the Edit menu is specified, which is mandatory even if mainwindows usually specify it, because it has to work even if a mainwindow doesn't have an Edit menu on its own. So the rule is simple: always provide a text for all menus.
Listing 13.3 notepadpart.rc: XML Description of the Notepad Part's User Interface
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="NotepadPart" version="1">
<MenuBar>
<Menu name="Edit"><Text>&Edit</Text>
<Action name="selectall"/>
</Menu>
</MenuBar>
<StatusBar/>
</kpartgui>
</syntaxhighlight>
An important task in the definition of a part is its constructor. It must at least define the instance, the widget, the actions, and the XML File. The constructor for this example could be as shown in Listing 13.4.
Listing 13.4 notepad_part.cpp part 1: Constructor
NotepadPart::NotepadPart( QWidget * parent, const char * name )
: KParts::ReadWritePart( parent, name )
{
KInstance * instance = new KInstance( "notepadpart" );
setInstance( instance );
m_edit = new QMultiLineEdit( parent, "multilineedit" );
m_edit->setFocus();
setWidget( m_edit );
(void)new KAction( i18n( "Select All" ), 0, this,
SLOT( slotSelectAll() ), actionCollection(), "selectall" );
setXMLFile( "notepadpart.rc" );
setReadWrite( true );
}
</syntaxhighlight>
After calling the parent constructor with parent and name, you create an instance, named notepadpart, and declare it to the framework using setInstance(). This is a temporary solution; you'll see later how to use a library-factory's instance. Then you create the multiline edit widget, give it the focus, and declare it as well, using setWidget().
The next step is to create the actions that your part provides. The "selectall" action is given a translated label, is connected to slotSelectAll(), and is created as a child of the action collection that the framework provides. This is important, because it's the only way to make it find the action later on, when parsing the XML file. This is why you don't even need to store the action in a variable, unless you want to be able to enable or disable it later.
You also need to give the framework the name of the XML file describing the part's GUI. As mentioned previously, it is usually installed into share/apps/instancename/, and in this case, you simply pass the filename with no path. It is also possible, but not recommended, to install the XML file anywhere else and provide a full path in setXMLFile().
Finally, the part is set to read/write mode. Read/write parts feature the setReadWrite() call, which enables you to set the read/write mode on or off. Most parts should reimplement this method to enable or disable anything that modifies the part, KActions as well as any direct modification provided by the widget itself. The reimplementation of setReadWrite() for the NotepadPart is shown in Listing 13.5.
Listing 13.5 notepad_part.cpp part 2: Implementation of setReadWrite
void NotepadPart::setReadWrite( bool rw )
{
m_edit->setReadOnly( !rw );
if (rw)
connect( m_edit, SIGNAL( textChanged() ), this, SLOT( setModified() ) );
else
disconnect( m_edit, SIGNAL( textChanged() ), this, SLOT( setModified() ) );
ReadWritePart::setReadWrite( rw ); // always call the parent implementation
}
</syntaxhighlight>
In the example, there are no actions to disable, but the multiline widget has to be set to its read-only mode.
The connection to setModified(), done in read/write mode only, enables the framework to keep track of the state of the document. When closing a document that has been modified, the framework automatically asks whether it should save it and allow you to cancel the close. Note that to make all this work, you just needed to connect a signal when the part is in read/write mode and disconnect it when it's in read-only mode. This avoids warnings when a loading a file, which changes the text.
It might seem a bit painful to have to handle both read/write and read-only mode, but doing this gives for free the possibility to embed the part as a viewer, in Konqueror, for instance, so it's usually worth doing.
Your part is created; you need to make it useful. The method that all read-only parts - and by inheritance, all read/write parts as well - must reimplement is the openFile() method. This is where a part opens and displays the local file, whose full path is provided in the member variable m_file, and which the framework downloaded from a remote location first, if necessary. Because your part is a text viewer, all it has to do is read the file into a QString and set the multiline widget's text from it, as shown in Listing 13.6.
Listing 13.6 notepad_part.cpp part 3: Implementation of openFile
bool NotepadPart::openFile()
{
QFile f(m_file);
QString s;
if ( f.open(IO_ReadOnly) )
{
QTextStream t( &f );
while ( !t.eof() ) {
s += t.readLine() + "\n";
}
f.close();
}
m_edit->setText(s);
return true;
}
</syntaxhighlight>
The last thing you need to do is, of course, to provide saving; otherwise, the user will not like it! All read/write parts have to reimplement saveFile() to save the document to m_file, as shown in Listing 13.7. Note that the framework takes care of Save As (changing the URL to Save To), as well as uploading the saved file, if necessary.
Listing 13.7 notepad_part.cpp part 4: Implementation of saveFile
bool NotepadPart::saveFile()
{
if ( !isReadWrite() )
return false;
QFile f(m_file);
QString s;
if ( f.open(IO_WriteOnly) ) {
QTextStream t( &f );
t << m_edit->text();
f.close();
return true ;
} else
return false;
}
</syntaxhighlight>
You know how to create a part now. But currently, it can be used only by linking directly to its code. Although this is enough in some cases, such as KWrite's part embedded by KWrite itself, it is much more flexible to provide dynamic linking to the library containing the part. This is not directly related to KParts, but it is necessary to make it possible for any application to use the part.
The first step is to compile the part in a shared library, which is really simple using automake. The relevant portion of Makefile.am is shown in Listing 13.8
Listing 13.8 Extract from Makefile.am
lib_LTLIBRARIES = libnotepad.la
libnotepad_la_SOURCES = notepad_part.cpp notepad_factory.cpp
libnotepad_la_LIBADD = $(LIB_KFILE) $(LIB_KPARTS)
libnotepad_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN)
METASOURCES = AUTO
Your part is now available in a shared library, but this is not enough. You must provide a way for anybody opening that library dynamically to create a part. This is done using a factory, derived from KLibFactory, which you'll do in the class NotepadFactory. An application willing to open a shared library dynamically uses the class KLibLoader, which takes care of locating the library, opening it, and calling an initialization function - here init_libnotepad(). This function creates a NotepadFactory and returns it to KLibLoader, which can then call the create method on the factory. This means that all you need to do in the library itself is define init_libnotepad() and the NotepadFactory.
The header for the factory is the one shown in Listing 13.9.
Listing 13.9 notepad_factory.h: Header File for NotepadFactory
- include <klibloader.h>
class KInstance;
class KAboutData;
class NotepadFactory: public KLibFactory
{
Q_OBJECT
public:
NotepadFactory( QObject * parent = 0, const char * name = 0 );
~NotepadFactory();
// reimplemented from KLibFactory
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;
static KAboutData * s_about;
};
</syntaxhighlight>
As required by KLibFactory, your factory implements the create method, which creates a Notepad part and sets it to read/write mode or read-only mode, depending on whether the classname is KParts::ReadWritePart or KParts::ReadOnlyPart.
It also features a static instance, which is used in the part, instead of creating your own instance for each part. It is static because usually there is only one instance per library.
This means the code of notepad_part.cpp should be modified to call setInstance( NotepadFactory::instance() ); instead of creating its own instance.
The implementation for the NotepadFactory is shown in Listing 13.10.
Listing 13.10 notepad_factory.cpp: NotepadFactory Implementation.
- include "notepad_factory.h"
- include <klocale.h>
- include <kstddirs.h>
- include <kinstance.h>
- include <kaboutdata.h>
- include "notepad_part.h"
extern "C"
{
void* init_libnotepad()
{
return new NotepadFactory;
}
};
KInstance* NotepadFactory::s_instance = 0L;
KAboutData* NotepadFactory::s_about = 0L;
NotepadFactory::NotepadFactory( QObject* parent, const char* name )
: KLibFactory( parent, name )
{
}
NotepadFactory::~NotepadFactory()
{
delete s_instance;
s_instance = 0L;
delete s_about;
}
QObject* NotepadFactory::create( QObject* parent, const char* name,
const char* classname, const QStringList & )
{
if ( parent && !parent->inherits("QWidget") )
{
kdError() << "NotepadFactory: parent does not inherit QWidget" << endl;
return 0L;
}
NotepadPart* part = new NotepadPart( (QWidget*) parent, name );
// readonly ?
if (QCString(classname) == "KParts::ReadOnlyPart")
part->setReadWrite(false);
// otherwise, it has to be readwrite
else if (QCString(classname) != "KParts::ReadWritePart")
{
kdError() << "classname isn't ReadOnlyPart nor ReadWritePart !" << endl;
return 0L;
}
emit objectCreated( part );
return part;
}
KInstance* NotepadFactory::instance()
{
if( !s_instance )
{
s_about = new KAboutData( "notepadpart",
I18N_NOOP( "Notepad" ), "2.0pre" );
s_instance = new KInstance( s_about );
}
return s_instance;
}
- include "notepad_factory.moc"
</syntaxhighlight>
The implementation is a bit long but contains nothing complex. Basically, you define the function that is the entry point of the library, init_libnotepad(). It needs to be linked as a C function to avoid C++ name mangling. C linkage means that the symbol in the library will match the function name.
Then you define the NotepadFactory. The create method checks that the parent is a widget because this is needed for your part (remember, you create your widget with the parent widget given as an argument to the constructor). After creating the part, it has to emit objectCreated so that the library loader can do a proper reference counting; it automatically unloads the library after all objects created from it have been destroyed.
The instance() method returns the static instance, creating it first, if necessary. To create an instance, I recommend that you give it a KAboutData pointer. This gives some information about the instance representing the library (here an instance name, a translatable description of it, and a version number). You can add a lot more information in the KAboutData object, such as authors, home page, and bug-report address. See the documentation for details.
The standard KDE dialogs such as the Bug Report Dialog and the About Dialog use the data stored in KAboutData to show information about the current program, but in the future they will probably be improved to show information about the active part as well, which can have completely different About data from the application.
Note
KParts provides a factory base class, KParts::Factory, which enhances KlibFactory by making it possible to have a parent for the widget different from the parent for the part. It also takes care of loading the translation message catalog for the newly created part. Look in kparts/factory.h for more on this.
Creating a KParts Application
If an application wants to use parts and the GUI merging feature, its own GUI needs to be defined in XML. The top level windows of the application will then use the class KParts::MainWindow.
Note that it's also possible to use a part in a standard application, using KTMainWindow, but then no GUI merging happens. In this case, only the functionality provided by the widget and by the part API are available, so the application has to create the GUI for part's functionality itself, or the part has to provide it through context menus. In any case, it is much less flexible.
As an example of a window based on KParts::MainWindow, you are going to create a PostScript viewer very easily, by embedding the part provided by KDE's PostScript viewer, KGhostView.
Note
You need to install the package kdegraphics if you want to test this example.
The first thing to look at is the mainwindow's GUI; an example is given in Listing 13.11.
Listing 13.11 ghostviewtest_shell.rc: The Mainwindow's GUI
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="KGVShell" version="1">
<MenuBar>
<Menu name="file"><text>&File</text>
<Action name="file_open"/>
<Merge/>
<Action name="file_quit"/>
</Menu>
</MenuBar>
<ToolBar name="KGV-ToolBar"><text>KGhostView</text>
<Action name="file_open"/>
<Action name="file_quit"/>
</ToolBar>
</kpartgui>
</syntaxhighlight>
By analogy with a command line's shell, a main window is often called a shell. In its GUI you define the actions that will always be shown, whichever part is active. The listing for a simple KParts mainwindow is shown in Listing 13.12
Listing 13.12 ghostviewtest.h: Header for a Simple KParts Mainwindow
- include <kparts/mainwindow.h>
class Shell : public KParts::MainWindow
{
Q_OBJECT
public:
Shell();
virtual ~Shell();
void openURL( const KURL & url );
protected slots:
void slotFileOpen();
private:
KParts::ReadOnlyPart *m_gvpart;
};
</syntaxhighlight>
The mainwindow inherits KParts::MainWindow instead of KTMainWindow. Nothing else is required; the openURL() here is just so that main() can call openURL() on the window. The URL could be passed to the constructor instead.
The code for the mainwindow embedding the KGhostView part is part of the KParts examples, which can be found under kdelibs/kparts/tests/ghostview*, so Listing 13.13 only shows the relevant lines of ghostview.cpp.
Listing 13.13 Excerpt of ghostviewtest.cpp: Implementation of the Simple KParts Mainwindow
Shell::Shell()
{
setXMLFile( "ghostviewtest_shell.rc" );
KAction * paOpen = new KAction( i18n( "&Open file" ), "fileopen", 0,
this, SLOT( slotFileOpen() ), actionCollection(), "file_open" );
KAction * paQuit = new KAction( i18n( "&Quit" ), "exit", 0,
this, SLOT( close() ), actionCollection(), "file_quit" );
// Try to find libkghostview
KLibFactory *factory = KLibLoader::self()->factory( "libkghostview" );
if (factory)
{
// Create the part
m_gvpart = (KParts::ReadOnlyPart *)factory->create( this, "kgvpart",
"KParts::ReadOnlyPart" );
// Set the main widget
setView( m_gvpart->widget() );
// Integrate its GUI
createGUI( m_gvpart );
}
else
kdFatal() << "No libkghostview found !" << endl;
}
Shell::~Shell()
{
delete m_gvpart;
}
void Shell::openURL( const KURL & url )
{
m_gvpart->openURL( url );
}
</syntaxhighlight>
A mainwindow is created much like a part, with an XML file and actions. To find a part, it uses KLibLoader to get the KLibFactory for the library. A flexible application would use .desktop files for this and KIO's trader for selecting the user's preferred component, but for the sake of simplicity, open the library by its name here. After the factory has been created, the mainwindow makes it create a ReadOnlyPart, and because here you have only one part in the window, the part's widget is set as the main widget of the window with setView. Then a mainwindow needs to call createGUI() to make the framework create the GUI, merging the actions of the mainwindow with those of the active part. A mainwindow with no part will simply call createGUI(0L).
Using this mainwindow, for instance from main(), is as simple as
Shell *shell = new Shell;
shell->openURL( url );
shell->show();
</syntaxhighlight>
Compile kdelibs/kparts/tests/ghostviewtest to test this simple example of how to embed a part.
Embedding More Than One Part in the Same Window
The previous example showed how to embed a part as the single widget of a window. KParts also makes it possible to embed more than one part in the same window, and it handles the activation of a part when the user clicks it (or uses Tab to give it the focus). This is the task of the PartManager.
To display more than one part in a window, the solution is usually to use a splitter, or even nested splitters, such as in Konqueror. KOffice has another way of embedding several parts - by using frames for the child parts - but it still uses PartManager.
Now modify the example to make it display, in addition to the PostScript document, the PostScript code for it. To display the text, the application uses the Notepad part in read-only mode. The two widgets will be hosted by a splitter.
Displaying raw PostScript is not very useful, but this example could, for instance, be turned into an application showing the LaTeX source and the PostScript result side by side.
ghostviewtest.h needs to be modified slightly to add the following private members:
KParts::ReadOnlyPart *m_notepadpart;
KParts::PartManager *m_manager;
QSplitter *m_splitter;
ghostviewtest.cpp needs to be more modified. To include the PartManager definition, use the following:
- include <kparts/partmanager.h>
</syntaxhighlight>
In the constructor, create the part manager and connect its main signal, activePartChanged, to your createGUI slot. This means you don't need to call createGUI directly; it is called every time the active part changes.
m_manager = new KParts::PartManager( this );
// When the manager says the active part changes,
// the builder updates (recreates) the GUI
connect( m_manager, SIGNAL( activePartChanged( KParts::Part * ) ),
this, SLOT( createGUI( KParts::Part * ) ) );
</syntaxhighlight>
Then create the splitter and transform the setView statement into the following:
m_splitter = new QSplitter( this );
setView( m_splitter );
</syntaxhighlight>
so that the main widget is now the splitter. Both parts need to be created with the splitter as a parent (instead of the window):
KLibFactory *factory = KLibLoader::self()->factory( "libkghostview" );
if (factory)
{
m_gvpart = (KParts::ReadOnlyPart *)factory->create( m_splitter,
"kgvpart", "KParts::ReadOnlyPart" );
}
else
kdFatal() << "No libkghostview found !" << endl;
factory = KLibLoader::self()->factory( "libnotepad" );
if (factory)
m_notepadpart = (KParts::ReadOnlyPart *)factory->create( m_splitter,
"knotepadpart", "KParts::ReadOnlyPart" );
else
kdFatal() << "No libnotepad found !" << endl;
</syntaxhighlight>
After the parts are created, they should be added to the part manager. At the same time, you can specify which one should initially be active:
m_manager->addPart( m_gvpart, true ); // sets as the active part
m_manager->addPart( m_notepadpart, false );
Then the splitter can be set to a minimum size, as shown:
m_splitter->setMinimumSize( 400, 300 );
m_splitter->show();
Finally, add the following line to openURL() to open the same URL in both parts:
m_notepadpart->openURL( url );
As you can see, the main idea is that the mainwindow creates a main widget (here, the splitter), creates all parts inside it, and registers the part to a part manager. Try clicking one part and then the other; each time the active part changes, the GUI is updated (both menus and toolbars) to show the GUI of the active part.
Note also the change in the window caption. This is handled by the Part class, which receives the GUIActivateEvent from the mainwindow when the part is activated or deactivated. To set a different caption for a part, you need to emit setWindowCaption both in openFile() and in guiActivateEvent().
Creating a KParts Plug-in
A plug-in is the way to implement some functionality out of a part but still in a shared library, with actions defined by the plug-in to access this functionality. Those actions, whose layout is described in XML as usual, can be merged in a part's user interface or in a mainwindow's, depending on whether it applies to a part or to an application.
Several reasons exist for using plug-ins. One is saving memory, because the plug-in is not loaded until one of its actions is called, but the main reason is reusability - the same plug-in can apply to several parts or applications. For instance, a spell-checker plug-in can apply to all kinds of text editors, mail composers, word processors, and even presenters.
A plug-in can have a user interface, such as the dialog box for the spell checker, but not necessarily. Plug-ins can also act directly on the part or the application or anything else.
The XML for a spell-checker plug-in is shown below:
<!DOCTYPE kpartgui>
<kpartgui library="libspellcheck">
<MenuBar>
<Menu name="edit"><Text>&Edit</Text>
<Action name="spellcheck"/>
</Menu>
</MenuBar>
</kpartgui>
</syntaxhighlight>
Note the additional attribute in the main tag: library defines the name of the library to open to find the plug-in. This is because no .desktop file exists for plug-ins. Installing the preceding XML file in partplugins/, under share/apps/notepadpart, automatically inserts the plug-in's action in the NotepadPart user interface.
You know how the plug-in's library will be opened; now you need only to create a factory in the library, as usual, and let it create an instance of the plug-in. Writing the factory, which doesn't even need an instance in the simple case, and the init_libspellcheck() function will be left as an exercise to the reader.
To define a plug-in, simply inherit KParts::Plugin and add slots for its actions:
- include <kparts/plugin.h>
class PluginSpellCheck : public KParts::Plugin
{
Q_OBJECT
public:
PluginSpellCheck( QObject* parent = 0, const char* name = 0 );
virtual ~PluginSpellCheck() {}
public slots:
void slotSpellCheck();
};
</syntaxhighlight>
In the implementation, you have to create the plug-in actions; no setXMLFile is here because it has been found by the part already.
Because in this example you are not going to create a real spell checker - a libkspell exists for that - call the action "select current line" and implement that in the slot.
- include "plugin_spellcheck.h"
- include "notepad.h" // this plugin applies to a notepad part
- include <qmultilineedit.h>
- include <kaction.h>
PluginSpellCheck::PluginSpellCheck( QObject* parent, const char* name )
: Plugin( parent, name )
{
(void) new KAction( i18n( "&Select current line (plug-in)" ), 0, this,
SLOT(slotSpellCheck()), actionCollection(), "spellcheck" );
}
void PluginSpellCheck::slotSpellCheck()
{
// Check that the parent is a NotepadPart
if ( !parent()->inherits("NotepadPart") )
kdFatal() << "Spell-check plug-in for wrong part (not NotepadPart)" << endl;
else
{
NotepadPart * part = (NotepadPart *) parent();
QMultiLineEdit * widget = (QMultiLineEdit *) part->widget();
widget->selectAll(); //selects current line !
}
}
</syntaxhighlight>
Note that to access the part's widget, the plug-in has to assume - and check - that it has been installed for a NotepadPart. This means that you should not install it under another part's directory. But selecting the current line in an image viewer wouldn't mean much anyway.
A more flexible plug-in would instead check and cast the parent to ReadWritePart and then check the type of its widget to be QMultiLineEdit.
Summary
After the presentation of component technology and how to lay out actions using XML, you have seen most of what KParts can do: three types of parts, part mainwindows, part manager, plug-ins, as well as how dynamic loading works - library factories and library loader.
You can do other interesting things with parts. Having a part embed itself in Konqueror is very simple; it's just a matter of providing a .desktop file for it, stating that it is a service that implements some servicetypes, which are the mimetypes that the part allows to view, plus the servicetype KParts::ReadOnlyPart. That's it. Konqueror will use the part to view the files of those mimetypes if no other service is set as more preferred in Configure File Types.
To provide better integration with Konqueror, you can also provide a KParts::BrowserExtension for the part, as defined in kparts/browserextension.h. This is what makes it possible to save and restore a view in Konqueror's history and for the part to use Konqueror's "standard actions." Examples of parts using the browser extension can be found in KView, KDVI, KGhostView, KWrite and all built-in Konqueror views.