Chapter 16. New Widgets in PyGTK 2.4

Table of Contents

16.1. The Action and ActionGroup Objects
16.1.1. Actions
16.1.2. ActionGroups
16.2. ComboBox and ComboBoxEntry Widgets
16.2.1. ComboBox Widgets
16.2.2. ComboBoxEntry Widgets
16.3. ColorButton and FontButton Widgets
16.3.1. ColorButton Widgets
16.3.2. FontButton Widgets
16.4. EntryCompletion Objects
16.5. Expander Widgets
16.6. File Selections using FileChooser-based Widgets
16.7. The UIManager
16.7.1. Overview
16.7.2. Creating a UIManager
16.7.3. Adding and Removing ActionGroups
16.7.4. UI Descriptions
16.7.5. Adding and Removing UI Descriptions
16.7.6. Accessing UI Widgets
16.7.7. A Simple UIManager Example
16.7.8. Merging UI Descriptions
16.7.9. UIManager Signals

Quite a few new widgets and support objects were added in PyGTK 2.4 including:

16.1. The Action and ActionGroup Objects

The Action and ActionGroup objects work together to provide the images, text, callbacks and accelerators for your application menus and toolbars. The UIManager uses Actions and ActionGroups to build the menubars and toolbars automatically based on a XML specification. It's much easier to create and populate menus and toolbars using the UIManager described in a later section. The following sections on the Action and ActionGroup objects describe how to directly apply these objects but I recommend using the UIManager whenever possible.

16.1.1. Actions

An 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, MenuItems or Toolbar items) to present the action to the user. There are two subclasses of Action:

ToggleActionAn Action that can be toggled between two states.
RadioActionAn 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 Toolbar Quit button could share the icon, mnemonic text and callback. Both of these UI elements could be proxies of the same Action.

Ordinary Button, ToggleButton and RadioButton widgets can also act as proxies for an Action though there is no support for these in the UIManager.

16.1.1.1. Creating Actions

An Action can be created using the constructor:

  action = gtk.Action(name, label, tooltip, stock_id)

name is a string used to identify the Action in an ActionGroup or in a UIManager specification. label and tooltip are strings used as the label and tooltip in proxy widgets. If label is None then the stock_id must be a string specifying a Stock Item to get the label from. If tooltip is None the Action will not have a tooltip.

As we'll see in Section 16.1.2, “ActionGroups” it's much easier to create Action objects using the ActionGroup convenience methods:

  actiongroup.add_actions(entries, user_data=None)
  actiongroup.add_toggle_actions(entries, user_data=None)
  actiongroup.add_radio_actions(entries, value=0, on_change=None, user_data=None)

More about these later but first I'll describe how to use an Action with a Button to illustrate the basic operations of connecting an Action to a proxy widget.

16.1.1.2. Using Actions

The basic procedure for using an Action with a Button proxy is illustrated by the simpleaction.py example program. The Button is connected to the Action using the method:

  action.connect_proxy(proxy)

where proxy is a MenuItem, ToolItem or Button widget.

An Action has one signal the "activate" signal that is triggered when the Action is activated usually as the result of a proxy widget being activated (for example a ToolButton is clicked). You just have connect a callback to this signal to handle the activation of any of the proxy widgets.

