12.5. Creating a Part

In this section, you create a very simple part for a text editor. If you have closely followed the previous section, you know that the part should inherit KParts::ReadWritePart.

At this point, it is a very good idea to read kparts/part.h, directly or preferably after running kdoc on it (see Chapter 15, "Creating Documentation," for information about kdoc). This tells you that a read/write part implementation has to provide the methods openFile() and saveFile().

The task of openFile() is obviously to open a local file, which the framework has previously downloaded for us in case the URL that the user wants to open is a remote one. In this case, the file you open is a temporary local file.

In saveFile(), the part saves to the local file, and in case it's a temporary file, the framework takes care of uploading the new file.

You can now sketch the header file for your part, which is called NotepadPart (see Listing 12.2).

Example 12.2. notepad_part.h: Header of the NotepadPart Class

   2  1: #ifndef __notepad_h__
   3  2: #define __notepad_h__
   4  3:
   5  4: #include <kparts/part.h>
   6  5:
   7  6: class QMultiLineEdit;
   8  7:
   9  8: class NotepadPart : public KParts::ReadWritePart
  10  9: {
  11 10:   Q_OBJECT
  12 11: public:
  13 12:   NotepadPart( QWidget * parent, const char * name = 0L );
  14 13:   virtual ~NotepadPart() {}
  15 14:
  16 15:   virtual void setReadWrite( bool rw );
  17 16:
  18 17: protected:
  19 18:   virtual bool openFile();
  20 19:   virtual bool saveFile();
  21 20:
  22 21: protected slots:
  23 22:   void slotSelectAll();
  24 23:
  25 24: protected:
  26 25:   QMultiLineEdit * m_edit;
  27 26:   KInstance *m_instance;
  28 27: };
  29 28:
  30 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 QMultiLineEdit (the multiline widget from Qt), and a KInstance. An instance enables access to global KDE objects, which can be different from the ones of the application. The application's configuration file and the one of any other instance is different, as well as the search paths for locate(), and so on. In KParts, this is used to locate the XML file describing the part, which is usually installed into share/apps/instancename/.

In addition, you define a slot, slotSelectAll(), to be connected to the action your part provides.

The corresponding XML file for the part NotepadPart is listed in Listing 12.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.

Example 12.3. notepadpart.rc: XML Description of the Notepad Part's User Interface

   2 <!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
   3 <kpartgui name="NotepadPart" version="1">
   4 <MenuBar>
   5  <Menu name="Edit"><Text>&amp;Edit</Text>
   6   <Action name="selectall"/>
   7  </Menu>
   8 </MenuBar>
   9 <StatusBar/>
  10 </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 in Listing 12.4.

Example 12.4. notepad_part.cpp part 1: Constructor

   2  1: NotepadPart::NotepadPart( QWidget * parent, const char * name )
   3  2:  : KParts::ReadWritePart( parent, name )
   4  3: {
   5  4:   KInstance * instance = new KInstance( "notepadpart" );
   6  5:   setInstance( instance );
   7  6:
   8  7:   m_edit = new QMultiLineEdit( parent, "multilineedit" );
   9  8:   m_edit->setFocus();
  10  9:   setWidget( m_edit );
  11 10:
  12 11:   (void)new KAction( i18n( "Select All" ), 0, this,
  13 12:         SLOT( slotSelectAll() ), actionCollection(), "selectall" );
  14 13:   setXMLFile( "notepadpart.rc" );
  15 14:
  16 15:   setReadWrite( true );
  17 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 multiline edit widget, give it the focus, and declare it as well, using setWidget().

The next step is to create the actions that your part provides. The "selectall" action is given a translated label, is connected to slotSelectAll(), and is created as a child of the action collection that the framework provides. This is important, because it's the only way to make it find the action later on, when parsing the XML file. This is why you don't even need to store the action in a variable, unless you want to be able to enable or disable it later.

You also need to give the framework the name of the XML file describing the part's GUI. As mentioned previously, it is usually installed into share/apps/instancename/, and in this case, you simply pass the filename with no path. It is also possible, but not recommended, to install the XML file anywhere else and provide a full path in setXMLFile().

Finally, the part is set to read/write mode. Read/write parts feature the setReadWrite() call , which enables you to set the read/write mode on or off. Most parts should reimplement this method to enable or disable anything that modifies the part, KActions as well as any direct modification provided by the widget itself. The reimplementation of setReadWrite() for the Notepad Part is shown in Listing 12.5.

Example 12.5. notepad_part.cpp part 2: Implementation of setReadWrite

   2 void NotepadPart::setReadWrite( bool rw )
   3 {
   4   m_edit->setReadOnly( !rw );
   5   if (rw)
   6     connect( m_edit, SIGNAL( textChanged() ), this, SLOT( setModified() ) );
   7   else
   8     disconnect( m_edit, SIGNAL( textChanged() ), this, SLOT( setModified() ) );
  10   ReadWritePart::setReadWrite( rw ); // always call the parent implementation
  11 }

In the example, there are no actions to disable, but the multiline widget has to be set to its read-only mode.

The connection to setModified(), done in read/write mode only, enables the framework to keep track of the state of the document. When closing a document that has been modified, the framework automatically asks whether it should save it and allow you to cancel the close. Note that to make all this work, you just needed to connect a signal when the part is in read/write mode and disconnect it when it's in read-only mode. This avoids warnings when a loading a file, which changes the text.

It might seem a bit painful to have to handle both read/write and read-only mode, but doing this gives for free the possibility to embed the part as a viewer, in Konqueror, for instance, so it's usually worth doing.

Your part is created; you need to make it useful. The method that all read-only parts—and by inheritance, all read/write parts as well—must reimplement is the openFile() method. This is where a part opens and displays the local file, whose full path is provided in the member variable m_file, and which the framework downloaded from a remote location first, if necessary. Because your part is a text viewer, all it has to do is read the file into a QString and set the multiline widget's text from it, as shown in Listing 12.6.

Example 12.6. notepad_part.cpp part 3: Implementation of openFile

   2  1: bool NotepadPart::openFile()
   3  2: {
   4  3:   QFile f(m_file);
   5  4:   QString s;
   6  5:   if ( f.open(IO_ReadOnly) )
   7  6:   {
   8  7:     QTextStream t( &f );
   9  8:     while ( !t.eof() ) {
  10  9:       s += t.readLine() + "\n";
  11 10:     }
  12 11:     f.close();
  13 12:   }
  14 13:   m_edit->setText(s);
  15 14:
  16 15:   return true;
  17 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 in Listing 12.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.

Example 12.7. notepad_part.cpp part 4: Implementation of saveFile

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