(→States) |
(→States) |
||
Line 375: | Line 375: | ||
* A state can contain both a list of actions to enable ''and'' a list of actions to disable. | * 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)''. | * 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. | ||
== Lookup of .rc files / versioning == | == Lookup of .rc files / versioning == |
Under Construction |
---|
This page is under construction. This page is actively being developed and updated with new information, and may be incomplete. You can help by editing this page |
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) |
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.
In the context of KXMLGUI, something that contains KActions. A menu, popupmenu or toolbar.
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.
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:
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:
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:
Often times the above principles of merging are not good enough. Fortunately, there are several ways to control the details of merging.
Note |
---|
I'm not sure the info in the Merge/MergeLocal-subchapter is correct. Use with care, and please correct and expand |
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).
<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?
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:
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.
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.
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:
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 ());
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);
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:
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.