Appendix D: Exercises Solutions and Hints
This appendix walks you through the solutions for each of the exercises found in this book, although the full code for the solutions can be downloaded from
www.gtkbook.com
. If you get stuck, this appendix gives you the tools to solve the exercises before you look at the code. You can then reference the downloadable solutions to see how I implemented each of the exercise applications.
Chapter
, Exercise 1: Using Events and Properties
The solution for this exercise should appear very similar to the exercises found throughout Chapter
. To begin, your application should include the following four basic steps that are required by every Python GTK+ application.
- 1.
Create the
Gtk.Application
instance.
- 2.
Create the
Gtk.ApplicationWindow
instances.
- 3.
Show the
Gtk.ApplicationWindow
instance using the
show_all()
method.
- 4.
Activate the
Gtk.ApplicationWindow
instance using the
present()
method.
In addition to these basic steps, you must also add a
Gtk.Label
widget to the top-level window. This label widget can be set as selectable with
set_selectable()
. Next, you should connect the
Gtk.ApplicationWindow
widget to the
"key-press-event"
signal, which is called every time the user presses a key when the window has focus.
In the
"key-press-event"
callback method, you can use the following Python code to determine whether the label is currently displaying the first or last name.
if string1.lower() == string2.lower():
The window and label text should be switched accordingly. You should then return
False
so that the application continues to handle the
"key-press-event"
.
Another solution is to just swap the window title and label text unconditionally. This is the approach used in the supplied exercise solution.
Chapter
, Exercise 1: Using Multiple Containers
This exercise helps you gain experience using a variety of container widgets that were covered in Chapter
, including
Gtk.Notebook
and
Gtk.Box
. Let’s analyze the content of each of these containers.
The
Gtk.Notebook
container should contain four tabs. Each tab in a notebook is associated with a label widget and a child widget. The
append_page()
method can be used to add new pages to a notebook. Each of these tabs should contain a
Gtk.Button
widget that is connected to the clicked signal. When a button is clicked, the notebook should move to the next page, wrapping around when the last page is reached. Connecting each clicked signal to the same callback function can do this.
Within the callback method, which is called
next_tab()
in the downloadable solution, you first need to check the page number. If the page number is less than three, you can simply call
next_page()
to move to the next page; otherwise, you can use
set_current_page()
to set the page number to zero. This same method can be used for moving to the previous page in the notebook.
The next container is a horizontal
Gtk.Box
that holds two buttons. The first button should move to the previous page in the
Gtk.Notebook
container when pressed. You can use the same method for moving to the next page for moving to the previous page, although it has to be reversed. The other button should close the window and exit the application when clicked. These buttons can be packed with
pack_end()
so that they appear against the right side of the horizontal box instead of the left side.
The last container in the application is a vertical
Gtk.Box
widget that should hold the
Gtk.Notebook
and horizontal
Gtk.Box
widgets. This vertical box can be packed into the top-level
Gtk.Window
widget to complete the application’s user interface.
Chapter
, Exercise 2: Even More Containers
This exercise solution is very similar to the previous exercise. The first difference is that the
Gtk.Notebook
tabs should be hidden with
set_show_tabs()
. Then, a
Gtk.Expander
container should be placed between each
Gtk.Button
widget and the Notebook tab. This allows you to show and hide the button found in each tab. The expander’s label can also be used to tell you which tab is currently displayed.
The last difference is that, instead of using a vertical
Gtk.Box
widget to pack the notebook and horizontal box, you should use a vertical
Gtk.Paned
widget. This container allows you to redistribute the allocated space for each of its two children by dragging the horizontal separator located between the two widgets.
Chapter
, Exercise 1: Renaming Files
In this exercise, you need to use several widgets that you learned about in Chapter
, including the stock buttons
Gtk.Entry
and
Gtk.FileChooserButton
. The purpose of this exercise is to allow the user to rename the selected file with a function built into Python.
The first step is to set up your user interface, which includes three interactive widgets. The first is a file chooser button, created with
Gtk.FileChooserButton.
new()
. The chooser’s action should be set to
Gtk.FileChooserAction
.OPEN
. This allows you to select only a single file. The
set_current_folder()
function can be used to set the current folder of the file chooser button to the user’s home directory, found using the Python method
os.path.expanduser('~')
.
This
Gtk.FileChooserButton
widget should be connected to the
"selection-changed"
signal. Within its callback function, you need to verify whether the file can be renamed. This can be done with a Python method called
os.access()
. The following call can use used within your application.
ret = os.access("/tmp/foo.txt", os.F_OK | os.W_OK)
If the file cannot be accessed or changed by the current user, the
Gtk.Entry
and
Gtk.Button
widgets should be disabled. This can be done by sending the opposite Boolean value as mode to the widget via the method
set_sensitive()
.
The next widget in the exercise is a
Gtk.Entry
, which allows the user to enter a new name for the widget. This is a new name for the file excluding the location, since this file name is appended to the
Gtk.FileChooserButton
’s
location when the file is renamed. The last widget, the
Gtk.Button
, should call the renaming function when clicked.
Within the button’s callback method, you first need to retrieve the current file and location from the file chooser button. The location, along with the content of the
Gtk.Entry
widget, can be used to build a new absolute path for the file. Lastly, you should use the Python
os.rename(src, dest)
function to rename the file. You should note that you must import the Python
os
module for any of the functions to work!
Chapter
, Exercise 2: Spin Buttons and Scales
This exercise is very different from the previous exercise; it lets you practice with the
Gtk.CheckButton
,
Gtk.SpinButton
, and
Gtk.Scale
widgets. When the check button is activated, the values of the spin button and horizontal scale should be synchronized; otherwise, they can move independently of each other.
To do this, the first step is to create two identical adjustments, one for each range widget. The toggle button in the solution is active on application launch so that the values are immediately synced.
The next step is to connect each of the range widgets to the same callback method/function for the
"value-changed"
signal. Within this function, the first step is to retrieve the current values of the spin button and scale. If the toggle button is active, these values are compared. Action is only taken if the values are not the same so that the value-changed signal is not repeatedly emitted.
Lastly, the callback function can use the Python built-in
isinstance()
function to figure out which type of widget holds the new value. Based on the result of the test, the other widget should be given the new value.
Chapter
, Exercise 1: Implementing File Chooser Dialogs
In this chapter’s only exercise, you are supposed to re-create the four types of file chooser dialogs by embedding a
Gtk.FileChooserWidget
widget into a
Gtk.Dialog
widget. The results of each action can simply be printed to standard output.
The main application window includes four buttons, one for each of the
Gtk.FileChooserWidget
action types, where the
Gtk.FileChooserAction
.OPEN
action allows you to select multiple files. These buttons can be packed into a vertical box and then into the top-level window.
Each of the callback functions follows the same pattern. It first creates a
Gtk.Dialog
widget and packs a
Gtk.FileChooserWidget
above the dialog’s action area by packing the dialog’s vbox member with
pack_start()
.
The next step is to run the dialog with
run()
. If the returned result is the response associated with acceptance of the action, you should output what would occur with
print()
. For example, you should tell the user that the file is saved; the folder has been created; the files is opened; or the folder was selected. In a
Gtk.FileChooserAction
.OPEN
action, you should output all the selected files.
Chapter
, Exercise 1: Text Editor
This exercise is the first instance of the text editor application that you encounter. It asks you to implement all of the functionality of the text editor.
There are a number of callback functions implemented for the text editor. These are the ability to create a new file; open an existing file; save the file; cut, copy, and paste selected text; and search for text in the document.
To create a new document, you should first ask the user whether or not the application should continue with a
Gtk.MessageDialog
widget. If the user chooses to continue, the downloadable exercise solution simply clears the
Gtk.TextBuffer
object and destroys the dialog; otherwise, the dialog is just destroyed.
Opening a document in the provided solution does not ask the user for confirmation, since it is easy to cancel the operation from the
Gtk.FileChooserDialog
widget. The file chooser dialog has an action type of
Gtk.FileChooserAction
.OPEN
. When a file is selected, its contents are read with the Python method
read()
and written into the text buffer. Saving in the exercise solution asks for a new file name every time the button is pressed. It calls
write()
to save the text to the selected file.
The clipboard functions are similar to those provided in Chapter
’s clipboard example. It uses the built-in text buffer functions for cut, copy, and paste actions. These actions are performed on the default clipboard,
Gdk.SELECTION_CLIPBOARD
.
Chapter
, Exercise 1: File Browser
In this chapter’s exercise, you implement a very simple file browser. It allows the user to browse throughout the system’s file structure and differentiate between files and folders. This exercise is meant to give you practice using the
Gtk.TreeView
widget. In Chapter
14
, it greatly expands into a more functional file browser.
The first step is to configure the tree view, which includes a single column. This column includes two cell renderers, one for a
GdkPixbuf
and one for the file or folder name, so you have to use the expanded method of tree view column creation that was discussed in Chapter
. The first cell renderer should use
Gtk.CellRendererPixbuf
and the second,
Gtk.CellRendererText
.
The tree model, a
Gtk.ListStore
is created with two columns with types of
GdkPixbuf.Pixbuf
and
GObject.TYPE_STRING
.
After the tree model is created in the downloadable exercise solution, the
populate_tree_model()
method is called, which displays the root folder of the file system on startup. The current path displayed by the file browser is stored in a global linked list called
current_path
. If the list is empty, the root folder is displayed; otherwise, a path is built out of the list’s content, and the
".."
directory entry is added to the tree model.
Then, GDir is used to walk through the contents of the directory, adding each file or folder to the tree model. You can use
os.path.isdir(location)
to check whether each is a file or folder, displaying the correct icon depending on the result.
The last step is to handle directory moves, which is done with
Gtk.TreeView
’s
"row-activated"
signal. If the selection is the
".."
entry, then the last element in the path is removed, and the tree model repopulated; otherwise, the new path is built out of the current location and the selection. If the selection is a folder, then the tree model is repopulated in the new directory. If it is a file, then the action is ignored and nothing else is done.
Chapter
10
, Exercise 1: Toolbars
This exercise alters Listing
10-1
(a simple pop-up menu) by replacing the buttons along the side with a
Gtk.Toolbar
created with
Gtk.Builder
. The following XML file can be used for creating the toolbar.
<?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.on_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_open"> <property name="visible">True</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Open Standard</property> <property name="action_name">app.on_openstandard</property>
<property name="icon_name">document-open</property> </object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="toolbutton_save"> <property name="visible">True</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Save Standard</property> <property name="action_name">app.on_savestandard</property>
<property name="icon_name">document-save</property> </object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="toolbutton_separator"> <property name="visible">True</property>
<property name="can_focus">False</property> </object>
</child>
<child>
<object class="GtkToolButton" id="toolbutton_cut"> <property name="visible">True</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Cut Standard</property> <property name="action_name">win.on_cutstandard</property>
<property name="icon_name">edit-cut</property> </object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="toolbutton_copy"> <property name="visible">True</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Copy Standard</property> <property name="action_name">win.on_copystandard</property>
<property name="icon_name">edit-copy</property> </object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="toolbutton_paste"> <property name="visible">True</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Paste Standard</property> <property name="action_name">win.on_pastestandard</property>
<property name="icon_name">edit-paste</property> </object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
</object>
</interface>
Within your application, you next need to create signal callback methods to process the signals generated by the callbacks. The rest of the text editor’s implementation is the same as in Listing
10-1
.
Chapter
10
, Exercise 2: Menu Bars
This exercise is an alteration of Listing
10-1
, where the buttons along the side are moved to a
Gtk.MenuBar
widget created with
Gtk.Builder
. The following UI file can be used for creating the toolbar.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenuBar" id="menubar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_File</property>
<property name="use_underline">True</property> <child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem"> <property name="label">gtk-new</property> <property name="visible">True</property> <property name="can_focus">False</property> <property name="use_stock">True</property>
<signal name="activate" handler="app.on_menu_new" swapped="no"/> </object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">True</property>
<signal name="activate" handler="app.on_menu_open" swapped="no"/> </object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">True</property>
<signal name="activate" handler="app.on_menu_save" swapped="no"/> </object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Edit</property> <property name="use_underline">True</property> <child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem"> <property name="label">gtk-cut</property> <property name="visible">True</property> <property name="can_focus">False</property> <property name="use_stock">True</property>
<signal name="activate" handler="win.on_menu_cut" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">True</property>
<signal name="activate" handler="win.on_menu_copy" swapped="no"/> </object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_stock">True</property>
<signal name="activate" handler="win.on_menu_paste" swapped="no"/> </object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
Within your application, you next need to create an the callback methods/functions that is associated with each of the toolbar items in the UI file. The rest of the exercise is the same as in Listing
10-1
.
Chapter
11
, Exercise 1: Glade Text Editor
This exercise expands on the Glade main window (see Listing 11-1), yet again by asking you to redesign the whole user interface in Glade. Instead of using buttons, you should implement a toolbar for text editing functions. You can then use
Gtk.Builder
to load the graphical user interface and connect the necessary signals. Figure
D-1
is a screenshot of the application for this exercise using a toolbar.
Within your application, you next need to create signal callback methods to process the signals generated by the callbacks. The rest of the text editor’s implementation is the same as in Listing
10-1
.
Chapter
11
, Exercise 2: Glade Text Editor with Menus
This exercise also expands on Listing 11-1. You to redesign the whole user interface in Glade. This time, though, instead of using buttons, you should implement a menu bar for text editing functions. You can then use
Gtk.Builder
to load the graphical user interface and connect the necessary signals. Figure
D-2
is a screenshot of the application for this exercise using a menu bar.
Chapter
13
, Exercise 1: Full Text Editor
This last text editor exercise is an extension of Listing
13-1
, “The Drawing Area Widget.” In it, you should add two additional features. The first is printing support, which allows the user to print the current text in the
Gtk.TextBuffer
widget. The printing support in the downloadable solution for this exercise is very similar to the printing example built in Chapter
13
, so you should check out that example’s description for more information about how this solution works.
The other additional feature is a recent file chooser menu for the Open toolbar item. To create this, you must convert the Open toolbar item to a
Gtk.MenuToolItem
widget. The default recent manager, obtained with
recentmanager.get_default()
, can be used to provide the recent files. Then, you can create the recent file chooser menu with
Gtk.RecentChoooserMenu.new_for_manager()
. This menu should be added to the Open menu tool button’s
Gtk.Menu
. You can use the
selection-done
signal to figure out which menu item is selected and what file should be opened.