[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14. Menus and Toolbars

This chapter is derived from Stewart Weiss's tutorial Menus and Toolbars in GTK. The original code snippets have been translated to Lisp.

GUI applications have menus and toolbars. They are an important part of how the user interacts with the application. Although menus and toolbars look like different things, they are both containers for widgets that, when clicked, result in the performance of actions. Menus contain menu items, and toolbars usually contain buttons. Although toolbars are actually more general than this in that they can contain arbitrary widgets, they are usually used to provide quick access to frequently used menu items.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.1 Menus

GTK+ knows several classes related to the creation of menus:

gtk-menu-shell

A gtk-menu-shell is the abstract base class used to derive the gtk-menu and gtk-menu-bar subclasses. A gtk-menu-shell is a container of gtk-menu-item objects arranged in a list which can be navigated, selected, and activated by the user to perform application functions. A gtk-menu-item can have a submenu associated with it, allowing for nested hierarchical menus.

gtk-menu-bar

The gtk-menu-bar is a subclass of gtk-menu-shell which contains one or more menu items. The result is a standard menu bar which can hold many menu items.

gtk-menu

A gtk-menu is a gtk-menu-shell that implements a drop down menu consisting of a list of gtk-menu-item objects which can be navigated and activated by the user to perform application functions. A gtk-menu is most commonly dropped down by activating a gtk-menu-item in a gtk-menu-bar or popped up by activating a gtk-menu-item in another gtk-menu. Applications can display a gtk-menu as a pop-up menu by calling the function gtk-menu-popup.

gtk-menu-item

The gtk-menu-item widget and the derived widgets are the only valid childs for menus. Their function is to correctly handle highlighting, alignment, events and submenus. As it derives from gtk-bin it can hold any valid child widget, although only a few are really useful.

gtk-check-menu-item

A gtk-check-menu-item is a menu item that maintains the state of a boolean value in addition to a gtk-menu-item usual role in activating application code. A check box indicating the state of the boolean value is displayed at the left side of the gtk-menu-item. Activating the gtk-menu-item toggles the value.

gtk-image-menu-item

A gtk-image-menu-item is a menu item which has an icon next to the text label. Note that the user can disable display of menu icons, so make sure to still fill in the text label.

gtk-separator-menu-item

The gtk-separator-menu-item is a separator used to group items within a menu. It displays a horizontal line with a shadow to make it appear sunken into the interface.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.1.1 Principles of Menus

Menu creation and menu handling follows the following:

In essence, menus form a recursively defined hierarchy. The root of this hierarchy is always a menubar. Usually menubars are horizontal, rectangular regions at the top of a window, but they can be vertical as well, and can be placed anywhere. Those labels that can be seen in the menubar, such as "File", "Edit" or "Help", are menu items. Menu items can have menus attached to them, so that when they get clicked, the menu appears. Each of the menus attached to a menu item may have menu items that have menus attached to them, and these may have items that have menus attached to them, and so on.

Use the term submenu refers to a menu that is attached to a menu item within another menu, but there is no special class of submenus; a submenu is just a menu. Because a menu item always exists as a child of either a menu or a menubar, the menu that is attached to a menu item is always a submenu of something else. This should make it easy to remember the fact that there is but a single way to attach a menu to a menu item with the generic function gtk-menu-item-submenu. The point is that the attached menu is of necessity a submenu of something else.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.1.2 Creating Menus by Hand

figures/menus-by-hand

Figure 14.1: Creating Menus by Hand

This method is called "by hand" because the menu is constructed in the same way that a typical house is constructed, by assembling the pieces and attaching them to each other, one by one. The outline of the steps that must be taken is:

  1. Create the menubar.
  2. Create the menu items that will be packed into the menubar.
  3. Pack the menu items into the menubar.
  4. Create the menus that the menu items will activate.
  5. Attach these submenus to the menu items.
  6. For each submenu (a) create the menu items that it will contain, and (b) pack these menu items into the submenu.

These steps are listed in a top-down sequence, but it is conceivable to carry them out in many different permutations.

An empty menubar is created with the function gtk-menu-bar-new or the call (make-instance 'gtk-menu-bar). The menubar itself should be added to its parent container with an appropriate packing function. Typically the menubar is put at the top of the content area just below the top-level windows's title bar, so the usual sequence is

(let ((vbox (gtk-box-new :vertical 0)))
  (let ((menu-bar (gtk-menu-bar-new)))
    (gtk-container-add vbox menu-bar))
  (gtk-container-add window vbox)
  ... )

For each menu in the menubar, a separate menu item is needed. Regular menu items can be created with the functions gtk-menu-item-new, gtk-menu-item-new-with-label, gtk-menu-item-new-with-mnemonic, or with the appropriate calls of the function make-instance.

The first of these creates a menu item with no label; later the generic function gtk-menu-item-label can be used to create a label for it. The second and third functions create menu items with either a plain label or with a label and a mnemonic, just like is done with buttons. There are four subclasses of menu items, among which are image menu items, which can contain an image instead of or in addition to a label.

There are three different ways to pack menu items into menubars and menus; they are all methods of the gtk-menu-shell base class: gtk-menu-shell-append, gtk-menu-shell-prepend, and gtk-menu-shell-insert.

The second argument in all three is the menu item to be put into the container. The differences are probably obvious. The append method adds the menu item to the end of the list of those already in the menu shell, whereas the prepend method inserts it before all of the items already in it. The insert method takes an integer position as the third argument, which is the position in the item list where child is added. Positions are numbered from 0 to (n-1). If an item is put into position k, then all items currently in the list at positions k through (n-1) are shifted downward in the list to make room for the new item.

The following code fragment creates a few labeled menu items, and packs them into the menubar in left-to-right order:

(let ((file-item (gtk-menu-item-new-with-label "File"))
      (view-item (gtk-menu-item-new-with-label "View"))
      (tools-item (gtk-menu-item-new-with-label "Tools"))
      (help-item (gtk-menu-item-new-with-label "Help")))
  (gtk-menu-shell-append menu-bar file-item)
  (gtk-menu-shell-append menu-bar view-item)
  (gtk-menu-shell-append menu-bar tools-item)
  (gtk-menu-shell-append menu-bar help-item)
  ... )

The next step is to create the menus that will be dropped down when these menu items are activated. Menus are created with the function gtk-menu-new.

For the above menu items, four menus are created and attached to the menu items with the generic function gtk-menu-item-submenu:

(let ((file-menu (gtk-menu-new))
      (view-menu (gtk-menu-new))
      (tools-menu (gtk-menu-new))
      (help-menu (gtk-menu-new)))
  (setf (gtk-menu-item-submenu file-item) file-menu)
  (setf (gtk-menu-item-submenu view-item) view-menu)
  (setf (gtk-menu-item-submenu tools-item) tools-menu)
  (setf (gtk-menu-item-submenu help-item) help-menu)
  ... )

The next step is to create the menu items to populate each of the menus, and add them to these menus. In the example, the "File" menu will have an "Open" item, a "Close" item, and an "Exit" item. Between the "Close" item and the "Exit" item a separator item is added. Separators are members of the gtk-separator-menu-item class and are created with the function gtk-separator-menu-item-new or the call (make-instance 'gtk-separator).

The "File" menus's items will be simple labeled items. The code to create them and pack them is:

 (let ((open-item (gtk-menu-item-new-with-label "Open"))
       (close-item (gtk-menu-item-new-with-label "Close"))
       (exit-item (gtk-menu-item-new-with-label "Exit")))
   (gtk-menu-shell-append file-menu open-item)
   (gtk-menu-shell-append file-menu close-item)
   (gtk-menu-shell-append file-menu (gtk-separator-menu-item-new))
   (gtk-menu-shell-append file-menu exit-item)
   ... )

To create a menu that contains submenus does not involve anything other than descending a level in the menu hierarchy and repeating these steps. To illustrate, a "Help" menu is designed so that it has two items, one of which is a menu item that, when activated, pops up a submenu. The first two steps are to create the two menu items and pack the into the "Help" menu:

(let ((query-item (gtk-menu-item-new-with-label "What's this?"))
      (about-help-item
        (gtk-menu-item-new-with-label "About this program")))
  (gtk-menu-shell-append help-menu query-item)
  (gtk-menu-shell-append help-menu (gtk-separator-menu-item-new))
  (gtk-menu-shell-append help-menu about-help-item)
  ... )

The next step is to create a submenu and attach it to the about-help-item:

(let ((about-help-menu (gtk-menu-new)))
  (setf (gtk-menu-item-submenu about-help-item) about-help-menu)
  ... )

The last step is to create a submenu and attach it to the about-help-menu:

(let ((about-tool-item (gtk-menu-item-new-with-label "About Tools"))
      (about-stuff-item
        (gtk-menu-item-new-with-label "About Other Stuff")))
  (gtk-menu-shell-append about-help-menu about-tool-item)
  (gtk-menu-shell-append about-help-menu about-stuff-item)
  ... )

The preceding steps create the menu items, but they are not yet connected to the "activate" signal. Menu items that have a submenu do not need to be connected to the "activate" signal; GTK+ arranges for that signal to open the submenu. But the others need to be connected. For example, the "Exit" menu item is connected to a callback to quit the application with

(g-signal-connect exit-item "activate"
                  (lambda (widget)
                    (declare (ignore widget))
                    (gtk-widget-destroy window))))

The following Lisp code shows a complete example for creating menus by hand. It includes all code snippets shown above. The output is shown in figure-menus-by-hand.

Example 14.1: Creating Menus by Hand

(defun example-menus-by-hand ()
  (within-main-loop
    ;; We set the "gtk-shell-shows-menubar" property to NIL to display the
    ;; menubar by the application itself and not by the desktop environment.
    (setf (gtk-settings-gtk-shell-shows-menubar (gtk-settings-get-default))
          nil)
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :default-width 425
                                 :default-height 250
                                 :title "Example Menus by Hand"))
          ;; A vbox to put in a menu and a button
          (vbox (gtk-box-new :vertical 0)))
      ;; Create a menu bar and the menu items for the menu bar
      (let ((menu-bar (gtk-menu-bar-new))
            (file-item (gtk-menu-item-new-with-label "File"))
            (view-item (gtk-menu-item-new-with-label "View"))
            (tools-item (gtk-menu-item-new-with-label "Tools"))
            (help-item (gtk-menu-item-new-with-label "Help")))
        ;; Add the menu bar to the main container
        (gtk-container-add vbox menu-bar)
        ;; Add the menu items to the menu bar
        (gtk-menu-shell-append menu-bar file-item)
        (gtk-menu-shell-append menu-bar view-item)
        (gtk-menu-shell-append menu-bar tools-item)
        (gtk-menu-shell-append menu-bar help-item)
        ;; Create the menus for the menu items in the menu bar
        (let ((file-menu (gtk-menu-new))
              (view-menu (gtk-menu-new))
              (tools-menu (gtk-menu-new))
              (help-menu (gtk-menu-new)))
          ;; Attach the submenus to the items of the menu bar
          (setf (gtk-menu-item-submenu file-item) file-menu)
          (setf (gtk-menu-item-submenu view-item) view-menu)
          (setf (gtk-menu-item-submenu tools-item) tools-menu)
          (setf (gtk-menu-item-submenu help-item) help-menu)
          ;; Create items to put into the File menu
          (let ((open-item (gtk-menu-item-new-with-label "Open"))
                (close-item (gtk-menu-item-new-with-label "Close"))
                (exit-item (gtk-menu-item-new-with-label "Exit")))
            ;; Append the items to the File menu
            (gtk-menu-shell-append file-menu open-item)
            (gtk-menu-shell-append file-menu close-item)
            (gtk-menu-shell-append file-menu (gtk-separator-menu-item-new))
            (gtk-menu-shell-append file-menu exit-item)
            ;; Add a signal handler for exit-item
            (g-signal-connect exit-item "activate"
                              (lambda (widget)
                                (declare (ignore widget))
                                (gtk-widget-destroy window))))

          ;; The view and tools menus will be empty for now

          ;; Create items to put into the Help menu
          (let ((query-item (gtk-menu-item-new-with-label "What's this?"))
                (about-help-item (gtk-menu-item-new-with-label "Info")))
            ;; Append the items to the About Help Menu
            (gtk-menu-shell-append help-menu query-item)
            (gtk-menu-shell-append help-menu (gtk-separator-menu-item-new))
            (gtk-menu-shell-append help-menu about-help-item)
            ;; Create a submenu and items for about-help-item
            (let ((about-help-menu (gtk-menu-new))
                  (about-tool-item
                    (gtk-menu-item-new-with-label "About This"))
                  (about-more-item
                    (gtk-menu-item-new-with-label "About That")))
              ;; Attach the submenu to the about-help-item
              (setf (gtk-menu-item-submenu about-help-item) about-help-menu)
              ;; Append the items to the about-help-menu
              (gtk-menu-shell-append about-help-menu about-tool-item)
              (gtk-menu-shell-append about-help-menu about-more-item)))))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (gtk-container-add window vbox)
      (gtk-widget-show-all window))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.1.3 Pop-Up Menus for Widgets

