KOffice - A short Introduction to the Document/View Model
To perform all the embedding magic in KOffice, the KOffice Library Classes have to extend the KParts concept. Due to that these classes are a little bit difficult to understand if you look at them for the first time without any background information. Even KDE/Qt savvy programmers might have to look very closely to fully understand these concepts. This might be one reason that there are rather few KOffice programmers around. With this document I'm trying to point out the basic concepts which are hidden deeply inside KOffice. I won't discuss the gory (implementation) details, but with that overview and some source code studies you should be able to write or improve a KOffice application. However, before starting a brand new KOffice application you should think whether it would be better to fix and improve an already existing KOffice application. At the moment many applications lack a maintainer (e.g. KFormula and KChart). I know that it's more fun to create own applications, but due to the lack of developers we need all helping hands we can get to fix the existing applications.
As a starting point for your studies you might want to use the example application in koffice/example. Note: Remove the configure.in.in file from this directory because it prevents the example from being compiled! Of course it's also funny to play with KWord and friends, but in the beginning it's easier with the example app, because it's really straightforward.
Structure of a KOffice Binary
KOffice applications are libraries which are loaded via a small wrapper application. Normally all the application's code will be compiled and linked to a library (e.g. libkword.so) and the main.cc file in the directory creates a KoApplication. The start() method of this class uses a special C "bootstrap" function (init_libkword()) to load this library (via dlopen() and friends). Then it creates a document and a shell - this "starts" a KOffice application (i.e. brings up a main window and opens an empty document or shows a template dialog). Applications have to be structured that way, otherwise they wouldn't be embeddable in other KOffice documents!
Basic Structure of a KOffice Application
KOffice applications aim to follow the document/view model. This means there is a document which holds all the necessary information, can store itself to disk, load a file from disk, and so on. To show this document, a view has to be created. As the document is highly independent from the view, it is possible to create more than one view to show it. Of course the document can be shown with different zoom factors and so on (this doesn't affect the document, but only the view), but if you change the document in one view (e.g. by writing a formula in a KSpread cell) all the other views will be updated, too. This means you don't have to worry about data-loss, file corruption, and so on, because you work on multiple views for one document.
A rule of thumb (of course not totally true) is, that you can't see a document, but you need at least one view to show it to the user.
To support embedding, the view mustn't be a main window with a menubar, toolbars, and so on, but an ordinary widget. This implies that there has to be something else, because if you can't see documents and the view is only a widget, where do all the toolbars come from...? Indeed, KOffice uses a shell to show the view (as a kind of main widget).
Now I'll discuss the three main parts of a KOffice application (namely shell, view, and document) in a more detailed manner. Then I'll give you some basic information on the additional stuff which is needed to make an application work (like the factory, the wrapper application, .desktop files, and so on). After that I'll give you a short overview on the interaction between these components. This will help you to understand the behavior of applications, and it will hopefully make it easier to find bugs.
Please have a look at the appropriate example files while reading the next paragraphs, because I'm referring to them in various sections.
The shell of a KOffice application is basicly a KTMainWindow with some extra spice. There is nothing spectacular there, and you normally only use the plain shell from the KOffice Libraries. If you really want to perform some shell tricks you'll have to derive from it, like KOffice Workspace does. It provides a basic File and a Help menu, see koffice/lib/kofficecore/koffice_shell.rc
The view class sets the XMLGUI rc-file and creates all the actions which are bound to the view (e.g. zooming,...). You may also create document specific actions there (like most of the current applications do, because some time ago you couldn't create actions in the document), but generally you should prefer to define the actions where the are used. If you aren't familiar with XMLGUI and the action pattern, I suggest reading the documentation for this first.
There are two very important methods in this class: paintEvent() and updateReadWrite(). The paintEvent() is responsible to set up a QPainter and prepare the document to be painted. This includes setting up zooming, double buffering, transparency, and so on. I can't tell you what you'll see there, because every application does this differently. Anyway, you can leave this part as it is done in the example code for the beginning. You can also see that this method calls paintEverything() which in turn calls the same method for the document. More on that later.
If a plain widget is not feasible as "main widget", also other widgets like QScrollView, QTableView, or something else may be used, but KoView's canvas() method has to be overriden to return a pointer to this widget. There are also other useful KoView methods to provide neat features; I suggest reading the KoView header (koffice/lib/kofficecore/koView.h) for further information.
The updateReadWrite() method is necessary, because KOffice can be switched to a "view-only" mode where the user is not able to change the document, but only view it.
This is a main part of the application. The document has to be able to paint itself to a QPainter (paintEverything() and friends), and it has to store all the information of the document, of course. It's called example_part.cc, because it's a KPart, internally. Therefore "document" and "part" are often used synonymously. As you can see, there are several methods which have to be reimplemented, otherwise the application won't compile (they are pure virtual). There are also other methods (for loading, saving,...) which have to be reimplemented if loading/saving, and so on should be supported. Please have a look at koffice/lib/kofficecore/koDocument.h for further information.
There is some code needed to glue all this together and to make it run. This code is in main.cc and *_factory.*. These files are "templates" which mostly have to be copied and pasted. This is also true for the .desktop files and the rc-File.
Interaction between Shell, Document, and View
As you can see, all applications support embedding, but how does this work? Well, there are several small hints on how it's done in the example code, but if you really want to have exact information, please look into the kofficecore libraries.
Basically it works like this:
- If the application runs standalone, the main() function creates a KoApplication. This application creates a document and a shell. The shell creates a view and in the end all this stuff is shown.
- If the application gets embedded, the "parent" application creates only a document of its child. You've heard above that a document is "invisible" without a view, but this is not totally true. This is the reason for the paint*() methods in KoDocument. Whenever the document is embedded it has no "real" view, but it paints itself on the view widget of its parent (i.e. the parent calls the paintEverything() method of the child document and passes its own painter). When the child gets activated (by a mouse click), the matching view is created and shown. Now the application is fully functional, but the shell is another one (i.e. the one from the parent application). This doesn't matter, because the shell merges the GUI of the child application (all the actions). Now only the shell's GUI (like the Help -> About... dialog) is provided by the shell of the parent application. Everything else in the menus and toolbars is provided by the child application.
The reason for that is, that it's not feasible to have one widget for every embedded part because of performance problems (imagine a document with 100 embedded formulas).