Next Previous Table of Contents
The goals of the OpenParts technology, based upon KOM, are:
To simplify the act of understanding OpenParts I will give a short example situation:
Imagine you have an a word processor and a formula editor, both being separate applications. If you now want to insert a formula into your word processor document by using your formula editor application this arises several problems: You can of course embed the formula editor's main widget window via swallowing by using XReparentWindow and friends, or easier by using QXEmbed. But then how do you want to edit your formula without having access to the formula editor's menubar / toolbar? In any way it would look ugly if these are part of the formula window. Wouldn't it be nice if the menus / toolbars of your word processor application would get replaced by the formula editor ones, except for some general menu /toolbar items? And when you go back to your text document the old menus / toolbars come back again.
Well, this is a perfect job for OpenParts :-) .
OpenParts solves the above described problem by introducing a new sytem of visual components and a new way of creating shared GUI elements, such as menus or toolbars, dynamically on demand.
In the implementation of OpenParts every element consists usually of two
classes, the interface implementation, where the class name ends with "If",
and the Qt/KDE object. So for example the OpenParts StatusBar element is
represented by two classes: OPStatusBar
, being derived from
KStatusBar, handles the Qt/KDE specific extensions, and
OPStatusBarIf
which is responsible for providing an implementation
of the actual OpenPartsUI::StatusBar interface by "translating" the interface
functionality into Qt/KDE function calls.
Since every Qt/KDE object in OpenParts is most often bound to such an
interface, like described above, there is usually an interface() function
which returns a reference to the OpenParts interface of the element. In case
of the above example OPStatusBar::interface()
returns a reference to
an OPStatusBarIf
object which is directly bound to this
OPStatusBar
object.
Similar to KOMApplication, the class OPApplication (derived from KOMApplication) is required when using OpenParts.
In OpenParts every window which has its own GUI and which is meant to be
displayed in a MainWindow is called a Part (just like the formula editor view
or the word processor document view in the above described example) , and
implements the OpenParts::Part interface by deriving from the class
OPPartIf
.
If you want to make a widget class a full-featured Part component then you
have to handle some things different than you might be used to, in regard to
the standard Qt/KDE widget framework. In fact now a widget is no more a simple
window in which you display some data, no, a Part is much more than this. In
particular a Part has, beside it's window (widget, which may of course contain
sub-windows or even other Parts (see OPFrame
documentation) ) a
full-featured GUI, consisting of a menubar with menus, toolbar(s) and a
statusbar.
The special thing with the GUI is the way it is created, handled/used and "destroyed" . All this has to be highly dynamic because now the user decides about which Part he wants to have active. OpenParts provides the basic framework for this:
The very first step you have to make is to tell the OpenParts Part Interface
(OPPartIf
) , the class you have to inherit from, what the actual
widget is, because OPartIf
does not inherit from QWidget. This gives
you the flexibility to separate your Part component from your actualy widget,
but you don't have to do this. You can simply multiply inherit from
OPPartIf
and QWidget
or the appropriate widget class. In any way you
specify your Part widget by calling setWidget( your_widget_here )
.
In most cases, when the Part component is the widget at the same time, you
simply call setWidget( this )
:-) . Make sure this call is done in
the constructor of your class!
The next important point is that you will want to re-implement the virtual
init()
function of OPPartIf
. This is highly recommended
since this function is called after your Part got registered by a MainWindow.
The idea behind this function is that at the time the constructor of a Part
gets executed, the Part itself is definitely not registered to a MainWindow,
yet. But in fact you need to know when your Part gets registered, in order to
register your Part at the GUI servant objects, which are only available via
the MainWindow's interface. A reference to the MainWindow is available through
the m_vMainWindow
variable, which will be automatically initialized
when the MainWindow registration is done, so don't use this variable before
your init()
function gets called (m_vMainWindow
will be nil
anyway) .
Now over to the details of the init()
function. Here you should place
all initialisation stuff which depends on being registered to a MainWindow.
In addition you can do the above mentioned registration at the GUI managing
objects. Usually this looks like the following example:
void MyPart::init() { //register at the menubar manager if you want to use/display a menubar OpenParts::MenuBarManager_var menuBarManager = m_vMainWindow->menuBarManager(); if ( !CORBA::is_nil( menuBarManager ) ) //check whether the shell window allows us to have a menubar menuBarManager->registerClient( id(), this ); //see chapter about the //*barManager objects //for further explanations //...the same with the toolbar OpenParts::ToolBarManager_var toolBarManager = m_vMainWindow->toolBarManager(); if ( !CORBA::is_nil( toolBarManager ) ) toolBarManager->registerClient( id(), this ); //better define a class wide variable, of course OpenPartsUI::StatusBar_var m_vMyStatusBar; OpenParts::StatusBarManager_var statusBarManager = m_vMainWindow->statusBarManager(); if ( !CORBA::is_nil( statusBarManager ) ) m_vMyStatusBar = statusBarManager->registerClient( id() ); }
Note that the registration calls for these three GUI element types are only
necessary if you really want to use them. For example if your Part does not
want to display any toolbar you should leave out the corresponding call. In
addition you might come up with the situation that for example the
toolBarManager()
call returns a nil reference, which indicates that
the MainWindow does not allow its Parts to have a toolbar. Obviously the same
applies for the menubar and the statusbar.
OpenParts makes use of KOM events to tell a Part about the construction/destruction of it's GUI. These are in particular:
OpenPartsUI::eventCreateMenuBar
("OpenPartsUI/CreateMenuBar")OpenPartsUI::MenuBar
OpenPartsUI::eventCreateToolBar
("OpenPartsUI/CreateToolBar")OpenPartsUI::ToolBarFactory
Depending on whether a Part wants to display a menubar and/or toolbar, the
managing objects emit these two events to it. In regard to your implementation
this means that you have to re-implement the event()
function
(remember: A Part is a full-featured KOM Component) .
The attached event arguments indicate whether the toolbar(s) or the menubar
are to be created or cleared. Check these arguments against
CORBA::is_nil()
and you know :-) .
The OpenParts StatusBar is handled different compared to the
MenuBar/ToolBar(s) . In fact it is easier: When registering at the OpenParts
StatusBarManager you receive your OpenParts::StatusBar
as return
value. You can then use the StatusBar everywhere in your Part, independend
from whether it is visible (active) or not.
In the init()
function a lot of registration stuff is done, and
corresponding to this in the cleanUp()
function (see chapter about
KOM::Base) you have to unregister from the GUI servant objects and free all
appropriate references, following KOM's model of symmetric references and
connections. Usually the code looks like this:
void MyPart::cleanUp() { if ( m_bIsClean ) return; //unregister our menubar OpenParts::MenuBarManager_var menuBarManager = m_vMainWindow->menuBarManager(); if ( !CORBA::is_nil( menuBarManager ) ) menuBarManager->unregisterClient( id() ); //...the same with the toolbar OpenParts::ToolBarManager_var toolBarManager = m_vMainWindow->toolBarManager(); if ( !CORBA::is_nil( toolBarManager ) ) toolBarManager->unregisterClient( id() ); OpenParts::StatusBarManager_var statusBarManager = m_vMainWindow->statusBarManager(); if ( !CORBA::is_nil( statusBarManager ) ) statusBarManager->unregisterClient( id() ); //free other references here //... //this is IMPORTANT!!! //Always call the cleanUp() method of the base class when you're done! OPPartIf::cleanUp(); }
A Part Child is a usual Part with three extra features:
OpenParts::Part
interface and it's implementation,
OPPartIf
, you don't have to deal with additional classes when
using Child Parts. Simply leave out the mapping of the OpenParts GUI
events in the Child Part and instead map the Child Part events described
below and assign the Parent Part via setParent()
. See the
interface description of OpenParts::Part
, in openparts.idl,
for further information about the events and the API in general.
Now that you know how to create full-featured Part components it is still
unexplained how Parts are really displayed/shown. Since Parts are no simple
QWidgets but CORBA objects we need a helping hand here, which is the
OPFrame
class. In fact OPFrame
is a QWidget, but in
conjuction with Qt's QXEmbed it embeds the Part's widget window. The usage of
OPFrame
is really easy, usually the code looks like this:
... somewhere in an application's widget ... myFrame = new OPFrame( the_parent_widget ); myFrame->attach( a_reference_to_the_part_we_want_to_embed ); myFrame->show();
In addition to the above example you can detach()
your Part, which
you should usually do on exit. Just have a look at opFrame.h, it is
documented.
One last important thing you have to know about OPFrame
is that this
class internally uses KOM referencing (using a KOMVar
variable) to
hold the Part. This means that there are two possible situations when using
OPFrame
:
OPFrame
which
increases/decreases the Part's KOM reference counter when it gets
attached/detached, and on the other hand there's the embedding
Widget/Object. So make sure that you know when you free your KOM
reference to the Part in regard to detaching the Part!OPFrame
is the only object holding a KOM
reference to the Part and the KOM reference counter therefore drops
down to zero and issues the complete destruction of the object.
Another important component is the so called OpenParts::MainWindow, being derived from a KTMainWindow in the implementation (and therefore the top-level window of your application) and being the shell around visible sub-windows and shared GUI elements.
The MainWindow's functionality is extended by some builtin aggregates, the
managing objects for the menu-/tool-/statusbar. These objects are either
available directly via the *barManager()
methods of the MainWindow's
interface or indirectly by being aggregates and therefore available via the
components interface repository (getInterface()
,
supportsInterface()
, ...) .
A Part can only be displayed in a MainWindow and the MainWindow has to know
about this. So before you can display a Part you have to register it to the
MainWindow. This is done by calling the Part Interface's
setMainWindow()
method (see previous chapter for further information
about the process of registration) and this will give the Part a unique ID
(which is for example used when addressing the part's GUI via the *bar manager
objects) .
The MainWindow, as shell, has full control over all shared GUI elements. This means that it is responsible for
The creation of the *bar managers can be easily done by simply performing a
dummy call to *barManager()
which usually returns a pointer to the
appropriate manager and also creates a new one if it does not exist yet. It is
recommended to perform these calls in the constructor of your MainWindow.
The creation/handling of the skeleton GUI is explained later in the chapters about OPMenu(Bar)/OPToolBar .
Your MainWindow emits a Qt signal (activePartChanged
) which informs
you about a focus change of the active part, meaning whenever the user clicks
on a non-active Part and it accepts the focus. Beside the pure informative
sense of this signal it is recommended to connect to this signal and perform
the following two steps in the slot implementation:
clear()
function, which will do the job and, beside some
internal stuff, emit the GUI events (see previous chapter) to the Part.create()
function, which will, similar to the activation, emit
events to the Part. This is not really required but it is highly
recommended. The following code is usually used for this:
void NameOfYourMainWindow::slotActivePartChanged( unsigned long old_id, unsigned long new_id ) { // clear the menu/tool/statusbar(s) menuBarManager()->clear(); toolBarManager()->clear(); statusBarManager()->clear(); // create the new Part's GUI menuBarManager()->create( new_id ); toolBarManager()->create( new_id ); statusBarManager()->create( new_id ); }
Now that the MainWindow handles all the shared "stuff" there is one thing which was not mentioned in this documentation, yet: What about the MainWindow's caption? The OpenParts MainWindow interface allows parts to have their own window captions, but how does OpenParts handle this?
Well, there are two ways:
OPMainWindow
provides you a so called AutoCaption
mode
which automatically changes the MainWindow's caption whenever the active
part changes. This is enabled by default.AutoCaption
mode, which leads to the situation that the window's caption is not
changed by OpenParts in any way but instead gives you control over it.
Well, now that we know when we have to construct/destruct a Part's GUI, via the OpenPartsUI events, we have to learn how to really create it, because we don't have the common KMenuBar, KToolBar, etc. classes anymore available. The replacement for them are CORBA Objects, described in openparts_ui.idl which is, together with the corresponding implementations, a part of the partsui module. The interfaces are 98%; similar to the KDE/Qt classes, so they're quite easy to use. Instead of bloating up this documentation with example code I rather suggest reading the tutorials in kdelibs/corba/tutorials .
Toolbars and menus are usually beautified with pixmaps, using QPixmap classes. As we now use a CORBA interface to access our GUI elements, QPixmap has become OpenPartsUI::Pixmap for OpenParts applications. OpenPartsUI::Pixmap is just a "stringified" QPixmap, and opUIUtils.(h,cc) contains some easy to use conversion routines.
In addition OPUIUtils contains string conversion routines between CORBA::WChar*
and QString. This is necessary since Qt version >=2.0 supports Unicode,
via QString, all over the place, and obviously GUI elements like menus or
toolbars have been converted to support this. OpenParts has been converted,
too, by using "wstring" (CORBA::WChar *) in the interfaces and by
using and providing conversion routines. These routines are static member
functions of the OPUIUtils class, just like with the pixmap conversion. To
simplify the usage, two macros have been defined: Q2C
and
C2Q
. The first one converts a QString into a CORBA::WChar * string
and the second one vice-versa.
When converting from QString to CORBA::WChar * the conversion routine allocates memory. To avoid memory leaks it is highly recommended to use CORBA::WString_var variables. Exactly the same applies for QPixmap -> OpenPartsUI::Pixmap conversions: Use OpenPartsUI::Pixmap_var , and you don't have to worry about leaks :-) .
Here's some example code, to show how to do it right:
... OpenPartsUI::Pixmap_var pm = OPUIUtils::convertPixmap( QPixmap_variable_here ); someToolBar->insertButton( pm, ... ); ... //use the same pm variable again pm = OPUIUtils::convertPixmap( another_qpixmap ); ...
Similar things have to be done with QString's:
... CORBA::WString_var text = Q2C( QString_here ); someMenuBar->insertItem7( text, ... ); ... // or you can write: someMenuBar->insertItem7( ( text = Q2C( QString_here ) ) , ...); ...
One note left: When you return a "wide string" (CORBA::WChar *) as
a function result by using Q2C
, make sure not to use
CORBA::wstring_dup()
.
Example:
return CORBA::string_dup( Q2C( QString_here ) ); //!!!! WRONG!!!!! ... return Q2C( QString_here ); // RIGHT! because Q2C already allocates the string
That's it! Have fun using KOM/OpenParts :-)
TODO:
Next Previous Table of Contents