figures/menu-popup

Figure 14.2: Creating a Pop-Up Menu

The same techniques for creating menus rooted in a menubar applies to the creation of pop-up menus for other widgets. For example, to create a button, which when the mouse button is pressed on it, would pop up a menu instead of taking some action, first the menu is created using the instructions above. Then a mouse button press event signal is connected to a callback that popped up the menu, using the function g-signal-connect. To illustrate, a small pop-up menu is created and two menu items are packed into it.

(let ((popup-menu (gtk-menu-new))
      (big-item (gtk-menu-item-new-with-label "Larger"))
      (small-item (gtk-menu-item-new-with-label "Smaller")))
  (gtk-menu-shell-append popup-menu big-item)
  (gtk-menu-shell-append popup-menu small-item)
  ... )

Next a callback is connected to the "button-press-event" signal. The callback will be responsible for popping up the menu with the function gtk-menu-popup. This function displays a menu and makes it available for selection. It exists precisely for the purpose of displaying context sensitive menus.

The first argument of the function is the menu to pop-up. All other arguments are keyword arguments with the keywords :parent-menu-shell, :parent-menu-item, :position-func, :button, and :activate-time. For normal use the default values for most of the arguments can be accepted.

The :button parameter should be the mouse button pressed to initiate the menu popup. If the menu popup was initiated by something other than a mouse press, such as a mouse button release or a key-press, button should be 0.

