© 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_6

6. Dialogs

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

This chapter introduces you to a special type of window called a dialog. Dialogs are windows that supplement the top-level window. The dialog is provided by Gtk.Dialog , a child class of Gtk.Window, extended with additional functionality. This means that it is possible to implement your entire interface in one or more dialogs, while leaving the main window hidden.

You can do anything with a dialog, such as display a message or prompt the user to select an option. Their purpose is to enhance user experience by providing some type of transient functionality.

In the first part of the chapter, you learn how to use Gtk.Dialog to create your own custom dialogs. The next section introduces the large number of built-in dialogs provided by GTK+. Lastly, you learn about a widget called Gtk.Assistant that allows you to create dialogs with multiple pages; assistants are meant to help the user through a multistage process.

In this chapter, you learn the following.
  • How to create your own custom dialogs using the Gtk.Dialog widget

  • How to give general information, error messages, and warnings to the user with the Gtk.MessageDialog widget

  • How to provide information about your application with Gtk.AboutDialog

  • What types of file chooser dialogs are available

  • The ways to collect information with font and color selection dialogs

  • How to create dialogs with multiple pages using the Gtk.Assistant widget

Creating Your Own Dialogs

A dialog is a special type of Gtk.Window that supplements the top-level window. It can give the user a message, retrieve information from the user, or provide some other transient type of action.

Dialog widgets are split in half by an invisible horizontal separator. The top part is where you place the main part of the dialog’s user interface. The bottom half is called the action area, and it holds a collection of buttons. When clicked, each button emits a unique response identifier that tells the programmer which button was clicked.

In most ways, the dialog widget can be treated as a window, because it is derived from the Gtk.Window class. However, when you have multiple windows, a parent-child relationship should be established between the dialog and the top-level window when the dialog is meant to supplement the top-level window.
vbox = mydialog.get_content_area()

Gtk.Dialog provides access a vertical box that has the action area defined at bottom of the box. The content area has yet to be defined. To define it you begin packing widgets at start of the vertical box. Therefore you must always use the pack_start() to add widgets to a Gtk.Dialog class. Buttons can easily be added to the action area with the add_button(button_text, response_id) method call.

Note

It is possible to manually implement the functionality of Gtk.Dialog by creating a Gtk.Window with all of the same widgets and establishing window relationships with set_transient_for() in addition to other functions provided by Gtk.Window. Gtk.Dialog is simply a convenience widget that provides standard methods.

Both the action area and a separator are packed at the end of the dialog’s vertical box. The provided by Gtk.Box (vbox) holds all the dialog content. Because the action area is packed at the end, you should use pack_start() to add widgets to a Gtk.Dialog as follows.
vbox = mydialog.get_ac_area()
vbox.pack_start (child, expand, fill, padding)

By packing widgets at the start of the box, the action area and the separator always remains at the bottom of the dialog.

Creating a Message Dialog

One advantage of Gtk.Dialog is that, no matter how complex the content of your dialog is, the same basic concepts can be applied to every dialog. To illustrate this, we begin by creating a very simple dialog that gives the user a message. Figure 6-1 is a screenshot of this dialog.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig1_HTML.jpg
Figure 6-1

A message dialog created programmatically

Listing 6-1 creates a simple dialog that notifies the user when the clicked signal is emitted by the button. This functionality is provided by the Gtk.MessageDialog widget, which is covered in a later section 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)
        self.set_border_width(10)
        button = Gtk.Button.new_with_mnemonic("_Click Me")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
        self.set_size_request(150, 50)
    def on_button_clicked(self, button, parent):
        dialog = Gtk.Dialog(title="Information", parent=parent,
                             flags=Gtk.DialogFlags.MODAL)
        dialog.add_button("Ok", Gtk.ResponseType.OK)
        label = Gtk.Label("The button was clicked.")
        image = Gtk.Image.new_from_icon_name("dialog-information",
                                             Gtk.IconSize.DIALOG)
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        hbox.pack_start(image, False, False, 0)
        hbox.pack_start(label, False, False, 0)
        dialog.vbox.pack_start(hbox, False, False, 0)
        dialog.show_all()
        dialog.run()
        dialog.destroy()
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="Dialogs")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-1

Your First Custom Dialog

Creating the Dialog