The source code for the simpleaction.py example program is:

    1   #!/usr/bin/env python
    2
    3   import pygtk
    4   pygtk.require('2.0')
    5   import gtk
    6
    7   class SimpleAction:
    8       def __init__(self):
    9           # Create the toplevel window
   10           window = gtk.Window()
   11           window.set_size_request(70, 30)
   12           window.connect('destroy', lambda w: gtk.main_quit())
   13
   14           # Create an accelerator group
   15           accelgroup = gtk.AccelGroup()
   16           # Add the accelerator group to the toplevel window
   17           window.add_accel_group(accelgroup)
   18
   19           # Create an action for quitting the program using a stock item
   20           action = gtk.Action('Quit', None, None, gtk.STOCK_QUIT)
   21           # Connect a callback to the action
   22           action.connect('activate', self.quit_cb)
   23
   24           # Create an ActionGroup named SimpleAction
   25           actiongroup = gtk.ActionGroup('SimpleAction')
   26           # Add the action to the actiongroup with an accelerator
   27           # None means use the stock item accelerator
   28           actiongroup.add_action_with_accel(action, None)
   29
   30           # Have the action use accelgroup
   31           action.set_accel_group(accelgroup)
   32
   33           # Connect the accelerator to the action
   34           action.connect_accelerator()
   35
   36           # Create the button to use as the action proxy widget
   37           quitbutton = gtk.Button()
   38           # add it to the window
   39           window.add(quitbutton)
   40
   41           # Connect the action to its proxy widget
   42           action.connect_proxy(quitbutton)
   43
   44           window.show_all()
   45           return
   46
   47       def quit_cb(self, b):
   48           print 'Quitting program'
   49           gtk.main_quit()
   50
   51   if __name__ == '__main__':
   52       sa = SimpleAction()
   53       gtk.main()

The example creates an Action (line 20) that uses a Stock Item to provide the label text with mnemonic, icon, accelerator and translation domain. If a Stock Item is not used you'll need to specify a label instead. Line 22 connects the "activate" signal of action to the self.quit_cb() method so that it is invoked when the Action is activated by quitbutton. Line 42 connects quitbutton to action as a proxy widget. When quitbutton is clicked it will activate action and thereby invoke the self.quit_cb() method. The simpleaction.py example uses quite a bit of code (lines 15, 17, 31 and 34 to setup the accelerator for the Button. The procedure is similar for MenuItems and Toolbar ToolItems.

Figure 16.1, “Simple Action Example” shows the simpleaction.py example in operation.

Figure 16.1. Simple Action Example

Simple Action Example

16.1.1.3. Creating Proxy Widgets

In the previous section we saw that an existing widget could be connected to an Action as a proxy. In this section we'll see how a proxy widget can be created using the Action methods:

  menuitem = action.create_menu_item()

  toolitem = action.create_tool_item()

