Gtk2::UIManager
In a large application, certain menus may become available depending on the outcome of other actions. A word processor program is a typical example. When you select a graphic, you will have different menus available than when you select text. A word processor usually feature toolbars and menus. This leaves you with two places to access certain menus, and they must be kept in sync.
Gtk+ features the Gtk2::UIManager
. This class makes managing the above scenario easy. This lesson will introduce the reader to Gtk2::UIManager
, and the Gtk+ classes involved when using it.
The Gtk2::UIManager
provides a way to create menus and toolbars from an XML-like description. The Gtk2::UIManager
uses Gtk2::ActionGroup
objects to manage the Gtk2::Action
objects providing the common substructure for the menu and toolbar items.
Using the Gtk2::UIManager
you can dynamically merge and demerge multiple UI descriptions and actions. This allows you to modify the menus and toolbars when the mode changes in the application (for example, changing from text editing to image editing), or when new plug-in features are added or removed from your application.
A UIManager can be used to create the menus and toolbars for an application user interface as follows:
Create a Gtk2::UIManager
instance.
Extract the Gtk2::AccelGroup
from the Gtk2::UIManager
and add it to the top level Gtk2::Window
Create the Gtk2::ActionGroup
instances and populate them with the appropriate Gtk2::Action
instances.
Add the Gtk2::ActionGroup
instances to the Gtk2::UIManager
in the order that the Gtk2::Action
instances should be found.
Add the UI XML descriptions to the Gtk2::UIManager
. Make sure that all Gtk2::Action
s referenced by the descriptions are available in the UIManager Gtk2::ActionGroup
instances.
Extract references to the menubar, menu and toolbar widgets by name for use in building the user interface.
Dynamically modify the user interface by adding and removing UI descriptions and by adding, rearranging and removing the associated Gtk2::ActionGroup
instances.
Gtk2::ActionGroup
and Gtk2::Action
The Gtk2::Action
and Gtk2::ActionGroup
objects work together to provide the images, text, callbacks and accelerators for your application menus and toolbars. The Gtk2::UIManager
uses Gtk2::Action
s and Gtk2::ActionGroup
s to build the menubars and toolbars automatically based on a XML specification.
Gtk2::Action
s An Gtk2::Action
object represents an action that the user can take using an application user interface. It contains information used by proxy UI elements (for example, Gtk2::MenuItem
s or Gtk2::Toolbar
items) to present the action to the user.There are two subclasses of Gtk2::Action
:
Table 18-1. Available Gtk2::Action
subclasses
Gtk2 Class Name | Use |
---|---|
Gtk2::ToggleAction | An Gtk2::Action that can be toggled between two states. |
Gtk2::RadioAction | An Gtk2::Action that can be grouped so that only one can be active. |
For example, the standard File->Quit menu item can be represented with an icon, mnemonic text and accelerator. When activated, the menu item triggers a callback that could exit the application. Likewise a Gtk2::Toolbar
Quit button could share the icon, mnemonic text and callback. Both of these UI elements could be proxies of the same Gtk2::Action
.
Although it is possible to create an instance of This is because we will use the |
Gtk2::ActionGroup
s Gtk2::Action
objects should be part of an Gtk2::ActionGroup
to provide common control over their visibility and sensitivity. For example, in a text processing application the menu items and toolbar buttons for specifying the text justification could be contained in an Gtk2::ActionGroup
. A user interface is expected to have multiple Gtk2::ActionGroup
objects that cover various aspects of the application. For example, global actions like creating new documents, opening and saving a document and quitting the application likely form one Gtk2::ActionGroup
while actions such as modifying the view of the document would form another.
The following sections will deal with ways to add the three action types to an Gtk2::ActionGroup
.
When we want to add actions, we need to declare an array first. This array will contain list elements. The data in those list elements will be used to create the actions in the Gtk2::ActionGroup
. In the following snippet of code we supply the data for two action items. Please see the comments on what each field of the list elements in the array represent.
my @actions_plain = ( # name, stock id, label, accelerator, tooltip, callback ["FileMenu", #name of the action. Must be specified. undef, #stock id for the action. Optional, default #value of undef if a label is specified. "_File", #label for the action.Optional, default #value of undef if a stock id is specified. undef, #accelerator for the action undef, #tooltip for the action. Optional, default value of undef. undef, #callback invoked when the action is activated. #Optional, default value of undef. ], #same as above, just in one line :) [ "New", 'gtk-new', "_New", "<control>N", "Create a new file", \&activate_action ], );
The order of items in the array does not matter, since the structure of where which item will eventually be displayed will be determined by the UI XML descriptions added to the The name field must be unique, and will be used in the XML descriptions to indicate which |
Gtk2::Action
instances, and add it to the Gtk2::ActionGroup
we use the 'add_actions'
method referencing our array of items. Optionally we can supply a second argument with user data.
$action_group->add_actions(\@actions_plain, undef);
Gtk2::ToggleAction
actions Adding a Gtk2::ToggleAction
instance is much the same as adding a Gtk2::Action
instance. There is just an extra field to be declared, which specify whether the toggle is active or not.
my @toggle_actions = ( ["Show_hidden", #name of the action. Must be specified. undef, #stock id for the action. Optional, default #value of undef if a label is specified. "_Show hidden files", #label for the action.Optional, default #value of undef if a stock id is specified. undef, #accelerator for the action undef, #tooltip for the action. Optional, default value of undef. undef, #callback invoked when the action is activated. #Optional, default value of undef. TRUE, #is active. Optional, default value is FALSE ], );To create the
Gtk2::ToggleAction
instances, and add it to the Gtk2::ActionGroup
we use the 'add_toggle_actions'
method referencing our array of items. Optionally we can supply a second argument with user data.
$action_group->add_toggle_actions(\@toggle_actions, undef);
Gtk2::RadioAction
actions The fields in the array used for adding Gtk2::RadioAction
instances is a bit different. We do not specify a "callback"
field, but rather a "value"
field. It is good practice to declare constants representing each "value"
, since it must be of type integer.
use constant COLOR_RED => 0; use constant COLOR_GREEN => 1; use constant COLOR_BLUE => 2; my @color_entries = ( # name, stock id, label, accelerator, tooltip, value [ "Red", undef, "_Red", "<control>R", "Blood", COLOR_RED ], [ "Green", undef, "_Green", "<control>G", "Grass", COLOR_GREEN ], [ "Blue", undef, "_Blue", "<control>B", "Sky", COLOR_BLUE ], );To create the
Gtk2::RadioAction
instances, and add it to the Gtk2::ActionGroup
we use the 'add_radio_actions'
method referencing our array of items. We also have to specify which Gtk2::RadioAction
must be currently active (giving one of the "value" entries from the declared array ), and can specify a callback sub with userdata.
$actions->add_radio_actions (\@color_entries, COLOR_RED, \&activate_radio_action);When the sub is called, it will be given the following values, which can then be used:
my ($action, $current) = @_;Both are instances of the selected
Gtk2::RadioAction
. To discover which one it is, use the "get_name"
method of Gtk2::Action
.
The Gtk2::ActionGroup
class has a methods to control the sensitivity and visibility of all the Gtk2::Action
s in it. They are the 'set_sensitive'
and 'set_visible'
methods respectively.
The above methods are also available on a "per action" instance. You can get actions from a Gtk2::ActionGroup
by using the 'get_action'
method. You may also want to remove actions by using the 'remove_action'
method. Consult the man page for more detail.
The UI descriptions accepted by Gtk2::UIManager
are simple XML definitions with the following elements:
Table 18-2. Available XML elements for the UI definition
XML element | Description |
---|---|
ui | The root element of a UI description. It can be omitted. Can contain menubar, popup, toolbar and accelerator elements. |
menubar | A top level element describing a Gtk2::MenuBar structure that can contain menuitem, separator, placeholder and menu elements. It has an optional name attribute. If name is not specified, "menubar" is used as the name. |
popup | A top level element describing a popup Gtk2::Menu structure that can contain menuitem, separator, placeholder, and menu elements. It has an optional name attribute. If name is not specified, "popup" is used as the name. |
toolbar | A top level element describing a Gtk2::Toolbar structure that can contain toolitem, separator and placeholder elements. It has an optional name attribute. If name is not specified, "toolbar" is used as the name. |
placeholder | An element identifying a position in a menubar, toolbar, popup or menu. A placeholder can contain menuitem, separator, placeholder, and menu elements. placeholder elements are used when merging UI descriptions to allow, for example, a menu to be built up from UI descriptions using common placeholder names. It has an optional name attribute. If name is not specified, "placeholder" is used as the name. |
menu | An element describing a Gtk2::Menu structure that can contain menuitem, separator, placeholder, and menu elements. A menu element has a required attribute action that names an Gtk2::Action object to be used to create the Gtk2::Menu . It also has optional name and position attributes. If name is not specified, the action name is used as the name. The position attribute can have either the value "top" or "bottom" with "bottom" the default if position is not specified. |
menuitem | An element describing a Gtk2::MenuItem . A menuitem element has a required attribute action that names an Gtk2::Action object to be used to create the Gtk2::MenuItem . It also has optional name and position attributes. If name is not specified, the action name is used as the name. The position attribute can have either the value "top" or "bottom" with "bottom" the default if position is not specified. |
toolitem | An element describing a toolbar Gtk2::ToolItem . A toolitem element has a required attribute action that names an Gtk2::Action object to be used to create the Gtk2::ToolItem . It also has optional name and position attributes. If name is not specified, the action name is used as the name. The position attribute can have either the value "top" or "bottom" with "bottom" the default if position is not specified. |
separator | An element describing a Gtk2::SeparatorMenuItem or a Gtk2::SeparatorToolItem as appropriate. |
accelerator | An element describing a keyboard accelerator. An accelerator element has a required attribute action that names an Gtk2::Action object that defines the accelerator key combination and is activated by the accelerator. It also has an optional name attribute. If name is not specified, the action name is used as the name.
|
Enough theory, lets see Gtk2::UIManager
in action. The sample program will imagine we have acquired a DVD writer, and our user interface needs to change the list of available optical media.
Table 18-3. Gtk2
object classes used
Gtk2 Class Name |
---|
Gtk2::Window |
Gtk2::VBox |
Gtk2::CheckButton |
Gtk2::HButtonBox |
Gtk2::UIManager |
Gtk2::ActionGroup |
Gtk2::Action |
Gtk2::MenuToolButton |
Gtk2::SeparatorToolItem |
The program can be found here: 'gtk2_uimanager.pl'
1 #! /usr/bin/perl -w 2 3 use strict; 4 use Gtk2 '-init'; 5 use Glib qw/TRUE FALSE/; 6 7 #=========================================================== 8 #Declare all the data first, to keep it away from the part 9 #containing the widgets. 10 11 #-------------------------- 12 my @entries = ( 13 #Fields for each action item: 14 #[name, stock_id, value, label, accelerator, tooltip, callback] 15 16 #file menus 17 [ "FileMenu",undef,"_File"], 18 [ "New", 'gtk-new', "_New", "<control>N", "Create a new file", \&activate_action ], 19 [ "Open", 'gtk-open', "_Open", "<control>O", "Open a file", \&activate_action ], 20 [ "Quit", 'gtk-quit', "_Quit", "<control>Q", "Quit", \&activate_action ], 21 22 #Media menus 23 [ "Media",undef,"_Media"], 24 [ "media_optical",'gtk-cdrom', "_Optical"], 25 26 ); 27 #-------------------------- 28 29 use constant CD_R_M => 0; 30 use constant CD_R_P => 1; 31 use constant CD_RW_M => 3; 32 use constant CD_RW_P => 4; 33 34 my @cd_types = ( 35 36 #Fields for each radio-action item: 37 #[name, stock_id, value, label, accelerator, tooltip, value] 38 39 [ "cd_r_m", undef, "CD-R", undef, "CD-R media", CD_R_M ], 40 [ "cd_r_p", undef, "CD+R", undef, "CD+R media", CD_R_P ], 41 [ "cd_rw_m", undef, "CD-RW", undef, "CD-RW media", CD_RW_M ], 42 [ "cd_rw_p", undef, "CD+RW", undef, "CD+RW media", CD_RW_P ], 43 44 ); 45 #-------------------------- 46 47 use constant DVD_R_M => 5; 48 use constant DVD_R_P => 6; 49 use constant DVD_RW_M => 7; 50 use constant DVD_RW_P => 8; 51 52 my @dvd_types = ( 53 54 [ "dvd_r_m", undef, "DVD-R", undef, "DVD-R media", DVD_R_M ], 55 [ "dvd_r_p", undef, "DVD+R", undef, "DVD+R media", DVD_R_P ], 56 [ "dvd_rw_m", undef, "DVD-RW", undef, "DVD-RW media", DVD_RW_M ], 57 [ "dvd_rw_p", undef, "DVD+RW", undef, "DVD+RW media", DVD_RW_P ], 58 59 ); 60 #-------------------------- 61 # 62 #=========================================================== 63 #Declare the two XML structures, one contains the basics 64 #the other one contains the elements for the DVD 65 66 my $ui_basic = "<ui> 67 <menubar name='MenuBar'> 68 <menu action='FileMenu'> 69 <menuitem action='New' position='top'/> 70 <menuitem action='Open' position='top'/> 71 <separator/> 72 <menuitem action='Quit'/> 73 </menu> 74 <menu action='Media'> 75 <menu action='media_optical'> 76 <menuitem action='cd_r_m'/> 77 <menuitem action='cd_r_p'/> 78 <menuitem action='cd_rw_m'/> 79 <menuitem action='cd_rw_p'/> 80 </menu> 81 </menu> 82 </menubar> 83 <toolbar name='Toolbar'> 84 <placeholder name='optical'> 85 <separator/> 86 <toolitem action='cd_r_m'/> 87 <toolitem action='cd_r_p'/> 88 <toolitem action='cd_rw_m'/> 89 <toolitem action='cd_rw_p'/> 90 </placeholder> 91 </toolbar> 92 </ui>"; 93 94 my $ui_dvd = "<ui> 95 <menubar name='MenuBar'> 96 <menu action='Media'> 97 <menu action='media_optical'> 98 <menuitem action='dvd_r_m'/> 99 <menuitem action='dvd_r_p'/> 100 <menuitem action='dvd_rw_m'/> 101 <menuitem action='dvd_rw_p'/> 102 </menu> 103 </menu> 104 </menubar> 105 <toolbar name='Toolbar'> 106 <placeholder name='optical'> 107 <toolitem action='dvd_r_m'/> 108 <toolitem action='dvd_r_p'/> 109 <toolitem action='dvd_rw_m'/> 110 <toolitem action='dvd_rw_p'/> 111 </placeholder> 112 </toolbar> 113 </ui>"; 114 #=========================================================== 115 116 #standard window creation, placement, and signal connecting 117 my $window = Gtk2::Window->new('toplevel'); 118 $window->signal_connect('delete_event' => sub { Gtk2->main_quit; }); 119 $window->set_border_width(5); 120 $window->set_position('center_always'); 121 122 #this vbox will return the bulk of the gui 123 my $vbox = &ret_vbox(); 124 125 #add and show the vbox 126 $window->add($vbox); 127 $window->show(); 128 129 #our main event-loop 130 Gtk2->main(); 131 132 sub ret_vbox { 133 134 #Flag indicating if the DVD was added or not 135 my $merge_flag = 0; 136 137 my $vbox = Gtk2::VBox->new(FALSE,5); 138 $vbox->set_size_request (600, 200); 139 140 #=========================================================== 141 #This part has to do with the Gtk2::UIManager: 142 143 # Create a Gtk2::UIManager instance 144 my $uimanager = Gtk2::UIManager->new; 145 146 #________ 147 #extract the accelgroup and add it to the window 148 my $accelgroup = $uimanager->get_accel_group; 149 $window->add_accel_group($accelgroup); 150 151 #________ 152 # Create the basic Gtk2::ActionGroup instance 153 #and fill it with Gtk2::Action instances 154 my $actions_basic = Gtk2::ActionGroup->new ("actions_basic"); 155 $actions_basic->add_actions (\@entries, undef); 156 157 # Add the actiongroup to the uimanager 158 $uimanager->insert_action_group($actions_basic,0); 159 160 #________ 161 # Create the CD Gtk2::ActionGroup instance 162 #and fill it with Gtk2::RadioAction instances 163 my $actions_media_cd = Gtk2::ActionGroup->new ("media_cd"); 164 $actions_media_cd->add_radio_actions(\@cd_types,CD_R_M,undef); 165 166 #get the group, this will be needed when we add radio actions 167 my $group = ($actions_media_cd->get_action('cd_r_m'))->get_group; 168 169 # Add the actiongroup to the uimanager 170 $uimanager->insert_action_group($actions_media_cd,0); 171 172 #________ 173 # Create the DVD Gtk2::ActionGroup instance 174 #and fill it with Gtk2::RadioAction instances 175 my $actions_media_dvd = Gtk2::ActionGroup->new ("media_dvd"); 176 $actions_media_dvd->add_radio_actions(\@dvd_types,CD_R_M,undef); 177 178 #run through the DVD types list, getting each action, 179 #and set its group ans state 180 foreach my $name (@dvd_types){ 181 182 my $action = $actions_media_dvd->get_action(${$name}[0]); 183 #here we use the group :) 184 $action->set_group($group); 185 #need to specify, else we will have TWO ACTIVE radio buttons 186 $action->set_active(FALSE); 187 } 188 189 # Add the actiongroup to the uimanager 190 $uimanager->insert_action_group($actions_media_dvd,0); 191 192 #________ 193 #add the basic XML description of the GUI 194 $uimanager->add_ui_from_string ($ui_basic); 195 196 #________ 197 #extract the menubar 198 my $menubar = $uimanager->get_widget('/MenuBar'); 199 200 $vbox->pack_start($menubar,FALSE,FALSE,0); 201 202 #________ 203 #extract the toolbar 204 my $toolbar = $uimanager->get_widget('/Toolbar'); 205 $toolbar->set_show_arrow(FALSE); 206 207 #Create a menu tool button and set its menu to the opticals. 208 my $t_mbtn_optical = Gtk2::MenuToolButton->new_from_stock('gtk-cdrom'); 209 $t_mbtn_optical->set_label('Optical'); 210 211 #the UIManager created an Gtk2::ImageMenuItem, we extract it and 212 #use its 'get_submenu' to get its attached menu widget :) 213 my $menu_item = $uimanager->get_widget('/MenuBar/Media/media_optical'); 214 $t_mbtn_optical->set_menu ($menu_item->get_submenu); 215 216 $toolbar->insert($t_mbtn_optical,0); 217 $toolbar->insert(Gtk2::SeparatorToolItem->new,1); 218 #=========================================================== 219 220 $vbox->pack_start($toolbar,FALSE,FALSE,0); 221 222 my $h_bb = Gtk2::HButtonBox->new; 223 $h_bb->set_layout('edge'); 224 225 my $cb_sensitive = Gtk2::CheckButton->new('CD Sensitive'); 226 $cb_sensitive->set_active(TRUE); 227 $cb_sensitive->signal_connect('toggled' =>sub{ 228 #sets the sensitivity of the CD menus 229 $actions_media_cd->set_sensitive($cb_sensitive->get_active); 230 }); 231 232 $h_bb->pack_start_defaults($cb_sensitive); 233 234 my $cb_visible = Gtk2::CheckButton->new('CD Visible'); 235 $cb_visible->set_active(TRUE); 236 $cb_visible->signal_connect('toggled' =>sub{ 237 #set the visibility of the CD menus 238 $actions_media_cd->set_visible($cb_visible->get_active); 239 }); 240 241 $h_bb->pack_start_defaults($cb_visible); 242 243 my $cb_dvd = Gtk2::CheckButton->new('Add a DVD Writer'); 244 $cb_dvd->signal_connect('toggled' =>sub { 245 #this will add or remove the DVD ui descriptions 246 #it returns a number greater than 0 if successful 247 if ($merge_flag){ 248 $uimanager->remove_ui($merge_flag); 249 $merge_flag = 0; 250 }else{ 251 $merge_flag = $uimanager->add_ui_from_string ($ui_dvd); 252 } 253 }); 254 255 $h_bb->pack_start_defaults($cb_dvd); 256 257 $vbox->pack_end($h_bb,FALSE,FALSE,0); 258 $vbox->show_all(); 259 return $vbox; 260 }
It is good practice to keep the data separate from the rest of the code. In our program we start of declaring data that will be used to generate the Gtk2::Action
instances in various Gtk2::ActionGroup
s later in the program.
We also declare two XML structures which will be used by our program. A basic one and one that will merge with the basic one when we simulate the addition of a DVD Writer.
When two XML structures merge, the one added last's elements will be added as siblings to the first one's elements. You can force position and grouping of widgets by making use of the position attribute, as well as the placeholder element.
The position attribute will normally be used to keep certain Gtk2::MenuItem
s at the top, or buttom. The placeholder element can be used to group together Gtk2::RadioToolButton
items in a Gtk2::Toolbar
.
The widget hierarchy created using a UI description is very similar to the XML element hierarchy except that placeholder elements are merged into their parents.
A widget in the hierarchy created by a UI description can be accessed using its path which is composed of the name of the widget element and its ancestor elements joined by slash ("/") characters.
This is used as a argument to the get_widget
method of Gtk2::UIManager
:
211 #the UIManager created an Gtk2::ImageMenuItem, we extract it and 212 #use its 'get_submenu' to get its attached menu widget :) 213 my $menu_item = $uimanager->get_widget('/MenuBar/Media/media_optical'); 214 $t_mbtn_optical->set_menu ($menu_item->get_submenu);
Remember to include placeholder elements in the path! We use the XML element hierarchy, and NOT the widget hierarchy as a path for the |
Remember to extract the group of a Gtk2::RadioAction
instance if you later want to append items to that group of Gtk2::RadioAction
's.
166 #get the group, this will be needed when we add radio actions 167 my $group = ($actions_media_cd->get_action('cd_r_m'))->get_group;Make sure when you append to set the
active
attribute of each new addition to FALSE, else you can end up with a set of radio buttons containing two active ones at the same time!
178 #run through the DVD types list, getting each action, 179 #and set its group ans state 180 foreach my $name (@dvd_types){ 181 182 my $action = $actions_media_dvd->get_action(${$name}[0]); 183 #here we use the group :) 184 $action->set_group($group); 185 #need to specify, else we will have TWO ACTIVE radio buttons 186 $action->set_active(FALSE); 187 }
You can disable the option to the user to select certain items in the gui using various ways. In the sample program we used three different ways. ( Visibility, sensitivity, and the merging / removing of XML UI descriptions.) When to use which method is for the programmer to decide.
Methods used to add a XML UI description returns an integer value. This value can be used again when you want to remove the XML UI description.
247 if ($merge_flag){ 248 $uimanager->remove_ui($merge_flag); 249 $merge_flag = 0; 250 }else{ 251 $merge_flag = $uimanager->add_ui_from_string ($ui_dvd); 252 }
The widgets that you extract from the Gtk2::UIManager
instance is standard, and can be used in any normal way. In the demo program, we added a Gtk2::MenuToolButton
and Gtk2::SeparatorToolItem
to it.