The first thing you need to do when the button in the main window is clicked is create the Gtk.Dialog widget with Gtk.Dialog.new_with_buttons(). The first two parameters of this function specify the title of the dialog, a pointer to the parent window, and the modality flag.
dialog = Gtk.Dialog(title="Information", parent=parent, flags=Gtk.DialogFlags.MODA

The dialog is set as the transient window of the parent window, which allows the window manager to center the dialog over the main window and keep it on top if necessary. This can be achieved for arbitrary windows by calling window.set_transient_for(). You can also provide None if you do not want the dialog to have or recognize a parent window.

Next, you can specify one or more dialog flags. Options for this parameter are given by the Gtk.DialogFlags enumeration. There are three available values, which are shown in the following list.
  • Gtk.DialogFlags.MODAL: Force the dialog to remain in focus on top of the parent window until closed. The user is prevented from interacting with the parent.

  • Gtk.DialogFlags.DESTROY_WITH_PARENT: Destroy the dialog when the parent is destroyed, but do not force the dialog to be in focus. This creates a nonmodal dialog unless you call dialog.run().

  • Gtk.DialogFlags.USE_HEADER_BAR: Create a dialog with actions in the header bar instead of the action area.

In Listing 6-1, specifying Gtk.DialogFlags.MODAL created a modal dialog. It is not necessary to specify a title or parent window; the values can be set to None. However, you should always set the title, so it can be drawn in the window manager; otherwise, the user has difficulty choosing the desired window.

In Listing 6-1, an OK button with a response of Gtk.ResponseType.OK was added to the dialog.

In GTK+ 2.x, all dialogs placed a horizontal separator between the main content and the action area of the dialog by default. That separator has been deprecated in GTK+ 3.x.

After the child widgets are created, they need to be added to the dialog. As I previously stated, child widgets are added to the dialog by calling box.pack_start(). The dialog is packed as follows.
image = Gtk.Image.new_from_icon_name("dialog-information", Gtk.IconSize.DIALOG)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
hbox.pack_start(image, False, False, 0)
hbox.pack_start(label, False, False, 0)
dialog.vbox.pack_start(hbox, False, False, 0)

At this point, you need to show the dialog and its child widgets, because dialog.run()only calls dialog.show() on the dialog itself. To do this, call dialog.show_all() on the dialog. If you do not show the widgets, only the separator and action area is visible when dialog.run() is called.

Response Identifiers

When a dialog is fully constructed, one method of showing the dialog is by calling dialog.run(). This function returns an integer called a response identifier when complete. It also prevents the user from interacting with anything outside of the dialog until it is destroyed or an action area button is clicked.
dialog.run()

Internally, dialog.run() creates a new main loop for the dialog, which prevents you from interacting with its parent window until a response identifier is emitted or the user closes the dialog. Regardless of what dialog flags you set, the dialog is always modal when you call this method, because it calls dialog.set_modal().

If the dialog is manually destroyed by using a method provided by the window manager, Gtk.ResponseType.NONE is returned; otherwise, dialog.run() returns the response identifier referring to the button that was clicked. A full list of available response identifiers from the Gtk.ResponseType enumeration is shown in Table 6-1. You should always use the identifier’s available values instead of random integer values, since they could change in future versions of GTK+.
Table 6-1

Gtk.ResponseType Enumeration Values

Identifiers

Value

Description

Gtk.ResponseType.NONE

–1

Returned if an action widget has no response ID, or if the dialog is programmatically hidden or destroyed.

Gtk.ResponseType.APPLY

–10

Returned by Apply buttons in GTK+ dialogs.

Gtk.ResponseType.HELP

–11

Returned by Help buttons in GTK+ dialogs.

Gtk.ResponseType.REJECT

–2

Generic response ID, not used by GTK+ dialogs.

Gtk.ResponseType.ACCEPT

–3

Generic response ID, not used by GTK+ dialogs.

Gtk.ResponseType.DELETE_EVENT

–4

Returned if the dialog is deleted.

Gtk.ResponseType.OK

–5

Returned by OK buttons in GTK + dialogs.

Gtk.ResponseType.CANCEL

–6

Returned by Cancel buttons in GTK+ dialogs.

Gtk.ResponseType.CLOSE

–7

Returned by Close buttons in GTK+ dialogs.

Gtk.ResponseType.YES

–8

Returned by Yes buttons in GTK + dialogs.

Gtk.ResponseType.No

–9

Returned by No buttons in GTK+ dialogs.

Of course, when you create your own dialogs and when using many of the built-in dialogs covered in the next few pages, you are free to choose which response identifier to use. However, you should try to resist the urge to apply a Gtk.ResponseType.CANCEL identifier to an OK button, or some other type of absurdity along those lines.

Note

You are free to create your own response identifiers, but you should use positive numbers, since all of the built-in identifiers are negative. This allows you to avoid conflicts when more identifiers are added in future versions of GTK+.

After the dialog returns a response identifier, you need to make sure to call dialog.destroy(), or it will cause a memory leak. GTK+ makes sure all of the dialog’s children are destroyed, but you need to remember to initiate the process.

By calling dialog.destroy(), all of the parent’s children are destroyed and its reference count drops. When an object’s reference count reaches zero, the object is finalized, and its memory freed.

The Gtk.Image Widget

Listing 6-1 introduces another new widget called Gtk.Image. Images can be loaded in a wide variety of ways, but one advantage of Gtk.Image is that it displays the named image “image-missing” if the loading has failed. It is also derived from Gtk.Widget, so it can be added as a child of a container unlike other image objects, such as Gdk.Pixbuf.

In our example, new_from_icon_name() created the Gtk.Image widget from a named theme item.
image = Gtk.Image.new_from_icon_name("dialog-information", Gtk.IconSize.DIALOG)
When loading an image, you also need to specify a size for the image. GTK+ automatically looks for a stock icon for the given size and resizes the image to that size if none is found. Available size parameters are specified by the Gtk.IconTheme enumeration, as seen in the following list.
  • Gtk.IconSize.INVALID: Unspecified size

  • Gtk.IconSize.MENU: 16×16 pixels

  • Gtk.IconSize.SMALL_TOOLBAR: 18×18 pixels

  • Gtk.IconSize.LARGE_TOOLBAR: 24×24 pixels

  • Gtk.IconSize.BUTTON: 24×24 pixels

  • Gtk.IconSize.DND: 32×32 pixels

  • Gtk.IconSize.DIALOG: 48×48 pixels

As you can see, theme Gtk.Image objects are usually used for smaller images, such as those that appear in buttons, menus, and dialogs, since theme images are provided in a discrete number of standard sizes. In Listing 6-1, the image was set to Gtk.IconSize.DIALOG or 48×48 pixels.

Multiple initialization functions for Gtk.Image are provided, which are described in the API documentation, but new_from_file() and new_from_pixbuf() are especially important to future examples in this book.
Gtk.Image.new_from_file(filename)

Gtk.Image automatically detects the image type of the file specified to new_from_file(). If the image cannot be loaded, it displays a broken-image icon. Therefore, this function never returns a None object. Gtk.Image also supports animations that occur within the image file.

Calling new_from_pixbuf() creates a new Gtk.Image widget out of a previously initialized Gdk.Pixbuf. Unlike new_from_file(), you can use this function to easily figure out whether the image is successfully loaded since you first have to create a Gdk.Pixbuf.
Gdk.Image.new_from_pixbuf(pixbuf)

You need to note that the Gtk.Image creates its own references to the Gdk.Pixbuf, so you need to release your reference to the object if it should be destroyed with the Gtk.Image.

Nonmodal Message Dialog

By calling dialog.run(), your dialog is always set as modal, which is not always desirable. To create a nonmodal dialog, you need to connect to Gtk.Dialog’s response signal.

In Listing 6-2, the message dialog from Figure 6-1 is reimplemented as a nonmodal dialog. You should try clicking the button in the main window multiple times in a row. This shows how you can not only create multiple instances of the same dialog but also access the main window from a nonmodal dialog.
#!/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_border_width(10)
        button = Gtk.Button.new_with_mnemonic("_Click Me")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
        self.set_size_request(150, 50)
        self.show_all()
    def on_button_clicked(self, button, parent):
        dialog = Gtk.Dialog(title="Information", parent=parent)
        dialog.add_button("Ok", Gtk.ResponseType.OK)
        label = Gtk.Label("The button was clicked.")
        image = Gtk.Image.new_from_icon_name("dialog-information",
                                               Gtk.IconSize.DIALOG)
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        hbox.pack_start(image, False, False, 0)
        hbox.pack_start(label, False, False, 0)
        dialog.vbox.pack_start(hbox, False, False, 0)
        dialog.connect("response", self.on_dialog_button_clicked)
        dialog.show_all()
    def on_dialog_button_clicked(self, dialog, response):
        dialog.destroy()
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="Dialogs")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-2

A Nonmodal Message Dialog

Creating a nonmodal dialog is very similar to the previous example, except you do not want to call dialog.run(). By calling this function, a modal dialog is created by blocking the parent window’s main loop regardless of the dialog flags.

Tip

You can still create a modal dialog without using dialog.run() by setting the Gtk.DialogFlags.MODAL flag. You can then connect to the response signal. This function simply provides a convenient way to create modal dialogs and handle response identifiers within one function.

By connecting to Gtk.Dialog’s response signal, you can wait for a response identifier to be emitted. By using this method, the dialog is not automatically unreferenced when a response identifier is emitted. The response callback method receives the dialog, the response identifier that was emitted, and the optional data parameter.

One of the most important decisions you have to make when designing a dialog is whether it is modal or nonmodal. As a rule of thumb, if the action needs to be completed before the user can continue working with the application, then the dialog should be modal. Examples of this would be message dialogs, dialogs that ask the user a question, and dialogs to open a file.

If there is no reason why the user cannot continue working while the dialog is open, you should use a nonmodal dialog. You also need to remember that multiple instances of nonmodal dialogs can be created unless you prevent this programmatically, so dialogs that must have only one instance should be created as modal.

Another Dialog Example