The API documentation states that the :activate-time parameter is used to conflict-resolve initiation of concurrent requests for mouse/keyboard grab requests. To function properly, this needs to be the time stamp of the user event that caused the initiation of the popup.

Putting this together, the callback should be:

(g-signal-connect button "button-press-event"
   (lambda (widget event)
     (declare (ignore widget))
     (gtk-menu-popup popup-menu
                     :button (gdk-event-button-button event)
                     :activate-time (gdk-event-button-time event))
     t))

The following code shows a complete example for creating a pop-up menu. It includes the code shown above. The output is shown in figure-menu-popup.

Example 14.2: Creating Pop-Up Menus

(defun example-menu-popup ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :default-width 250
                                 :default-height 150
                                 :title "Example Popup Menu"))
          (button (gtk-button-new-with-label "Click me")))
      ;; Create pop-up menu for button
      (let ((popup-menu (gtk-menu-new))
            (big-item (gtk-menu-item-new-with-label "Larger"))
            (small-item (gtk-menu-item-new-with-label "Smaller")))
        (gtk-menu-shell-append popup-menu big-item)
        (gtk-menu-shell-append popup-menu small-item)
        (gtk-widget-show-all popup-menu)
        ;; Signal handler to pop up the menu
        (g-signal-connect button "button-press-event"
           (lambda (widget event)
             (gtk-menu-popup popup-menu
                             :button (gdk-event-button-button event)
                             :activate-time (gdk-event-button-time event))
             t)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (gtk-container-add window button)
      (gtk-widget-show-all window))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.2 Toolbars

figures/toolbar-by-hand

Figure 14.3: Creating a Toolbar

Toolbars provide quick access to commonly used actions. They are containers that should be populated with instances of the gtk-tool-item class. Usually you will insert toolbar buttons into a toolbar. Toolbar buttons belong to the gtk-tool-button class, which is a sub class of gtk-tool-item. There are also two subclasses of the tool button class: gtk-menu-tool-button and gtk-toggle-tool-button, which has a subclass gtk-radio-tool-button.

A toolbar is created with only a single function gtk-toolbar-new. Once it is created, tool items can be inserted into it, using the function gtk-toolbar-insert.

This inserts the tool item at position pos. If pos is 0 the item is prepended to the start of the toolbar. If pos is negative, the item is appended to the end of the toolbar. Therefore, if items are inserted successively into a toolbar passing -1 as pos, they will appear in the toolbar in left to right order.

Although tool items can be created with the function gtk-tool-item-new; we will have little use for this function, as we will be putting only buttons and separators into our toolbars. Each of these has its own specialized constructors. To create a toolbar button, you can use any of two different methods: gtk-tool-button-new or gtk-tool-button-new-from-stock.

The first method requires that you supply a custom icon and label; the second lets you pick a stock ID. You can use any stock item from the documentation. As we have not yet covered how to create icons, we will stay with the second method in the examples that follow. The following code fragment creates a toolbar and a few toolbar buttons using stock items and puts them into a toolbar.

(let ((toolbar (gtk-toolbar-new))
      (new-button (gtk-tool-button-new-from-stock "gtk-new"))
      (open-button (gtk-tool-button-new-from-stock "gtk-open"))
      (save-button (gtk-tool-button-new-from-stock "gtk-save"))
      (quit-button (gtk-tool-button-new-from-stock "gtk-quit"))
      (separator (make-instance 'gtk-separator-tool-item
                                :draw nil)))
  (gtk-toolbar-insert toolbar new-button -1)
  (gtk-toolbar-insert toolbar open-button -1)
  (gtk-toolbar-insert toolbar save-button -1)
  (gtk-toolbar-insert toolbar separator -1)
  (gtk-toolbar-insert toolbar quit-button -1)
  ... )

You can create separator items using the function gtk-separator-tool-item-new. This creates a vertical separator in horizontal toolbar. If for some reason you want the buttons to the right of the separator to be grouped at the far end of the toolbar, you can use the separator like a "spring" to push them to that end by setting its "expand" property to true and its "draw" property to nil, using the sequence

(let (...
      (separator (gtk-separator-tool-item-new))
      ...)
  (gtk-separator-tool-item-set-draw separator nil)
  (gtk-tool-item-set-expand separator t)
  ... )

The "expand" property is inherited from gtk-tool-item whereas the "draw" property is specific to the separator. Because "draw" is a property of the gtk-separator-tool-item class, we can save one function call, when using the function make-instance to create a gtk-separator-tool-item widget.

(let (...
      (separator (make-instance 'gtk-separator-tool-item
                                :draw nil))
      ...)
  (gtk-tool-item-set-expand separator t)
  ... )

Toolbar buttons are buttons, not items, and therefore they emit a "clicked" signal. To respond to button clicks, connect a callback to the button as if it were an ordinary button, such as

(g-signal-connect quit-button "clicked"
                  (lambda (widget)
                    (declare (ignore widget))
                    (gtk-widget-destroy window)))

A complete program showing how to create a simple toolbar using this manual method is shown in the following example. The output is shown in figure-toolbar-by-hand.

Example 14.3: Creating a Toolbar

(defun example-toolbar-by-hand ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :default-width 250
                                 :default-height 150
                                 :title "Example Toolbar"))
          ;; A vbox to put a menu and a button in
          (vbox (gtk-box-new :vertical 0)))
      (let ((toolbar (gtk-toolbar-new))
            (new-button (gtk-tool-button-new-from-stock "gtk-new"))
            (open-button (gtk-tool-button-new-from-stock "gtk-open"))
            (save-button (gtk-tool-button-new-from-stock "gtk-save"))
            (quit-button (gtk-tool-button-new-from-stock "gtk-quit"))
            (separator (make-instance 'gtk-separator-tool-item
                                      :draw nil)))
        (gtk-toolbar-insert toolbar new-button -1)
        (gtk-toolbar-insert toolbar open-button -1)
        (gtk-toolbar-insert toolbar save-button -1)
        (gtk-toolbar-insert toolbar separator -1)
        (gtk-toolbar-insert toolbar quit-button -1)
        (gtk-tool-item-set-expand separator t)
        (gtk-box-pack-start vbox toolbar :fill nil :expand nil :padding 3)
        ;; Connect a signal handler to the quit button
        (g-signal-connect quit-button "clicked"
                          (lambda (widget)
                            (declare (ignore widget))
                            (gtk-widget-destroy window))))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (gtk-container-add window vbox)
      (gtk-widget-show-all window))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.3 GtkUIManager

