This problem is solved by a set of classes called XMLGUI. Basically, this separates actions (coded in C++) from their appearance in menu bars and tool bars (coded in XML). Without modifying any source code, menus can be simply customized by adjusting an XML file. Furthermore, it helps to make sure that standard actions (such as File->Open or Help->About) appear in the locations suggested by the style guide. XMLGUI is especially important for modular programs, where the items appearing in the menu bar may come from many different plugins or parts.
KDE's class for toplevel windows, KMainWindow, inherits KXMLGUIClient and therefore supports XMLGUI out of the box. All actions created within it must have the client's actionCollection() as parent. A call to createGUI() will then build the whole set of menu and tool bars defined the applications XML file (conventionally with the suffix ui.rc).
rcdir = $(kde_datadir)/kview rc_DATA = kviewui.rc |
Here is an excerpt from the kviewui.rc file. For simplicity, we show only the definition of the View menu.
<!DOCTYPE kpartgui> <kpartgui name="kview"> <MenuBar> <Menu name="view" > <Action name="zoom50" /> <Action name="zoom100" /> <Action name="zoom200" /> <Action name="zoomMaxpect" /> <Separator/> <Action name="fullscreen" /> </Menu> </MenuBar> </kpartgui> |
The corresponding part of the setup in C++ is:
KStdAction::zoomIn ( this, SLOT(slotZoomIn()), actionCollection() ); KStdAction::zoomOut ( this, SLOT(slotZoomOut()), actionCollection() ); KStdAction::zoom ( this, SLOT(slotZoom()), actionCollection() ); new KAction ( i18n("&Half size"), ALT+Key_0, this, SLOT(slotHalfSize()), actionCollection(), "zoom50" ); new KAction ( i18n("&Normal size"), ALT+Key_1, this, SLOT(slotDoubleSize()), actionCollection(), "zoom100" ); new KAction ( i18n("&Double size"), ALT+Key_2, this, SLOT(slotDoubleSize()), actionCollection(), "zoom200" ); new KAction ( i18n("&Fill Screen"), ALT+Key_3, this, SLOT(slotFillScreen()), actionCollection(), "zoomMaxpect" ); new KAction ( i18n("Fullscreen &Mode"), CTRL+SHIFT+Key_F, this, SLOT(slotFullScreen()), actionCollection(), "fullscreen" ); |
The View menu resulting from this GUI definition looks like in this screenshot:
The XML file begins with a document type declaration. The DTD for kpartgui can be found in the kdelibs sources in kdeui/kpartgui.dtd. The outermost element of the file contains the instance name of the application as attribute. It can also contain a version number in the form "version=2". This is useful when you release new versions of an application with a changed menu structure, e.g. with more features. If you bump up the version number of the ui.rc file, KDE makes sure that any customized version of the file is discarded and the new file is used instead.
The next line, <MenuBar> contains a declaration of a menu bar. You can also insert any number of <ToolBar> declarations in order to create some tool bars. The menu contains a submenu with the name "view". This name is already predefined, and thus you see a translated version of the word "View" in the screenshot. If you declare your own submenus, you have to add the title explicitly. For example, KView has a submenu with the title "Image" which is declared as follows:
<Menu name="image" > <text>&Image</text> ... </Menu> |
Let us come back to the example. KView's View menu contains a couple of custom actions: zoom50, zoom100, zoom200, zoomMaxpect and fullscreen, declared with a <Action> element. The separator in the screenshots corresponds with the <Separator> element.
You will note that some menu items do not not have a corresponding element in the XML file. These are standard actions. Standard actions are created by the class KStdAction. When you create such actions in your application (such as in the C++ example above), they will automatically be inserted in a prescribed position, and possibly with an icon and a shortcut key. You can look up these locations in the file kdeui/ui_standards.rc in the kdelibs sources.
<ToolBar name="locationToolBar" fullWidth="true" newline="true" > <text>Location Toolbar</text> <Action name="clear_location" /> <Action name="location_label" /> <Action name="toolbar_url_combo" /> <Action name="go_url" /> </ToolBar> |
The first thing we notice is that there are a lot more attributes than for menu bars. These include:
<Menu name="file"> <text>&Location</text> ... <ActionList name="openwith"> ... </Menu> |
The function KXMLGUIClient::plugActionList() is then used to add actions to be displayed, whereas the function KXMLGuiClient::unplugActionList() removes all plugged actions. The routine responsible for updating looks as follows:
void MainWindow::updateOpenWithActions() { unplugActionList("openwith"); openWithActions.clear(); for ( /* iterate over the relevant services */ ) { KAction *action = new KAction( ...); openWithActions.append(action); } plugActionList("openwith", openWithActions); } |
Note that in contrast to the static actions, the ones created here are not constructed with the action collection as parent, and you are responsible for deleting them for yourself. The simplest way to achieve this is by using openWithActions.setAutoDelete(true) in the above example.
Also note that to be able to extend menus this way, you need to call createGUI() with the second parameter (conserveMemory) set to "false". If you don't, you won't get any error but your actions won't appear in the menu.
void MainWindow::popupRequested() { QWidget *w = factory()->container("context_popup", this); QPopupMenu *popup = static_cast<QPopupMenu *>(w); popup->exec(QCursor::pos()); } |
The method KXMLGUIFactory::container() used above looks whether it finds a container in the XML file with the given name. Thus, a possible definition could look as follows:
... <Menu name="context_popup"> <Action name="file_add"/> <Action name="file_remove"/> </Menu> ... |
Bernd Gehrmann [email protected]