Now that you have created a simple message dialog from scratch, it is time to produce a more complex dialog. In Listing 6-3, a few pieces of basic information about the user are propagated using Python’s utility functions. A dialog, which is shown in Figure 6-2, allows you to edit each piece of information.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig2_HTML.jpg
Figure 6-2

A simple Gtk.Dialog widget

This information is, of course, not actually changed within the user’s system; the new text is simply output to the screen. This example illustrates the fact that, regardless of the complexity of the dialog, the basic principles of how to handle response identifiers are still the only ones that are necessary.

You could easily implement this as a nonmodal dialog as well, although this would not be of much use since the dialog itself is the application’s top-level window.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import os
import getpass
import socket
import pwd
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        button = Gtk.Button.new_with_mnemonic("_Click Me")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
        self.set_size_request(180, 50)
        self.show_all()
    def on_button_clicked(self, button, parent):
        dialog = Gtk.Dialog(title="Edit User Information",
                            parent=parent, flags=Gtk.DialogFlags.MODAL)
        dialog.add_button("Ok", Gtk.ResponseType.OK)
        dialog.add_button("Cancel", Gtk.ResponseType.CANCEL)
        dialog.set_default_response(Gtk.ResponseType.OK)
        lbl1 = Gtk.Label("User Name:")
        lbl2 = Gtk.Label("Real Name:")
        lbl3 = Gtk.Label("Home Dir:")
        lbl4 = Gtk.Label("Host Name:")
        user = Gtk.Entry()
        real_name = Gtk.Entry()
        home = Gtk.Entry()
        host = Gtk.Entry()
        user.set_text(getpass.getuser())
        real_name.set_text(pwd.getpwuid(os.getuid())[4])
        home.set_text(os.environ['HOME'])
        host.set_text(socket.gethostname())
        grid = Gtk.Grid()
        grid.attach(lbl1, 0, 0, 1, 1)
        grid.attach(lbl2, 0, 1, 1, 1)
        grid.attach(lbl3, 0, 2, 1, 1)
        grid.attach(lbl4, 0, 3, 1, 1)
        grid.attach(user, 1, 0, 1, 1)
        grid.attach(real_name, 1, 1, 1, 1)
        grid.attach(home, 1, 2, 1, 1)
        grid.attach(host, 1, 3, 1, 1)
        dialog.vbox.pack_start(grid, False, False, 5)
        dialog.show_all()
        result = dialog.run()
        if result == Gtk.ResponseType.OK:
            print("User Name: " + user.get_text())
            print("Real Name: " +
            real_name.get_text()) print("Home: " +
            home.get_text()) print("Host: " +
            host.get_text())
        dialog.destroy()
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="Simple Dialog")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-3

Editing Information in a Dialog

The proper way to handle any modal dialog is to use the response identifiers, deriving the correct response based on the clicked button. Since there was only one response that needed to be deliberately detected, a conditional if statement was used in Listing 6-3.

However, let’s assume that you need to handle multiple response identifiers. In this case, an if statement would be a better solution, since it was created to compare a single variable to multiple selections, as shown in the following code snippet.
result = dialog.run()
if result == Gtk.ResponseType.OK:
    # ... Handle result ...
elif result == Gtk.ResponseType.APPLY:
    # ... Handle result ...
else:
    # ... Handle default result ...
dialog.destroy()

Built-in Dialogs

There are many types of dialogs already built into GTK+. Although not all of the available dialogs are covered in this chapter, you are given a strong understanding of the concepts needed to use any built-in dialog. This section covers Gtk.MessageDialog, GtkAboutDialog, Gtk.FileChooserDialog , Gtk.FontChooserDialog, and Gtk.ColorChooserDialog.

Message Dialogs

Message dialogs give one of four types of informational messages: general information, error messages, warnings, and questions. This type of dialog decides the icon to display, the title of the dialog, and the buttons to add.

There is also a general type provided that makes no assumption as to the content of the message. In most cases, you will not want to use this, since the four provided types would fill most of your needs.

It is very simple to re-create the Gtk.MessageDialog widget. The first two examples implemented a simple message dialog, but Gtk.MessageDialog already provides this functionality, so you should not need to re-create the widget. Using Gtk.MessageDialog saves on typing and avoids the need to re-create this widget many times, since most applications make heavy use of Gtk.MessageDialog. It also provides a uniform look for message dialogs across all GTK+ applications.

Figure 6-3 shows an example of a Gtk.MessageDialog (compare this to Figure 6-1), which gives the user visual notification of a button’s clicked signal.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig3_HTML.jpg
Figure 6-3

A Gtk.MessageDialog widget

Since the content of the message is not critical, its type is set to a general message. This message dialog can be produced using the code shown in Listing 6-4.
#!/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_border_width(10)
        button = Gtk.Button.new_with_mnemonic("_Click Me")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
        self.set_size_request(150, 50)
    def on_button_clicked(self, button, parent):
        dialog = Gtk.MessageDialog(type=Gtk.MessageType.INFO, parent=parent,
                                    flags=Gtk.DialogFlags.MODAL,
                                    buttons=("Ok", Gtk.ResponseType.OK),
                                    text="The button was clicked.",
                                    title="Information")
        dialog.run()
        dialog.destroy()
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="Dialogs")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-4

Using a Gtk.MessageDialog

After the button in the main window is clicked, this example creates a new Gtk.MessageDialog.

The parent window can be set to None if necessary, but in most cases, a parent-child relationship should be established. If you do not set a parent widget, the message dialog will not be centered above the parent window.

Message dialogs should be addressed by the user immediately, because they present some type of important message or critical question that needs the user’s attention. By not setting a parent window, the message dialog can be easily ignored, which is not the desired action in most cases.
dialog = Gtk.MessageDialog.(type=Gtk.MessageType.INFO, parent=parent,
                    flags=Gtk.DialogFlags.MODAL,
                    buttons=("Ok", Gtk.ResponseType.OK),
                    text="The button was clicked.",
                    title="Information")

You specify one or more dialog flags. Options for this parameter are given by the Gtk.DialogFlags enumeration that was used when creating custom dialogs in the previous three examples.

Unlike GTK+ 2.x, the 3.x Gtk.MessageDialog does not use any positional parameters. Instead, it uses keyword parameters exclusively. Also note that Gtk.MessageDialog does not use a new method. This is because Gtk.MessageDialog creates a subclass of Gtk.MessageDialog and the keywords determine what kind subclass is created.

Also note that the light bulb icon image is missing from the message dialog. This is due to philosophy changes in GTK+ 3.x. If you must have icons in your dialogs then you need to use Gtk.Dialog to hand create your dialogs.

Multiple buttons are supported by including a comma-separated list of buttons/response ids using the "buttons" keyword.

You have no control over the visual formatting of the message provided to Gtk.MessageDialog. If you would like to use the Pango Text Markup Language to format the message dialog’s text, you can leave out the "text" keyword from the Gtk.MessageDialog call. Then call set_markup(str) method with a string of Pango markup to set the text of the message.

It is possible to add a secondary text to the message dialog, which causes the first message to be set as bold with format_secondary_text(). The text string provided to this function should be similar to the format supported by the C printf().

This feature is very useful, because it allows you to give a quick summary in the primary text and go into detail with the secondary text.

About Dialogs