A gtk-ui-manager is an object that can dynamically construct a user interface consisting of menus and toolbars from a UI description. A UI description is a specification of what menu and toolbar widgets should be present in an application and is described in an XML format. A gtk-ui-manager makes it possible to change menus and toolbars dynamically using what is called UI merging.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.3.1 Actions

The principal objects manipulated by a gtk-ui-manager are actions, which are instances of the gtk-action class. Actions represent operations that the user can perform. Associated with an action are

The callback function is the function that is executed when the action is activated. The action name is how it is referred to, not what appears in a menu item or toolbar button, which is its label. Actions can have associated keyboard accelerators and tooltips. Their visibility and sensitivity can be controlled as well. The idea is that you can create actions that the gtk-ui-manager can bind to proxies such as menu items and toolbar buttons.

The gtk-action class has methods to create icons, menu items and toolbar items representing itself, as well as get and set methods for accessing and changing its properties.

The gtk-action class also has two subclasses: gtk-toggle-action and gtk-recent-action. The gtk-toggle-action class has a gtk-radio-action subclass. These correspond to toggle buttons and radio buttons respectively.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.3.2 UI Definitions

You can specify the set user interface action elements in your application with an XML description called a UI definition. A UI definition is a textual description that represents the actions and the widgets that will be associated with them. It must b e bracketed by the pair of tags <ui> and </ui>. Within these tags you describe your user interface in a hierarchical way, by defining menubars, which would contain menus, which in turn contain menus and menu items, toolbars, which would contain tool items, and pop-up menus, which can contain menus and menu items. The set of tags that can be used in these UI definitions, with their descriptions and attributes, is

