Difference between revisions of "TDEParts"

From Trinity Desktop Project Wiki
Jump to navigation Jump to search
(+KDE3)
(Updated for TDE (1 section still needs to be updated))
Line 2: Line 2:
 
[[Category:Architecture]]
 
[[Category:Architecture]]
 
[[Category:Developers]]
 
[[Category:Developers]]
  +
{{Applicable to TDE}}
{{KDE3}}
 
  +
  +
  +
'''TDEParts''' are elaborate widgets with a user-interface defined in terms of actions (menu items, toolbar icons). The <tt>tdeparts</tt> library also provides a common framework for TDE applications which want to use parts.
  +
  +
This tutorial explains how to create simple TDEParts and integrate them into applications.
  +
  +
  +
__TOC__
  +
  +
  +
== Background ==
   
 
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.
 
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.
Line 8: Line 19:
 
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.
 
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).
+
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. Non-graphical 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.
 
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.
Line 14: Line 25:
 
==The Difference Between Components and Widgets==
 
==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.
+
A TDE 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.
+
The usual example is a text editor component. Its widget is a multi-line 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.
 
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.
+
Another example of very useful component is an image viewer. When using Trinity'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.
   
  +
{{Box
Note: Note that KOffice parts are a bit different because they don't embed as a full window, but as a frame into the parent's view, which can be moved, resized, and even rotated - a functionality only KOffice has. This and the document/view architecture of KOffice applications mean that the framework for KOffice parts, although based on KParts, is much more elaborate and out of topic here.
 
  +
|caption=Note
 
 
|text=Note that KOffice parts are a bit different because they don't embed as a full window, but as a frame into the parent's view, which can be moved, resized, and even rotated - a functionality only KOffice has. This and the document/view architecture of KOffice applications mean that the framework for KOffice parts, although based on KParts, is much more elaborate and out of topic here.
 
  +
|icon=Messagebox_info.png
<br />
 
  +
|background=#E9F0FF
  +
|highlight=#93B2FF
  +
}}
   
 
So, when do you use a part and when do you use a widget?
 
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.
+
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 multi-line 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==
 
   
 
==The Trinity 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.
 
   
 
KParts is the framework for TDE parts, based on standard TDE/TQt objects, such as <tt>TQWidget</tt> and <tt>KTMainWindow</tt>. It defines a very simple set of classes: part, plugin, main window 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 ''part'', as previously described, is the name for a TDE 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 ''plugin'' is 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 ''plugin'' is 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 ''mainwindow'' is 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 KParts ''mainwindow'' is 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 manager'' is 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.
+
* A ''part manager'' is 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.
 
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.
Line 48: Line 61:
 
==Describing User Interface in XML==
 
==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.
+
The XML file used by a part or a main window 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.
 
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.
+
A sample XML file for a main window looks like the one shown in Listing 13.1.
   
 
====''Listing 13.1 Excerpt of konqueror.rc: A User Interface Described in XML''====
 
====''Listing 13.1 Excerpt of konqueror.rc: A User Interface Described in XML''====
Line 95: Line 108:
 
</syntaxhighlight>
 
</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 &amp;. 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 DOCTYPE tag contains the name of the main element, which should be set to kpartgui. The top-level elements are <code>MenuBar</code> and <code>ToolBar</code>, as expected. In the <code>MenuBar</code>, 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 &amp;. Inside a <code>Menu</code> 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.
+
The toolbars are then described. Note that the main toolbar has to be called mainToolBar because its settings can be different. TDEToolBar 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.
+
Another important tag is the <code>Merge</code> 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 main window.
   
The merging happens when a part simply uses the same menu name or toolbar name as the mainwindow.
+
The merging happens when a part simply uses the same menu name or toolbar name as the main window.
   
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.
+
If a <code>Merge</code> tag is specified as a child of the <code>MenuBar</code> 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.
+
The <code>Merge</code> 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:
+
Another advanced use of the <code>Merge</code> 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:
   
 
<syntaxhighlight lang="xml">
 
<syntaxhighlight lang="xml">
Line 117: Line 130:
 
</syntaxhighlight>
 
</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.
+
Using the name attribute for the <code>Merge</code> tag allows you to control at which position each XML fragment is merged, but it is usually unnecessary.
   
 
==Read-Only and Read/Write Parts==
 
==Read-Only and Read/Write Parts==
Line 125: Line 138:
 
===Read-Only Parts===
 
===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.
+
The class <tt>ReadOnlyPart</tt> 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 TDE to provide network transparency wherever possible, which is why most TDE 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 <code>openFile()</code>, 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.
+
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 <code>openURL()</code> and get all the progress information from the part. All this is not available in the generic <tt>Part</tt> class.
   
 
===Read-Write Parts===
 
===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.
+
The third kind of part is the <tt>ReadWritePart</tt>, 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.
 
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.
Line 137: Line 150:
 
==Creating a Part==
 