The Gtk.AboutDialog widget provides you with a simple way to provide the user with information about an application. This dialog is usually displayed when the item in the Help menu is chosen. However, since menus are not covered until Chapter 10, our example dialog is used as the top-level window.

Various types of information are shown with Gtk.AboutDialog, including the name of the application, copyright, current version, license content, authors, documenters, artists, and translators. Because an application won’t have all of this information, every property is optional. The main window displays only the basic information, which is seen along with the author credits in Figure 6-4.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig4_HTML.jpg
Figure 6-4

An About credit dialog and author credit

By clicking the Credits button, the user is presented with any authors, documenters, translators, and artists that are provided. The License button pops up a new dialog that shows the given license content.

Listing 6-5 is a simple example that shows you how to use every available property of the Gtk.AboutDialog widget.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        button = Gtk.Button.new_with_mnemonic("_Click Me")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
        self.set_size_request(150, 50)
        self.show_all()
    def on_button_clicked(self, button, parent):
        authors = ["Author #1", "Author #2"]
        documenters = ["Documenter #1", "Documenter
        #2"] dialog = Gtk.AboutDialog(parent=parent)
        logo = GdkPixbuf.Pixbuf.new_from_file("./logo.png")
        if logo != None:
            dialog.set_logo(logo)
        else:
            print("A GdkPixbuf Error has occurred.")
        dialog.set_name("Gtk.AboutDialog")
        dialog.set_version("3.0")
        dialog.set_copyright("(C) 2007 Andrew Krause")
        dialog.set_comments("All about Gtk.AboutDialog")
        dialog.set_license("Free to all!")
        dialog.set_website("http://book.andrewKrause.net")
        dialog.set_website_label("book.andrewkrause.net")
        dialog.set_authors(authors)
        dialog.set_documenters(documenters)
        dialog.set_translator_credits("Translator #1 Translator #2")
        dialog.connect("response", self.on_dialog_button_clicked)
        dialog.run()
    def on_dialog_button_clicked(self, dialog, response):
        dialog.destroy()
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="About Dialog")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-5

Using a Gtk.AboutDialog

Many properties are available for you to set when creating your own Gtk.AboutDialog instance. Table 6-2 summarizes those options that were used in Listing 6-5. If the license is not specified, the License button is not visible. The Credits button is not visible if there are no credits.
Table 6-2

Gtk.AboutDialog Option Values

Property

Description

program_name

The application’s name.

version

The current version of the application the user is running.

copyright

A short copyright string that should not span more than one or two lines.

comments

A short description of the application that should not span more than one or two lines.

license

License information that is displayed in a secondary dialog. Setting this to None hides the License button.

web site

The home page URL of the application.

web site_label

A label that is displayed instead of the URL.

authors

A Python list of authors who have contributed code to the project.

artists

A Python list of artists who have created graphics for the project.

documenters

A Python list of documenters who have written documentation for the project.

translator_credits

A string that specifies the translator(s) of the current language.

logo

Usually loaded from a file, this Gdk.Pixbuf object is the application’s logo.

Unlike author, artist, and documenter credits, the translator credits are only a single string. This is because the translator string should be set to the person that translated the language currently in use. Internationalization and gettext are not topics for this book. For more information, you should visit www.gnu.org/software/gettext .

Gdk.Pixbuf

GdkPixbuf is a class that contains information about an image stored in memory. It allows you to build images manually by placing shapes or pixels or to load a pre-built image from a file. The latter is preferred in most cases, so that is what is covered in this book.

Since GdkPixbuf is derived from GObject, it supports referencing. This means that the same image can be used in multiple locations in a program by increasing the reference count with ref(). Dereferencing GdkPixbuf objects (pixbufs) is performed automatically in almost all cases.

To load a pixbuf from a file, you can use GdkPixbuf.new_from_file(), which was used in Listing 6-5. This function loads the image with an initial size set to the actual size of the image.
logo = GdkPixbuf.Pixbuf.new_from_file("./logo.png")
After you load the image, you can resize it with scale_simple(). This function accepts the new size parameters of the Gdk.Pixbuf and the interpolation mode to use for the scaling.
pixbuf.scale_simple(dest_width, dest_height, interp_type)
The following are the four GdkPixbuf.InterpType modes.
  • GdkPixbuf.InterpType.NEAREST: Sampling is performed on the nearest neighboring pixel. This mode is very fast, but it produces the lowest quality of scaling. It should never be used for scaling an image to a smaller size!

  • GdkPixbuf.InterpType.TILES: This mode renders every pixel as a shape of color and uses antialiasing for the edges. This is similar to using GdkPixbuf.InterpType.NEAREST for making an image larger or GdkPixbuf.InterpType.BILINEAR for reducing its size.

  • GdkPixbuf.InterpType.BILINEAR: This mode is the best mode for resizing images in both directions, because it has a balance between its speed and the quality of the image.

  • GdkPixbuf.InterpType.HYPER: While it is very high quality, this method is also very slow. It should only be used when speed is not a concern. Therefore, it should never be used for any application that the user would expect a fast display time. In one function call, GdkPixbuf.new_from_file_at_size() conveniently resizes the image immediately after it loads from the file.

Many other features are provided in the GdkPixbuf class, but only a few of these are covered, as needed. For further information on GdkPixbuf, you should reference the API documentation.

Gtk.FileChooser Dialogs

In the last chapter, you learned about Gtk.FileChooser and the Gtk.FileChooserButton widget. Recall that Gtk.FileChooser is not a widget, but an abstract class. Abstract classes differ from real classes, because they may not implement the methods they declare.

GTK+ provides the following three widgets that subclass the Gtk.FileChooser class.
  • Gtk.FileChooserButton : The file chooser button was covered in the previous chapter. It allows the user to choose one file or folder by displaying a Gtk.FileChooser dialog when clicked.

  • Gtk.FileChooserDialog : This is the actual widget that allows the user to choose a file folder. It can also facilitate the creation of a folder or saving of a file. When you use Gtk.FileChooserDialog, you are actually using a file chooser widget packed into a Gtk.Dialog .

  • Gtk.FileChooserWidget: This is the actual widget that allows the user to choose a file folder. It can also facilitate the creation of a folder or saving of a file. When you use Gtk.FileChooserDialog , you are actually using a file chooser widget packed into a Gtk.Dialog .

You have already learned about Gtk.FileChooserButton and have used a file chooser to open one file and to select a directory. There are three other abilities provided by the file chooser widget. In the next three examples, you learn how to use a file chooser dialog to save a file, create a directory, and choose multiple files.

Saving Files

Figure 6-5 shows a Gtk.FileChooserDialog widget that is saving a file. You will notice that it is similar to the next two figures as well, because all types of file chooser dialogs have a consistent look so that it is minimally confusing to new users and maximally efficient to all. The widget also uses the same code to implement each dialog type to minimize the amount of necessary code.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig5_HTML.jpg
Figure 6-5

A file chooser dialog for saving

