© W. David Ashley and Andrew Krause 2019
W. David Ashley and Andrew KrauseFoundations of PyGTK Developmenthttps://doi.org/10.1007/978-1-4842-4179-0_10

10. Menus and Toolbars

W. David Ashley1  and Andrew Krause2
(1)
AUSTIN, TX, USA
(2)
Leesburg, VA, USA
 

This chapter teaches you how to create pop-up menus, menu bars, and toolbars. You begin by creating each manually, so you learn how the widgets are constructed. This gives you a firm understanding of all of the concepts on which menus and toolbars rely.

After you understand each widget, you are introduced to Gtk.Builder, which allows you to dynamically create menus and toolbars through custom XML files. Each user interface file is loaded, and each element applied to a corresponding action object, which tells the item how it is displayed and how it acts.

In this chapter, you learn the following.
  • How to create pop-up menus, menu bars, and toolbars

  • How to apply keyboard accelerators to menu items

  • What the Gtk.StatusBar widget is and how you can use it to provide more information to the user about a menu item

  • What types of menu and toolbar items are provided by GTK+

  • How to dynamically create menus and toolbars with UI files

  • How to create custom stock items with Gtk.IconFactory

Pop-up Menus

You begin this chapter by learning how to create a pop-up menu. A pop-up menu is a Gtk.Menu widget that is displayed to the user when the right mouse button is clicked while hovering above certain widgets. Some widgets, such as Gtk.Entry and Gtk.TextView, already have pop-up menus built into the widget by default.

If you want to change the pop-up menu of a widget that offers one by default, you should edit the supplied Gtk.Menu widget in the pop-up callback function. For example, both Gtk.Entry and Gtk.TextView have a populate-popup signal, which receives the Gtk.Menu that is going to be displayed. You can edit this menu in any way you see fit before displaying it to the user.

Creating a Pop-up Menu

For most widgets, you need to create your own pop-up menu. In this section, you are going to learn how to supply a pop-up menu to a Gtk.ProgressBar widget. The pop-up menu we are going to implement is presented in Figure 10-1.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig1_HTML.jpg
Figure 10-1

A simple pop-up menu with three menu items

The three pop-up menu items pulse the progress bar, set it as 100 percent complete, and clear it. In Listing 10-1, an event box contains the progress bar. Because Gtk.ProgressBar, like Gtk.Label , is not able to detect GDK events by itself, we need to catch button-press-event signals using an event box.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, -1)
        # Create all of the necessary widgets and initialize the pop-up menu.  menu = Gtk.Menu.new()
        eventbox = Gtk.EventBox.new()
        progress = Gtk.ProgressBar.new() progress.set_text("Nothing Yet Happened")
        progress.set_show_text(True) self.create_popup_menu(menu, progress)
        progress.set_pulse_step(0.05) eventbox.set_above_child(False)
        eventbox.connect("button_press_event", self.button_press_event, menu) eventbox.add(progress)
        self.add(eventbox)
        eventbox.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        eventbox.realize()
    def create_popup_menu(self, menu, progress):
        pulse = Gtk.MenuItem.new_with_label("Pulse Progress")
        fill = Gtk.MenuItem.new_with_label("Set as Complete")
        clear = Gtk.MenuItem.new_with_label("Clear Progress")
        separator = Gtk.SeparatorMenuItem.new()
        pulse.connect("activate", self.pulse_activated, progress)
        fill.connect("activate", self.fill_activated, progress)
        clear.connect("activate", self.clear_activated, progress)
        menu.append(pulse)
        menu.append(separator)
        menu.append(fill)
        menu.append(clear)
        menu.attach_to_widget(progress, None)
        menu.show_all()
    def button_press_event(self, eventbox, event, menu):
        pass
    def pulse_activated(self, item, progress):
        pass
    def fill_activated(self, item, progress):
        pass
    def clear_activated(self, item, progress):
        pass
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Pop-up Menus")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-1

Simple Pop-up Menu

In most cases, you want to use button-press-event to detect when the user wants the pop-up menu to be shown. This allows you to check whether the right mouse button was clicked. If the right mouse button was clicked, Gdk.EventButton’s button member is equal to 3.

However, Gtk.Widget also provides the popup-menu signal, which is activated when the user presses built-in key accelerators to activate the pop-up menu. Most users use the mouse to activate pop-up menus, so this is not usually a factor in GTK+ applications. Nevertheless, if you would like to handle this signal as well, you should create a third function that displays the pop-up menu that is called by both callback functions.

New menus are created with Gtk.Menu.new(). The menu is initialized with no initial content, so the next step is to create menu items.

In this section, we cover two types of menu items. The first is the base class for all other types of menu items, Gtk.MenuItem. There are three initialization functions provided for Gtk.MenuItem: Gtk.MenuItem.new(), Gtk.MenuItem.new_with_label(), and Gtk.MenuItem.new_with_mnemonic().
pulse = Gtk.MenuItem.new_with_label("Pulse Progress")

In most cases, you do not need to use the Gtk.MenuItem.new(), because a menu item with no content is not of much use. If you use that function to initialize the menu item, you have to construct each aspect of the menu in code instead of allowing GTK+ to handle the specifics.

Note

Menu item mnemonics are not the same thing as keyboard accelerators. A mnemonic activates the menu item when the user presses Alt and the appropriate alphanumeric key while the menu has focus. A keyboard accelerator is a custom key combination that causes a callback function to be run when the combination is pressed. You learn about keyboard accelerators for menus in the next section.

The other type of basic menu item is Gtk.SeparatorMenuItem, which places a generic separator at its location. You can use Gtk.SeparatorMenuItem.new() to create a new separator menu item.

Separators are extremely important when designing a menu structure, because they organize menu items into groups so that the user can easily find the appropriate item. For example, in the File menu, menu items are often organized into groups that open files, save files, print files, and close the application. Rarely should you have many menu items listed without a separator in between them (e.g., a list of recent files might appear without a separator). In most cases, you should group similar menu items together and place a separator between adjacent groups.

After the menu items are created, you need to connect each menu item to the activate signal, which is emitted when the user selects the item. Alternatively, you can use the activate-item signal, which is also emitted when a submenu of the given menu item is displayed. There is no discernable difference between the two unless the menu item expands into a submenu.

Each activate and activate-item callback function receives the Gtk.MenuItem widget that initiated the action and any data you need to pass to the function. In Listing 10-2, three menu item callback functions are provided to pulse the progress bar, fill it to 100 percent complete, and clear all progress.

