Difference between revisions of "Development/Architecture/KDE4/XMLGUI Technology"

Jump to: navigation, search
(Add a todo link to mail by david faure)
(added link to the schema definition)
 
(9 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{improve|This entire page needs a thorough review.
+
{{improve|This entire page needs a thorough review.}}
 
+
[http://lists.kde.org/?l=kde-core-devel&m=124976855215580&w=2 These clarifications] should be incorporated into this page, in particular regarding "append" in DefineGroup.
[[http://http://lists.kde.org/?l=kde-core-devel&m=124976855215580&w=2 These clarifications]], should be incorporated into this page, in particular regarding "append" in DefineGroup.}}
+
 
{{Note|I don't really feel overly qualified to write this page, but I've spent a whole lot of time figuring out some of the following things for lack of good documentation. So I'll make a start... --[[User:Tfry|Tfry]] 14:02, 22 May 2009 (UTC)}}
 
{{Note|I don't really feel overly qualified to write this page, but I've spent a whole lot of time figuring out some of the following things for lack of good documentation. So I'll make a start... --[[User:Tfry|Tfry]] 14:02, 22 May 2009 (UTC)}}
  
 
= Related information resources =
 
= Related information resources =
 
* [[Development/Tutorials/Using_KActions|KAction tutorial]]
 
* [[Development/Tutorials/Using_KActions|KAction tutorial]]
* [http://developer.kde.org/documentation/library/kdeqt/kde3arch/xmlgui.html Documentation for KDE 3 (still mostly valid)]
+
* [[Development/Architecture/KDE3/XMLGUI_Technology|Documentation for KDE 3 (still mostly valid)]] [http://developer.kde.org/documentation/library/kdeqt/kde3arch/xmlgui.html (Original)]
 
* [[Development/Architecture/KDE4/KParts|KParts - perhaps the most important use case for advanced KXMLGUI stuff]]
 
* [[Development/Architecture/KDE4/KParts|KParts - perhaps the most important use case for advanced KXMLGUI stuff]]
* [http://websvn.kde.org/trunk/KDE/kdelibs/kdeui/xmlgui/kpartgui.dtd?view=markup kpartgui / KXMLGUI document type definition]
+
* [https://projects.kde.org/projects/kde/kdelibs/repository/revisions/master/entry/kdeui/xmlgui/kpartgui.dtd kpartgui / KXMLGUI document type definition]
 
* API documentation on KXMLGUI classes
 
* API documentation on KXMLGUI classes
 
** {{class|KXMLGUIClient|kdelibs|4.x}} - The base class for components that use and xml-gui
 
** {{class|KXMLGUIClient|kdelibs|4.x}} - The base class for components that use and xml-gui
Line 16: Line 15:
 
= Basics =
 
= Basics =
 
For basic information, please start reading at [[Development/Tutorials/Using_KActions]]. For most simple cases you should be able to write a ui.rc for your application by simply expanding on the given examples.
 
For basic information, please start reading at [[Development/Tutorials/Using_KActions]]. For most simple cases you should be able to write a ui.rc for your application by simply expanding on the given examples.
 +
 +
The full XML schema definition for XMLGUI can be found [http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd here]
  
 
= Terminology =
 
= Terminology =
Line 25: Line 26:
  
 
== How does merging happen ==
 
== How does merging happen ==
The first thing that happens is that the ui.rc file of you application is merged in the KDE global [http://websvn.kde.org/trunk/KDE/kdelibs/kdeui/xmlgui/ui_standards.rc?view=markup ui_standards.rc]-file. This is to make sure that standard elements all end up in the KDE standard places.
+
The first thing that happens is that the ui.rc file of you application is merged in the KDE global [https://projects.kde.org/projects/kde/kdelibs/repository/revisions/master/entry/kdeui/xmlgui/ui_standards.rc?view=markup ui_standards.rc]-file. This is to make sure that standard elements all end up in the KDE standard places.
  
 
Now, when you add further KXMLGUIClients (e.g. a KPart, or a plugin), in general the KXMLGUIFactory does the following:
 
Now, when you add further KXMLGUIClients (e.g. a KPart, or a plugin), in general the KXMLGUIFactory does the following:
Line 34: Line 35:
  
 
Bringing the clients together in C++:
 
Bringing the clients together in C++:
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
MyMainWindow::MyMainWindow (...) : ... {
 
MyMainWindow::MyMainWindow (...) : ... {
 
[...]
 
[...]
Line 54: Line 55:
 
[...]
 
[...]
 
}
 
}
</code>
+
</syntaxhighlight>
  
 
main_ui.rc
 
main_ui.rc
<code xml>
+
<syntaxhighlight lang="xml">
 
<?xml version="1.0" encoding="UTF-8"?>
 
<?xml version="1.0" encoding="UTF-8"?>
 
<gui [...]>
 
<gui [...]>
Line 70: Line 71:
 
   </MenuBar>
 
   </MenuBar>
 
</gui>
 
</gui>
</code>
+
</syntaxhighlight>
  
 
component_a.rc
 
component_a.rc
<code xml>
+
<syntaxhighlight lang="xml">
 
<?xml version="1.0" encoding="UTF-8"?>
 
<?xml version="1.0" encoding="UTF-8"?>
 
<gui [...]>
 
<gui [...]>
Line 88: Line 89:
 
   </MenuBar>
 
   </MenuBar>
 
</gui>
 
</gui>
</code>
+
</syntaxhighlight>
  
 
component_b.rc
 
component_b.rc
<code xml>
+
<syntaxhighlight lang="xml">
 
<?xml version="1.0" encoding="UTF-8"?>
 
<?xml version="1.0" encoding="UTF-8"?>
 
<gui [...]>
 
<gui [...]>
Line 106: Line 107:
 
   </MenuBar>
 
   </MenuBar>
 
</gui>
 
</gui>
</code>
+
</syntaxhighlight>
  
 
Now all of these together would look like this:
 
Now all of these together would look like this:
 
{{Note|The XML-files are not really merged together at all, only the GUI itself is. But this is an easy way to show the effect of the combination of these KXMLGUIClients.}}
 
{{Note|The XML-files are not really merged together at all, only the GUI itself is. But this is an easy way to show the effect of the combination of these KXMLGUIClients.}}
  
<code xml>
+
<syntaxhighlight lang="xml">
 
<gui>
 
<gui>
 
   <MenuBar>
 
   <MenuBar>
Line 131: Line 132:
 
   </MenuBar>
 
   </MenuBar>
 
</gui>
 
</gui>
</code>
+
</syntaxhighlight>
  
 
Things to note:
 
Things to note:
 
* Actions from component_a.rc are placed below actions from main_ui.rc, because MyComponentA gets added after MyMainWindow. Similarily, actions from component_b.rc are placed below those from main_ui.rc and component_a.rc.
 
* Actions from component_a.rc are placed below actions from main_ui.rc, because MyComponentA gets added after MyMainWindow. Similarily, actions from component_b.rc are placed below those from main_ui.rc and component_a.rc.
 
* When component_a.rc is read, a menu named "menu2" already exist, and is therefore merged with the existing one. Due to this, "menu3" gets appended at the end of the menubar, instead of between "menu1" and "menu2". Similarily the order of the definition of the menus in component_b.rc is effectively ignored, because those menus have already been defined, previously.
 
* When component_a.rc is read, a menu named "menu2" already exist, and is therefore merged with the existing one. Due to this, "menu3" gets appended at the end of the menubar, instead of between "menu1" and "menu2". Similarily the order of the definition of the menus in component_b.rc is effectively ignored, because those menus have already been defined, previously.
 +
* {{Note|When using several clients in a single application, each should be given a unique name as specified using <syntaxhighlight lang="xml"><gui name="..."></syntaxhighlight>. Otherwise you will experience strange subtle problems.}}
  
 
== Elements to control merging ==
 
== Elements to control merging ==
 
Often times the above principles of merging are not good enough. Fortunately, there are several ways to control the details of merging.
 
Often times the above principles of merging are not good enough. Fortunately, there are several ways to control the details of merging.
  
=== MergeLocal / Merge ===
+
=== <Merge> ===
{{improve|I'm not sure the info in the Merge/MergeLocal-subchapter is correct. Use with care, and please correct and expand}}
+
The <Merge>-tag is the most basic element to control merging. It means that any actions from child clients will be inserted at this point, instead of at the end of the respective container. This is useful especially, if you want to make sure that one or more actions always remain at the end of a menu/toolbar. To achieve this, simply use a ui.rc-file like this:
==== <MergeLocal> ====
+
 
This can only be used in the KDE global [http://websvn.kde.org/trunk/KDE/kdelibs/kdeui/xmlgui/ui_standards.rc?view=markup ui_standards.rc]-file. It defines points where other entries can be merged in. It can be used both with a ''name''-attribute, or without (in the latter case it acts as a catch-all merge place).
+
<syntaxhighlight lang="xml">
 +
  <Menu name="my_menu"><text>&amp;Cool stuff</text>
 +
    <Action name="first_action"/>
 +
    <Merge/>
 +
    <Action name="last_action"/>
 +
  </Menu>
 +
</syntaxhighlight>
 +
 
 +
=== <MergeLocal> ===
 +
This can only be used in the KDE global [https://projects.kde.org/projects/kde/kdelibs/repository/revisions/master/entry/kdeui/xmlgui/ui_standards.rc?view=markup ui_standards.rc]-file. It defines points where other entries can be merged in. It can be used both with a ''name''-attribute, or without (in the latter case it acts as a catch-all merge place).
  
 
Then, in your RC file, you can add a certain action or menu to a given MergeLocal.
 
Then, in your RC file, you can add a certain action or menu to a given MergeLocal.
  
 
'''ui_standards.rc'''
 
'''ui_standards.rc'''
<code xml>
+
<syntaxhighlight lang="xml">
 
   <Menu name="settings"><text>&amp;Settings</text>
 
   <Menu name="settings"><text>&amp;Settings</text>
 
     ...
 
     ...
Line 156: Line 167:
 
     ...
 
     ...
 
   </Menu>
 
   </Menu>
</code>
+
</syntaxhighlight>
  
 
'''my_component.rc'''
 
'''my_component.rc'''
<code xml>
+
<syntaxhighlight lang="xml">
 
   <Menu name="settings"><text>&amp;Settings</text>
 
   <Menu name="settings"><text>&amp;Settings</text>
 
     <Action name="my_action" append="show_merge"/>
 
     <Action name="my_action" append="show_merge"/>
 
   </Menu>
 
   </Menu>
</code>
+
</syntaxhighlight>
  
 
This way, the action "my_action" will be appended to the Settings menu after the "Show Statusbar" action, not at the end of the menu.
 
This way, the action "my_action" will be appended to the Settings menu after the "Show Statusbar" action, not at the end of the menu.
Line 170: Line 181:
  
 
If you want to achieve the same result from a part or plugin, you can add a ''<DefineGroup>'' section in your main window's rc file that has the desired ''append'' attribute. Then, in your part/plugin rc file you create the Action with a ''group'' attribute.
 
If you want to achieve the same result from a part or plugin, you can add a ''<DefineGroup>'' section in your main window's rc file that has the desired ''append'' attribute. Then, in your part/plugin rc file you create the Action with a ''group'' attribute.
 
==== <Merge> ====
 
<Merge> is the counterpart to <MergeLocal> and defines what is to be merged into a <MergeLocal>-point. TODO: Is this correct, examples, with/without names?
 
  
 
=== DefineGroup ===
 
=== DefineGroup ===
 
While <MergeLocal> can only be used in the KDE global ui_standards.rc, <DefineGroup> allows you to achieve something very similar: In the main_ui.rc put this:
 
While <MergeLocal> can only be used in the KDE global ui_standards.rc, <DefineGroup> allows you to achieve something very similar: In the main_ui.rc put this:
  
<code xml>
+
<syntaxhighlight lang="xml">
 
   <Menu name="menu">
 
   <Menu name="menu">
 
     <Action name="main1"/>
 
     <Action name="main1"/>
Line 185: Line 193:
 
     <Action name="main3"/>
 
     <Action name="main3"/>
 
   </Menu>
 
   </Menu>
</code>
+
</syntaxhighlight>
  
 
and in a child clients component_a.rc:
 
and in a child clients component_a.rc:
  
<code xml>
+
<syntaxhighlight lang="xml">
 
   <Menu name="menu">
 
   <Menu name="menu">
 
     <Action name="a1"/>
 
     <Action name="a1"/>
Line 198: Line 206:
 
     <Action name="a6"/>
 
     <Action name="a6"/>
 
   </Menu>
 
   </Menu>
</code>
+
</syntaxhighlight>
  
 
This effectively gets merged as:
 
This effectively gets merged as:
<code xml>
+
<syntaxhighlight lang="xml">
 
   <Menu name="menu">
 
   <Menu name="menu">
 
     <Action name="main1"/>
 
     <Action name="main1"/>
Line 213: Line 221:
 
     <Action name="a6"/>
 
     <Action name="a6"/>
 
   </Menu>
 
   </Menu>
</code>
+
</syntaxhighlight>
  
 
Things to note:
 
Things to note:
Line 229: Line 237:
 
However, it is important to note, that limitations to merging apply in this case. Most importantly, a mergepoint <DefineGroup> can only be defined in the .rc-file, where the container is first defined (at least in kdelibs 4.2). E.g. consider you have the following main_ui.rc:
 
However, it is important to note, that limitations to merging apply in this case. Most importantly, a mergepoint <DefineGroup> can only be defined in the .rc-file, where the container is first defined (at least in kdelibs 4.2). E.g. consider you have the following main_ui.rc:
  
<code xml>
+
<syntaxhighlight lang="xml">
 
<Menu name="menu1">
 
<Menu name="menu1">
 
   <Action name="main1"/>
 
   <Action name="main1"/>
Line 235: Line 243:
 
   <Action name="main2"/>
 
   <Action name="main2"/>
 
</Menu>
 
</Menu>
</code>
+
</syntaxhighlight>
  
 
Now you insert a child with this ui.rc:
 
Now you insert a child with this ui.rc:
<code xml>
+
<syntaxhighlight lang="xml">
 
<Menu name="menu1"/>
 
<Menu name="menu1"/>
 
   <Action name="a1"/>
 
   <Action name="a1"/>
Line 249: Line 257:
 
   <Action name="a4"/>
 
   <Action name="a4"/>
 
</Menu>
 
</Menu>
</code>
+
</syntaxhighlight>
  
 
Finally you insert a second child with this ui.rc:
 
Finally you insert a second child with this ui.rc:
<code xml>
+
<syntaxhighlight lang="xml">
 
<Menu name="menu1"/>
 
<Menu name="menu1"/>
 
   <Action name="b1" group="maingroup"/>
 
   <Action name="b1" group="maingroup"/>
Line 260: Line 268:
 
   <Action name="b3" group="agroup2"/>
 
   <Action name="b3" group="agroup2"/>
 
</Menu>
 
</Menu>
</code>
+
</syntaxhighlight>
  
 
The result of this will be:
 
The result of this will be:
<code xml>
+
<syntaxhighlight lang="xml">
 
<Menu name="menu1"/>
 
<Menu name="menu1"/>
 
   <Action name="main1"/>
 
   <Action name="main1"/>
Line 277: Line 285:
 
   <Action name="a4"/>
 
   <Action name="a4"/>
 
</Menu>
 
</Menu>
</code>
+
</syntaxhighlight>
  
 
As you can see, group "agroup1" simply does not work, as it was defined inside a container that had previously been parsed. In contrast, "agroup2" works as expected, as "menu2" was not previously defined.
 
As you can see, group "agroup1" simply does not work, as it was defined inside a container that had previously been parsed. In contrast, "agroup2" works as expected, as "menu2" was not previously defined.
Line 289: Line 297:
 
* Some KParts offer different UI sets. For example {{class|KHTMLPart|kdelibs|4.x}} offers different GUI-profiles in its constructor. Watch out for that, it might be just the set of functionality that you want.
 
* Some KParts offer different UI sets. For example {{class|KHTMLPart|kdelibs|4.x}} offers different GUI-profiles in its constructor. Watch out for that, it might be just the set of functionality that you want.
 
* You can also ''not'' merge a part GUI into your application. Instead you can simply add the actions/menus you want to your main_ui.rc, and connect them like this:
 
* You can also ''not'' merge a part GUI into your application. Instead you can simply add the actions/menus you want to your main_ui.rc, and connect them like this:
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
SomePart *part = new SomePart ();
 
SomePart *part = new SomePart ();
 
KAction *part_action = part->action ("cool_action");
 
KAction *part_action = part->action ("cool_action");
 
KAction *proxy_action = actionCollection ()->addAction ("cool_action_proxy", part_action, SLOT(trigger()));
 
KAction *proxy_action = actionCollection ()->addAction ("cool_action_proxy", part_action, SLOT(trigger()));
 
proxy_action->setText (part_action->text ());
 
proxy_action->setText (part_action->text ());
</code>
+
</syntaxhighlight>
 
::Of course, this can become quite tedious.
 
::Of course, this can become quite tedious.
 
* Finally, you can choose the hacky way and manipulate the XMLGUI-definition of the part to remove some of the stuff you don't want:
 
* Finally, you can choose the hacky way and manipulate the XMLGUI-definition of the part to remove some of the stuff you don't want:
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
void removeNamedElementsRecursive (const QStringList &names, QDomNode &parent) {
 
void removeNamedElementsRecursive (const QStringList &names, QDomNode &parent) {
 
QDomNode nchild;
 
QDomNode nchild;
Line 374: Line 382:
 
removeContainers (my_part, QStringList () << "action_we_dont_want" << "menu_we_dont_need", true);
 
removeContainers (my_part, QStringList () << "action_we_dont_want" << "menu_we_dont_need", true);
 
moveContainers (my_part, "Menu", "fancy_toplevel_menu", "fancy_menu_a_bit_less_visible", true);
 
moveContainers (my_part, "Menu", "fancy_toplevel_menu", "fancy_menu_a_bit_less_visible", true);
</code>
+
</syntaxhighlight>
 
::'''Warning''': setXMLGUIBuildDocument() is (now) marked as for internal use, only. Unfortunately, setXMLFile() or setDOM() which would allow for a cleaner solution are not public in KXMLGUIClient.
 
::'''Warning''': setXMLGUIBuildDocument() is (now) marked as for internal use, only. Unfortunately, setXMLFile() or setDOM() which would allow for a cleaner solution are not public in KXMLGUIClient.
  
Line 381: Line 389:
 
Often you have sets of actions that you want to enable/disable according to some conditions. A very common example is enabling / disabling Cut/Copy-actions depending on whether there is a text-selection or not. This is easy to realize in plain C++-code, but KXMLGUI offers support for this in a very simple and clean way. Just put something like the following in your ui.rc:
 
Often you have sets of actions that you want to enable/disable according to some conditions. A very common example is enabling / disabling Cut/Copy-actions depending on whether there is a text-selection or not. This is easy to realize in plain C++-code, but KXMLGUI offers support for this in a very simple and clean way. Just put something like the following in your ui.rc:
  
<code xml>
+
<syntaxhighlight lang="xml">
 
  <State name="has_selection" >
 
  <State name="has_selection" >
 
   <enable>
 
   <enable>
Line 396: Line 404:
 
   </disable>
 
   </disable>
 
  </State>
 
  </State>
</code>
+
</syntaxhighlight>
  
 
Now all you need to do in your C++-code is call KXMLGUIClient::stateChanged(), whenever the selection has changed.
 
Now all you need to do in your C++-code is call KXMLGUIClient::stateChanged(), whenever the selection has changed.
Line 409: Line 417:
  
 
If you are developing a KPart for re-use in other applications, you will construct a KComponentData from your KAboutData. If the KXMLGUIClient/KPart you develop is only to be used inside your application, you probably want to set the following:
 
If you are developing a KPart for re-use in other applications, you will construct a KComponentData from your KAboutData. If the KXMLGUIClient/KPart you develop is only to be used inside your application, you probably want to set the following:
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
   setComponentData(KGlobal::mainComponent());
 
   setComponentData(KGlobal::mainComponent());
</code>
+
</syntaxhighlight>
  
 
== Lookup of .rc files / versioning ==
 
== Lookup of .rc files / versioning ==

Latest revision as of 14:42, 24 October 2013

noframe
 
This section needs improvements: Please help us to

cleanup confusing sections and fix sections which contain a todo


This entire page needs a thorough review.

These clarifications should be incorporated into this page, in particular regarding "append" in DefineGroup.

noframe
 
Note
I don't really feel overly qualified to write this page, but I've spent a whole lot of time figuring out some of the following things for lack of good documentation. So I'll make a start... --Tfry 14:02, 22 May 2009 (UTC)

Contents

[edit] Related information resources

[edit] Basics

For basic information, please start reading at Development/Tutorials/Using_KActions. For most simple cases you should be able to write a ui.rc for your application by simply expanding on the given examples.

The full XML schema definition for XMLGUI can be found here

[edit] Terminology

[edit] Container

In the context of KXMLGUI, something that contains KActions. A menu, popupmenu or toolbar.

[edit] Merging several KXMLGUI definitions

Once you have more than a single KXMLGUIClient/KPart in an application, it becomes important to control just how / just where menu entries and toolbar icons are merged.

[edit] How does merging happen

The first thing that happens is that the ui.rc file of you application is merged in the KDE global ui_standards.rc-file. This is to make sure that standard elements all end up in the KDE standard places.

Now, when you add further KXMLGUIClients (e.g. a KPart, or a plugin), in general the KXMLGUIFactory does the following:

  • So called containers ("Menu", "ToolBar") are matched by their name. So if your application_ui.rc defines a menu with name="my_stuff", and the plugin_ui.rc of a plugin also has a menu by that name, the elements from that menu in the plugin get added to the elements of that menu in the application.
  • All entries within a container are appended at the end, in the order they get added. This means, if you add KXMLGUIClient A after KXMLGUIClient B, the actions from A will generally end up after the actions from B, and vice versa.

Of course KXMLGUI allows for sophisticated ways to control this merging, and we'll deal with those in a minute, but first let us look at this basic scenario:

Bringing the clients together in C++:

MyMainWindow::MyMainWindow (...) : ... {
[...]
  setXMLFile ("main_ui.rc");
  insertChildClient (new MyComponentA ());
  insertChildClient (new MyComponentB ());
[...]
}
 
MyComponentA () : public KXMLGUIClient () {
[...]
  setXMLFile ("component_a.rc");
[...]
}
 
MyComponentB () : public KXMLGUIClient () {
[...]
  setXMLFile ("component_b.rc");
[...]
}

main_ui.rc

<?xml version="1.0" encoding="UTF-8"?>
<gui [...]>
  <MenuBar>
    <Menu name="menu1" >
      <Action name="main_first" />
      <Action name="main_second" />
      <Action name="main_third" />
    </Menu>
    <Menu name="menu2" >
    </Menu>
  </MenuBar>
</gui>

component_a.rc

<?xml version="1.0" encoding="UTF-8"?>
<gui [...]>
  <MenuBar>
    <Menu name="menu1" >
      <Action name="a_first" />
    </Menu>
    <Menu name="menu3" >
      <Action name="a_second" />
    </Menu>
    <Menu name="menu2" >
      <Action name="a_third" />
    </Menu>
  </MenuBar>
</gui>

component_b.rc

<?xml version="1.0" encoding="UTF-8"?>
<gui [...]>
  <MenuBar>
    <Menu name="menu3" >
      <Action name="b_first" />
    </Menu>
    <Menu name="menu2" >
      <Action name="b_second" />
    </Menu>
    <Menu name="menu1" >
      <Action name="b_third" />
    </Menu>
  </MenuBar>
</gui>

Now all of these together would look like this:

noframe
 
Note
The XML-files are not really merged together at all, only the GUI itself is. But this is an easy way to show the effect of the combination of these KXMLGUIClients.
<gui>
  <MenuBar>
    <Menu name="menu1" >
      <Action name="main_first" />
      <Action name="main_second" />
      <Action name="main_third" />
      <Action name="a_first" />
      <Action name="b_third" />
    </Menu>
    <Menu name="menu2" >
      <Action name="a_third" />
      <Action name="b_second" />
    </Menu>
    <Menu name="menu3" >
      <Action name="a_second" />
      <Action name="b_first" />
    </Menu>
  </MenuBar>
</gui>

Things to note:

  • Actions from component_a.rc are placed below actions from main_ui.rc, because MyComponentA gets added after MyMainWindow. Similarily, actions from component_b.rc are placed below those from main_ui.rc and component_a.rc.
  • When component_a.rc is read, a menu named "menu2" already exist, and is therefore merged with the existing one. Due to this, "menu3" gets appended at the end of the menubar, instead of between "menu1" and "menu2". Similarily the order of the definition of the menus in component_b.rc is effectively ignored, because those menus have already been defined, previously.
noframe
 
Note
When using several clients in a single application, each should be given a unique name as specified using
<gui name="...">
. Otherwise you will experience strange subtle problems.

[edit] Elements to control merging

Often times the above principles of merging are not good enough. Fortunately, there are several ways to control the details of merging.

[edit] <Merge>

The <Merge>-tag is the most basic element to control merging. It means that any actions from child clients will be inserted at this point, instead of at the end of the respective container. This is useful especially, if you want to make sure that one or more actions always remain at the end of a menu/toolbar. To achieve this, simply use a ui.rc-file like this:

  <Menu name="my_menu"><text>&amp;Cool stuff</text>
    <Action name="first_action"/>
    <Merge/>
    <Action name="last_action"/>
  </Menu>

[edit] <MergeLocal>

This can only be used in the KDE global ui_standards.rc-file. It defines points where other entries can be merged in. It can be used both with a name-attribute, or without (in the latter case it acts as a catch-all merge place).

Then, in your RC file, you can add a certain action or menu to a given MergeLocal.

ui_standards.rc

  <Menu name="settings"><text>&amp;Settings</text>
    ...
    <Action name="options_show_statusbar"/>
    <MergeLocal name="show_merge"/>
    <Separator/>
    ...
  </Menu>

my_component.rc

  <Menu name="settings"><text>&amp;Settings</text>
    <Action name="my_action" append="show_merge"/>
  </Menu>

This way, the action "my_action" will be appended to the Settings menu after the "Show Statusbar" action, not at the end of the menu.

However, the append attribute is only available for the mainwindow, not for parts and plugins.

If you want to achieve the same result from a part or plugin, you can add a <DefineGroup> section in your main window's rc file that has the desired append attribute. Then, in your part/plugin rc file you create the Action with a group attribute.

[edit] DefineGroup

While <MergeLocal> can only be used in the KDE global ui_standards.rc, <DefineGroup> allows you to achieve something very similar: In the main_ui.rc put this:

  <Menu name="menu">
    <Action name="main1"/>
    <DefineGroup name="main_group1" append="main_group1"/>
    <Action name="main2"/>
    <DefineGroup name="main_group2" append="main_group2"/>
    <Action name="main3"/>
  </Menu>

and in a child clients component_a.rc:

  <Menu name="menu">
    <Action name="a1"/>
    <Action name="a2" group="main_group2"/>
    <Action name="a3"/>
    <Action name="a4" group="main_group1"/>
    <Action name="a5" group="main_group1"/>
    <Action name="a6"/>
  </Menu>

This effectively gets merged as:

  <Menu name="menu">
    <Action name="main1"/>
    <Action name="a4"/>
    <Action name="a5"/>
    <Action name="main2"/>
    <Action name="a2"/>
    <Action name="main3"/>
    <Action name="a1"/>
    <Action name="a3"/>
    <Action name="a6"/>
  </Menu>

Things to note:

  • Actions a1, a3, and a6 are appended at the end of the menu, as this is the default behavior.
  • Actions a4, and a5 are inserted at the point where "main_group1" is defined, and Action a2 is inserted at the place of "main_group2".

[edit] ActionList

The <ActionList>-tag allows you to insert a group of actions at a particular point in the GUI at runtime. KXMLGUIClient::plugActionList() has good documentation on this.

[edit] Limitations of merging and workarounds

[edit] merging into children of a KXMLGUIClient

KXMLGUIClients can form a hierarchy of parent/child-clients, and any number of child-clients can simply be inserted into a KXMLGUIClient to extend its GUI. Use cases for this include having a part-enabled application, and one of the parts including further plugins. Also in some cases its useful to split the GUI of an application into several different ui.rc-files, so allow better reuse of code.

However, it is important to note, that limitations to merging apply in this case. Most importantly, a mergepoint <DefineGroup> can only be defined in the .rc-file, where the container is first defined (at least in kdelibs 4.2). E.g. consider you have the following main_ui.rc:

<Menu name="menu1">
  <Action name="main1"/>
  <DefineGroup name="maingroup" append="group"/>
  <Action name="main2"/>
</Menu>

Now you insert a child with this ui.rc:

<Menu name="menu1"/>
  <Action name="a1"/>
  <DefineGroup name="agroup1" append="agroup1"/>
  <Action name="a2"/>
</Menu>
<Menu name="menu2"/>
  <Action name="a3"/>
  <DefineGroup name="agroup2" append="agroup2"/>
  <Action name="a4"/>
</Menu>

Finally you insert a second child with this ui.rc:

<Menu name="menu1"/>
  <Action name="b1" group="maingroup"/>
  <Action name="b2" group="agroup1"/>
</Menu>
<Menu name="menu2"/>
  <Action name="b3" group="agroup2"/>
</Menu>

The result of this will be:

<Menu name="menu1"/>
  <Action name="main1"/>
  <Action name="b1"/>
  <Action name="main2"/>
  <Action name="a1"/>
  <Action name="a2"/>
  <Action name="b2"/>
</Menu>
<Menu name="menu2"/>
  <Action name="a3"/>
  <Action name="b3"/>
  <Action name="a4"/>
</Menu>

As you can see, group "agroup1" simply does not work, as it was defined inside a container that had previously been parsed. In contrast, "agroup2" works as expected, as "menu2" was not previously defined.

The solution in this case is to define all needed groups in the topmost ui.rc-file.

[edit] stripping down a KPart's GUI

KParts allow you to add a whole lot of powerful functionality with just a few lines of code. That's great, but sometimes a KPart GUI offers more features than you actually want and clutters your applications GUI with features that are not really useful in the context of your application.

In this case you have the following options:

  • Some KParts offer different UI sets. For example KHTMLPart offers different GUI-profiles in its constructor. Watch out for that, it might be just the set of functionality that you want.
  • You can also not merge a part GUI into your application. Instead you can simply add the actions/menus you want to your main_ui.rc, and connect them like this:
SomePart *part = new SomePart ();
KAction *part_action = part->action ("cool_action");
KAction *proxy_action = actionCollection ()->addAction ("cool_action_proxy", part_action, SLOT(trigger()));
proxy_action->setText (part_action->text ());
Of course, this can become quite tedious.
  • Finally, you can choose the hacky way and manipulate the XMLGUI-definition of the part to remove some of the stuff you don't want:
void removeNamedElementsRecursive (const QStringList &names, QDomNode &parent) {
	QDomNode nchild;
 
	for (QDomNode child = parent.firstChild (); !child.isNull (); child = nchild) {
		removeNamedElementsRecursive (names, child);
 
		nchild = child.nextSibling ();		// need to fetch next sibling here, as we might remove the child below
		if (child.isElement ()) {
			QDomElement e = child.toElement ();
			if (names.contains (e.attribute ("name"))) {
				parent.removeChild (child);
			}
		}
	}
}
 
void removeContainers (KXMLGUIClient *from, const QStringList &names, bool recursive) {
	QDomDocument doc = from->xmlguiBuildDocument ();
	if  (doc.documentElement ().isNull ()) doc = from->domDocument ();
 
	QDomElement e = doc.documentElement ();
	removeNamedElementsRecursive (names, e);
	from->setXMLGUIBuildDocument (doc);
 
	if (recursive) {
		QList<KXMLGUIClient*> children = from->childClients ();
		QList<KXMLGUIClient*>::const_iterator it;
		for (it = children.constBegin (); it != children.constEnd (); ++it) {
			removeContainers ((*it), names, true);
		}
	}
}
 
void moveContainer (KXMLGUIClient *client, const QString &tagname, const QString &name, const QString &to_name, bool recursive) {
	QDomDocument doc = client->xmlguiBuildDocument ();
	if  (doc.documentElement ().isNull ()) doc = client->domDocument ();
 
	// find the given elements
	QDomElement e = doc.documentElement ();
 
	QDomElement from_elem;
	QDomElement to_elem;
 
	QDomNodeList list = e.elementsByTagName (tagname);
	int count = list.count ();
	for (int i = 0; i < count; ++i) {
		QDomElement elem = list.item (i).toElement ();
		if (elem.isNull ()) continue;
		if (elem.attribute ("name") == name) {
			from_elem = elem;
		} else if (elem.attribute ("name") == to_name) {
			to_elem = elem;
		}
	}
 
	// move
	from_elem.parentNode ().removeChild (from_elem);
	to_elem.appendChild (from_elem);
 
	// set result
	client->setXMLGUIBuildDocument (doc);
 
	// recurse
	if (recursive) {
		QList<KXMLGUIClient*> children = client->childClients ();
		QList<KXMLGUIClient*>::const_iterator it;
		for (it = children.constBegin (); it != children.constEnd (); ++it) {
			moveContainer (*it, tagname, name, to_name, true);
		}
	}
}
 
[...]
 
removeContainers (my_part, QStringList () << "action_we_dont_want" << "menu_we_dont_need", true);
moveContainers (my_part, "Menu", "fancy_toplevel_menu", "fancy_menu_a_bit_less_visible", true);
Warning: setXMLGUIBuildDocument() is (now) marked as for internal use, only. Unfortunately, setXMLFile() or setDOM() which would allow for a cleaner solution are not public in KXMLGUIClient.

[edit] Further topics

[edit] States

Often you have sets of actions that you want to enable/disable according to some conditions. A very common example is enabling / disabling Cut/Copy-actions depending on whether there is a text-selection or not. This is easy to realize in plain C++-code, but KXMLGUI offers support for this in a very simple and clean way. Just put something like the following in your ui.rc:

 <State name="has_selection" >
  <enable>
   <Action name="edit_cut" />
   <Action name="edit_copy" />
   <Action name="invert_selection" />
  </enable>
 </State>
 <State name="has_no_selection" >
  <disable>
   <Action name="edit_cut" />
   <Action name="edit_copy" />
   <Action name="invert_selection" />
  </disable>
 </State>

Now all you need to do in your C++-code is call KXMLGUIClient::stateChanged(), whenever the selection has changed.

Notes:

  • A state can contain both a list of actions to enable and a list of actions to disable.
  • In the above example, the state "has_selection" is exactly the opposite to "has_no_selection" in terms of which actions to enable/disable. Strictly, in this case a single state "has_selection" is enough. For the no selection-case you would then call stateChanged ("has_selection", StateReverse).
  • A state-change affects only the client for which stateChanged() is called, not any of its siblings or children. If you want to change a state on several clients at once, you need to implement that yourself.

[edit] ComponentData

The componentData () (see KXMLGUIClient::setComponentData()) is used to differentiate between logical components. This affects, where configuration settings are stored for this KXMLGUIClient, and e.g. the KShortCutDialog uses this to group actions into different components.

If you are developing a KPart for re-use in other applications, you will construct a KComponentData from your KAboutData. If the KXMLGUIClient/KPart you develop is only to be used inside your application, you probably want to set the following:

  setComponentData(KGlobal::mainComponent());

[edit] Lookup of .rc files / versioning

Usually, you supply a relative filename to KXMLGUIClient::setXML(). KDE looks for this file in a number of places (KXMLGUIClient::findMostRecentXMLFile), including in the user's home directory (see --kde4-config --path data), which is the first entry in the path. This means, ui-files can actually be overridden by the user (and among other things, this is used to save user settings such as customized toolbars or shortcuts.

This is why it is important to always increase the version number or our ui.rc-file, when you make changes. This way you make sure, that your updated version is used, and not a previous version is picked up in the user's home directory. Note: The version must be an integer number, i.e. it may not contain dots or other non-digits like an application version number.


This page was last modified on 24 October 2013, at 14:42. This page has been accessed 14,712 times. Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2.