File chooser dialogs are used in the same way as the previous two dialogs covered in this chapter, except you need to handle the response code returned by Gtk.Dialog.new(). Listing 6-6 allows the user to choose a file name and sets the button’s text to that file name if the correct response identifier is returned.
#!/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_border_width(10)
        self.set_size_request(200, 100)
        button = Gtk.Button.new_with_label("Save as ...")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
    def on_button_clicked(self, button, parentwin):
        dialog = Gtk.FileChooserDialog(title="Save file as ...",
                                       parent=parentwin,
                                       action=Gtk.FileChooserAction.SAVE,
                                       buttons=("_Cancel",
                                                Gtk.ResponseType.CANCEL,
                                       "_Save", Gtk.ResponseType.ACCEPT))
        response = dialog.run()
        if response == Gtk.ResponseType.ACCEPT:
            filename = dialog.get_filename()
            button.set_label(filename)
        dialog.destroy()
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="Save a File")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-6

Using a Gtk.AboutDialog

All file chooser dialogs are created with the Gtk.FileChooserDialog() regardless of what options you choose. As with other dialogs, you begin by setting the title of the dialog and the parent window. The parent window should always be set, because file chooser dialogs should be modal.
dialog = Gtk.FileChooserDialog(title="Save file as ...",
                           parent=parentwin,
                           action=Gtk.FileChooserAction.SAVE,
                           buttons=("_Cancel", Gtk.ResponseType.CANCEL,
                           "_Save", Gtk.ResponseType.ACCEPT))
Next, as with file chooser buttons, you have to choose the action of file chooser that is created. All four action types provided by the Gtk.FileChooser abstract class are available to Gtk.FileChooserDialog . These are described in the following list.
  • Gtk.FileChooserAction .SAVE: The user is prompted to enter a file name and browse throughout the file system for a location. The returned file is the chosen path with the new file name appended to the end. Gtk.FileChooser provides methods that allow you to ask for confirmation if the user enters a file name that already exists.

  • Gtk.FileChooserAction .OPEN: The file chooser only allows the user to select one or more files that already exist on the user’s system. The user is able to browse throughout the file system or choose a bookmarked location.

  • Gtk.FileChooserAction.SELECT_FOLDER: This is very similar to the save action, because it allows the user to choose a location and specify a new folder name. The user can enter a new folder name that is created when the file chooser returns or click the Create Folder button, shown in Figure 5-6, which creates a new folder in the current directory.

  • Gtk.FileChooserAction .CREATE_FOLDER: This is very similar to the save action, because it allows the user to choose a location and specify a new folder name. The user can enter a new folder name that is created when the file chooser returns or click the Create Folder button, shown in Figure 5-6, which creates a new folder in the current directory.

Lastly, you have to provide a name/response ID list of buttons to add to the action area. In Listing 6-6, when the Cancel button is clicked, Gtk.ResponseType.CANCEL is emitted, and when the Save button is clicked, GTK_RESPONSE_ACCEPT is emitted.

Creating a Folder

GTK+ allows you not only to select a folder but also to create a folder. A Gtk.FileChooserDialog widget using this type can be seen in Figure 6-6, which is a screenshot of Listing 6-7.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig6_HTML.jpg
Figure 6-6

A file chooser dialog for creating a folder

The dialog in Listing 6-7 handles creating the new folder when accepted by the user, so you do not need to take any further action beyond destroying the dialog.
#!/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_border_width(10)
        self.set_size_request(200, 100)
        button = Gtk.Button.new_with_label("Create a Folder ...")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
    def on_button_clicked(self, button, parentwin):
        dialog = Gtk.FileChooserDialog(title="Create a Folder ...",
                                       parent=parentwin,
                                       action=Gtk.FileChooserAction.SAVE,
                                       buttons=("_Cancel",
                                                Gtk.ResponseType.CANCEL,
                                        "_Ok", Gtk.ResponseType.OK))
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            filename = dialog.get_filename()
            print("Creating directory: %s " % filename)
        dialog.destroy()
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="Create Folder")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-7

Using a Gtk.AboutDialog

The full folder name of the dialog can be retrieved by using the same function that retrieved the file name in the previous example, get_filename() . The standard os.mkdir() method from the os module creates a folder in the specified location on all supported operating systems.

Selecting Multiple Files

Figure 6-7 shows a standard file chooser dialog that allows the user to choose a file. The difference between Gtk.FileChooserDialog and Gtk.FileChooserButton using the Gtk.FileChooserAction .OPEN type is that dialogs are capable of selecting multiple files while buttons are restricted to one file.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig7_HTML.jpg
Figure 6-7

A file chooser dialog for selecting multiple files

Listing 6-8 shows you how to handle multiple file selections. It is very similar to single file selections except for the fact that selections are returned in a Python list.
#!/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_border_width(10)
        self.set_size_request(200, 100)
        button = Gtk.Button.new_with_label("Open file(s) ...")
        button.connect("clicked", self.on_button_clicked, self)
        self.add(button)
    def on_button_clicked(self, button, parentwin):
        dialog = Gtk.FileChooserDialog(title="Open file(s) ...",
                                       parent=parentwin,
                                       action=Gtk.FileChooserAction.OPEN,
                                       buttons=("_Cancel",
                                                Gtk.ResponseType.CANCEL,
                                        "_Open", Gtk.ResponseType.ACCEPT))
        dialog.set_select_multiple(True)
        response = dialog.run()
        if response == Gtk.ResponseType.ACCEPT:
            filenames = dialog.get_filenames()
            i = 0
            while i < len(filenames):
                file = filenames[i]
                print(file + " was selected.")
                i += 1
        dialog.destroy()
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="Open Nultiple Files")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-8

Using A Gtk.FileChooserDialog to Select Multiple Files

The get_filenames() function returns a Python list of the selected file(s).
filenames = dialog.get_filenames()

Color Selection Dialogs

In the previous chapter, you learned about the Gtk.ColorButton widget, which allowed the user to select a color. After clicking that button, the user was presented with a dialog. Although not specified at the time, that dialog was a Gtk.ColorSelectionDialog widget.

Similar to Gtk.FileChooserDialog , the color selection dialog is actually a Gtk.Dialog container with a Gtk.ColorSelection widget packed as its child widget. Gtk.ColorSelection can easily be used on its own. However, since a dialog is a natural way of presenting the widget, GTK+ provides Gtk.ColorSelectionDialog. A color selection dialog is shown in Figure 6-8.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig8_HTML.jpg
Figure 6-8

A file chooser dialog for selecting multiple files

Listing 6-9 contains a top-level window that has two buttons. When the first button is clicked, a modal Gtk.ColorSelectionDialog is created. The other button creates a nonmodal Gtk.ColorSelectionDialog. Each chooses global color and opacity values.