Now that you have created all of the menu items, you need to add them to the menu. Gtk.Menu is derived from Gtk.MenuShell, which is an abstract base class that contains and displays submenus and menu items. Menu items can be added to a menu shell with menu.append(). This function appends each item to the end of the menu shell.
menu.append(pulse)

Additionally, you can use menu.prepend() or menu.insert() add a menu item to the beginning of the menu or insert it into an arbitrary position respectively. Positions accepted by menu.insert() begin with an index of zero.

After setting all of the Gtk.Menu’s children as visible, you should call menu.attach_to_widget() so that the pop-up menu is associated to a specific widget. This function accepts the pop-up menu and the widget that it is attached to.
menu.attach_to_widget(progress, None)

The last parameter of menu.attach_to_widget() accepts a Gtk.MenuDetachFunc, which can call a specific function when the menu is detached from the widget.

Pop-up Menu Callback Methods

After creating the necessary widgets, you need to handle the button-press-event signal, which is shown in Listing 10-2. In this example, the pop-up menu is displayed every time the right mouse button clicks the progress bar.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, -1)
        # Create all of the necessary widgets and initialize the pop-up
        menu. menu = Gtk.Menu.new()
        eventbox = Gtk.EventBox.new() progress =
        Gtk.ProgressBar.new()
        progress.set_text("Nothing Yet Happened")
        progress.set_show_text(True) self.create_popup_menu(menu, progress)
        progress.set_pulse_step(0.05) eventbox.set_above_child(False)
        eventbox.connect("button_press_event", self.button_press_event, menu) eventbox.add(progress)
        self.add(eventbox)
        eventbox.set_events(Gdk.EventMask.BUTTON_PRESS_MASK) eventbox.realize()
    def create_popup_menu(self, menu, progress):
        pulse = Gtk.MenuItem.new_with_label("Pulse Progress")
        fill = Gtk.MenuItem.new_with_label("Set as Complete")
        clear = Gtk.MenuItem.new_with_label("Clear Progress")
        separator = Gtk.SeparatorMenuItem.new()
        pulse.connect("activate", self.pulse_activated, progress)
        fill.connect("activate", self.fill_activated, progress)
        clear.connect("activate", self.clear_activated, progress)
        menu.append(pulse)
        menu.append(separator)
        menu.append(fill)
        menu.append(clear)
        menu.attach_to_widget(progress, None)
        menu.show_all()
    def button_press_event(self, eventbox, event, menu):
        if event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS: menu.popup(None, None, None, None, event.button, event.time) return True
        return False
    def pulse_activated(self, item, progress):
        progress.pulse()
        progress.set_text("Pulse!")
    def fill_activated(self, item, progress):
        progress.set_fraction(1.0)
        progress.set_text("One Hundred Percent")
    def clear_activated(self, item, progress):
        progress.set_fraction(0.0)
        progress.set_text("Reset to Zero")
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Pop-up Menus")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-2

Callback Functions for the Simple Pop-up Menu

In the button-press-event callback function in Listing 10-2, you can use menu.popup() to display the menu on the screen.
menu.popup(parent_menu_shell, parent_menu_item, func, func_data, button, event_time)

In Listing 10-2 all parameters were set to None except for the mouse button that was clicked to cause the event (event ➤ button) and the time when the event occurred (event.time). If the pop-up menu was activated by something other than a button, you should supply 0 to the button parameter.

Note

If the action was invoked by a popup-menu signal, the event time will not be available. In that case, you can use Gtk.get_current_event_time(). This function returns the timestamp of the current event or Gdk.CURRENT_TIME if there are no recent events.

Usually, parent_menu_shell, parent_menu_item, func, and func_data are set to None, because they are used when the menu is a part of a menu bar structure. The parent_menu_shell widget is the menu shell that contains the item that caused the pop-up initialization. Alternatively, you can supply parent_menu_item, which is the menu item that caused the pop-up initialization.

Gtk.MenuPositionFunc is a function that decides at what position on the screen the menu should be drawn. It accepts func_data as an optional last parameter. These parameters are not frequently used in applications, so they can safely be set to None. In our example, the pop-up menu was already associated with the progress bar, so it is drawn in the correct location.

Keyboard Accelerators

When creating a menu, one of the most important things to do is to set up keyboard accelerators. A keyboard accelerator is a key combination created from one accelerator key and one or more modifiers, such as Ctrl or Shift. When the user presses the key combination, the appropriate signal is emitted.

Listing 10-3 is an extension of the progress bar pop-up menu application that adds keyboard accelerators to the menu items. The progress bar is pulsed when the user presses Ctrl+P, filled with Ctrl+F, and cleared with Ctrl+C.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, -1)
        # Create all of the necessary widgets and initialize the pop-up menu. menu = Gtk.Menu.new()
        eventbox = Gtk.EventBox.new() progress = Gtk.ProgressBar.new() progress.set_text("Nothing Yet Happened") progress.set_show_text(True) self.create_popup_menu(menu, progress) progress.set_pulse_step(0.05) eventbox.set_above_child(False)
        eventbox.connect("button_press_event", self.button_press_event, menu) eventbox.add(progress)
        self.add(eventbox)
        eventbox.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        eventbox.realize()
    def create_popup_menu(self, menu, progress):
        group = Gtk.AccelGroup.new()
        self.add_accel_group(group)
        menu.set_accel_group(group)
        pulse = Gtk.MenuItem.new_with_label("Pulse Progress")
        fill = Gtk.MenuItem.new_with_label("Set as Complete")
        clear = Gtk.MenuItem.new_with_label("Clear Progress")
        separator = Gtk.SeparatorMenuItem.new()
        # Add the necessary keyboard accelerators.
        pulse.add_accelerator("activate", group, Gdk.KEY_P, Gdk.ModifierType.CONTROL, Gtk.AccelFlags.VISIBLE)
        fill.add_accelerator("activate", group, Gdk.KEY_F, Gdk.ModifierType.CONTROL, Gtk.AccelFlags.VISIBLE)
        clear.add_accelerator("activate", group, Gdk.KEY_C, Gdk.ModifierType.CONTROL, Gtk.AccelFlags.VISIBLE)
        pulse.connect("activate", self.pulse_activated, progress)
        fill.connect("activate", self.fill_activated, progress)
        clear.connect("activate", self.clear_activated, progress)
        menu.append(pulse)
        menu.append(separator)
        menu.append(fill)
        menu.append(clear)
        menu.attach_to_widget(progress, None)
        menu.show_all()
    def button_press_event(self, eventbox, event, menu):
        if event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS: menu.popup(None, None, None, None, event.button, event.time) return True
        return False
    def pulse_activated(self, item, progress):
        progress.pulse()
        progress.set_text("Pulse!")
    def fill_activated(self, item, progress):
        progress.set_fraction(1.0)
        progress.set_text("One Hundred Percent")
    def clear_activated(self, item, progress):
        progress.set_fraction(0.0)
        progress.set_text("Reset to Zero")
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Pop-up Menus")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-3