The basicaction.py example illustrates a MenuItem, ToolButton and a Button sharing an Action. The MenuItem and the ToolButton are created using the above methods. The basicaction.py example program source code is:

    1   #!/usr/bin/env python
    2
    3   import pygtk
    4   pygtk.require('2.0')
    5   import gtk
    6
    7   class BasicAction:
    8       def __init__(self):
    9           # Create the toplevel window
   10           window = gtk.Window()
   11           window.connect('destroy', lambda w: gtk.main_quit())
   12           vbox = gtk.VBox()
   13           vbox.show()
   14           window.add(vbox)
   15
   16           # Create an accelerator group
   17           accelgroup = gtk.AccelGroup()
   18           # Add the accelerator group to the toplevel window
   19           window.add_accel_group(accelgroup)
   20
   21           # Create an action for quitting the program using a stock item
   22           action = gtk.Action('Quit', '_Quit me!', 'Quit the Program',
   23                               gtk.STOCK_QUIT)
   24           action.set_property('short-label', '_Quit')
   25           # Connect a callback to the action
   26           action.connect('activate', self.quit_cb)
   27
   28           # Create an ActionGroup named BasicAction
   29           actiongroup = gtk.ActionGroup('BasicAction')
   30           # Add the action to the actiongroup with an accelerator
   31           # None means use the stock item accelerator
   32           actiongroup.add_action_with_accel(action, None)
   33
   34           # Have the action use accelgroup
   35           action.set_accel_group(accelgroup)
   36
   37           # Create a MenuBar
   38           menubar = gtk.MenuBar()
   39           menubar.show()
   40           vbox.pack_start(menubar, False)
   41
   42           # Create the File Action and MenuItem
   43           file_action = gtk.Action('File', '_File', None, None)
   44           actiongroup.add_action(file_action)
   45           file_menuitem = file_action.create_menu_item()
   46           menubar.append(file_menuitem)
   47
   48           # Create the File Menu
   49           file_menu = gtk.Menu()
   50           file_menuitem.set_submenu(file_menu)
   51
   52           # Create a proxy MenuItem
   53           menuitem = action.create_menu_item()
   54           file_menu.append(menuitem)
   55
   56           # Create a Toolbar
   57           toolbar = gtk.Toolbar()
   58           toolbar.show()
   59           vbox.pack_start(toolbar, False)
   60
   61           # Create a proxy ToolItem
   62           toolitem = action.create_tool_item()
   63           toolbar.insert(toolitem, 0)
   64
   65           # Create and pack a Label
   66           label = gtk.Label('''
   67   Select File->Quit me! or
   68   click the toolbar Quit button or
   69   click the Quit button below or
   70   press Control+q
   71   to quit.
   72   ''')
   73           label.show()
   74           vbox.pack_start(label)
   75
   76           # Create a button to use as another proxy widget
   77           quitbutton = gtk.Button()
   78           # add it to the window
   79           vbox.pack_start(quitbutton, False)
   80
   81           # Connect the action to its proxy widget
   82           action.connect_proxy(quitbutton)
   83           # Have to set tooltip after toolitem is added to toolbar
   84           action.set_property('tooltip', action.get_property('tooltip'))
   85           tooltips = gtk.Tooltips()
   86           tooltips.set_tip(quitbutton, action.get_property('tooltip'))
   87
   88           window.show()
   89           return
   90
   91       def quit_cb(self, b):
   92           print 'Quitting program'
   93           gtk.main_quit()
   94
   95   if __name__ == '__main__':
   96       ba = BasicAction()
   97       gtk.main()

This example introduces an ActionGroup to hold the Actions used in the program. Section 16.1.2, “ActionGroups” will go into more detail on the use of ActionGroups.

The code in lines 9-14 sets up a top level window containing a VBox. Lines 16-35 set up the "Quit" Action similar to that in the simpleaction.py example program and add it with the gtk.STOCK_QUIT Stock Item accelerator (line 32) to the "BasicAction" ActionGroup (created in line 29). Note that, unlike the simpleaction.py example program, you don't have to call the connect_accelerator() method for the action since it is called automatically when the create_menu_item() method is called in line 53.

Lines 38-40 create a MenuBar and pack it into the VBox. Lines 43-44 create an Action (file_action) for the File menu and add it to actiongroup. The File and Quit menu items are created in lines 45 and 53 and added to menubar and file_menu respectively in lines 46 and 54.

Likewise a Toolbar is created and added to the VBox in lines 57-59. The proxy ToolItem is created and added to toolbar in lines 62-63. Note the Action tooltip must be set (line 84) after the ToolItem is added to the Toolbar for it to be used. Also the Button tooltip must be added manually (lines 84-86).

Figure 16.2, “Basic Action Example” displays the basicaction.py example program in operation:

Figure 16.2. Basic Action Example

Basic Action Example

A proxy widget can be disconnected from an Action by using the method:

  action.disconnect_proxy(proxy)

16.1.1.4. Action Properties

An Action has a number of properties that control the display and function of its proxy widgets. The most important of these are the "sensitive" and "visible" properties. The "sensitive" property determines the sensitivity of the proxy widgets. If "sensitive" is FALSE the proxy widgets are not activatable and will usually be displayed "grayed out". Likewise, the "visible" property determines whether the proxy widgets will be visible. If an Action's "visible" property is FALSE its proxy widgets will be hidden.