Tag

Description

Attributes

Closing
Tag

<menubar>

gtk-menu-bar

name, action

yes

<toolbar>

gtk-toolbar

name, action

yes

<popup>

toplevel gtk-menu

name, action,
accelerators

yes

<menu>

gtk-menu attached to a menu item

name, action, position

yes

<menuitem>

gtk-menu-item subclass, the exact type depends on the action

name, action, position,
always-show-image

no

<toolitem>

gtk-tool-item subclass, the exact type depends on the action

name, action, position

no

<separator>

gtk-separator-menu-item or
gtk-separator-tool-item

name, action, expand

no

<accelerator>

keyboard accelerator

name, action

no

<placeholder>

placeholder for dynamically adding an item

name, action

yes

Example

The following example shows a UI definition of a menubar and its submenus.

<ui>
  <menubar name='MainMenu'>
    <menu name='File' action='FileMenu'>
      <menuitem name='Open'action='Open' always-show-image='true'/>
      <menuitem name='Close' action='Close' always-show-image='true'/>
      <separator/>
      <menuitem name='Exit' action='Exit'/>
    </menu>
    <menu action='ViewMenu'>
      <menuitem name='ZoomIn' action='ZoomIn'/>
      <menuitem name='ZoomOut' action='ZoomOut'/>
      <separator/>
      <menuitem name='FullScreen' action='FullScreen'/>
      <separator/>
      <menuitem name='JustifyLeft' action='JustifyLeft'/>
      <menuitem name='JustifyCenter' action='JustifyCenter'/>
      <menuitem name='JustifyRight' action='JustifyRight'/>
      <menu action='IndentMenu'>
        <menuitem action='Indent'/>
        <menuitem action='Unindent'/>
      </menu>
    </menu>
  </menubar>
</ui>

Notes

We can create a toolbar definition in a similar way:

<ui>
  <toolbar name='ToolBar' action="ToolBarAction">
    <placeholder name="ExtraToolItems">
      <separator/>
      <toolitem name="ZoomIn"action="ZoomIn"/>
      <toolitem name="ZoomOut"action="ZoomOut"/>
      <separator/>
      <toolitem name='FullScreen' action='FullScreen'/>
      <separator/>
      <toolitem name='JustifyLeft' action='JustifyLeft'/>
      <toolitem name='JustifyCenter' action='JustifyCenter'/>
      <toolitem name='JustifyRight' action='JustifyRight'/>
    </placeholder>
  </toolbar>
</ui>

Notice that the tool items have the same action names as some of the menu items. This is how you can create multiple proxies for the same action. When the gtk-ui-manager loads these descriptions, and you take the appropriate steps in your program, they will be connected to the same callback functions.

Notice also that there is a placeholder in the toolbar defined above. We can use that placeholder to dynamically add more tool items in that position. It does not occupy space in the toolbar widget; it just marks a position to be accessed, so there is no downside to putting these placeholders into the UI definition.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.3.3 Actions Groups

Actions are organized into action groups. An action group is essentially a map from names to gtk-action objects. Action groups are the easiest means for adding actions to a UI manager object.

In general, related actions should be placed into a single group. More precisely, since the UI manager can add and remove actions as groups, if the interface is supposed to change dynamically, then all actions that should be available in the same state of the application should be in the same group. It is typical that multiple action groups are defined for a particular user interface. Most nontrivial applications will make use of multiple groups. For example, in an application that can play media files, when a media file is open, the playback actions (play, pause, rewind, etc.) would be in a group that could be added and removed as needed.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.3.4 Creating the UI