Adding Accelerators to Menu Items

Keyboard accelerators are stored as an instance of Gtk.AccelGroup. To implement accelerators in your application, you need to create a new accelerator group with Gtk.AccelGroup.new(). This accelerator group must be added to the Gtk.Window where the menu appears for it to take effect. It must also be associated with any menus that take advantage of its accelerators. In Listing 10-3, this is performed immediately after creating the Gtk.AccelGroup with self.add_accel_group() and menu.set_accel_group().

It is possible to manually create keyboard accelerators with Gtk.AccelMap, but in most cases, widget.add_accelerator() provides all of the necessary functionality. The only problem that this method presents is that the user cannot change keyboard accelerators created with this function during runtime.
widget.add_accelerator(signal_name, group, accel_key, mods, flags)

To add an accelerator to a widget, you can use widget.add_accelerator(), which emits the signal specified by signal_name on the widget when the user presses the key combination. You need to specify your accelerator group to the function, which must be associated with the window and the menu as previously stated.

An accelerator key and one or more modifier keys form the complete key combination. A list of available accelerator keys is available in the PyGObject API Reference. All the definitions for the available keys can be included by with the statement import GDK.

Modifiers are specified by the Gdk.ModifierType enumeration. The most often used modifiers are Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.CONTROL_MASK, and Gdk.ModifierType.MOD1_MASK, which correspond to the Shift, Ctrl, and Alt keys respectively.

Tip

When dealing with key codes, you need to be careful because you many need to supply multiple keys for the same action in some cases. For example, if you want to catch the number 1 key, you need to watch for Gdk.KEY_1 and Gdk.KEY_KP_1 - they correspond to the 1 key at the top of the keyboard and the 1 key on the numeric keypad.

The last parameter of widget.add_accelerator() is an accelerator flag. There are three flags defined by the Gtk.AccelFlags enumeration. The accelerator is visible in a label if Gtk.AccelFlags.VISIBLE is set. Gtk.AccelFlags.LOCKED prevents the user from modifying the accelerator. Gtk.AccelFlags.MASK sets both flags for the widget accelerator.

Status Bar Hints

Usually placed along the bottom of the main window, the Gtk.Statusbar widget can give the user further information about what is going on in the application. A status bar can also be very useful with menus, because you can provide more information to the user about the functionality of the menu item that the mouse cursor is hovering over. A screenshot of a status bar is shown in Figure 10-2.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig2_HTML.jpg
Figure 10-2

A pop-up menu with status bar hints

The Status Bar Widget

While the status bar can only display one message at a time, the widget actually stores a stack of messages. The currently displayed message is on the top of the stack. When you pop a message from the stack, the previous message is displayed. If there are no more strings left on the stack after you pop a message from the top, no message is displayed on the status bar.

New status bar widgets are created with Gtk.Ststusbar.new(). This creates a new Gtk.Statusbar widget with an empty message stack. Before you are able to add or remove a message from the new status bar’s stack, you must retrieve a context identifier with statusbar.get_context_id().
id = statusbar.get_context_id(description)

The context identifier is a unique unsigned integer that is associated with a context description string. This identifier is used for all messages of a specific type, which allows you to categorize messages on the stack.

For example, if your status bar holds hyperlinks and IP addresses, you could create two context identifiers from the strings “URL” and “IP”. When you push or pop messages to and from the stack, you have to specify a context identifier. This allows separate parts of your application to push and pop messages to and from the status bar message stack without affecting each other.

Tip

It is important to use different context identifiers for different categories of messages. If one part of your application is trying to give a message to the user while the other is trying to remove its own message, you do not want the wrong message to be popped from the stack!

After you generate a context identifier, you can add a message to the top of the status bar’s stack with statusbar.push(). This function returns a unique message identifier for the string that was just added. This identifier can be used later to remove the message from the stack, regardless of its location.
statusbar.push(context_id, message)
There are two ways to remove a message from the stack. If you want to remove a message from the top of the stack for a specific context ID, you can use statusbar.pop(). This function removes the message that is highest on the status bar’s stack with a context identifier of context_id.
statusbar.pop(context_id)
It is also possible to remove a specific message from the status bar’s message stack with statusbar.remove(). To do this, you must provide the context identifier of the message and the message identifier of the message you want to remove, which was returned by statusbar.push() when it was added.
statusbar.remove(context_id, message_id)

Menu Item Information

One useful role of the status bar is to give the user more information about the menu item the mouse cursor is currently hovering over. An example of this was shown in Figure 10-2, which is a screenshot of the progress bar pop-up menu application in Listing 10-4.