==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.
+
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 <tt>KParts::ReadWritePart</tt>.
   
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().
+
At this point, it is a very good idea to read <tt>kparts/part.h</tt> and the official [http://trinitydesktop.org/docs/trinity/tdelibs/tdeparts/html/classes.html tdeparts classes documentation]. This tells you that a read/write part implementation has to provide the methods <code>openFile()</code> and <code>saveFile()</code>.
  +
<!-- directly or preferably after running kdoc on it (see Chapter 16, "Creating Documentation," for information about kdoc).-->
   
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.
+
The task of <code>openFile()</code> 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.
+
In <code>saveFile()</code>, 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).
 
You can now sketch the header file for your part, which is called NotepadPart (see Listing 13.2).
   
  +
<!-- Listing 13.2 -->
====''Listing 13.2 notepad_part.h: Header of the ''NotepadPart'' Class''====
+
==== notepad_part.h: Header of the ''NotepadPart'' Class ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
Line 153: Line 168:
 
#define __notepad_h__
 
#define __notepad_h__
   
#include <kparts/part.h>
+
#include <tdeparts/part.h>
   
class QMultiLineEdit;
+
class TQMultiLineEdit;
   
 
class NotepadPart : public KParts::ReadWritePart
 
class NotepadPart : public KParts::ReadWritePart
 
{
 
{
  +
TQ_OBJECT
Q_OBJECT
 
 
public:
 
public:
NotepadPart( QWidget * parent, const char * name = 0L );
+
NotepadPart( TQWidget * parent, const char * name = 0L );
 
virtual ~NotepadPart() {}
 
virtual ~NotepadPart() {}
   
Line 174: Line 189:
   
 
protected:
 
protected:
QMultiLineEdit * m_edit;
+
TQMultiLineEdit * m_edit;
KInstance *m_instance;
+
TDEInstance *m_instance;
 
};
 
};
   
Line 183: Line 198:
 
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 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''/.
+
The class members are a <tt>TQMultiLineEdit</tt> (the multi-line widget from TQt), and a <tt>TDEInstance</tt>. An instance enables access to global TDE 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 <code>locate()</code>, and so on. In TDEParts, 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.
+
In addition, you define a slot, <code>slotSelectAll()</code>, 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.
+
The corresponding XML file for the part <tt>NotepadPart</tt> is listed in Listing 13.3 and defines its GUI by an action named <code>selectall</code>, 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 main windows usually specify it, because it has to work even if a main window doesn't have an Edit menu on its own. So the rule is simple: ''always provide a text for all menus''.
   
  +
<!-- Listing 13.3 -->
====''Listing 13.3 notepadpart.rc: XML Description of the Notepad Part's User Interface''====
+
==== notepadpart.rc: XML Description of the Notepad Part's User Interface ====
   
 
<syntaxhighlight lang="xml">
 
<syntaxhighlight lang="xml">
Line 203: Line 219:
 
</syntaxhighlight>
 
</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.
+
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 below:
   
====''Listing 13.4 notepad_part.cpp part 1: Constructor''====
+
<!-- Listing 13.4 -->
  +
==== notepad_part.cpp: Constructor ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
NotepadPart::NotepadPart( QWidget * parent, const char * name )
+
NotepadPart::NotepadPart( TQWidget * parent, const char * name )
 
: KParts::ReadWritePart( parent, name )
 
: KParts::ReadWritePart( parent, name )
 