The basic steps in creating the UI are to

  1. Define the UI in an XML format, either in a separate file or in a constant string within the source code.
  2. Create the actions and action groups.
  3. Create a UI manager.
  4. Add the action groups to the UI manager.
  5. Extract the accelerators from the UI manager and add them to the top-level window.
  6. Add the UI definition to the UI manager from the file or string.
  7. Get the menubar and toolbar widgets from the UI manager and pack them into the window.
  8. Create the callbacks referenced in the action objects created in step 2.

We will next describe how to program each of steps 2 through 7.

Creating Actions and Action Groups

The function to create an action group is gtk-action-group-new. The name argument can be used by various methods for accessing this particular action group. It should reflect what this particular group's purpose or common feature is. Actions are added to an action group in one of two ways. You can add them one at a time with the function gtk-action-group-add-action, or as a list of related actions with the function gtk-action-group-add-actions.

The problem with the first method is that it is tedious to add actions one by one, and that this method does not provide a means to add the accelerators for the actions without additional steps. Even if there is just a single action in the group, it is more convenient to use the second function. To use the function gtk-action-group-add-actions, you first have to create a list of action entries, which looks like the following example:

(list
  (list <name> <stock-id> <label> <accelerator> <tooltip> <callback>)
  ... )

The members of the list have the following meanings:

name

The name of the action.

stock-id

The stock ID for the action, or the name of an icon from the icon theme.

label

The label for the action. This field should typically be marked for translation, see the function gtk-action-group-set-translation-domain. If label is nil, the label of the stock item with ID stock-id is used.

accelerator

The accelerator for the action, in the format understood by the function gtk-accelerator-parse.

tooltip

The tooltip for the action. This field should typically be marked for translation, see the function gtk-action-group-set-translation-domain.

callback

The function to call when the action is activated.

The name must match the name of the action to which it corresponds in the UI definition. The stock-id can be nil, as can the label. The accelerator syntax is very flexible. You can specify control keys, function keys and even ordinary characters, for example, using "<Control>a", "Ctrl>a","<ctrl>a", or "<Shift><Alt>F1", "<Release>z", or "minus", to name a few. If you use a stock item, it is not necessary to supply an accelerator, unless you want to override the default one. The tooltip is a string that will appear when the cursor hovers over the proxy for this action entry.

Below is an example of a declaration of a small list of action entries.

