Development/Architecture/KDE4/XMLGUI Technology: Difference between revisions

    From KDE TechBase
    (continue)
    Line 14: Line 14:
    = 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.
    = Terminology =
    == Container ==
    Something that contains KActions. A menu, popupmenu or toolbar.


    = Merging several KXMLGUI definitions =
    = Merging several KXMLGUI definitions =
    Line 188: Line 192:


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


    == Limitations of merging and workarounds ==
    == Limitations of merging and workarounds ==


    === merging of grand-children ===
    === merging into children of a KXMLGUIClient ===
    (e.g. a plugin inside a part inside a main window)
    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:
     
    <code xml>
    <Menu name="menu1">
      <Action name="main1"/>
      <DefineGroup name="maingroup" append="group"/>
      <Action name="main2"/>
    </Menu>
    </code>
     
    Now you insert a child with this ui.rc:
    <code xml>
    <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>
    </code>
     
    Finally you insert a second child with this ui.rc:
    <code xml>
    <Menu name="menu1"/>
      <Action name="b1" group="maingroup"/>
      <Action name="b2" group="agroup1"/>
    </Menu>
    <Menu name="menu2"/>
      <Action name="b3" group="agroup2"/>
    </Menu>
    </code>
     
    The result of this will be:
    <code xml>
    <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>
    </code>
     
    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.


    === stripping down a KPart's GUI ===
    === stripping down a KPart's GUI ===
    (hack to (re-)move unwanted elements)


    = Further topics =
    = Further topics =
    == Lookup of .rc files / versioning ==
    == Lookup of .rc files / versioning ==
    == ActionProperties ==
    == ActionProperties ==

    Revision as of 19:26, 24 May 2009

    noframe
    noframe
     
    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)


    Related information resources

    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.

    Terminology

    Container

    Something that contains KActions. A menu, popupmenu or toolbar.

    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.

    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:

    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.

    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.

    MergeLocal / Merge

    Note
    I'm not sure the info in this sub-chapter is correct. Use with care, and please correct, if needed.

    <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).

    <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

    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".

    ActionList

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

    Limitations of merging and workarounds

    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.

    stripping down a KPart's GUI

    Further topics

    Lookup of .rc files / versioning

    ActionProperties