As we'll see in the next section, an Action's sensitivity or visibility is also controlled by the sensitivity or visibility of the ActionGroup it belongs to. Therefore, for an Action to be sensitive (or visible) both it and its ActionGroup must be sensitive (or visible). To determine the effective sensitivity or visibility of an Action you should use the following methods:

  result = action.is_sensitive()

  result = action.is_visible()

The name assigned to an Action is contained in its "name" property which is set when the Action is created. You can retrieve that name using the method:

  name = action.get_name()

Other properties that control the display of the proxy widgets of an Action include:

"hide-if-empty"If TRUE, empty menu proxies for this action are hidden.
"is-important"If TRUE, ToolItem proxies for this action show text in gtk.TOOLBAR_BOTH_HORIZ mode.
"visible-horizontal"If TRUE, the ToolItem is visible when the toolbar is in a horizontal orientation.
"visible-vertical"If TRUE, the ToolItem is visible when the toolbar is in a vertical orientation.

Other properties of interest include:

"label"The label used for menu items and buttons that activate this action.
"short-label"A shorter label that may be used on toolbar buttons and buttons.
"stock-id"The Stock Item to be used to retrieve the icon, label and accelerator to be used in widgets representing this action.
"tooltip"A tooltip for this action.

Note that the basicaction.py example program overrides the gtk.STOCK_QUIT label with "_Quit me!" and sets the "short-label" property to "_Quit". The short label is used for the ToolButton and the Button labels but the full label is used for the MenuItem label. Also note that the tooltip cannot be set on a ToolItem until it is added to a Toolbar.

16.1.1.5. Actions and Accelerators

An Action has three methods that are used to set up an accelerator:

  action.set_accel_group(accel_group)

  action.set_accel_path(accel_path)

  action.connect_accelerator()

These, in conjunction with the gtk.ActionGroup.add_action_with_accel() method, should cover most cases of accelerator set up.

An AccelGroup must always be set for an Action. The set_accel_path() method is called by the gtk.ActionGroup.add_action_with_accel() method. If set_accel_path() is used the accelerator path should match the default format: "<Actions>/actiongroup_name/action_name". Finally, the connect_accelerator() method is called to complete the accelerator set up.

Note

An Action must have an AccelGroup and an accelerator path associated with it before connect_accelerator() is called.

Since the connect_accelerator() method can be called several times (i.e. once for each proxy widget), the number of calls is counted so that an equal number of disconnect_accelerator() calls must be made before removing the accelerator.

As illustrated in the previous example programs, an Action accelerator can be used by all the proxy widgets. An Action should be part of an ActionGroup in order to use the default accelerator path that has the format: "<Actions>/actiongroup_name/action_name". The easiest way to add an accelerator is to use the gtk.ActionGroup.add_action_with_accel() method and the following general procedure:

  • Create an AccelGroup and add it to the top level window.
  • Create a new ActionGroup
  • Create an Action specifying a Stock Item with an accelerator.
  • Add the Action to the ActionGroup using the gtk.ActionGroup.add_action_with_accel() method specifying None to use the Stock Item accelerator or an accelerator string acceptable to gtk.accelerator_parse().
  • Set the AccelGroup for the Action using the gtk.Action.set_accel_group() method.
  • Complete the accelerator set up using the gtk.Action.connect_accelerator() method.

Any proxy widgets created by or connected to the Action will use the accelerator.

16.1.1.6. Toggle Actions

As mentioned previously a ToggleAction is a subclass of Action that can be toggled between two states. The constructor for a ToggleAction takes the same parameters as an Action:

  toggleaction = gtk.ToggleAction(name, label, tooltip, stock_id)

In addition to the Action methods the following ToggleAction methods:

  toggleaction.set_active(is_active)
  is_active = toggleaction.get_active()

set and get the current state of toggleaction. is_active is a boolean value.

You can connect to the "toggled" signal specifying a callback with the signature:

  def toggled_cb(toggleaction, user_data)

The "toggled" signal is emitted when the ToggleAction changes state.

