12.6. 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 in Listing 12.8


Example 12.8. Extract from Makefile.am

   1 
   2 lib_LTLIBRARIES = libnotepad.la
   3 libnotepad_la_SOURCES = notepad_part.cpp notepad_factory.cpp
   4 libnotepad_la_LIBADD = $(LIB_KFILE) $(LIB_KPARTS)
   5 libnotepad_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN)
   6 METASOURCES = AUTO
   7 				
   8 				
   9 				
  10 			

Your part is now available in a shared library, but this is not enough. You must provide a way for anybody opening that library dynamically to create a part. This is done using a factory, derived from KLibFactory, which you'll do in the class NotepadFactory. An application willing to open a shared library dynamically uses the class KLibLoader, which takes care of locating the library, opening it, and calling an initialization function—here init_libnotepad(). This function creates a NotepadFactory and returns it to KLibLoader, which can then call the create method on the factory. This means that all you need to do in the library itself is define init_libnotepad() and the NotepadFactory.

The header for the factory is the one shown in Listing 12.9.


Example 12.9. notepad_factory.h: Header File for NotepadFactory

   1 
   2  1: #include <klibloader.h>
   3  2: class KInstance;
   4  3: class KAboutData;
   5  4: class NotepadFactory: public KLibFactory
   6  5: {
   7  6:   Q_OBJECT
   8  7: public:
   9  8:   NotepadFactory( QObject * parent = 0, const char * name = 0 );
  10  9:   ~NotepadFactory();
  11 10:
  12 11:   // reimplemented from KLibFactory
  13 12:   virtual QObject * create( QObject * parent = 0, const char * name = 0,
  14 13:         const char * classname = "QObject",
  15 14:         const QStringList &args = QStringList());
  16 15:
  17 16:   static KInstance * instance();
  18 17:
  19 18: private:
  20 19:  static KInstance * s_instance;
  21 20:  static KAboutData * s_about;
  22 21: };
  23 				
  24 				
  25 				
  26 				
  27 			

As required by KLibFactory, your factory implements the create method, which creates a Notepad part and sets it to read/write mode or read-only mode, depending on whether the classname is KParts::ReadWritePart or KParts::ReadOnlyPart.

It also features a static instance, which is used in the part, instead of creating your own instance for each part. It is static because usually there is only one instance per library.

This means the code of notepad_part.cpp should be modified to call setInstance( NotepadFactory::instance() ); instead of creating its own instance.

The implementation for the NotepadFactory is shown in Listing 12.10.


Example 12.10. notepad_factory.cpp: NotepadFactory Implementation

   1 
   2  1: #include "notepad_factory.h"
   3  2:
   4  3: #include <klocale.h>
   5  4: #include <kstddirs.h>
   6  5: #include <kinstance.h>
   7  6: #include <kaboutdata.h>
   8  7:
   9  8: #include "notepad_part.h"
  10  9:
  11 10: extern "C"
  12 11: {
  13 12:     void* init_libnotepad()
  14 13:     {
  15 14:     return new NotepadFactory;
  16 15:     }
  17 16: };
  18 17:
  19 18: KInstance* NotepadFactory::s_instance = 0L;
  20 19: KAboutData* NotepadFactory::s_about = 0L;
  21 20:
  22 21: NotepadFactory::NotepadFactory( QObject* parent, const char* name )
  23 22:     : KLibFactory( parent, name )
  24 23: {
  25 24: }
  26 				
  27 				
  28 				
  29 				
  30 				
  31 				
  32 				
  33 				
  34 				
  35 				
  36 				
  37 25:
  38 26: NotepadFactory::~NotepadFactory()
  39 27: {
  40 28:   delete s_instance;
  41 29:   s_instance = 0L;
  42 30:   delete s_about;
  43 31: }
  44 32:
  45 33: QObject* NotepadFactory::create( QObject* parent, const char* name,
  46 34:                                 const char* classname, const QStringList&)
  47 35: {
  48 36:   if ( parent &&!parent->inherits("QWidget") )
  49 37:   {
  50 38:     kdError() << "NotepadFactory: parent does not inherit QWidget" << endl;
  51 39:     return 0L;
  52 40:   }
  53 41:
  54 42:   NotepadPart* part = new NotepadPart( (QWidget*) parent, name );
  55 43:   // readonly ?
  56 44:   if (QCString(classname) == "KParts::ReadOnlyPart")
  57 45:      part->setReadWrite(false);
  58 46:
  59 47:   // otherwise, it has to be readwrite
  60 48:   else if (QCString(classname) != "KParts::ReadWritePart")
  61 49:   {
  62 50:     kdError() << "classname isn't ReadOnlyPart nor ReadWritePart !" << endl;
  63 51:     return 0L;
  64 52:   }
  65 53:
  66 54:   emit objectCreated( part );
  67 55:   return part;
  68 56: }
  69 57:
  70 58: KInstance* NotepadFactory::instance()
  71 59: {
  72 60:   if( !s_instance )
  73 61:   {
  74 62:     s_about = new KAboutData( "notepadpart",
  75 63:                               I18N_NOOP( "Notepad" ), "2.0pre" );
  76 64:     s_instance = new KInstance( s_about );
  77 65:   }
  78 66:   return s_instance;
  79 67: }
  80 68:
  81 69: #include "notepad_factory.moc"
  82 				
  83 				
  84 				
  85 				
  86 				
  87 				
  88 				
  89 				
  90 				
  91 				
  92 				
  93 			

The implementation is a bit long but contains nothing complex. Basically, you define the function that is the entry point of the library, init_libnotepad(). It needs to be linked as a C function to avoid C++ name mangling. C linkage means that the symbol in the library will match the function name.

Then you define the NotepadFactory. The create method checks that the parent is a widget because this is needed for your part (remember, you create your widget with the parent widget given as an argument to the constructor). After creating the part, it has to emit objectCreated so that the library loader can do a proper reference counting; it automatically unloads the library after all objects created from it have been destroyed.

The instance() method returns the static instance, creating it first, if necessary. To create an instance, I recommend that you give it a KAboutData pointer. This gives some information about the instance representing the library (here an instance name, a translatable description of it, and a version number). You can add a lot more information in the KAboutData object, such as authors, home page, and bug-report address. See the documentation for details.

The standard KDE dialogs such as the Bug Report Dialog and the About Dialog use the data stored in KAboutData to show information about the current program, but in the future they will probably be improved to show information about the active part as well, which can have completely different About data from the application.

Note

KParts provides a factory base class, KParts::Factory, which enhances KlibFactory by making it possible to have a parent for the widget different from the parent for the part. It also takes care of loading the translation message catalog for the newly created part. Look in kparts/factory.h for more on this.