This example also loops through program arguments, setting the initial color value if provided. This allows you to pass an initial color when launching the application.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
global_color = Gdk.RGBA(red=.50, green=.50, blue=.50,
alpha=1.0).to_color() global_alpha = 65535
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(200, 100)
        modal = Gtk.Button.new_with_label("Modal")
        nonmodal = Gtk.Button.new_with_label("Non-Modal")
        modal.connect("clicked", self.on_run_color_selection_dialog,
                       self, True)
        nonmodal.connect("clicked", self.on_run_color_selection_dialog,
                          self, False)
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        hbox.pack_start(modal, False, False, 5)
        hbox.pack_start(nonmodal, False, False, 5)
        self.add(hbox)
    def on_dialog_response(self, dialog, result):
        if result == Gtk.ResponseType.OK:
            colorsel = dialog.get_color_selection()
            alpha = colorsel.get_current_alpha()
            color = colorsel.get_current_color()
            print(color.to_string())
            global_color = color
            global_alpha = alpha
        dialog.destroy()
    def on_run_color_selection_dialog(self, button, window, domodal):
        if domodal:
            title = ("Choose Color -- Modal")
        else:
            title = ("Choose Color -- Non-Modal")
        dialog = Gtk.ColorSelectionDialog(title=title, parent=window,
                                          modal=domodal)
        colorsel = dialog.get_color_selection()
        colorsel.set_has_opacity_control(True)
        colorsel.set_current_color(global_color)
        dialog.connect("response", self.on_dialog_response)
        dialog.show_all()
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="Color Selection Dialog”)
            self.window.show_all()
            self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-9

Using a Gtk.ColorSelectionDialog

The only function provided by the Gtk.ColorSelectionDialog class is Gtk.ColorSelectionDialog(). The following code can get the selected color.
alpha = colorsel.get_current_alpha()
color = colorsel.get_current_color()
print(color.to_string())

Gtk.ColorSelectionDialog provides direct access to its four available child widgets. The first, colorsel is the Gtk.ColorSelection widget that facilitates color selection. The other three are an OK button, a Cancel button, and a Help button. By default, the Help button is hidden. You can use show() or the show_all() method to set it visible.

As with Listing 6-2, this example connects to the response signal, which receives all of the response identifiers regardless of whether the dialog is modal or nonmodal. The dialog is set as modal or nonmodal with the "modal" keyword on the insanitation of the Gtk.ColorSelectionDialog class.
Gtk.ColorSelectionDialog(title=title, parent=window, modal=domodal)
Listing 6-9 shows a fourth color property apart from its RGB values, its opacity (alpha value). Ranging between 0 and 65,535, this value regulates how transparent the color is drawn, where 0 is fully transparent and 65,535 is opaque. By default, the opacity control is turned off within color selection widgets. You can call the method set_has_opacity_control() to enable the feature.
colorsel.set_has_opacity_control(boolean)

When opacity is turned on, the hexadecimal color value is sixteen digits long, four digits for each of the values: red, green, blue, and alpha. You must use colorsel.get_current_alpha() to retrieve its value from the color selection widget.

Font Selection Dialogs

The font selection dialog is a dialog that allows the user to select a font and is the dialog shown when a Gtk.FontButton button is clicked. As with Gtk.ColorSelectionDialog, direct access to the action area buttons is provided through the Gtk.FontSelectionDialog structure. An example font selection dialog is shown in Figure 6-9, which should look similar to the one you saw in the last chapter.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig9_HTML.jpg
Figure 6-9

A font selection dialog

Figure 6-9 is the result of running Listing 6-10.

#!/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_border_width(10)
        self.set_size_request(200, 100)
        button = Gtk.Button.new_with_label("Run Font Selection Dialog")
        button.connect("clicked", self.on_run_font_selection_dialog)
        self.add(button)
    def on_run_font_selection_dialog(self, button):
        dialog = Gtk.FontSelectionDialog(title="Choose a Font",
                                         buttons=("Apply", Gtk.ResponseType.APPLY),
                                         parent=self)
        dialog.set_preview_text("GTK+ 3 Development With Python")
        dialog.connect("response", self.on_dialog_response)
      dialog.run()
    def on_dialog_response(self, dialog, response):
        if response == Gtk.ResponseType.OK or response == Gtk.ResponseType.APPLY:
            font = dialog.get_font_name()
            message = Gtk.MessageDialog(title="Selected Font",
                                        flags=Gtk.DialogFlags.MODAL,
                                        type=Gtk.MessageType.INFO,
                                        text=font,
                                        buttons=("Ok", Gtk.ResponseType.OK),
                                        parent=dialog);
            message.run()
            message.destroy()
            if response == Gtk.ResponseType.OK:
                dialog.destroy()
        else:
            dialog.destroy()
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="Font Selection Dialog”)
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-10

Using a Gtk.FontSelectionDialog

The font selection dialog initialization function, Gtk.FontSelectionDialog(), returns a new Gtk.FontSelectionDialog widget with the specified title.

The dialog itself contains three buttons: OK, Apply, and Cancel. They emit the Gtk.ResponseType.OK, Gtk.ResponseType.APPLY, and Gtk.ResponseType.CANCEL signals respectively.

There is no need to create a modal dialog, because the font selection dialog is connected to a response signal.

If the user clicks the OK button, the user is presented with the selected font, and the dialog is destroyed. By clicking Apply, the selected font is presented to the user, but the dialog is not destroyed. This allows you to apply the new font so the user can view the changes without closing the dialog.

The font selection widget contains a Gtk.Entry widget that allows the user to preview the font. By default, the preview text is set to “abcdefghijk ABCDEFGHIJK”. This is somewhat boring, so I decided to reset it to “GTK+ 3 Development With Python”, the title of this book.

The last methods provided by Gtk.FontSelectionDialog() allow you to set and retrieve the current font string. The font string used by dialog.set_font_name() and dialog.get_font_name() is in the same format that we parsed with Pango.FontDescription in the previous chapter.

Dialogs with Multiple Pages

With the release of GTK+ 2.10, a widget called Gtk.Assistant was introduced. Gtk.Assistant makes it easier to create dialogs with multiple stages, because you do not have to programmatically create the whole dialog. This allows you to split otherwise complex dialogs, into steps that guide the user. This functionality is implemented by what are often referred to as wizards in various applications.

Figure 6-10 shows the first page of a simple Gtk.Assistant widget, which was created using the code in Listing 6-11. This example begins by giving the user general information. The next page will not allow the user to proceed until text is entered in a Gtk.Entry widget. The third page will not allow the user to proceed until a Gtk.CheckButton button is activated. The fourth page will not let you do anything until the progress bar is filled, and the last page gives a summary of what has happened. This is the general flow that every Gtk.Assistant widget should follow.
../images/142357_2_En_6_Chapter/142357_2_En_6_Fig10_HTML.jpg
Figure 6-10

The first page of a Gtk.Assistant widget