{
 
{
KInstance * instance = new KInstance( "notepadpart" );
+
TDEInstance * instance = new TDEInstance( "notepadpart" );
 
setInstance( instance );
 
setInstance( instance );
   
m_edit = new QMultiLineEdit( parent, "multilineedit" );
+
m_edit = new TQMultiLineEdit( parent, "multilineedit" );
 
m_edit->setFocus();
 
m_edit->setFocus();
 
setWidget( m_edit );
 
setWidget( m_edit );
   
(void)new KAction( i18n( "Select All" ), 0, this,
+
(void)new TDEAction( i18n( "Select All" ), 0, this,
 
SLOT( slotSelectAll() ), actionCollection(), "selectall" );
 
SLOT( slotSelectAll() ), actionCollection(), "selectall" );
 
setXMLFile( "notepadpart.rc" );
 
setXMLFile( "notepadpart.rc" );
Line 226: Line 243:
 
</syntaxhighlight>
 
</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().
+
After calling the parent constructor with parent and name, you create an instance, named <tt>notepadpart</tt>, and declare it to the framework using <code>setInstance()</code>. This is a temporary solution; you'll see later how to use a library-factory's instance. Then you create the multi-line edit widget, give it the focus, and declare it as well, using <code>setWidget()</code>.
   
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.
+
The next step is to create the actions that your part provides. The <code>selectall</code> action is given a translated label, is connected to <code>slotSelectAll()</code>, 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().
+
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 <tt>share/apps/''instancename''/</tt>, 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 <code>setXMLFile()</code>.
   
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.
+
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, TDEActions as well as any direct modification provided by the widget itself. The reimplementation of <code>setReadWrite()</code> for the <tt>NotepadPart</tt> is shown below:
   
  +
<!-- Listing 13.5 -->
====''Listing 13.5 notepad_part.cpp part 2: Implementation of setReadWrite''====
+
==== notepad_part.cpp: Implementation of setReadWrite ====
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
Line 251: Line 269:
 
In the example, there are no actions to disable, but the multiline widget has to be set to its read-only mode.
 
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.
+
The connection to <code>setModified()</code>, 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.
 
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.
+
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 <code>openFile()</code> 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 TQString and set the multi-line widget's text from it:
   
  +
<!-- Listing 13.6 -->
====''Listing 13.6 notepad_part.cpp part 3: Implementation of openFile''====
+
==== notepad_part.cpp: Implementation of <code>openFile()</code> ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
 
bool NotepadPart::openFile()
 
bool NotepadPart::openFile()
 
{
 
{
QFile f(m_file);
+
TQFile f(m_file);
QString s;
+
TQString s;
 
if ( f.open(IO_ReadOnly) )
 
if ( f.open(IO_ReadOnly) )
 
{
 
{
QTextStream t( &f );
+
TQTextStream t( &f );
 
while ( !t.eof() ) {
 
while ( !t.eof() ) {
 
s += t.readLine() + "\n";
 
s += t.readLine() + "\n";
Line 278: Line 297:
 
</syntaxhighlight>
 
</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.
+
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 <code>saveFile()</code> to save the document to m_file, as shown below. 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 -->
====''Listing 13.7 notepad_part.cpp part 4: Implementation of saveFile''====
+
==== notepad_part.cpp: Implementation of <code>saveFile()</code> ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
Line 287: Line 307:
 
if ( !isReadWrite() )
 
if ( !isReadWrite() )
 
return false;
 
return false;
QFile f(m_file);
+
TQFile f(m_file);
QString s;
+
TQString s;
 
if ( f.open(IO_WriteOnly) ) {
 
if ( f.open(IO_WriteOnly) ) {
QTextStream t( &f );
+
TQTextStream t( &f );
 
t << m_edit->text();
 
t << m_edit->text();
 
f.close();
 
f.close();
Line 303: Line 323:
 
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.
 
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
+
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 below:
   
====''Listing 13.8 Extract from Makefile.am''====
+
<!-- Listing 13.8 -->
  +
==== Extract from Makefile.am ====
  +
{{Outdated
  +
|reason=Automake rules
  +
|{{CURRENTYEAR}}|{{CURRENTMONTH}}|{{CURRENTDAY}}
  +
}}
   
 
lib_LTLIBRARIES = libnotepad.la
 
lib_LTLIBRARIES = libnotepad.la
Line 314: Line 339:
 
METASOURCES = AUTO
 
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.
+
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 <tt>KLibFactory</tt>, which you'll do in the class <tt>NotepadFactory</tt>. An application willing to open a shared library dynamically uses the class <tt>KLibLoader</tt>, which takes care of locating the library, opening it, and calling an initialization function - here <code>init_libnotepad()</code>. This function creates a <tt>NotepadFactory</tt> and returns it to <tt>KLibLoader</tt>, which can then call the create method on the factory. This means that all you need to do in the library itself is define <code>init_libnotepad()</code> and the <tt>NotepadFactory</tt>.
   
The header for the factory is the one shown in Listing 13.9.
+
The header for the factory is the one shown below:
   
  +
<!-- Listing 13.9 -->
====''Listing 13.9 notepad_factory.h: Header File for NotepadFactory''====
+
==== notepad_factory.h: Header File for <tt>NotepadFactory</tt> ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
 
#include <klibloader.h>
 
#include <klibloader.h>
class KInstance;
+
class TDEInstance;
class KAboutData;
+
class TDEAboutData;
 
class NotepadFactory: public KLibFactory
 
class NotepadFactory: public KLibFactory
 
{
 
{
  +
TQ_OBJECT
Q_OBJECT
 
 
public:
 
public:
NotepadFactory( QObject * parent = 0, const char * name = 0 );
+
NotepadFactory( TQObject * parent = 0, const char * name = 0 );
 
~NotepadFactory();
 
~NotepadFactory();
   
 
// reimplemented from KLibFactory
 
// reimplemented from KLibFactory
virtual QObject * create( QObject * parent = 0, const char * name = 0,
+
virtual TQObject * create( TQObject * parent = 0, const char * name = 0,
const char * classname = "QObject",
+
const char * classname = "TQObject",
const QStringList &args = QStringList());
+
const TQStringList &args = TQStringList());
   
static KInstance * instance();
+
static TDEInstance * instance();
   
 
private:
 
private:
static KInstance * s_instance;
+
static TDEInstance * s_instance;
static KAboutData * s_about;
+
static TDEAboutData * s_about;
 
};
 
};
 
</syntaxhighlight>
 
</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.
+
As required by <tt>KLibFactory</tt>, 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 <tt>KParts::ReadWritePart</tt> or <tt>KParts::ReadOnlyPart</tt>.
   
 
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.
 
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.
+
This means the code of <tt>notepad_part.cpp</tt> should be modified to call <code>setInstance( NotepadFactory::instance() );</code> instead of creating its own instance.
   
The implementation for the NotepadFactory is shown in Listing 13.10.
+
The implementation for the <tt>NotepadFactory</tt> is shown below:
   
  +
<!-- Listing 13.10 -->
====''Listing 13.10 notepad_factory.cpp: NotepadFactory Implementation.''====
+
==== notepad_factory.cpp: <tt>NotepadFactory</tt> Implementation. ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
 
#include "notepad_factory.h"
 
#include "notepad_factory.h"
   
#include <klocale.h>
+
#include <tdelocale.h>
#include <kstddirs.h>
+
#include <tdeinstance.h>
#include <kinstance.h>
+
#include <tdeaboutdata.h>
#include <kaboutdata.h>
+
#include <kstandarddirs.h>
   
 
#include "notepad_part.h"
 
#include "notepad_part.h"
Line 372: Line 399:
 
};
 
};
   
KInstance* NotepadFactory::s_instance = 0L;
+
TDEInstance* NotepadFactory::s_instance = 0L;
KAboutData* NotepadFactory::s_about = 0L;
+
TDEAboutData* NotepadFactory::s_about = 0L;
   
NotepadFactory::NotepadFactory( QObject* parent, const char* name )
+
NotepadFactory::NotepadFactory( TQObject* parent, const char* name )
 
: KLibFactory( parent, name )
 
: KLibFactory( parent, name )
 
{
 
{
Line 387: Line 414:
 
}
 
}
   
QObject* NotepadFactory::create( QObject* parent, const char* name,
+
TQObject* NotepadFactory::create( TQObject* parent, const char* name,
const char* classname, const QStringList & )
+
const char* classname, const TQStringList & )
 
{
 
{
if ( parent && !parent->inherits("QWidget") )
+
if ( parent && !parent->inherits("TQWidget") )
 
{
 
{
kdError() << "NotepadFactory: parent does not inherit QWidget" << endl;
+
kdError() << "NotepadFactory: parent does not inherit TQWidget" << endl;
 
return 0L;
 
return 0L;
 
}
 
}
   
NotepadPart* part = new NotepadPart( (QWidget*) parent, name );
+
NotepadPart* part = new NotepadPart( (TQWidget*) parent, name );
 
// readonly ?
 
// readonly ?
if (QCString(classname) == "KParts::ReadOnlyPart")
+
if (TQCString(classname) == "KParts::ReadOnlyPart")
 
part->setReadWrite(false);
 
part->setReadWrite(false);
   
 
// otherwise, it has to be readwrite
 
// otherwise, it has to be readwrite
else if (QCString(classname) != "KParts::ReadWritePart")
+
else if (TQCString(classname) != "KParts::ReadWritePart")
 
{
 
{
 
kdError() << "classname isn't ReadOnlyPart nor ReadWritePart !" << endl;
 
kdError() << "classname isn't ReadOnlyPart nor ReadWritePart !" << endl;
Line 412: Line 439:
 
}
 
}
   
KInstance* NotepadFactory::instance()
+
TDEInstance* NotepadFactory::instance()
 
{
 
{
 
if( !s_instance )
 
if( !s_instance )
 
{
 
{
s_about = new KAboutData( "notepadpart",
+
s_about = new TDEAboutData( "notepadpart",
 
I18N_NOOP( "Notepad" ), "2.0pre" );
 
I18N_NOOP( "Notepad" ), "2.0pre" );
s_instance = new KInstance( s_about );
+
s_instance = new TDEInstance( s_about );
 
}
 
}
 
return s_instance;
 
return s_instance;
Line 426: Line 453:
 
</syntaxhighlight>
 
</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.
+
The implementation is a bit long but contains nothing complex. Basically, you define the function that is the entry point of the library, <code>init_libnotepad()</code>. 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.
+
Then you define the <tt>NotepadFactory</tt>. 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 <code>instance()</code> 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 <tt>TDEAboutData</tt> 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.
+
The standard Trinity dialogs such as the Bug Report Dialog and the About Dialog use the data stored in <tt>TDEAboutData</tt> 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.
   
  +
{{Box
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.
 
  +
|caption=Note
 
|text=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 <tt>kparts/factory.h</tt> for more on this.
  +
|icon=Messagebox_info.png
  +
|background=#E9F0FF
  +
|highlight=#93B2FF
  +
}}
   
==Creating a KParts Application==
+
==Creating a TDEParts 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.
+
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 <tt>KParts::MainWindow</tt>.
   
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.
+
Note that it's also possible to use a part in a standard application, using <tt>KTMainWindow</tt>, 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.
+
As an example of a window based on <tt>KParts::MainWindow</tt>, you are going to create a PostScript viewer very easily, by embedding the part provided by KDE's PostScript viewer, KGhostView.
   
  +
{{Box
Note: You need to install the package kdegraphics if you want to test this example.
 
  +
|caption=Note
 
|text=You need to install the package <tt>tdegraphics</tt> if you want to test this example.
  +
|icon=Messagebox_info.png
  +
|background=#E9F0FF
  +
|highlight=#93B2FF
  +
}}
   
The first thing to look at is the mainwindow's GUI; an example is given in Listing 13.11.
+
The first thing to look at is the main window's GUI; an example is given below:
   
  +
<!-- Listing 13.11 -->
====''Listing 13.11 ghostviewtest_shell.rc: The Mainwindow's GUI''====
+
==== ghostviewtest_shell.rc: The main window's GUI ====
   
 
<syntaxhighlight lang="xml">
 
<syntaxhighlight lang="xml">
Line 467: Line 507:
 
</syntaxhighlight>
 
</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
+
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 TDEParts main window is shown below.
   
  +
<!-- Listing 13.12 -->
====''Listing 13.12 ghostviewtest.h: Header for a Simple KParts Mainwindow''====
+
==== ghostviewtest.h: Header for a simple TDEParts main window ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
#include <kparts/mainwindow.h>
+
#include <tdeparts/mainwindow.h>
   
 
class Shell : public KParts::MainWindow
 
class Shell : public KParts::MainWindow
 
{
 
{
  +
TQ_OBJECT
Q_OBJECT
 
 
public:
 
public:
 
Shell();
 
Shell();
Line 491: Line 532:
 
</syntaxhighlight>
 
</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 mainwindow inherits <tt>KParts::MainWindow</tt> instead of <tt>KTMainWindow</tt>. Nothing else is required; the <code>openURL()</code> here is just so that <code>main()</code> can call <code>openURL()</code> 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.
+
The code for the mainwindow embedding the <tt>KGhostView</tt> part is part of the TDEParts examples, which can be found under <tt>tdelibs/tdeparts/tests/ghostview*</tt>, so the listing below only shows the relevant lines of <tt>ghostview.cpp</tt>.
   
  +
<!-- Listing 13.13 -->
====''Listing 13.13 Excerpt of ghostviewtest.cpp: Implementation of the Simple KParts Mainwindow''====
+
==== Excerpt of ghostviewtest.cpp: Implementation of the simple TDEParts main window ====
   
 
<syntaxhighlight lang="cpp-qt" line>
 
<syntaxhighlight lang="cpp-qt" line>
Line 502: Line 544:
 
setXMLFile( "ghostviewtest_shell.rc" );
 
setXMLFile( "ghostviewtest_shell.rc" );
   
KAction * paOpen = new KAction( i18n( "&Open file" ), "fileopen", 0,
+
TDEAction * paOpen = new KAction( i18n( "&Open file" ), "fileopen", 0,
 
this, SLOT( slotFileOpen() ), actionCollection(), "file_open" );
 
this, SLOT( slotFileOpen() ), actionCollection(), "file_open" );
   
KAction * paQuit = new KAction( i18n( "&Quit" ), "exit", 0,
+
TDEAction * paQuit = new KAction( i18n( "&Quit" ), "exit", 0,
 
this, SLOT( close() ), actionCollection(), "file_quit" );
 
this, SLOT( close() ), actionCollection(), "file_quit" );
   
Line 535: Line 577:
 
</syntaxhighlight>
 
</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).
+
A mainwindow is created much like a part, with an XML file and actions. To find a part, it uses <tt>KLibLoader</tt> to get the <tt>KLibFactory</tt> for the library. A flexible application would use .desktop files for this and TDEIO'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 <code>createGUI()</code> 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 <code>createGUI(0L)</code>.
   
Using this mainwindow, for instance from main(), is as simple as
+
Using this mainwindow, for instance from <code>main()</code>, is as simple as
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
Line 546: Line 588:
 
</syntaxhighlight>
 
</syntaxhighlight>
   
Compile kdelibs/kparts/tests/ghostviewtest to test this simple example of how to embed a part.
+
Compile <tt>tdelibs/tdeparts/tests/ghostviewtest</tt> to test this simple example of how to embed a part.
   
 
==Embedding More Than One Part in the Same Window==
 
==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.
+
The previous example showed how to embed a part as the single widget of a window. TDEParts 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 <tt>PartManager</tt>.
   
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.
+
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 <tt>PartManager</tt>.
   
 
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.
 
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.
Line 558: Line 600:
 
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.
 
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:
+
<tt>ghostviewtest.h</tt> needs to be modified slightly to add the following private members:
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
Line 564: Line 606:
 
KParts::PartManager *m_manager;
 
KParts::PartManager *m_manager;
   
QSplitter *m_splitter;
+
TQSplitter *m_splitter;
 
</syntaxhighlight>
 
</syntaxhighlight>
   
ghostviewtest.cpp needs to be more modified. To include the PartManager definition, use the following:
+
<tt>ghostviewtest.cpp</tt> needs to be more modified. To include the PartManager definition, use the following:
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
#include <kparts/partmanager.h>
+
#include <tdeparts/partmanager.h>
 
</syntaxhighlight>
 
</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.
+
In the constructor, create the part manager and connect its main signal, <code>activePartChanged</code>, to your <code>createGUI</code> slot. This means you don't need to call <code>createGUI</code> directly; it is called every time the active part changes.
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
Line 586: Line 628:
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
m_splitter = new QSplitter( this );
+
m_splitter = new TQSplitter( this );
   
 
setView( m_splitter );
 
setView( m_splitter );
Line 613: Line 655:
 
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:
 
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:
   
  +
<syntaxhighlight lang="cpp-qt">
 
m_manager->addPart( m_gvpart, true ); // sets as the active part
 
m_manager->addPart( m_gvpart, true ); // sets as the active part
   
 
m_manager->addPart( m_notepadpart, false );
 
m_manager->addPart( m_notepadpart, false );
  +
</syntaxhighlight>
   
 
Then the splitter can be set to a minimum size, as shown:
 
Then the splitter can be set to a minimum size, as shown:
   
  +
<syntaxhighlight lang="cpp-qt">
 
m_splitter->setMinimumSize( 400, 300 );
 
m_splitter->setMinimumSize( 400, 300 );
   
 
m_splitter->show();
 
m_splitter->show();
  +
</syntaxhighlight>
   
Finally, add the following line to openURL() to open the same URL in both parts:
+
Finally, add the following line to <code>openURL()</code> to open the same URL in both parts:
   
  +
<syntaxhighlight lang="cpp-qt">
 
m_notepadpart->openURL( url );
 
m_notepadpart->openURL( url );
  +
</syntaxhighlight>
   
 
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.
 
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().
+
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 <code>openFile()</code> and in <code>guiActivateEvent()</code>.
   
==Creating a KParts Plug-in==
+
==Creating a TDEParts 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.
 
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.
Line 652: Line 700:
 
</syntaxhighlight>
 
</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.
+
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 <tt>partplugins/</tt>, under <tt>share/apps/notepadpart</tt>, 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.
+
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 <code>init_libspellcheck()</code> 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:
 
To define a plug-in, simply inherit KParts::Plugin and add slots for its actions:
   
 
<syntaxhighlight lang="cpp-qt">
 
<syntaxhighlight lang="cpp-qt">
#include <kparts/plugin.h>
+
#include <tdeparts/plugin.h>
   
 
class PluginSpellCheck : public KParts::Plugin
 
class PluginSpellCheck : public KParts::Plugin
 
{
 
{
Q_OBJECT
+
TQ_OBJECT
 
public:
 
public:
PluginSpellCheck( QObject* parent = 0, const char* name = 0 );
+
PluginSpellCheck( TQObject* parent = 0, const char* name = 0 );
 
virtual ~PluginSpellCheck() {}
 
virtual ~PluginSpellCheck() {}
   
Line 673: Line 721:
 
</syntaxhighlight>
 
</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.
+
In the implementation, you have to create the plug-in actions; no <code>setXMLFile</code> 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.
 
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.
Line 680: Line 728:
 
#include "plugin_spellcheck.h"
 
#include "plugin_spellcheck.h"
 
#include "notepad.h" // this plugin applies to a notepad part
 
#include "notepad.h" // this plugin applies to a notepad part
#include <qmultilineedit.h>
+
#include <tqmultilineedit.h>
#include <kaction.h>
+
#include <tdeaction.h>
   
 
PluginSpellCheck::PluginSpellCheck( QObject* parent, const char* name )
 
PluginSpellCheck::PluginSpellCheck( QObject* parent, const char* name )
 
: Plugin( parent, name )
 
: Plugin( parent, name )
 
{
 
{
(void) new KAction( i18n( "&Select current line (plug-in)" ), 0, this,
+
(void) new TDEAction( i18n( "&Select current line (plug-in)" ), 0, this,
 
SLOT(slotSpellCheck()), actionCollection(), "spellcheck" );
 
SLOT(slotSpellCheck()), actionCollection(), "spellcheck" );
 
}
 
}
Line 698: Line 746:
 
{
 
{
 
NotepadPart * part = (NotepadPart *) parent();
 
NotepadPart * part = (NotepadPart *) parent();
QMultiLineEdit * widget = (QMultiLineEdit *) part->widget();
+
TQMultiLineEdit * widget = (TQMultiLineEdit *) part->widget();
 
widget->selectAll(); //selects current line !
 
widget->selectAll(); //selects current line !
 
}
 
}
Line 706: Line 754:
 
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.
 
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.
+
A more flexible plug-in would instead check and cast the parent to ReadWritePart and then check the type of its widget to be TQMultiLineEdit.
   
 
==Summary==
 
==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.
+
After the presentation of component technology and how to lay out actions using XML, you have seen most of what TDEParts 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.
 
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.
+
To provide better integration with Konqueror, you can also provide a KParts::BrowserExtension for the part, as defined in <tt>tdeparts/browserextension.h</tt>. 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.

Revision as of 15:26, 17 September 2021

TDE Logo.png
Information on this page is applicable to TDE
This page contains archived KDE 3.x content from various sources which is directly applicable to (or has been updated for) the Trinity Desktop Environment.


TDEParts are elaborate widgets with a user-interface defined in terms of actions (menu items, toolbar icons). The tdeparts library also provides a common framework for TDE applications which want to use parts.

This tutorial explains how to create simple TDEParts and integrate them into applications.



Background

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. Non-graphical 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 TDE 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 multi-line 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 Trinity'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.

Messagebox info.png
Note
Note that KOffice parts are a bit different because they don't embed as a full window, but as a frame into the parent's view, which can be moved, resized, and even rotated - a functionality only KOffice has. This and the document/view architecture of KOffice applications mean that the framework for KOffice parts, although based on KParts, is much more elaborate and out of topic here.

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 multi-line 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 Trinity Component Framework

KParts is the framework for TDE parts, based on standard TDE/TQt objects, such as TQWidget and KTMainWindow. It defines a very simple set of classes: part, plugin, main window and part manager.

  • A part, as previously described, is the name for a TDE 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 plugin is 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 mainwindow is 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 manager is 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 main window 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 main window 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>&amp;File</Text>
    <Action name="find"/>
    <Separator/>
    <Action name="print"/>
    <Separator/>
    <Action name="close"/>
   </Menu>
   <Menu name="edit"><Text>&amp;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>

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. TDEToolBar 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 main window.

The merging happens when a part simply uses the same menu name or toolbar name as the main window.

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 />

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 TDE to provide network transparency wherever possible, which is why most TDE 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 and the official tdeparts classes documentation. 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).

notepad_part.h: Header of the NotepadPart Class

 1#ifndef __notepad_h__
 2#define __notepad_h__
 3
 4#include <tdeparts/part.h>
 5
 6class TQMultiLineEdit;
 7
 8class NotepadPart : public KParts::ReadWritePart
 9{
10  TQ_OBJECT
11public:
12  NotepadPart( TQWidget * parent, const char * name = 0L );
13  virtual ~NotepadPart() {}
14
15  virtual void setReadWrite( bool rw );
16
17protected:
18  virtual bool openFile();
19  virtual bool saveFile();
20
21protected slots:
22  void slotSelectAll();
23
24protected:
25  TQMultiLineEdit * m_edit;
26  TDEInstance *m_instance;
27};
28
29#endif

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 TQMultiLineEdit (the multi-line widget from TQt), and a TDEInstance. An instance enables access to global TDE 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 TDEParts, 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 main windows usually specify it, because it has to work even if a main window doesn't have an Edit menu on its own. So the rule is simple: always provide a text for all menus.

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>&amp;Edit</Text>
  <Action name="selectall"/>
 </Menu>
</MenuBar>
<StatusBar/>
</kpartgui>

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 below:

notepad_part.cpp: Constructor

 1NotepadPart::NotepadPart( TQWidget * parent, const char * name )
 2 : KParts::ReadWritePart( parent, name )
 3{
 4  TDEInstance * instance = new TDEInstance( "notepadpart" );
 5  setInstance( instance );
 6
 7  m_edit = new TQMultiLineEdit( parent, "multilineedit" );
 8  m_edit->setFocus();
 9  setWidget( m_edit );
10
11  (void)new TDEAction( i18n( "Select All" ), 0, this,
12        SLOT( slotSelectAll() ), actionCollection(), "selectall" );
13  setXMLFile( "notepadpart.rc" );
14
15  setReadWrite( true );
16}

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 multi-line 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, TDEActions as well as any direct modification provided by the widget itself. The reimplementation of setReadWrite() for the NotepadPart is shown below:

notepad_part.cpp: 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
}

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 TQString and set the multi-line widget's text from it:

notepad_part.cpp: Implementation of openFile()

 1bool NotepadPart::openFile()
 2{
 3  TQFile f(m_file);
 4  TQString s;
 5  if ( f.open(IO_ReadOnly) )
 6  {
 7    TQTextStream t( &f );
 8    while ( !t.eof() ) {
 9      s += t.readLine() + "\n";
10    }
11    f.close();
12  }
13  m_edit->setText(s);
14
15  return true;
16}

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 below. Note that the framework takes care of Save As (changing the URL to Save To), as well as uploading the saved file, if necessary.

notepad_part.cpp: Implementation of saveFile()

 1bool NotepadPart::saveFile()
 2{
 3  if ( !isReadWrite() )
 4    return false;
 5  TQFile f(m_file);
 6  TQString s;
 7  if ( f.open(IO_WriteOnly) ) {
 8    TQTextStream t( &f );
 9    t << m_edit->text();
10    f.close();
11    return true ;
12  } else
13    return false;
14}

Making a Part Available Using Shared Libraries

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 below:

Extract from Makefile.am

Clock.png
Information found here might be outdated!
This page or section has been marked as having outdated and/or no more relevant information. Please regard information in this page with extra caution.
This warning was placed on 2024/11/24 . (Reason: Automake rules)
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 below:

notepad_factory.h: Header File for NotepadFactory

 1#include <klibloader.h>
 2class TDEInstance;
 3class TDEAboutData;
 4class NotepadFactory: public KLibFactory
 5{
 6  TQ_OBJECT
 7public:
 8  NotepadFactory( TQObject * parent = 0, const char * name = 0 );
 9  ~NotepadFactory();
10
11  // reimplemented from KLibFactory
12  virtual TQObject * create( TQObject * parent = 0, const char * name = 0,
13        const char * classname = "TQObject",
14        const TQStringList &args = TQStringList());
15
16  static TDEInstance * instance();
17
18private:
19  static TDEInstance * s_instance;
20  static TDEAboutData * s_about;
21};

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 below:

notepad_factory.cpp: NotepadFactory Implementation.

 1#include "notepad_factory.h"
 2
 3#include <tdelocale.h>
 4#include <tdeinstance.h>
 5#include <tdeaboutdata.h>
 6#include <kstandarddirs.h>
 7
 8#include "notepad_part.h"
 9
10extern "C"
11{
12  void* init_libnotepad()
13  {
14    return new NotepadFactory;
15  }
16};
17
18TDEInstance* NotepadFactory::s_instance = 0L;
19TDEAboutData* NotepadFactory::s_about = 0L;
20
21NotepadFactory::NotepadFactory( TQObject* parent, const char* name )
22    : KLibFactory( parent, name )
23{
24}
25
26NotepadFactory::~NotepadFactory()
27{
28  delete s_instance;
29  s_instance = 0L;
30  delete s_about;
31}
32
33TQObject* NotepadFactory::create( TQObject* parent, const char* name,
34                                 const char* classname, const TQStringList & )
35{
36  if ( parent && !parent->inherits("TQWidget") )
37  {
38    kdError() << "NotepadFactory: parent does not inherit TQWidget" << endl;
39    return 0L;
40  }
41
42  NotepadPart* part = new NotepadPart( (TQWidget*) parent, name );
43  // readonly ?
44  if (TQCString(classname) == "KParts::ReadOnlyPart")
45     part->setReadWrite(false);
46
47  // otherwise, it has to be readwrite
48  else if (TQCString(classname) != "KParts::ReadWritePart")
49  {
50    kdError() << "classname isn't ReadOnlyPart nor ReadWritePart !" << endl;
51    return 0L;
52  }
53
54  emit objectCreated( part );
55  return part;
56}
57
58TDEInstance* NotepadFactory::instance()
59{
60  if( !s_instance )
61  {
62    s_about = new TDEAboutData( "notepadpart",
63                              I18N_NOOP( "Notepad" ), "2.0pre" );
64    s_instance = new TDEInstance( s_about );
65  }
66  return s_instance;
67}
68
69#include "notepad_factory.moc"

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 TDEAboutData object, such as authors, home page, and bug-report address. See the documentation for details.

The standard Trinity dialogs such as the Bug Report Dialog and the About Dialog use the data stored in TDEAboutData 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.

Messagebox info.png
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 TDEParts 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.

Messagebox info.png
Note
You need to install the package tdegraphics if you want to test this example.

The first thing to look at is the main window's GUI; an example is given below:

ghostviewtest_shell.rc: The main window's GUI

<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="KGVShell" version="1">
<MenuBar>
 <Menu name="file"><text>&amp;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>

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 TDEParts main window is shown below.

ghostviewtest.h: Header for a simple TDEParts main window

 1#include <tdeparts/mainwindow.h>
 2
 3class Shell : public KParts::MainWindow
 4{
 5  TQ_OBJECT
 6public:
 7  Shell();
 8  virtual ~Shell();
 9
10  void openURL( const KURL & url );
11
12protected slots:
13  void slotFileOpen();
14
15private:
16  KParts::ReadOnlyPart *m_gvpart;
17};

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 TDEParts examples, which can be found under tdelibs/tdeparts/tests/ghostview*, so the listing below only shows the relevant lines of ghostview.cpp.

Excerpt of ghostviewtest.cpp: Implementation of the simple TDEParts main window

 1Shell::Shell()
 2{
 3  setXMLFile( "ghostviewtest_shell.rc" );
 4
 5  TDEAction * paOpen = new KAction( i18n( "&Open file" ), "fileopen", 0,
 6            this, SLOT( slotFileOpen() ), actionCollection(), "file_open" );
 7
 8  TDEAction * paQuit = new KAction( i18n( "&Quit" ), "exit", 0,
 9            this, SLOT( close() ), actionCollection(), "file_quit" );
10
11  // Try to find libkghostview
12  KLibFactory *factory = KLibLoader::self()->factory( "libkghostview" );
13  if (factory)
14  {
15    // Create the part
16    m_gvpart = (KParts::ReadOnlyPart *)factory->create( this, "kgvpart",
17               "KParts::ReadOnlyPart" );
18    // Set the main widget
19    setView( m_gvpart->widget() );
20    // Integrate its GUI
21    createGUI( m_gvpart );
22  }
23  else
24    kdFatal() << "No libkghostview found !" << endl;
25}
26
27Shell::~Shell()
28{
29  delete m_gvpart;
30}
31
32void Shell::openURL( const KURL & url )
33{
34  m_gvpart->openURL( url );
35}

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 TDEIO'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();

Compile tdelibs/tdeparts/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. TDEParts 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;

  TQSplitter *m_splitter;

ghostviewtest.cpp needs to be more modified. To include the PartManager definition, use the following:

#include <tdeparts/partmanager.h>

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 * ) ) );

Then create the splitter and transform the setView statement into the following:

  m_splitter = new TQSplitter( this );

  setView( m_splitter );

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;

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 TDEParts 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>&amp;Edit</Text>
  <Action name="spellcheck"/>
 </Menu>
</MenuBar>
</kpartgui>

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 <tdeparts/plugin.h>

class PluginSpellCheck : public KParts::Plugin
{
    TQ_OBJECT
public:
    PluginSpellCheck( TQObject* parent = 0, const char* name = 0 );
    virtual ~PluginSpellCheck() {}

public slots:
    void slotSpellCheck();
};

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 <tqmultilineedit.h>
#include <tdeaction.h>

PluginSpellCheck::PluginSpellCheck( QObject* parent, const char* name )
    : Plugin( parent, name )
{
    (void) new TDEAction( 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();
         TQMultiLineEdit * widget = (TQMultiLineEdit *) part->widget();
         widget->selectAll(); //selects current line !
    }
}

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

Summary

After the presentation of component technology and how to lay out actions using XML, you have seen most of what TDEParts 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 tdeparts/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.