To implement status bar hints, you should connect each of your menu items to Gtk.Widget’s "enter-notify-event" and "leave-notify-event" signals. Listing 10-4 shows the progress bar pop-up menu application you have already learned about, except status bar hints are provided when the mouse cursor moves over a menu item.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppMenuItem(Gtk.MenuItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    def __setattr__(self, name, value):
        self.__dict__[name] = value
    def __getattr__(self, name):
        return self.__dict__[name]
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, -1)
        # Create all of the necessary widgets and initialize the pop-up menu. menu = Gtk.Menu.new()
        eventbox = Gtk.EventBox.new() progress = Gtk.ProgressBar.new()
        progress.set_text("Nothing Yet Happened")
        progress.set_show_text(True)
        statusbar = Gtk.Statusbar.new()
        self.create_popup_menu(menu, progress, statusbar)
        progress.set_pulse_step(0.05)
        eventbox.set_above_child(False)
        eventbox.connect("button_press_event", self.button_press_event, menu)
        eventbox.add(progress)
        vbox = Gtk.Box.new(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        vbox.pack_start(eventbox, False, True, 0)
        vbox.pack_start(statusbar, False, True, 0)
        self.add(vbox)
        eventbox.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        eventbox.realize()
    def create_popup_menu(self, menu, progress, statusbar):
        pulse = AppMenuItem(label="Pulse Progress")
        fill = AppMenuItem(label="Set as Complete")
        clear = AppMenuItem(label="Clear Progress")
        separator = Gtk.SeparatorMenuItem.new()
        pulse.connect("activate", self.pulse_activated, progress)
        fill.connect("activate", self.fill_activated, progress)
        clear.connect("activate", self.clear_activated, progress)
        Connect signals to each menu item for status bar messages. pulse.connect("enter-notify-event", self.statusbar_hint, statusbar) pulse.connect("leave-notify-event", self.statusbar_hint, statusbar) fill.connect("enter-notify-event", self.statusbar_hint, statusbar) fill.connect("leave-notify-event", self.statusbar_hint, statusbar) clear.connect("enter-notify-event", self.statusbar_hint, statusbar) clear.connect("leave-notify-event", self.statusbar_hint, statusbar) pulse.__setattr__("menuhint", "Pulse the progress bar one step.") fill.__setattr__("menuhint", "Set the progress bar to 100%.") clear.__setattr__("menuhint", "Clear the progress bar to 0%.") menu.append(pulse)
        menu.append(separator)
        menu.append(fill)
        menu.append(clear)
        menu.attach_to_widget(progress, None) menu.show_all()
    def button_press_event(self, eventbox, event, menu):
        if event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS: menu.popup(None, None, None, None, event.button, event.time) return True
        return False
    def pulse_activated(self, item, progress):
        progress.pulse()
        progress.set_text("Pulse!")
    def fill_activated(self, item, progress):
        progress.set_fraction(1.0)
        progress.set_text("One Hundred Percent")
    def clear_activated(self, item, progress): progress.set_fraction(0.0) progress.set_text("Reset to Zero")
    def statusbar_hint(self, menuitem, event, statusbar): id = statusbar.get_context_id("MenuItemHints")
        if event.type == Gdk.EventType.ENTER_NOTIFY:
            hint = menuitem.__getattr__("menuhint")
            id = statusbar.push(id, hint)
        elif event.type == Gdk.EventType.LEAVE_NOTIFY:
            statusbar.pop(id)
        return False
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Pop-up Menus")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-4

Displaying More Information About a Menu Item

When implementing status bar hints, you first need to figure out what signals are necessary. We want to be able to add a message to the status bar when the mouse cursor moves over the menu item and remove it when the mouse cursor leaves. From this description, using "enter-notify-event" and "leave-notify-event" is a good solution.

Since the GTK+ 3 interface to Python 3 does not implement the get_data() and set_data() methods on GTK+ objects, we need to subclass the Gtk.MenuItem class to implement the corresponding Python 3 attributes. This methodology is used on some other examples in this book as well.

One advantage of using these two signals is that we only need one callback function, because the prototype for each receives a Gdk.EventProximity object. From this object, we can discern between Gdk.EventType.ENTER_NOTIFY and Gdk.EventType.LEAVE_NOTIFY events. You want to return False from the callback function, because you do not want to prevent GTK+ from handling the event; you only want to enhance what is performed when it is emitted.

Within the statusbar_hint() callback method, you should first retrieve a context identifier for the menu item messages. You can use whatever string you want, as long as your application remembers what was used. Listing 10-4 described all the menu item messages added to the status bar. If other parts of the application used the status bar, using a different context identifier would leave the menu item hints untouched.
id = statusbar.get_context_id("MenuItemHints")
If the event type is Gdk.EventType.ENTER_NOTIFY, you need to show the message to the user. In the create_popup_menu() method, a data parameter was added to each menu item called "menuhint". This is a more in-depth description of what the menu item does, which is displayed to the user.
hint = menuitem.__getattr__("menuhint")
statusbar.push(id, hint)

Then, with statusbar.push(), the message can be added to the status bar under the "MenuItemHints" context identifier. This message is placed on the top of the stack and displayed to the user. You may want to consider processing all GTK+ events after calling this method, since the user interface should reflect the changes immediately.

However, if the event type is Gdk.EventType.LEAVE_NOTIFY, you need to remove the last menu item message that was added with the same context identifier. The most recent message can be removed from the stack with statusbar.pop().

Menu Items

Thus far, you have learned about flat menus that display label and separator menu items. It is also possible to add a submenu to an existing menu item. GTK+ also provides a number of other Gtk.MenuItem objects. Figure 10-3 shows a pop-up menu that contains a submenu along with image, check, and radio menu items.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig3_HTML.jpg
Figure 10-3

Image, check, and radio menu items

Submenus

Submenus in GTK+ are not created by a separate type of menu item widget but by calling menuitem.set_submenu(). This method calls menu.attach_to_widget() to attach the submenu to the menu item and places an arrow beside the menu item to show that it now has a submenu. If the menu item already has a submenu, it is replaced with the given Gtk.Menu widget.
menuitem.set_submenu(submenu)

Submenus are very useful if you have a list of very specific options that would clutter an otherwise organized menu structure. When using a submenu, you can use the “activate-item” signal provided by the Gtk.MenuItem widget, which is emitted when the menu item displays its submenu.

In addition to Gtk.MenuItem and menu item separators, there are three other types of menu item objects: image, check, and radio menu items; these are covered in the remainder of this section.

Image Menu Items

Warning

The Gtk.ImageMenuItem class has been deprecated since GTK+ 3.1. Do not use it in new code and be aware it could disappear completely in a newer version of GTK+.

Gtk.ImageMenuItem is very similar to its parent class Gtk.MenuItem except it shows a small image to the left of the menu item label. There are four functions provided for creating a new image menu item.

The first function, imagemenuitem.new() creates a new Gtk.ImageMenuItem object with an empty label and no associated image. You can use image menu item’s image property to set the image displayed by the menu item.
Gtk.ImageMenuItem.new()
Additionally, you can create a new image menu item from a stock identifier with Gtk.ImageMenuItem.new_from_stock(). This function creates the Gtk.ImageMenuItem with the label and image associated with stock_id. This function accepts stock identifier strings.
Gtk.ImageMenuItem.new_from_stock(stockid, accel_group)

The second parameter of this function accepts an accelerator group, which is set to the default accelerator of the stock item. If you want to manually set the keyboard accelerator for the menu item as we did in Listing 10-3, you can specify None for this parameter.

Also, you can use Gtk.ImageMenuItem.new_with_label() to create a new Gtk.ImageMenuItem initially with only a label. Later, you can use the image property to add an image widget. GTK+ also provided the method imagemenuitem.set_image(), which allows you to edit the image property of the widget.
Gtk.ImageMenuItem.new_with_label(label)

Also, GTK+ provides Gtk.ImageMenuItem.new_with_mnemonic(), which creates an image menu item with a mnemonic label. As with the previous method, you have to set the image property after the menu item is created.

Check Menu Items

Gtk.CheckMenuItem allows you to create a menu item that displays a check symbol beside the label, depending on whether its Boolean active property is True or False. This would allow the user to view whether an option is activated or deactivated.

As with Gtk.MenuItem, three initialization functions are provided.

Gtk.CheckMenuItem.new(), Gtk.CheckItem.new_with_label(), and Gtk.CheckMenuItem.new_with_mnemonic(). These functions create a Gtk.CheckMenuItem with no label, with an initial label, or with a mnemonic label, respectively.
Gtk.CheckMenuItem.new()
Gtk.CheckMenuItem.new_with_label(label)
Gtk.CheckMenuItem.new_with_mnemonic(label)

As previously stated, the current state of the check menu item is held by the active property of the widget. GTK+ provides two functions, checkmenuitem.set_active() and checkmenuitem.get_active() to set and retrieve the active value.

As with all check button widgets, you are able to use the "toggled" signal, which is emitted when the user toggles the state of the menu item. GTK+ takes care of updating the state of the check button, so this signal is simply to allow you to update your application to reflect the changed value.

Gtk.CheckMenuItem also provides checkmenuitem.set_inconsistent(), which alters the inconsistent property of the menu item. When set to True, the check menu item displays a third “in between” state that is neither active nor inactive. This can show the user that a choice must be made that has yet to be set or that the property is both set and unset for different parts of a selection.

Radio Menu Items

Gtk.RadioMenuItem is a widget derived from Gtk.CheckMenuItem. It is rendered as a radio button instead of a check button by setting check menu item’s draw-as-radio property to True. Radio menu items work the same way as normal radio buttons.

The first radio button should be created with one of the following functions. You can set the radio button group to None, since requisite elements are added to the group by referencing the first element. These functions create an empty menu item, a menu item with a label, and a menu item with a mnemonic, respectively.
Gtk.RadioMenuItem.new(group)
Gtk.RadioMenuItem.new_with_label(group, text)
Gtk.RadioMenuItem.new_with_mnemonic(group, text)
All other radio menu items should be created with one of the following three functions, which add it to the radio button group associated with group. These functions create an empty menu item, a menu item with a label, and a menu item with a mnemonic, respectively.
Gtk.RadioMenuItem.new_from_widget(group)
Gtk.RadioMenuItem.new_from_widget_with_label(group, text)
Gtk.RadioMenuItem.new_from_widget_with_mnemonic(group, text)

Menu Bars

Gtk.MenuBar is a widget that organizes multiple pop-up menus into a horizontal or vertical row. Each root element is a Gtk.MenuItem that pops down into a submenu. An instance of Gtk.MenuBar is usually displayed along the top of the main application window to provide access to functionality provided by the application. An example menu bar is shown in Figure 10-4.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig4_HTML.jpg
Figure 10-4

Menu bar with three menus

In Listing 10-5, a Gtk.MenuBar widget is created with three menus: File, Edit, and Help. Each of the menus is actually a Gtk.MenuItem with a submenu. A number of menu items are then added to each submenu.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_size_request(250, -1)
        menubar = Gtk.MenuBar.new()
        file = Gtk.MenuItem.new_with_label("File")
        edit = Gtk.MenuItem.new_with_label("Edit")
        help = Gtk.MenuItem.new_with_label("Help")
        filemenu = Gtk.Menu.new()
        editmenu = Gtk.Menu.new()
        helpmenu = Gtk.Menu.new()
        file.set_submenu(filemenu)
        edit.set_submenu(editmenu)
        help.set_submenu(helpmenu)
        menubar.append(file)
        menubar.append(edit)
        menubar.append(help)
        # Create the File menu content.
        new = Gtk.MenuItem.new_with_label("New")
        open = Gtk.MenuItem.new_with_label("Open")
        filemenu.append(new)
        filemenu.append(open)
        # Create the Edit menu content.
        cut = Gtk.MenuItem.new_with_label("Cut")
        copy = Gtk.MenuItem.new_with_label("Copy")
        paste = Gtk.MenuItem.new_with_label("Paste")
        editmenu.append(cut)
        editmenu.append(copy)
        editmenu.append(paste)
        # Create the Help menu content.
        contents = Gtk.MenuItem.new_with_label("Help")
        about = Gtk.MenuItem.new_with_label("About")
        helpmenu.append(contents)
        helpmenu.append(about)
    self.add(menubar)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Menu Bars")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-5

Creating Groups of Menus

New Gtk.MenuBar widgets are created with Gtk.MenuBar.new(). This creates an empty menu shell into which you can add content.

After you create the menu bar, you can define the pack direction of the menu bar items with menubar.set_pack_direction(). Values for the pack_direction property are defined by the Gtk.PackDirection enumeration and include Gtk.PackDirection.LTR, Gtk.PackDirection.RTL, Gtk.PackDirection.TTB, or Gtk.PackDirection.BTT. These pack the menu items from left to right, right to left, top to bottom, or bottom to top, respectively. By default, child widgets are packed from left to right.

Gtk.MenuBar also provides another property called child-pack-direction, which sets what direction the menu items of the menu bar’s children are packed. In other words, it controls how submenu items are packed. Values for this property are also defined by the Gtk.PackDirection enumeration.

Each child item in the menu bar is actually a Gtk.MenuItem widget. Since Gtk.MenuBar is derived from Gtk.MenuShell, you can use the menuitem.append() method to add an item to the bar as shown in the following line.
menubar.append(file)

You can also use file.prepend() or file.insert() to add an item to the beginning or in an arbitrary position of the menu bar.

You next need to call file.set_submenu() to add a submenu to each of the root menu items. Each of the submenus is a Gtk.Menu widget created in the same way as pop-up menus. GTK+ then takes care of showing submenus to the user when necessary.
file.set_submenu(filemenu)

Toolbars

A Gtk.Toolbar is a type of container that holds a number of widgets in a horizontal or vertical row. It is meant to allow easy customization of a large number of widgets with very little trouble. Typically, toolbars hold tool buttons that can display an image along with a text string. However, toolbars are actually able to hold any type of widget. A toolbar holding four tool buttons and a separator is shown in Figure 10-5.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig5_HTML.jpg
Figure 10-5

A toolbar showing both images and text

In Listing 10-6, a simple toolbar is created that shows five tool items in a horizontal row. Each toolbar item displays an icon and a label that describes the purpose of the item. The toolbar is also set to display an arrow that provides access to toolbar items that do not fit in the menu.

In this example, a toolbar provides cut, copy, paste, and select-all functionality to a Gtk.Entry widget. The AppWindow() method creates the toolbar, packing it above the Gtk.Entry. It then calls create_toolbar(), which populates the toolbar with tool items and connects the necessary signals.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        toolbar = Gtk.Toolbar.new()
        entry = Gtk.Entry.new()
        vbox.pack_start(toolbar, True, False, 0)
        vbox.pack_start(entry, True, False, 0)
        self.create_toolbar(toolbar, entry)
        self.add(vbox)
        self.set_size_request(310, 75)
    def create_toolbar(self, toolbar, entry): icon_theme = Gtk.IconTheme.get_default()
        icon = icon_theme.load_icon("edit-cut", -1,
                                    Gtk.IconLookupFlags.FORCE_SIZE)
        image = Gtk.Image.new_from_pixbuf(icon)
        cut = Gtk.ToolButton.new(image, "Cut")
        icon = icon_theme.load_icon("edit-copy", -1,
        Gtk.IconLookupFlags.FORCE_SIZE)
        image = Gtk.Image.new_from_pixbuf(icon)
        copy = Gtk.ToolButton.new(image, "Copy")
        icon = icon_theme.load_icon("edit-paste", -1,
                                    Gtk.IconLookupFlags.FORCE_SIZE)
        image = Gtk.Image.new_from_pixbuf(icon)
        paste = Gtk.ToolButton.new(image, "Paste")
        icon = icon_theme.load_icon("edit-select-all", -1, Gtk.IconLookupFlags.FORCE_SIZE)
        image = Gtk.Image.new_from_pixbuf(icon)
        selectall = Gtk.ToolButton.new(image, "Select All")
        separator = Gtk.SeparatorToolItem.new()
        toolbar.set_show_arrow(True)
        toolbar.set_style(Gtk.ToolbarStyle.BOTH)
        toolbar.insert(cut, 0)
        toolbar.insert(copy, 1)
        toolbar.insert(paste, 2)
        toolbar.insert(separator, 3)
        toolbar.insert(selectall, 4)
        cut.connect("clicked", self.cut_clipboard, entry)
        copy.connect("clicked", self.copy_clipboard, entry)
        paste.connect("clicked", self.paste_clipboard, entry)
        selectall.connect("clicked", self.select_all, entry)
    def cut_clipboard(self, button, entry):
        entry.cut_clipboard()
    def copy_clipboard(self, button, entry):
        entry.copy_clipboard()
    def paste_clipboard(self, button, entry):
        entry.paste_clipboard()
    def select_all(self, button, entry):
        entry.select_region(0, -1)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Toolbar")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-6

Creating a Gtk.Toolbar Widget

New toolbars are created with Gtk.Toolbar.new(), which was called before the create_toolbar() function shown in Listing 10-6. This creates an empty Gtk.Toolbar widget in which you can add tool buttons.

Gtk.Toolbar provides a number of properties for customizing how it appears and interacts with the user, including the orientation, button style, and the ability to give access to items that do not fit in the toolbar.

If all of the toolbar items cannot be displayed on the toolbar because there is not enough room, then an overflow menu appears if you set toolbar.set_show_arrow() to True. If all of the items can be displayed on the toolbar, the arrow is hidden from view.
toolbar.set_show_arrow(boolean)
Another Gtk.Toolbar property is the style by which all of the menu items are displayed, which is set with toolbar.set_style(). You should note that this property could be overridden by the theme, so you should provide the option of using the default style by calling toolbar.unset_style(). There are four toolbar styles, which are defined by the Gtk.ToolbarStyle enumeration.
  • Gtk.ToolbarStyle.ICONS: Show only icons for each tool button in the toolbar.

  • Gtk.ToolbarStyle.TEXT: how only labels for each tool button in the toolbar.

  • Gtk.ToolbarStyle.BOTH: Show both icons and labels for each tool button, where the icon is located above its label.

  • Gtk.ToolbarStyle.BOTH_HORIZ: Show both icons and labels for each tool button, where the icon is to the left of the label. The label text of a tool item is only shown if the "is-important" property for the item is set to True.

Another important property of the toolbar is the orientation that can be set with toolbar.set_orientation(). There are two possible values defined by the Gtk.Orientation enumeration, Gtk.Orientation.HORIZONTAL and Gtk.Orientation.VERTICAL, which can make the toolbar horizontal (default) or vertical.

Toolbar Items

Listing 10-6 introduces three important tool item types: Gtk.ToolItem, Gtk.ToolButton, and Gtk.SeparatorToolItem. All tool buttons are derived from the Gtk.ToolItem class, which holds basic properties that are used by all tool items.

If you are using the Gtk.ToolbarStyle.BOTH_HORIZ style, then an essential property installed in Gtk.ToolItem is the "is-important" setting. The label text of the toolbar item is only shown for this style if this property is set to True.

As with menus, separator tool items are provided by Gtk.SeparatorToolItem and are created with Gtk.SeparatorToolItem.new(). Separator tool items have a draw property, which draws a separator when set to True. If you set draw to False, it places padding at its location without any visual separator.

Tip

If you set the expand property of a Gtk.SeparatorToolItem to True and its draw property to False, you force all tool items after the separator to the end of the toolbar.

Most toolbar items are of the type Gtk.ToolButton. Gtk.ToolButton provides only the single initialization method Gtk.ToolButton.new() as all other initialization methods have been deprecated since GTK+ 3.1. Gtk.ToolButton.new() can create a Gtk.ToolButton with a custom icon and label. Each of these properties can be set to None.
Gtk.ToolButton.new(icon, label)

It is possible to manually set the label and icon after initialization with toolbutton.set_label() and toolbutton.set_icon_widget(). These functions provide access to tool button’s label and icon-widget properties.

Additionally, you can define your own widget to use instead of the default Gtk.Label widget of the tool button with toolbutton.set_label_widget(). This allows you to embed an arbitrary widget, such as an entry or combo box, into the tool button. If this property is set to None, the default label is used.
toolbutton.set_label_widget(label_widget)
After you create the toolbar items, you can insert each Gtk.ToolItem into the toolbar with toolbar.insert().
toolbar.insert(item, pos)

The second parameter of toolbar.insert() accepts the position to insert the item into the toolbar. Tool button positions are indexed from zero. A negative position appends the item to the end of the toolbar.

Toggle Tool Buttons

Gtk.ToggleToolButton is derived from Gtk.ToolButton, and therefore only implements initialization and toggle abilities. Toggle tool buttons provide the functionality of a Gtk.ToggleButton widget in the form of a toolbar item. It allows the user to view whether the option is set or unset.

Toggle tool buttons are tool buttons that remain depressed when the active property is set to True. You can use the toggled signal to receive notification when the state of the toggle button has been changed.

There is only one way to create a new Gtk.ToggleToolButton. This is with Gtk.ToggleToolButton.new(), which creates an empty tool button. You can then use the methods provided by Gtk.ToolButton to add a label and image.
Gtk.ToggleToolButton.new()

Radio Tool Buttons

Gtk.RadioToolButton is derived from Gtk.ToggleToolButton, so it inherits the “active” property and "toggled" signal. Therefore, the widget only needs to give a way for you to create new radio tool buttons and add them to a radio group.

A radio tool button should be created with Gtk.RadioToolButton.new(), where the radio group is set to None. This creates a default initial radio group for the radio tool button.
Gtk.RadioToolButton.new(group)

Gtk.RadioToolButton inherits functions from Gtk.ToolButton, which provides functions and properties that can then set the label of the radio tool button, if necessary.

All requisite elements should be created with Gtk.RadioToolButton.from_widget(). Setting group as the first radio tool button adds all requisite items added to the same group.
Gtk.RadioToolButton.new_from_widget(group)

Gtk.RadioToolButton provides one property, group, which is another radio tool button that belongs to the radio group. This allows you to link all of the radio buttons together so that only one is selected at a time.

Menu Tool Buttons

Gtk.MenuToolButton, derived from Gtk.ToggleToolButton, allows you to attach a menu to a tool button. The widget places an arrow beside the image and label that provides access to the associated menu. For example, you could use Gtk.MenuToolButton to add a list of recently opened files to a toolbar button. Figure 10-6 is a screenshot of a menu tool button that is used for this purpose.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig6_HTML.jpg
Figure 10-6

A menu tool button showing recently opened files

Listing 10-7 shows you how to implement a menu tool button. The actual tool button is created in a similar way as any other Gtk.ToolButton except there is an extra step of attaching a menu to the Gtk.MenuToolButton widget.
recent = Gtk.Menu.new()
# Add a number of menu items where each corresponds to one recent file. icon_theme = Gtk.IconTheme.get_default()
icon = icon_theme.load_icon("document-open", -1,
Gtk.IconLookupFlags.FORCE_SIZE)
image = Gtk.Image.new_from_pixbuf(icon)
open = Gtk.MenuToolButton.new(image, "Open")
open.set_menu(recent)
Listing 10-7

Using Gtk.MenuToolButton

In Listing 10-7, the menu tool button was created with an image and a label with Gtk.MenuToolButton.new(image, label). You can set either of these parameters to None if you want to set them at a later time using Gtk.ToolButton properties.
Gtk.MenuToolButton.new(image, label)

What makes Gtk.MenuToolButton unique is that an arrow to the right of the tool button provides the user with access to a menu. The tool button’s menu is set with menutoolbutton.set_menu() or by setting the menu property to a Gtk.Menu widget. This menu is displayed to the user when the arrow is clicked.

Dynamic Menu Creation

Note

The Gtk.UIManager was deprecated in GTK+ 3.1 so the creation and loading of UI files are not covered in this section. Instead, the new Gtk.Builder class and its associated XML files are covered. Gtk.Builder is a more powerful and flexible system for managing external user interface descriptions and actions. It also provides addition capabilities and reduces the amount of work needed to create and manage user interfaces.

While it is possible to manually create every menu and toolbar item, doing so can take up a large amount of space and cause you to have to code monotonously for longer than necessary. To automate menu and toolbar creation, GTK+ allows you to dynamically create menus from XML files.

The Gtk.Builder class can create many user interface objects, including menus, menu bars, pop-up menus, entire dialogs, main windows, and many others. This section concentrates on different types of menus, but you should keep in mind that Gtk.Builder can build many other kinds of user interface objects.

Creating XML Files

User interface files are constructed in XML format. All of the content has to be contained between <interface> and </interface> tags. One type of dynamic UI that you can create is a Gtk.Menu with the <menu> tag shown in Listing 10-8.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="menubar">
    <submenu>
      <attribute name="label">File</attribute>
    </submenu>
    <submenu>
      <attribute name="label">Edit</attribute>
    </submenu>
    <submenu>
      <attribute name="label">Choices</attribute>
    </submenu>
    <submenu>
      <attribute name="label">Help</attribute>
    </submenu>
  </menu>
</interface>
Listing 10-8

Menu UI File

Every menu and item tag should have a unique ID associated with it so that you can access that item directly from your code. While not necessary, you should always add the name property to every menu and item. The name property can access the actual widget.

Each <menu> can have any number of <item> children. Both of these tags must be closed according to normal XML rules. If a tag does not have a closing tag (e.g., <menu/>), you must place a forward slash character (/) at the end of the tag so the parser knows the tag has ended.

Each <menu> and <item> tags can have other children as well, such as the <section> and <attribute> tags. The <section> tag organizes <item> tags. The <attribute> tags are used to describe (i.e., add properties) to both <menu> and <item> tags.

The <attribute> tag has multiple purposes but one purpose common to all <item> tags is the one containing the label property. This property supplies the label string that is visible on the <item>. In this case, the <item> tags correspond to a Gtk.MenuItem label attribute specifies the string that appears in the menu item.

Another <attribute> tag that appears with each <item> tags is the action attribute. This tag specifies the action to be taken when the <item> is clicked. The action specified is closely tied to the Gtk.Application and the Gtk.ApplicationWindow class (or their subclasses). The target of each action specifies which class instance—the Gtk.ApplicationWindow or the Gtk.Application—creates the Gio.SimpleAction and connects it to a method in the same class instance for processing the action. You can think of the <attribute> action tag as a kind of signal name that is an alias for the real signal to be processed.

The action attribute is applied to all elements except top-level widgets and separators. When loading the UI file to associate a Gtk.Action object to each element, Gtk.Builder uses the action attributes. Gtk.Action holds information about how the item is drawn and what callback method should be called, if any, when the item is activated.

Separators can be placed in a menu with the <separator/> tag. You do not need to provide name or action information for separators, because a generic Gtk.SeparatorMenuItem is added.

In addition to menu bars, you can create toolbars in a UI file with the <toolbar> tag, as shown in Listing 10-9.
<?xml version='1.0' encoding='utf-8' ?>
<interface>
<requires lib='gtk+' version='3.4'/>
<object class="GtkToolbar" id="toolbar">
  <property name="visible">True</property>
  <property name="can_focus">False</property>
  <child>
    <object class="GtkToolButton" id="toolbutton_new">
      <property name="visible">True</property> <property name="can_focus">False</property>
      <property name="tooltip_text" translatable="yes">New Standard</property> <property name="action_name">app.newstandard</property>
      <property name="icon_name">document-new</property>
    </object>
    <packing>
    <property name="expand">False</property> <property name="homogeneous">True</property>
    </packing>
  </child>
  <child>
    <object class="GtkToolButton" id="toolbutton_quit"> <property name="visible">True</property> <property name="can_focus">False</property>
      <property name="tooltip_text" translatable="yes">Quit</property> <property name="action_name">app.quit</property>
      <property name="icon_name">application-exit</property> </object>
      <packing>
        <property name="expand">False</property>
        <property name="homogeneous">True</property>
      </packing>
</child>
</object>
</interface>
Listing 10-9

Toolbar UI File

Each toolbar can contain any number of <toolitem> elements. Tool items are specified in the same manner as menu items, with an action ("action") and an ID. You can use the ID for elements in separate UI files, but you should not use the same names if, for example, the toolbar and menu bar are located in the same file.

However, you can and should use the same action for multiple elements. This causes each element to be drawn in the same way and to be connected to the same callback method. The advantage of this is that you need to define only one Gtk.Action for each item type. For example, the same action is used for the Cut element in the UI files in Listing 10-8 through 10-10.

Tip

While the toolbar, menu bar, and pop-up menu were split into separate UI files, you can include as many of these widgets as you want in one file. The only requirement is that the whole file content is contained between the <interface> and </interface> tags.

In addition to toolbars and menu bars, it is possible to define pop-up menus in a UI file, as illustrated in Listing 10-10. Notice that there are repeated actions in Listing 10-8, Listing 10-9, and Listing 10-10. Repeating actions allows you to define only a single Gtk.Action object instead of separate objects for each instance of an action.
<?xml version='1.0' encoding='utf-8' ?>
<interface>
  <menu id="app-menu">
    <section>
        <item>
            <attribute name="label">About</attribute> <attribute name="action">app.about</attribute>
        </item>
        <item>
            <attribute name="label">Quit</attribute> <attribute name="action">app.quit</attribute>
        </item>
    </section>
  </menu>
</interface>
Listing 10-10

Pop-up UI File

The last type of top-level widget supported by UI files is the pop-up menu, denoted by the <popup> tag. Since a pop-up menu is the same thing as a normal menu, you can still use <menuitem> elements as children.

Loading XML Files

After you create your UI files, you need to load them into your application and retrieve the necessary widgets. To do this, you need to utilize the functionality provided by Gtk.ActionGroup and Gtk.Builder.

Gtk.ActionGroup is a set of items with name, stock identifier, label, keyboard accelerator, tooltip, and callback methods. The name of the each action can be set to an action parameter from a UI file to associate it with a UI element.

Gtk.Builder is a class that allows you to dynamically load one or more user interface definitions. It automatically creates an accelerator group based on associated action groups and allows you to reference widgets based on the "ID" parameter from the UI file.

In Listing 10-11, Gtk.UIManager loads the menu bar and toolbar from the UI files in Listing 10-10. The resulting application is shown in Figure 10-7.
../images/142357_2_En_10_Chapter/142357_2_En_10_Fig7_HTML.jpg
Figure 10-7

A menu bar and a toolbar that are dynamically loaded

Each of the menu and tool items in the application are connected to empty callback methods, because this example is only meant to show you how to dynamically load menus and toolbars from UI definitions. You implement callback methods with actual content in the two exercises found at the end of this chapter.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    def change_label(self):
        pass
    def maximize(self):
        pass
    def about(self):
        pass
    def quit(self):
        self.destroy()
    def newstandard(self):
        pass
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Hello World!")
            builder = Gtk.Builder()
            builder.add_from_file("./Menu_XML_File.ui")
            builder.add_from_file("./Toolbar_UI_File.xml")
            builder.connect_signals(self.window)
            self.set_menubar(builder.get_object("menubar"))
            self.window.add(builder.get_object("toolbar"))
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 10-11

Loading a Menu with Gtk.Builder

Test Your Understanding

The following two exercises give an overview of what you have learned about menus and toolbars throughout the chapter.

In addition to completing them, you may want to create examples of pop-up menus with other widgets that do not support them by default. Also, after finishing both of these exercises, you should expand them by creating your own stock icons that are used in place of the default items.

Exercise 1: Toolbars

In Chapter 8, you created a simple text editor using the Gtk.TextView widget. In this exercise, expand on that application and provide a toolbar for actions instead of a vertical box filled with Gtk.Button widgets.

Although manual toolbar creation is possible, in most applications, you want to utilize the Gtk.Builder method for toolbar creation. Therefore, use that method in this exercise. You should also create your own with Gtk.IconFactory.

Oftentimes, it is advantageous for an application to provide the toolbar as a child of a handle box. Do this for your text editor, placing the toolbar above the text view. Also, set up the toolbar so that the textual descriptor is shown below every tool button.

This first exercise taught you how to build your own toolbars. It also showed you how to use the Gtk.HandleBox container. In the next exercise, you reimplement the Text Editor application with a menu bar.

Exercise 2: Menu Bars

In this exercise, implement the same application as in Exercise 1, except use a menu bar this time. You should continue to use Gtk.Builder, but the menu does not need to be contained by a Gtk.HandleBox.

Since tooltips are not shown for menu items automatically, use a status bar to provide more information about each item. The menu bar should contain two menus: File and Edit. You should also provide a Quit menu item in the File menu.

Summary

In this chapter, you learned two methods for creating menus, toolbars, and menu bars. The first method was the manual method, which was more difficult but introduced you to all of the necessary widgets.

The first example showed you how to use basic menu items to implement a pop-up menu for a progress bar. This example was expanded on to provide keyboard accelerators and more information to the user with the Gtk.Statusbar widget. You also learned about submenus as well as image, toggle, and radio menu items.

The next section showed you how to use menu items with submenus to implement a menu bar with a Gtk.MenuShell. This menu bar could be displayed horizontally or vertically and forward or backward.

Toolbars are simply a horizontal or vertical list of buttons. Each button contains an icon and label text. You learned about three additional types of toolbar buttons: toggles, radio buttons, and tool buttons with a supplemental menu.

Then, after much hard work, you were taught how to create dynamically loadable menus. Each menu or toolbar is held in a UI definition file, which is loaded by the Gtk.Builder class. The Builder associates each object with the appropriate action and creates the widgets according to the UI definition.

Last, you learned how to create your own custom icons. It is necessary to create your own icons, because arrays of actions require an identifier to add an icon to an action.

In the next chapter, we are going to take a short break from coding and cover the design of graphical user interfaces with the Glade user interface builder. This application creates user interface XML files, which can be dynamically loaded when your application starts. You then learn how to handle these files programmatically with Gtk.Builder.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.232.11