A MenuItem proxy widget of a ToggleAction will be displayed like a CheckMenuItem by default. To have the proxy MenuItem displayed like a RadioMenuItem set the "draw-as-radio" property to TRUE using the method:

  toggleaction.set_draw_as_radio(draw_as_radio)

You can use the following method to determine whether the ToggleAction MenuItems will be displayed like RadioMenuItems:

  draw_as_radio = toggleaction.get_draw_as_radio()

16.1.1.7. Radio Actions

A RadioAction is a subclass of ToggleAction that can be grouped so that only one RadioAction is active at a time. The corresponding proxy widgets are the RadioMenuItem and RadioToolButton.

The constructor for a RadioAction takes the same arguments as an Action with the addition of a unique integer value that is used to identify the active RadioAction in a group:

  radioaction = gtk.RadioAction(name, label, tooltip, stock_id, value)

The group for a RadioAction can be set using the method:

  radioaction.set_group(group)

where group is another RadioAction that radioaction should be grouped with. The group containing a RadioAction can be retrieved using the method:

  group = radioaction.get_group()

that returns a list of the group of RadioAction objects that includes radioaction.

The value of the currently active group member can retrieved using the method:

  active_value = radioaction.get_current_value()

You can connect a callback to the "changed" signal to be notified when the active member of the RadioAction group has been changed. Note that you only have to connect to one of the RadioAction objects to track changes. The callback signature is:

  def changed_cb(radioaction, current, user_data)

where current is the currently active RadioAction in the group.

16.1.1.8. An Actions Example

The actions.py example program illustrates the use of the Action, ToggleAction and RadioAction objects. Figure 16.3, “Actions Example” displays the example program in operation:

Figure 16.3. Actions Example

Actions Example

This example is similar enough to the basicaction.py example program that a detailed description is not necessary.

16.1.2. ActionGroups

As mentioned in the previous section, related Action objects should be added to an 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 ActionGroup. A user interface is expected to have multiple 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 ActionGroup while actions such as modifying the view of the document would form another.

16.1.2.1. Creating ActionGroups

An ActionGroup is created using the constructor:

  actiongroup = gtk.ActionGroup(name)

where name is a unique name for the ActionGroup. The name should be unique because it is used to form the default accelerator path for its Action objects.

The ActionGroup name can be retrieved using the method:

  name = actiongroup.get_name()

or by retrieving the contents of the "name" property.

16.1.2.2. Adding Actions

As illustrated in Section 16.1.1, “Actions” an existing Action can be added to an ActionGroup using one of the methods:

  actiongroup.add_action(action)

  actiongroup.add_action_with_accel(action, accelerator)

where action is the Action to be added and accelerator is a string accelerator specification acceptable to gtk.accelerator_parse(). If accelerator is None the accelerator (if any) associated with the "stock-id" property of action will be used. As previously noted the add_action_wih_accel() method is preferred if you want to use accelerators.

The ActionGroup offers three convenience methods that make the job of creating and adding Action objects to an ActionGroup much easier:

  actiongroup.add_actions(entries, user_data=None)

  actiongroup.add_toggle_actions(entries, user_data=None)

  actiongroup.add_radio_actions(entries, value=0, on_change=None, user_data=None)

The entries parameter is a sequence of action entry tuples that provide the information used to create the actions that are added to the ActionGroup. The RadioAction with the value of value is initially set active. on_change is a callback that is connected to the "changed" signal of the first RadioAction in the group. The signature of on_changed is:

  def on_changed_cb(radioaction, current, user_data)

The entry tuples for Action objects contain:

  • The name of the action. Must be specified.
  • The stock id for the action. Optional with a default value of None if a label is specified.
  • The label for the action. Optional with a default value of None if a stock id is specified.
  • The accelerator for the action, in the format understood by the gtk.accelerator_parse() function. Optional with a default value of None.
  • The tooltip for the action. Optional with a default value of None.
  • The callback function invoked when the action is activated. Optional with a default value of None.