(defvar file-entries
        (list (list "FileMenu"
                    nil
                    "_File")
              (list "Open"
                    "gtk-open"
                    "_Open"
                    "<control>O"
                    "Open a file"
                    #'on-open-file)
              (list "Close"
                    "gtk-close"
                    "_Close"
                    "<control>W"
                    "Close a file" #'on-close-file)
              (list "Exit"
                    "gtk-quit"
                    "E_xit" "<control>Q"
                    "Exit the program"
                    #'(lambda (widget)
                        (declare (ignore widget))
                        (gtk-widget-destroy toplevel-window)))))

Notice that the FileMenu action does not have a tooltip nor a callback. The Open, Close, and Exit actions have both a mnemonic label and an accelerator. Having defined this list, it can be added to a group as follows:

(let ((action-group (gtk-action-group-new "common-actions")))
  (gtk-action-group-add-actions action-group file-entries)
  ... )

Multiple action entry lists can be added to a single action group. In fact, you probably will need to do this, because toggle actions and radio actions must be defined differently. A gtk-toggle-action entry contains all of the members of a gtk-action entry, as well as an additional boolean flag is-active.

The is-active flag indicates whether or not the action is active or inactive. To add toggle action entries to an action group you need to use the function gtk-action-group-add-toggle-actions designed for that purpose.

The function differs from gtk-action-group-add-actions only in that it expects a list of gtk-toggle-action entries. To illustrate, we could define a list with a single toggle action entry:

(defvar toggle-entries
        (list (list "FullScreen"
                    "gtk-fullscreen"
                    "_FullScreen"
                    "F11"
                    "Switch between full screen and windowed mode"
                    #'on-full-screen
                    nil)))

and add it to the same group as above with

(gtk-action-group-add-toggle-actions common-actions toggle-entries)

GTK+ defines radio action entries separately. Usually you use radio buttons when there are three or more alternatives. If there are just two, a toggle is the cleaner interface element. Because radio actions can have more than two values, the list's last element is an integer instead of a boolean.

Unlike ordinary actions and toggle actions, which can have different callbacks for each action, radio action entries do not specify a callback function. Furthermore, the last member of this structure is the value that that particular radio action has. If for example, there are three radio actions for how text is to b e aligned, left, right, or centered, then one would have the value 0, the next, 1, and the third, 2. An example of a list of radio action entries is below.

(defvar radio-entries
        (list (list "JustifyLeft"
                    "gtk-justify-left"
                    "_Left"
                    nil
                    "Left justify text"
                    0)
              (list "JustifyCenter"
                    "gtk-justify-center"
                    "_Center"
                    nil
                    "Center the text"
                    1)
              (list "JustifyRight"
                    "gtk-justify-right"
                    "_Right"
                    nil
                    "Right justify the text"
                    2)))

Because radio action entries do not have a callback function as a member, the function gtk-action-group-add-radio-actions to add radio actions to an action group specifies a single callback to be used for all of the actions in the array of radio actions being added. This is the callback that will be called in response to the "changed" signal.

Also, this function has another parameter that specifies the value that should be active initially. It is either one of the values in the individual radio action entries, or -1 to indicate that none should b e active to start. We could add the radio_entries action list to our group with the call

(gtk-action-group-add-radio-actions action-group
                                    radio-entries
                                    0
                                    #'on-change)

specifying that the JustifyLeft action is the initial value.

Creating the UIManager and Adding the Action Groups

A gtk-ui-manager is created with the function gtk-ui-manager-new. This creates a UI manager object that can then be used for creating and managing the application's user interface. It is now ready to be populated with the action groups that you already defined. To insert an action group into the UI manager, use the function gtk-ui-manager-insert-action-group.

The first argument is the object returned by the call to create the UI manager. The second is the group to be inserted. The pos argument specifies the position in the list of action groups managed by this UI manager. Action groups that are earlier in this list will be accessed before those that are later in this list. A consequence of this is that, if an action with the same name, e. g. "Open", is in two different groups, the entry in the group with smaller position will hide the one in the group with larger position. For example, if an "Open" action is in groups named action-group1 and action-group2, and action-group1 is inserted at position 1, and action-group2 is at position 2, then the entry for the "Open" action in action-group1 will be used by the UI manager when its proxy is activated. If it has a different callback or label or accelerator, these will be associated with this action, not the one in action-group2. You can use this feature if you need to change the semantics of a menu item or toolbar button, but not the menu item or button itself.

While we are on the subject of inserting actions, we might as well look at how you can remove an action group, if you have need to do that dynamically. That function is gtk-ui-manager-remove-action-group. This searches the list of action groups in the UI manager and deletes the one which is passed to it.

Extracting Accelerators and adding them to the Top-Level Window

Accelerators are key combinations that provide quick access to the actions in a window. They are usually associated with the top-level window so that key-presses while that window has focus can be handled by the top-level window's "key-press-event" handler, which can propagate it through the chain of widgets. The problem is that the accelerators are stored within the UI manager, not the top-level window, when you insert the action groups into it. The UI manager aggregates the accelerators into its private data as action groups are added to it. However it provides a method of extracting them. The set of accelerators can be extracted into a gtk-accel-group object that can be added into a top-level window. The function that does this is gtk-ui-manager-get-accel-group. The function that adds this group into a top-level window is gtk-window-add-accel-group. The following code-snippet will extract the accelerators and add them to the top-level window:

(let ((accel-group (gtk-ui-manager-get-accel-group ui-manager)))
  (gtk-window-add-accel-group window accel-group)
  ... )

Loading the UI Definition

If the UI definition is in a separate file, it can be loaded using the function gtk-ui-manager-add-ui-from-file. The first argument is the UI manager object, the second, a filename passed as a UTF-8 string. If this function is successful, it will return a positive integer called a merge-id. Merge-ids will be explained in the next section. If the function fails, for one reason or another, the return value will be zero. Therefore it is a good idea to check the return value of the function. The following code fragment tests both conditions and terminates the program with an error message if there is an error:

(let ((merge-id (gtk-ui-manager-add-ui-from-file ui-manager
                                                 "menu-1.xml")))
  (when (eql 0 merge-id)
    (error-message "Could not load UI Manager definition"))
  ... )

The function error-message displays a message dialog with a suitable message.

An alternative to storing the UI definition in a file in the source code tree is to store the UI definition as a string within a source code file itself. If the UI definition is in a string, then it can be added with the function gtk-ui-manager-add-ui-from-string.

The buffer argument is the name of the string containing the UI definition. The return value is also either a positive integer on success, in which case it is a valid merge-id, or zero on failure. The following listing shows how to define a UI definition in a string.

(defparameter ui-constant
  "<ui>
     <menubar name='MainMenu'>
       <menu action='FileMenu'>
         <placeholder name='FilePlace'/>
         <separator/>
         <menuitem action='Exit'/>"
       </menu>"
       <menu action='ViewMenu'>"
         <menuitem action='ZoomIn'/>"
         <menuitem action='ZoomOut'/>"
         <separator/>"
         <menuitem action='FullScreen'/>"
         <separator/>"
       </menu>"
     </menubar>"
   </ui>")

This would then be added to the UI manager with the fragment

(let ((merge-id (gtk-ui-manager-add-ui-from-string ui-manager 
                                                   ui-constant)))
  (when (eql 0 merge-id)
    (error-message "Could not load UI Manager definition"))
  ... )

Getting the Widgets

The last step is to retrieve the widgets that the UI manager created when the UI definition was loaded into it, and pack those widgets into the window where you want them to be. This is where the names of the UI definition elements come into play. The UI manager can find a widget for you when you give it the absolute pathname of the element that you want to construct. The absolute pathname is a string starting with a forward slash '/', much like a file's absolute pathname, with a sequence of the ancestor elements in the XML tree of that element.

Elements which do not have a name or action attribute in the XML (e. g. <popup>) can be addressed by their XML element name (e. g. "popup"). The root element ("/ui") can be omitted in the path.

As an example, the absolute pathname of the FileMenu in the UI definition above is "/MainMenu/FileMenu".

The function gtk-ui-manager-get-widget finds the widget that the UI manager constructed, whose name matches the pathname that you give it. If you give it the name of a menubar, you get a menubar widget with its entire subtree. If you give it the name of a menu, you get the menu item to which the menu is attached, not the menu.

If our UI definition had a menubar and toolbar at the top level named "MainMenu" and "MainToolBar" respectively, we could get them from the UI manager using

(let ((menubar (gtk-ui-manget-get-widget ui-manager "/MainMenu"))
      (toolbar (gtk-ui-manager-get-widget ui-manager "/MainToolBar")))
  ... )

We could then pack these into a gtk-box one below the other in our main window, and we would be finished, except of course for defining all of the required callback functions.

Note. The widgets that are constructed by a UI manager are not tied to the life-cycle of that UI manager. It does acquire a reference to them, but when you add the widgets returned by this function to a container or if you explicitly ref them, they will survive the destruction of the UI manager. (Read the notes on memory management in GTK if you are unfamiliar with these concepts.)

Lastly, you can tell the UI manager to create tear-off menus if you want, using the function gtk-ui-manager-set-add-tearoffs. By passing true, all menus (except popup menus) will have the tear-off property.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

14.3.5 UI Merging

One of the most powerful features of the UI manager is its ability to dynamically change the menus and toolbars by overlaying or inserting menu items or toolbar items over others and removing them later. This feature is called UI merging. The ability to merge elements is based on the use of the pathnames to the UI elements defined in the UI definition, and merge-ids.

A merge-id is an unsigned integer value that is associated with a particular UI definition inside the UI manager. The functions that add UI definitions into the UI manager, such as gtk-ui-manager-add-ui-from-string and gtk-ui-manager-add-ui-from-file, return a merge-id that can be used at a later time, for example, to remove that particular UI definition. The function that removes a UI definition is gtk-ui-manager-remove-ui.

This is given the merge-id of the UI definition to be removed. For example, if I create a UI with the call

(let ((merge-id (gtk-ui-manager-add-ui-from-string ui_manager
                                                   ui_toolbar)))
  ... )

and I later want to remove the toolbar from the window, I would call

(gtk-ui-manager-remove-ui ui-manger merge-id)

In order to add an element such as a toolbar in one part of the code, and later remove it in a callback function, you would need to make the merge-id either a shared variable, or attach it as a property to a widget that the callback is passed.

There is a third function gtk-ui-manager-add-ui for adding a new element to the user interface. The parameters have the following meanings:

self

a gtk-ui-manager

merge-id

the merge-id for the merged UI

path

a path

name

the name for the added UI element

action

the name of the action to b e proxied, or nil to add a separator

type

the type of UI element to add

top

if true, the UI element is added before its siblings, otherwise it is added after its siblings.

This function can add a single element to the UI, such as a menu item, a toolbar item, a menu, or a menubar. It cannot add an entire UI definition such as the ones contained in the strings defined above. Furthermore, it cannot be used to insert an element in a place where such an element cannot be inserted. For example, you cannot insert a toolbar inside a menu, or a menu inside a menu, but you can insert a menu item in a menu, or a menu in a menubar.

In order to use this function, you need a merge-id to give to it. It will assign associate the new UI element to this merge-id so that it can be removed at a later time. New merge-ids are created with the function gtk-ui-manager-new-merge-id. The third parameter is the absolute path name to the position at which you want to add the new UI element. For example, if you want to insert a new menu item at the top of the File menu, the path would be "/MainMenu/FileMenu". The fourth parameter is a name that you want this item to have for future access and the fifth is the name for the action, which must exist already, that should b e connected to this element.

The type must be a member of the gtk-ui-manager-item-type, which has the following values

:auto
:menubar
:menu
:toolbar
:placeholder
:popup
:menuitem
:toolitem
:separator
:accelerator

Their meanings should be self-explanatory, except for the first. You can use :auto as the type to let GTK decide the type of the element that can be inserted at the indicated path. Lastly, if you want the element to be above the element that is currently in that position, you set top to true, otherwise nil.

As an example, suppose that I want to add a Print menu item in my File menu just below the Open menu item. I could use the following code fragment, assuming that I have already defined an action named Print:

(let ((merge-id (gtk-ui-manager-new-merge-id ui-manager)))
  (gtk-ui-manager-add-ui ui-manager
                         merge-id
                         "/MainMenu/FileMenu/Open"
                         "Print"
                         "Print"
                         :menu-item
                         nil)
    ... )

This will insert the Print menu item into the proper position.

Assuming that your menu is to be changed dynamically, these steps will not be enough to make the menu elements appear dynamically. The UI manager does not handle the task of packing new toolbars or menubars into their places in the window. However, it does emit the "add-widget" signal for each generated menubar and toolbar. Your application can respond to this signal with a callback function that can pack the UI element into the appropriate position. Therefore, two additional steps are needed by a program that adds and removes menubars or toolbars:

The callback for this signal has the prototype

lambda (merge widget)

The first parameter is the UI manager emitting the signal, the second is the widget that has been added. For example

(g-signal-connect ui-manager "add-widget"
   (lambda (merge widget)
     (declare (ignore merge))
     (gtk-box-pack-start menu-box widget :fill nil :expand nil)
     (gtk-widget-show widget)))

This will pack the menubar or toolbar after any other widgets in the parent, assuming that menu-box is a gtk-box of some kind that the menu or toolbar should be packed into. It must show the widget to realize it.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Crategus on January, 10 2016 using texi2html 1.76.