#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import time
class assistant(Gtk.Assistant):
    progress = None
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_size_request(450, 300)
        self.set_title("Gtk.Assistant Example")
        self.connect("destroy", Gtk.main_quit, None)
        # create page 0
        page0_widget = Gtk.Label("This in an example of a Gtk.Assistant. By "
                                 + "clicking the forward button, you can " +
                                 "continue to the next section!")
        self.append_page(page0_widget)
        self.set_page_title(page0_widget, "Introduction")
        self.set_page_type(page0_widget, Gtk.AssistantPageType.INTRO)
        self.set_page_complete(page0_widget, True)
        # create page 1
        page1_widget = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        label = Gtk.Label("Your Name: ")
        entry = Gtk.Entry()
        page1_widget.pack_start(label, False, False, 5)
        page1_widget.pack_start(entry, False, False, 5)
        self.append_page(page1_widget)
        self.set_page_title(page1_widget, "")
        self.set_page_type(page1_widget, Gtk.AssistantPageType.CONTENT)
        self.set_page_complete(page1_widget, False)
        # create page 2
        page2_widget = Gtk.CheckButton.new_with_label("Click me to Continue!")
        self.append_page(page2_widget)
        self.set_page_title(page2_widget, "Click the Check Button")
        self.set_page_type(page2_widget, Gtk.AssistantPageType.CONTENT)
        self.set_page_complete(page2_widget, False)
        # create page 3
        page3_widget = Gtk.Alignment.new(0.5, 0.5, 0.0, 0.0)
        button = Gtk.Button.new_with_label("Click Me!")
        self.progress = Gtk.ProgressBar()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        hbox.pack_start(self.progress, True, False, 5)
        hbox.pack_start(button, False, False, 5)
        page3_widget.add(hbox)
        self.append_page(page3_widget)
        self.set_page_title(page3_widget, "Click the Check Button")
        self.set_page_type(page3_widget, Gtk.AssistantPageType.PROGRESS)
        self.set_page_complete(page3_widget, False)
        # create page 4
        page4_widget = Gtk.Label("Text has been entered in the label and the " + "combo box is clicked. If you are done, then "
                                 + "it is time to leave!")
        self.append_page(page4_widget)
        self.set_page_title(page4_widget, "Confirmation")
        self.set_page_type(page4_widget, Gtk.AssistantPageType.CONFIRM)
        self.set_page_complete(page4_widget, True)
        # set up the callbacks
        entry.connect("changed",self.entry_changed)
        # page2_widget.connect("toggled",self.button_toggle)
        button.connect("clicked", self.button_clicked)
        self.connect("cancel", self.assistant_canceled)
        self.connect("close", self.assistant_close)
    def entry_changed(self, entry):
        text = entry.get_text()
        num = self.get_current_page()
        page = self.get_nth_page(num)
        self.set_page_complete(page, len(text) > 0)
    def button_toggled(self, toggle):
        active = toggle.get_active()
        self.set_page_complete(toggle, active)
    def button_clicked(self, button):
        percent = 0.0
        button.set_sensitive(False)
        page = self.get_nth_page(3)
        while (percent <= 100.0):
            message = str(percent) + " complete"
            print(message)
            self.progress.set_fraction(percent / 100.0)
            self.progress.set_text(message)
            while (Gtk.events_pending()):
                Gtk.main_iteration()
            time.sleep(1)
            percent += 5.0
        self.set_page_complete(page, True)
    def assistant_canceled(self, response):
        self.destroy()
    def assistant_close(self, response):
        print("You would apply your changes
        now!") self.destroy()
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
        self.set_border_width(25)
        button = Gtk.Button.new_with_mnemonic("_Open Assistant")
        button.connect("clicked", self.on_start_button_clicked)
        button.set_relief(Gtk.ReliefStyle.NORMAL)
        self.add(button)
        self.set_size_request(200, 100)
    def on_start_button_clicked(self, button):
        assistant()
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="Gtk.Assistant")
    self.window.show_all()
    self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 6-11

The Gtk.Assistant Widget

Creating Gtk.Assistant Pages

A Gtk.Assistant widget is a dialog with multiple pages, although it is actually not derived from Gtk.Dialog . By calling Gtk.Assistant(), you create a new Gtk.Assistant widget with no initial pages.
index = assistant.append_page(widget)
There is no actual page widget for assistants, because each page is actually a child widget that is added with assistant.prepend_page(), assistant.append_page(), or assistant.insert_page(). Each of these functions accepts the child widget (added as the contents of the page) and returns the new page’s index. Each page has a number of properties that can be set, each of which is optional. A list of these options follows.
  • Page title: Every page should have a title, so the user knows what it is for. Your first page should be an introductory page that tells the user information about the assistant. The last page must be a summary or confirmation page that makes sure the user is ready to apply the previous changes.

  • Header image: In the top panel, you can display an optional image to the left of the title. This is often the application’s logo or an image that complements the assistant’s purpose.

  • Side image: This optional image is placed along the left side of the assistant beside the main page content. It is used for aesthetic appeal.

  • Page type: The page type must always be set, or it defaults to Gtk.AssistantPageType.CONTENT. The last page must always be a confirmation or summary page. You should also make the first page an introductory page that gives the user information about what task the assistant performs.

After you have set the page’s properties, you must choose what type of page it is. There are five types of pages. The first page should always be Gtk.AssistantPageType.INTRO. The last page should always be Gtk.AssistantPageType.CONFIRM or Gtk.AssistantPageType.SUMMARY—if your assistant does not end with one of those two types of pages, it will not work correctly. All the available page types are described in the following list.
  • Gtk.AssistantPageType.CONTENT: This type of page has general content, which means it is used for almost every page in the assistant. It should never be used for the last page in an assistant.

  • Gtk.AssistantPageType.INTRO: This type of page has introductory information for the user. This should only be set for the first page in the assistant. Although not required, introductory pages give the user direction; they should be used in most assistants.

  • Gtk.AssistantPageType.CONFIRM: The page allows the user to confirm or deny a set of changes. It is typically used for changes that cannot be undone or may cause something to break if not set correctly. This should only be set for the last page of the assistant.

  • Gtk.AssistantPageType.SUMMARY: The page gives a summary of the changes that have occurred. This should only be set for the last page of the assistant.

  • Gtk.AssistantPageType.PROGRESS: When a task takes a long time to complete, this blocks the assistant until the page is marked as complete. The difference between this page and a normal content page is that all of the buttons are disabled and the user is prevented from closing the assistant.

Caution

If you do not set the last page type as Gtk.AssistantPageType.CONFIRM or Gtk.AssistantPageType.SUMMARY, your application will abort with a GTK+ error when computing the last button state.

Since Gtk.Assistant is not derived from Gtk.Dialog , you cannot use dialog.run() (or any other Gtk.Dialog method) on this widget. Instead, the following four signals are provided for you to handle button-clicked signals.
  • "apply": This signal is emitted when the Apply button or Forward button clicks any assistant page.

  • "cancel": This signal is emitted when the Cancel button clicks any assistant page.

  • "close": This signal is emitted when the Close button or Apply button on the last page in the assistant is clicked.

  • "prepare": Before making a new page visible, this signal is emitted so that you can do any preparation work before it is visible to the user.

You can connect to all Gtk.Assistant signals with assistant.connect() or any other signal connection function provided by GLib . Excluding "prepare", the callback methods for Gtk.Assistant signals receive the assistant and the user data parameter. The callback method for the prepare signal also accepts the child widget of the current page.

By default, every page is set as incomplete. You have to manually set each page as complete when the time is right with assistant.set_page_complete() or the Gtk.Assistant will not be able to proceed to the next page.
assistant.set_page_complete(page, boolean)

On every page, a Cancel button is displayed in addition to a few others. On pages other than the first one, a Back button is displayed that is always sensitive. This allows you to visit the previously displayed page and make changes.

Note

The page that is visited when the user clicks the Back button is not always the previous page according to the page index. It is the previously displayed page, which may be different based on how you defined the page flow of your assistant.

On every page except the last, a Forward button is placed, which allows the user to move to the next page. On the last page, an Apply button is displayed that allows the user to apply the changes. However, until the page is set as complete, the assistant sets the Forward or Apply button as insensitive. This allows you to prevent the user from proceeding until some action is taken.