You must minimally specify a value for the name field and a value in either the stock id field or the label field. If you specify a label then you can specify None for the stock id if you aren't using one. For example the following method call:

  actiongroup.add_actions([('quit', gtk.STOCK_QUIT, '_Quit me!', None, 
                            'Quit the Program', quit_cb)])

adds an action to actiongroup for exiting a program.

The entry tuples for the ToggleAction objects are similar to the Action entry tuples except there is an additional optional flag field containing a boolean value indicating whether the action is active. The default value for the flag field is FALSE. For example the following method call:

  actiongroup.add_toggle_actions([('mute, None, '_Mute', '<control>m', 
                                   'Mute the volume', mute_cb, True)])

adds a ToggleAction to actiongroup and sets it to be initially active.

The entry tuples for the RadioAction objects are similar to the Action entry tuples but specify a value field instead of a callback field:

  • The name of the action. Must be specified.
  • The stock id for the action. Optional with a default value of None if a label is specified.
  • The label for the action.Optional with a default value of None if a stock id is specified.
  • The accelerator for the action, in the format understood by the gtk.accelerator_parse() function. Optional with a default value of None.
  • The tooltip for the action. Optional with a default value of None.
  • The value to set on the radio action. Optional with a default value of 0. Should always be specified in applications.

For example the following code fragment:

  radioactionlist = [('am', None, '_AM', '<control>a', 'AM Radio', 0)
                     ('fm', None, '_FM', '<control>f', 'FM Radio', 1)
                     ('ssb', None, '_SSB', '<control>s', 'SSB Radio', 2)]
  actiongroup.add_radio_actions(radioactionlist, 0, changed_cb)

creates three RadioAction objects and sets the initial active action to 'am' and the callback that is invoked when any of the actions is activated to changed_cb.

16.1.2.3. Retrieving Actions

An Action can be retrieved by name from an ActionGroup by using the method:

  action = actiongroup.get_action(action_name)

A list of all the Action objects contained in an ActionGroup can be retrieved using the method:

  actionlist = actiongroup.list_actions()

16.1.2.4. Controlling Actions

The sensitivity and visibility of all Action objects in an ActionGroup can be controlled by setting the associated property values. The following convenience methods get and set the properties:

  is_sensitive = actiongroup.get_sensitive()
  actiongroup.set_sensitive(sensitive)

  is_visible = actiongroup.get_visible()
  actiongroup.set_visible(visible)

Finally you can remove an Action from an ActionGroup using the method:

  actiongroup.remove_action(action)

16.1.2.5. An ActionGroup Example

The actiongroup.py example program duplicates the menubar and toolbar of the actions.py example program using the ActionGroup methods. In addition the program provides buttons to control the sensitivity and visibility of the menu items and toolbar items. Figure 16.4, “ActionGroup Example” illustrates the program in operation:

Figure 16.4. ActionGroup Example

ActionGroup Example

16.1.2.6. ActionGroup Signals

Your application can track the connection and removal of proxy widgets to the Action objects in an ActionGroup using the "connect-proxy" and disconnect-proxy" signals. The signatures of your signal handler callbacks should be:

  def connect_proxy_cb(actiongroup, action, proxy, user_params)

  def disconnect_proxy_cb(actiongroup, action, proxy, user_params)

For example, you might want to track these changes to make some additional changes to the properties of the new proxy widget when it is connected or to update some other part of the user interface when a proxy widget is disconnected.

The "pre-activate" and "post-activate" signals allow your application to do some additional processing immediately before or after an action is activated. The signatures of the signal handler callbacks should be:

  def pre_activate_cb(actiongroup, action, user_params)

  def post_activate_cb(actiongroup, action, user_params)

These signals are mostly used by the UIManager to provide global notification for all Action objects in ActionGroup objects used by it.