In Listing 6-11, the first and last pages of the assistant were set as complete, because they were merely informative pages. This is the case in most assistants, since they should begin with an introduction page and end with a confirmation or summary page.

The other two pages are where it becomes interesting. On the second page, we want to make sure that the user cannot proceed until text is entered in the Gtk.Entry widget. It would seem that we should just check when text has been inserted and be done with it.

However, what happens if the user deletes all of the text? In this case, the forward button should be disabled yet again. To handle both of these actions, you can use Gtk.Editable’s changed signal. This allows you to check the current state of the text in the entry upon every change, as in Listing 6-11.

On the third page, we want to enable the forward button only when the check button is active. To do this, we used the toggled signal of Gtk.ToggleButton to check the current state of the check button. Based on this state, the forward button’s sensitivity was set.

The fourth page has a type of Gtk.AssistantPageType.PROGRESS, which disables all actions until the page is set as complete. The user is instructed to click a button, which begins the process of filling a Gtk.ProgressBar widget 10 percent every second. When the progress bar is filled, the page is set as complete.

Gtk.ProgressBar

The Gtk.Assistant example introduced another new widget called Gtk.ProgressBar. Progress bars are a simple way to show how much of a process has been completed and is useful for processes that take a long time to handle. Progress bars give the user a visual cue that progress is being made, so they do not think the program has frozen.

New progress bars are created with Gtk.ProgressBar(). The implementation of Gtk.ProgressBar was made a lot simpler with the release of GTK+ 2.0, so be careful when using the API documentation, because a number of the displayed functions and properties are depreciated. The two examples following show you how to correctly use the Gtk.ProgressBar widget.
percent = 0.0
button.set_sensitive(False)
page = self.get_nth_page(3)
while (percent <= 100.0):
    message = str(percent) + " complete"
    print(message)
    self.progress.set_fraction(percent / 100.0)
    self.progress.set_text(message)
    while (Gtk.events_pending()):
        Gtk.main_iteration()
    time.sleep(1)
    percent += 5.0

You may also want to display text that can complement the progress bar. In the preceding example, progress.set_text() displayed the percent complete statistic, which is superimposed on the progress bar widget.

If you are not able to detect the progress of the process, you can use pulses. In the preceding example, progress.pulse() moved the progress bar one step for every pending event that was processed. You can set the pulse step with progress.set_pulse_step().
progress.set_pulse_step(0.1)
while (Gtk.events_pending ()):
    Gtk.main_iteration()
    progress.pulse()

By setting the pulse step to 0.1, the progress bar fills up in the first ten steps and clears itself in the next ten. This process continues for as long as you continue pulsing the progress bar.

Page Forward Methods

There are times that you may want to skip to specific assistant pages if conditions are correct. For example, let’s assume your application is creating a new project. Depending on the chosen language, you want to jump to either the third or fourth page. In this case, you want to define your own Gtk.AssistantPageFunc method for forward motion.

You can use assistant.set_forward_page_func() to define a new page forward function for the assistant. By default, GTK+ increments directly through the pages in order, one page at a time. By defining a new forward function, you can define the flow.
assistant.set_forward_page_func(page_func, data)
For example, assistant_forward() is a simple Gtk.AssistantPageFunc implementation that moves from page two to either three or four, depending on the condition returned by decide_next_page().
def assistant_forward(self, current_page, data):
    next_page = 0;
    if current_page == 0:
        next_page = 1
    elif current_page == 1:
        next_page = (decide_next_page() ? 2 : 3)
    elif current_page == 2 or current_page == 3:
        next_page = 4
    else:
        next_page = -1
    return next_page

Note

By returning –1 from a page forward function, the user is presented with a critical error and the assistant will not move to another page. The critical error message will tell the user that the page flow is broken.

In the assistant.forward() method, flow is changed based on the Boolean value returned by the fictional function decide_next_page(). In either case, the last page is page 4. If the current page is not within bounds, –1 is returned, so an exception is thrown by GTK+.

While this Gtk.Assistant example is very simple, implementations of this widget can become very complex as they expand in number of pages. This widget could be re-created with a dialog, a Gtk.Notebook with hidden tabs, and a few buttons. (I have had to do that very thing multiple times!), but it makes the process a lot easier.)

Test Your Understanding

In the exercise for this chapter, you are creating custom dialogs of your own. Each of the dialogs is an implementation of a type of file chooser dialog. However, you are embedding a Gtk.FileChooserWidget into a Gtk.Dialog to re-create the functionality of the built-in dialogs.

Exercise 1: Implementing File Chooser Dialogs

In this exercise, you create a window with four buttons. Each button opens a different dialog when clicked, which implements one of the four Gtk.FileChooser actions. You should use Gtk.FileChooserWidget added to Gtk.Dialog instead of the prebuilt Gtk.FileChooserDialog .
  • Your dialog implements a Gtk.FileChooserAction .SAVE file chooser dialog. The chosen file name should be printed to the screen.

  • Your dialog implements a Gtk.FileChooserAction.CREATE_FOLDER file chooser dialog. The new folder name should be printed to the screen. You have to manually create the new folder with a Python function.

  • Your dialog implements a Gtk.FileChooserAction.OPEN file chooser dialog. The chosen file names should be printed to the screen.

  • Your dialog implements a Gtk.FileChooserAction .SELECT_FOLDER file chooser dialog. The chosen folder path should be printed to the screen.

You need to set each of the dialogs to a decent size so that the entire content is visible to the user. If you get stuck during this exercise, you can find a solution in Appendix D.

Summary

In this chapter, you learned how to create your own custom dialogs. To do this, you need to first initialize the dialog. Then, action area buttons need to be added as well as the main content to the dialog’s vertical Gtk.Box.

Dialogs can be created as modal or nonmodal. A modal dialog created with dialog.run() blocks the user from interacting with the parent window until it is destroyed by creating a main loop for the dialog. It also centers the dialog above its parent window. Nonmodal dialogs allow the user to interact with any other window in the application and will not force focus on the dialog.

After learning about the built-in dialogs, you learned about multiple types of built-in dialogs provided by GTK+.
  • Message dialog (Gtk.MessageDialog): Provides a general message, error message, warning, or simple yes/no question to the user.

  • About dialog (Gtk.AboutDialog): Shows information about the application, including version, copyright, license, authors, and others.

  • File chooser dialog ( Gtk.FileChooserDialog ): Allows the user to choose a file, choose multiple files, save a file, choose a directory, or create a directory.

  • Color selection dialog (Gtk.ColorSelectionDialog): Allows the user to choose a color along with an optional opacity value.

  • Font selection dialog (Gtk.FontSelectionDialog): Allows the user to choose a font and its size and style properties.

The last section of this chapter showed you a widget called Gtk.Assistant, which was introduced in GTK+ 2.10. It allows you to create dialogs with multiple stages. It is important to note that assistants are not actually a type of Gtk.Dialog widget but are directly derived from the Gtk.Window class. This means that you have to handle these by connecting signals in the main loop instead of calling dialog.run().

You now have a firm understanding of many important aspects of GTK+. The Chapter 9 explains the multiline text entry widget called Gtk.TextView. Other topics include the clipboard and the Gtk.SourceView library.

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

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