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

9. Tree View Widget

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

This chapter show you how to use the Gtk.ScrolledWindow widget in combination with another powerful widget known as Gtk.TreeView. The tree view widget can be used to display data in lists or trees that span one or many columns. For example, a Gtk.TreeView can be used to implement a file browser or display the build the output of an integrated development environment.

Gtk.TreeView is an involved widget, because it provides a wide variety of features, so be sure to carefully read through each section of this chapter. However, once you learn this powerful widget, you are able to apply it in many applications.

This chapter introduces you to a large number of features provided by Gtk.TreeView. The information presented in this chapter enables you to mold the tree view widget to meet your needs. Specifically, in this chapter, you learn the following.
  • What objects are used to create a Gtk.TreeView and how its model-view-controller design makes it unique

  • How to create lists and tree structures with the Gtk.TreeView widget

  • When to use Gtk.TreePath, Gtk.TreeIter, or Gtk.TreeRowReference to reference rows within a Gtk.TreeView

  • How to handle double-clicks, single row selections, and multiple row selections

  • How to create editable tree view cells or customize individual cells with cell renderer functions

  • The widgets you can embed within a cell, including toggle buttons, pixbufs, spin buttons, combo boxes, progress bars, and keyboard accelerator strings

Parts of a Tree View

The Gtk.TreeView widget is used to display data organized as a list or a tree. The data displayed in the view is organized into columns and rows. The user is able to select one or multiple rows within the tree view using the mouse or keyboard. A screenshot of the Nautilus application using Gtk.TreeView is shown in Figure 9-1.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig1_HTML.jpg
Figure 9-1

Using The Gtk.TreeView widget

Gtk.TreeView is a difficult widget to use and an even more difficult widget to understand, so this whole chapter is dedicated to using it. However, once you understand how the widget works, you are able to apply it to a wide variety of applications, because it is possible to customize almost every aspect of the way the widget is displayed to the user.

What makes Gtk.TreeView unique is that it follows a design concept that is commonly referred to as model-view-controller (MVC) design. MVC is a design method where the information and the way it is rendered are completely independent of each other, similar to the relationship between Gtk.TextView and Gtk.TextBuffer.

Gtk.TreeModel

Data itself is stored within classes that implement the Gtk.TreeModel interface. GTK+ provides four types of built-in tree model classes, but only Gtk.ListStore and Gtk.TreeStore is covered in this chapter.

The Gtk.TreeModel interface provides a standard set of methods for retrieving general information about the data that is stored. For example, it allows you to get the number of rows in the tree and the number of children of a certain row. Gtk.TreeModel also gives you a way to retrieve the data that is stored in a specific row of the store.

Note

Models, renderers, and columns are referred to as objects instead of widgets, even though they are a part of the GTK+ library. This is an important distinction—since they are not derived from Gtk.Widget, they do not have the same set of functions, properties, and signals that are available to GTK+ widgets.

Gtk.ListStore allows you to create a list of elements with multiple columns. Each row is a child of the root node, so only one level of rows is displayed. Basically, Gtk.ListStore is a tree structure that has no hierarchy. It is only provided because faster algorithms exist for interacting with models that do not have any child items.

Gtk.TreeStore provides the same functionality as Gtk.ListStore, except the data can be organized into a multilayered tree. GTK+ provides a method for creating your own custom model types as well, but the two available types should be suitable in most cases.

While Gtk.ListStore and Gtk.TreeStore should fit most applications, a time may come when you need to implement your own store object. For example, if it needs to hold a huge number of rows, you should create a new model that is more efficient. In Chapter 12, you learn how to create new classes derived from GObject, which can be used as a guide to get you started deriving a new class that implements the Gtk.TreeModel interface.

After you have created the tree model, the view is used to display the data. By separating the tree view and its model, you are able to display the same set of data in multiple views. These views can be exact copies of each other, or the data can be displayed in varying ways. All the views are updated simultaneously as you make alterations to a model.

Tip

While it may not immediately seem beneficial to display the same set of data in multiple tree views, consider a file browser. If you need to display the same set of files in multiple file browsers, using the same model for each view would save memory as well as make your program run considerably faster. This is also useful when you want to provide multiple display options for the file browser. When switching between display modes, you do not need to alter the data itself.

Models are composed of columns that contain the same data type and rows that hold each set of data. Each model column can hold a single type of data. A tree model column should not be confused with a tree view column, which is composed of a single header but may be rendered with data from multiple model columns. For example, a tree column may display a text string that has a foreground color defined by a model column that is not visible to the user. Figure 9-2 illustrates the difference between model columns and tree columns.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig2_HTML.jpg
Figure 9-2

The relationship between model and tree columns

Each row within a model contains one piece of data corresponding to each model column. In Figure 9-2, each row contains a text string and a Gdk.Color value. These two values are used to display the text with the corresponding color in the tree column. You learn how to implement this in code later in this chapter. For now, you should simply understand the differences between the two types of columns and how they relate.

New list and tree stores are created with a number of columns, each defined by an existing GObject.TYPE. Usually, you need to use only those already implemented in GLib . For example, if you want to display text you can use GObject.TYPE_STRING, GObject.TYPE_BOOLEAN, and a few of the number types like GObject.TYPE_INT.

Tip

Since it is possible to store an arbitrary data type with GObject.TYPE_POINTER, one or more tree model columns can be used to simply store information about every row. You just need to be careful when there are a large number of rows, because memory usage quickly escalates. You also have to take care of freeing the pointers yourself.

Gtk.TreeViewColumn and Gtk.CellRenderer

As previously mentioned, a tree view displays one or more Gtk.TreeViewColumn objects. Tree columns are composed of a header and cells of data that are organized into one column. Each tree view column also contains one or more visible columns of data. For example, in a file browser, a tree view column may contain one column of images and one column of file names.

The header of the Gtk.TreeViewColumn widget contains a title that describes what data is held in the cells below. If you make the column sortable, the rows are sorted when one of the column headers is clicked.

Tree view columns do not actually render anything to the screen. This is done with an object derived from Gtk.CellRenderer. Cell renderers are packed into tree view columns similar to how you add widgets into a horizontal box. Each tree view column can contain one or more cell renderers, which are used to render the data. For example, in a file browser, the image column would be rendered with

Gtk.CellRendererPixbuf and the file name with Gtk.CellRendererText. An example of this was shown in Figure 9-1.

Each cell renderer is responsible for rendering a column of cells, one for every row in the tree view. It begins with the first row, rendering its cell and then proceeding to the next row down until the whole column, or part of the column, is rendered.

In GTK+ 3 the g_object_set() function is no longer available. So you must add attributes to the renderer. Column attributes correspond to tree model columns and are associated with cell renderer properties, as shown in Figure 9-3. These properties are applied to each cell as it is rendered.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig3_HTML.jpg
Figure 9-3

Applying cell renderer properties

In Figure 9-3, there are two tree model columns with the types GObject.TYPE_STRING and Gdk.RGBA. These are applied to Gtk.CellRendererText’s text and foreground properties and used to render the tree view column accordingly.

An additional way to change cell renderer properties is by defining a cell data function. This function is called for every row in the tree view before it is rendered. This allows you to customize how every cell is rendered without the need for the data to be stored in a tree model. For example, a cell data function can be used to define how many decimal places of a floating-point number to display. Cell data functions are covered in detail in the “Cell Data Methods” section of this chapter.

This chapter also covers cell renderers that are used to display text (strings, numbers, and Boolean values), toggle buttons, spin buttons, progress bars, pixbufs, combo boxes, and keyboard accelerators. In addition, you can create custom cell renderer types, but this is usually not needed, since GTK+ now provides such a wide variety of types.

This section has taught you what objects are needed to use the Gtk.TreeView widget, what they do, and how they interrelate. Now that you have a basic understanding of the Gtk.TreeView widget, the next section has a simple example of the Gtk.ListStore tree model.

Using Gtk.ListStore

Recall from the previous section that Gtk.TreeModel is simply an interface implemented by data stores, such as Gtk.ListStore. Gtk.ListStore is used to create lists of data that have no hierarchical relationship among rows.

In this section, a simple Grocery List application is implemented that contains three columns, all of which use Gtk.CellRendererText. Figure 9-4 is a screenshot of this application. The first column is a boolean value displaying True or False that defines whether or not the product should be purchased.

Tip

You usually do not want to display Boolean values as text, because if you have many Boolean columns, it becomes unmanageable for the user. Instead, you want to use toggle buttons. You learn how to do this with Gtk.CellRendererToggle in a later section. Boolean values are often also used as column attributes to define cell renderer properties.

../images/142357_2_En_9_Chapter/142357_2_En_9_Fig4_HTML.jpg
Figure 9-4

A tree view widget using a Gtk.ListStore tree model

Listing 9-1 creates a Gtk.ListStore object, which displays a list of groceries. In addition to displaying the products, the list store also displays whether to buy the product and how many of them to buy.

This Grocery List application is used for many examples throughout the rest of the chapter. Therefore, the content of some functions may be excluded later on if it is presented in previous examples. Also, to keep things organized, in every example, setup_tree_view() is used to set up columns and renderers. Full code listings for every example can be downloaded at www.gtkbook.com .
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
BUY_IT = 0
QUANTITY = 1
PRODUCT = 2
GroceryItem = (( True, 1, "Paper Towels" ),
               ( True, 2, "Bread" ),
               ( False, 1, "Butter" ),
               ( True, 1, "Milk" ),
               ( False, 3, "Chips" ),
               ( True, 4, "Soda" ))
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, 175)
        treeview = Gtk.TreeView.new()
        self.setup_tree_view(treeview)
        store = Gtk.ListStore.new((GObject.TYPE_BOOLEAN,
                               GObject.TYPE_INT,
                               GObject.TYPE_STRING))
        for row in GroceryItem:
            iter = store.append(None)
            store.set(iter, BUY_IT, row[BUY_IT], QUANTITY,
                      row[QUANTITY], PRODUCT, row[PRODUCT])
        treeview.set_model(store)
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(treeview)
        self.add(scrolled_win)
    def setup_tree_view(self, treeview):
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Buy", renderer, text=BUY_IT)
        treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Count", renderer, text=QUANTITY)
        treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Product", renderer, text=PRODUCT)
        treeview.append_column(column)
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="Grocery List")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 9-1

Using a Gtk.FontSelectionDialog

Creating the Tree View

Creating the Gtk.TreeView widget is the easiest part of the process. You need only to call Gtk.TreeView.new(). A tree model can easily be applied to a Gtk.TreeView after initialization with treeview.set_model(store).

Until GTK+ 3 came along, there were functions to hide/unhide the column header for a Gtk.TreeViewColumn. Those functions have been deprecated in GTK+ 3 and now all column headers are always visible.

Gtk.TreeViewColumn headers provide more functionality beyond column titles for some tree views. In sortable tree models, clicking the column header can initiate sorting of all of the rows according to the data held in the corresponding column. It also gives a visual indication of the sort order of the column if applicable. You should not hide the headers if the user needs them to sort the tree view rows.

As a GTK+ developer, you should be very careful about changing visual properties. Users have the ability to choose themes that fit their needs, and you can make your application unusable by changing how widgets are displayed.

Renderers and Columns

After creating the Gtk.TreeView, you need to add one or more columns to the view for it to be of any use. Each Gtk.TreeViewColumn is composed of a header, which displays a short description of its content, and at least one cell renderer. Tree view columns do not actually render any content. Tree view columns hold one or more cell renderers that are used to draw the data on the screen.

All cell renderers are derived from the Gtk.CellRenderer class and are referred to as objects in this chapter, because Gtk.CellRenderer is derived directly from GObject, not from Gtk.Widget. Each cell renderer contains a number of properties that determine how the data is drawn within a cell.

The Gtk.CellRenderer class provides common properties to all derivative renderers, including background color, size parameters, alignments, visibility, sensitivity, and padding. A full list of Gtk.CellRenderer properties can be found in Appendix A. It also provides the editing-canceled and editing-started signals, which allow you to implement editing in custom cell renderers.

In Listing 9-1, you were introduced to Gtk.CellRendererText, which is capable of rendering strings, numbers, and boolean values as text. Textual cell renderers are initialized with Gtk.CellRendererText.new().

Gtk.CellRendererText provides a number of additional properties that dictate how each cell is rendered. You should always set the text property, which is the string that is displayed in the cell. The rest of the properties are similar to those used with text tags.

Gtk.CellRendererText contains a large number of properties that dictate how every row is rendered. renderer.foreground-rgba() was used in the following example to set the foreground color of every piece of text in the renderer to orange. Some properties have a corresponding set property as well, which must be set to True if you want the value to be used. For example, you should set foreground-set to True for the changes takes effect.
renderer.props.foreground-rgba = Gdk.RGBA(red=1.0, green=0.65, blue=0.0,
                                          alpha=1.0)
After you create a cell renderer, it needs to be added to a Gtk.TreeViewColumn. Tree view columns can be created with Gtk.TreeViewColumn() if you only want the column to display one cell renderer. In the following code, a tree view column is created with the title “Buy” and a renderer with one attribute. This attribute is referred to as BUY_IT (with a value of 0) when the Gtk.ListStore is populated.
column = Gtk.TreeViewColumn("Buy", renderer, text=BUY_IT)

The preceding function accepts a string to display in the column header, a cell renderer, and a list of attributes. Each attribute contains a string that refers to the renderer property and the tree view column number. The important thing to realize is that the column number provided to Gtk.TreeViewColumn() refers to the tree model column, which may not be the same as the number of tree model columns or cell renderers used by the tree view.

It turns out that the Gtk.TreeViewColumn() is very hard to implement piecemeal in Python 3. It is not just convenience method, but the preferred method for creating a Gtk.TreeViewColumn(). The following code snippet is the correct way to create a Gtk.TreeViewColumn() in Python 3 and assign at least one attribute.
renderer = Gtk.CellRendererText.new()
column = Gtk.TreeViewColumn("Buy", renderer, text=BUY_IT)
treeview.append_column(column)

If you want to add multiple renderers to the tree view column, you need to pack each renderer and set its attributes separately. For example, in a file manager, you might want to include a text and an image renderer in the same column. However, if every column only needs one cell renderer, it is easiest to use Gtk.TreeViewColumn().

Note

If you want a property, such as the foreground color, set to the same value for every row in the column, you should apply that property directly to the cell renderer with renderer.foreground-rgba(). However, if the property varies depending on the row, you should add it as an attribute of the column for the given renderer.

After you have finished setting up a tree view column, it needs to be added to the tree view with treeview.append_column(column). Columns may also be added into an arbitrary position of the tree view with treeview.insert_column(column) or removed from the view with treeview.remove_column(column).

Creating the Gtk.ListStore

The tree view columns are now set up with the desired cell renderers, so it is time to create the tree model that interfaces between the renderers and the tree view. For the example found in Listing 9-1, we used Gtk.ListStore so that the items would be shown as a list of elements.

New list stores are created with Gtk.ListStore.new(). This function accepts the number of columns and the type of the data each column holds. In Listing 9-1, the list store has three columns that store boolean, integer, and string data types.
Gtk.ListStore.new((GObject.TYPE_BOOLEAN, GObject.TYPE_INT,
                   GObject.TYPE_STRING))

In Python 3, the column type parameters are formed into a tuple. That tells the method not only the column type but also the number of columns.

After creating the list store, you need to add rows with store.append(None) for it to be of any use. This method appends a new row to the list store, and the iterator is set to point to the new row. You learn more about tree iterators in a later section of this chapter. For now, it is adequate for you to know that it points to the new tree view row.
iter = store.append(None)
store.set(iter, BUY_IT, row[BUY_IT], QUANTITY, row[QUANTITY],
          PRODUCT, row[PRODUCT])

Next, we need to set which column and what values are to be loaded with data. This is done with the store.set() method. One or more rows can be set with this method. The preceding example stores values in each column of the row from left to right, but the column can be listed in any order since we are also specifying the column number where the value is loaded.

Note

Gtk.CellRendererText automatically converts Boolean values and numbers into text strings that can be rendered on the screen. Therefore, the type of data applied to a text attribute column does not have to be text itself, but just has to be consistent with the list store column type that was defined during initialization of the Gtk.ListStore.

There are multiple other functions for adding rows to a list store, including store.prepend() and store.insert(). A full list of available functions can be found in the Gtk.ListStore API documentation.

In addition to adding rows, you can also remove them with store.remove(). This function removes the row that Gtk.TreeIter refers to. After the row is removed, the iterator points to the next row in the list store, and the function returns True. If the last row was just removed, the iterator becomes invalid, and the function returns False.
store.remove(iter)

In addition, store.clear() is provided, which can be used to remove all rows from a list store. You are left with a Gtk.ListStore that contains no data.

After the list store is created, you need to call treeview.set_model() to add it to the tree view. By calling this method, the reference count of the tree model is incremented by one.

Using Gtk.TreeStore

There is one other type of built-in tree model called Gtk.TreeStore, which organizes rows into a multilevel tree structure. It is possible to implement a list with a Gtk.TreeStore tree model as well, but this is not recommended because some overhead is added when the object assumes that the row may have one or more children.

Figure 9-5 shows an example tree store, which contains two root elements, each with children of its own. By clicking the expander to the left of a row with children, you can show or hide its children. This is similar to the functionality provided by the Gtk.Expander widget.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig5_HTML.jpg
Figure 9-5

A tree view widget using a Gtk.TreeStore tree model

The only difference between a Gtk.TreeView implemented with a Gtk.TreeStore instead of a Gtk.ListStore is in the creation of the store. Adding columns and renderers is performed in the same manner with both models, because columns are a part of the view not the model. Executing Listing 9-2 will produce the dialog in Figure 9-5.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
BUY_IT = 0
QUANTITY = 1
PRODUCT = 2
PRODUCT_CATEGORY = 0
PRODUCT_CHILD = 1
GroceryItem = (( PRODUCT_CATEGORY, True, 0, "Cleaning Supplies"),
               ( PRODUCT_CHILD, True, 1, "Paper Towels" ),
               ( PRODUCT_CHILD, True, 3, "Toilet Paper" ),
               ( PRODUCT_CATEGORY, True, 0, "Food"),
               ( PRODUCT_CHILD, True, 2, "Bread" ),
               ( PRODUCT_CHILD, False, 1, "Butter" ),
               ( PRODUCT_CHILD, True, 1, "Milk" ),
               ( PRODUCT_CHILD, False, 3, "Chips" ),
               ( PRODUCT_CHILD, True, 4, "Soda" ))
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(275, 270)
        treeview = Gtk.TreeView.new()
        self.setup_tree_view(treeview)
        store = Gtk.TreeStore.new((GObject.TYPE_BOOLEAN,
                               GObject.TYPE_INT,
                               GObject.TYPE_STRING))
        iter = None
        i = 0
        for row in GroceryItem:
            (ptype, buy, quant, prod) = row
            if ptype == PRODUCT_CATEGORY:
                j = i + 1
                (ptype1, buy1, quant1, prod1) = GroceryItem[j]
                while j < len(GroceryItem) and ptype1 == PRODUCT_CHILD:
                    if buy1:
                        quant += quant1
                    j += 1;
                    if j < len(GroceryItem):
                        (ptype1, buy1, quant1, prod1) = GroceryItem[j] iter = store.append(None)
                store.set(iter, BUY_IT, buy, QUANTITY, quant, PRODUCT, prod)
            else:
                child = store.append(iter)
                store.set(child, BUY_IT, buy, QUANTITY, quant, PRODUCT, prod)
            i += 1
        treeview.set_model(store)
        treeview.expand_all()
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(treeview)
        self.add(scrolled_win)
    def setup_tree_view(self, treeview):
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Buy", renderer, text=BUY_IT)
        treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Count", renderer, text=QUANTITY)
        treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Product", renderer, text=PRODUCT)
        treeview.append_column(column)
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="Grocery List")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 9-2

Creating a Gtk.TreeStore

Tree stores are initialized with Gtk.TreeStore.new(), which accepts the same parameters as Gtk.ListStore.new(). The column type parameters are formed into a tuple. That tells the method not only the column type but also the number of columns.

Adding rows to a tree store is a little different than adding rows to a list store. You add rows to a tree store with store.append(), which accepts one iterator or None. The iterator should point to the parent row of the new row. The method returns an iterator that points to the inserted row when the function returns, and the second.
iter = store.append(None)

In the preceding call to store.append(), a root element was appended to the list by passing None as the parent iterator. The iter tree iterator returned by the method was set to the location of the new row.

In the second call to store.append(), which follows, the row is added as a child of iter. Next, the child tree iterator is returned set to the current location of the new row within the tree store when the method returns.
child = store.append(iter)

As with list stores, there are many methods available for adding rows to a tree store. These include store.insert(), store.prepend(), and store.insert_before() to name a few. For a full list of methods, you should reference the Gtk.TreeStore API documentation.

After you add a row to the tree store, it is simply an empty row with no data. To add data to the row, call store.set(). This function works in the same way as store.set(). It accepts the tree store, a tree iterator pointing to the location of the row, and a list of column-data pairs. These column numbers correspond to those you used when setting up the cell renderer attributes.
store.set(child, BUY_IT, buy, QUANTITY, quant, PRODUCT, prod)
In addition to adding rows to a tree store, you can also remove them with store.remove(). This function removes the row that is referred to by Gtk.TreeIter. After the row is removed, iter points to the next row in the tree store, and the function returns True. If the row that you removed was the last in the tree store, the iterator becomes invalid, and the function returns False.
store.remove(iter)

In addition, store.clear() is provided, which can be used to remove all rows from a tree store. You are left with a Gtk.TreeStore that contains no data.

In Listing 9-2, treeview.expand_all() is called to expand all of the rows. This is a recursive function that expands every possible row, although it only affects tree models that have child-parent row relationships. In addition, you can collapse all of the rows with treeview.collapse_all(). By default, all rows are collapsed.

Referencing Rows

Three objects are available for referring to a specific row within a tree model; each has its own unique advantages. They are Gtk.TreePath, Gtk.TreeIter, and Gtk.TreeRowReference. In the following sections, you learn how each object works and how to use them within your own programs.

Tree Paths

For example, if you are presented with the string 3:7:5, you would start at the fourth root element (recall that indexing begins at zero, so element three is actually the fourth element in the level). You would next proceed to the eighth child of that root element. The row in question is that child’s sixth child.

To illustrate this graphically, Figure 9-6 shows the tree view created in Figure 9-5 with the tree paths labeled. Each root element is referred to as only one element, 0 and 1. The first root element has two children, referred to as 0:0 and 0:1.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig6_HTML.jpg
Figure 9-6

Tree paths for a tree view using Gtk.TreeStore

Two functions are provided that allow you to convert back and forth between a path and its equivalent string: treepath.to_string() and Gtk.TreePath.new_from_string(). You usually do not have to deal with the string path directly unless you are trying to save the state of a tree view, but using it helps in understanding the way tree paths work.

Listing 9-3 gives a short example of using tree paths. It begins by creating a new path that points to the Bread product row. Next, treepath.up() moves up one level in the path. When you convert the path back into a string, you see that the resulting output is 1, pointing to the Food row.
treepath = Gtk.TreePath.new_from_string("1:0")
treepath.up(path)
str = treepath.to_string(path)
print(str)
Listing 9-3

Converting Between Paths and Strings

Tip

If you need to get a tree iterator and only have the path string available, you can convert the string into a Gtk.TreePath and then to a Gtk.TreeIter. However, a better solution would be to skip the intermediate step with treemodel.get_iter_from_string(), which converts a tree path string directly into a tree iterator.

In addition to treepath.up(), there are other functions that allow you to navigate through a tree model. You can use treepath.down() to move to the child row and treepath.next() or treepath.prev() to move to the next or previous row in the same level. When you move to the previous row or parent row, False is returned if it was not successful.

At times, you may need to have a tree path as a list of integers instead of a string. The treepath.get_indices() function returns the integers that compose the path string.
treepath.get_indices(path)

Problems can arise with tree paths when a row is added or removed from the tree model. The path could end up pointing to a different row within the tree or, worse, a row that does not exist anymore! For example, if a tree path points to the last element of a tree and you remove that row, it now points beyond the limits of the tree. To get around this problem, you can convert the tree path into a tree row reference.

Tree Row References

Gtk.TreeRowReference objects are used to watch a tree model for changes. Internally, they connect to the "row-inserted", "row-deleted", and "rows-reordered" signals, updating the stored path based on the changes.

New tree row references are created with Gtk.TreeRowReference.new() from an existing Gtk.TreeModel and Gtk.TreePath. The tree path copied into the row reference is updated as changes occur within the model.
treerowref.new(model, path)

When you need to retrieve the path, you can use treerowref.get_path(), which returns None if the row no longer exists within the model. Tree row references are able to update the tree path based on changes within the tree model, but if you remove all elements from the same level as the tree path’s row, it no longer has a row to point to.

You should be aware that tree row references do add a small bit of overhead processing when adding, removing, or sorting rows within a tree model, since the references have to handle all of the signals emitted by these actions. This overhead does not matter for most applications, because there will not be enough rows for the user to notice. However, if your application contains a large number of rows, you should use tree row references wisely.

Tree Iterators

GTK+ provides the Gtk.TreeIter object, which can be used to reference a specific row within a Gtk.TreeModel. These iterators are used internally by models, which means that you should never directly alter the content of a tree iterator.

You have already seen multiple instances of Gtk.TreeIter, from which you can discern that tree iterators are used in a similar way to Gtk.TreeIter. Tree iterators are used for manipulation of tree models. Tree paths, however, are used to point to rows within a tree model in a way that provides a human-readable interface. Tree row references can be used to make sure that tree paths adjust where they point throughout changes of a tree model.

GTK+ provides a number of built-in methods to perform operations on the tree iterators. Typically, iterators are used to add rows to a model, set the content of a row, and retrieve the content of a model. In Figure 9-1 and Figure 9-2, tree iterators were used to add rows to Gtk.ListStore and Gtk.TreeStore models and then set the initial content of each row.

Gtk.TreeModel provides a number of iter_*() methods, which can be used to move iterators and retrieve information about them. For example, to move to the next iterator position, you could use treemodel.iter_next(), which returns True if the action was successful. A full list of available functions can be found in the Gtk.TreeModel API documentation.

It is easy to convert between tree iterators and tree paths with the use of treemodel.get_path() and treemodel.get_iter(). The tree path or iterator must be valid for either of these functions to work correctly. Listing 9-4 gives a short example of how to convert between Gtk.TreeIter and Gtk.TreePath.
path = treemodel.get_path(model, iter)
iter = treemodel.get_iter(model, path)
Listing 9-4

Converting Between Paths and Iterators

The first method in Listing 9-4, treemodel.get_path() converts a valid tree iterator into a tree path. That path is then sent to treemodel.get_iter(), which converts it back into an iterator. Notice that the second method accepts two parameters.

One problem presented by Gtk.TreeIter is that the iterator is not guaranteed to exist after a model is edited. This is not true in all cases, and you can use treemodel.get_flags() to check the Gtk.TreeModelFlags.ITERS_PERSIST flag, which is turned on by default for Gtk.ListStore and Gtk.TreeStore. If this flag is set, the tree iterator is always valid as long as the row exists.
treemodel.get_flags()

Even if the iterator is set to persist, it is not a good idea to store tree iterator objects, since they are used internally by tree models. Instead, you should use tree row references to keep track of rows over time, since references will not become invalidated when the tree model changes.

Adding Rows and Handling Selections

Both of the examples that you have been given up to this point define the tree model during startup. The content does not change after it is initially set. In this section, the Grocery List application is expanded to allow the user to add and remove products. Before the example is introduced, you learn how to handle single and multiple selections.

Single Selections

Selection information is held for each tree view by a Gtk.TreeSelection object. You can retrieve this object with treeview.get_selection(). A Gtk.TreeSelection object is automatically created for you for every Gtk.TreeView, so there is never a need to create your own tree selection.

Caution

Gtk.TreeSelection provides one signal, “changed”, which is emitted when the selection has changed. You should be careful when using this signal, because it is not always reliable. It can be emitted when no changes occur by the user selecting a row that is already selected. Therefore, it is best to use the signals provided by Gtk.TreeView for selection handling, which is in Appendix B.

Tree views support multiple types of selections. You can change the selection type with treeselection.set_mode(). Selection types are defined by the Gtk.SelectionMode enumeration, which includes the following values.
  • Gtk.SelectionMode.NONE: The user is prohibited from selecting any rows.

  • Gtk.SelectionMode.SINGLE: The user may select up to one row, though it is possible that no row is selected. By default, tree selections are initialized with Gtk.SelectionMode.SINGLE.

  • Gtk.SelectionMode.BROWSE: The user is able to select exactly one row. In some rare cases, there may not be a selected row. This option actually prohibits the user from deselecting a row except when the selection is moved to another row.

  • Gtk.SelectionMode.MULTIPLE: The user may select any number of rows. The user is able to use the Ctrl and Shift keys to select additional elements or ranges of elements.

If you have defined the selection type as Gtk.SelectionMode.SINGLE or Gtk.SelectionMode.BROWSE, you can be sure that only one row is selected. For tree views with one selection, you can use treeselection.get_selected() to retrieve the selected row.
treeselection.get_selected(model, iter)

The treeselection.get_selected() method can be used to retrieve the tree model associated with the Gtk.TreeSelection object and a tree iterator pointing to the selected row. True is returned if the model and iterator were successfully set. This function will not work with a selection mode of Gtk.SelectionMode.MULTIPLE!

If no row has been selected, the tree iterator is set to None, and False is returned from the function. Therefore, treeselection.get_selected() can also be used as a test to check whether or not there is a selected row.

Multiple Selections

If your tree selection allows multiple rows to be selected (Gtk.SelectionMode.MULTIPLE), you have two options for handling selections, calling a function for every row or retrieving all of the selected rows as a Python list. Your first option is to call a function for every selected row with treeselection.selected_foreach().
treeselection.selected_foreach(selected, foreach_func, None)
This function allows you to call selected_foreach_func() for every selected row, passing an optional data parameter. In the preceding example, None was passed to the function. The function must be either a Python function or method, an example of which is seen in Listing 9-5. The function in Listing 9-5 retrieves the product string and prints it to the screen.
foreach_func(model, path, iter, data)
    (text,) = model.get(iter, PRODUCT)
    print ("Selected Product: %s" % text)
Listing 9-5

Selected for-each Function

Note

You should not modify the tree model or selection from within the foreach_func implementation! GTK+ gives critical errors to the user if you do so, because invalid tree paths and iterators may result.

Also note the method model.get() always return a tuple, even if you only ask for a single column.

One problem with using tree selection foreach_func functions is that you are not able to manipulate the selection from within the function. To remedy this problem, a better solution would be to use treeselection.get_selected_rows(), which returns a Python list of Gtk.TreePath objects, each pointing to a selected row.
treeselection.get_selected_rows(model)

You can then perform some operation on each row within the list. However, you need to be careful. If you need to edit the tree model within the List, you want to first convert all of the tree paths to tree row references, so they continue to be valid throughout the duration of your actions.

If you want to loop through all of the rows manually, you are also able to use treeselection.count_selected_rows(), which returns the number of rows that are currently selected.

Adding New Rows

Now that you have been introduced to selections, it is time to add the ability to add new products to the list.

The only difference in this example in comparison to the previous Grocery List application is visible in Figure 9-7, which shows that an Add and Remove buttons were added along the bottom of the tree view. Also, the selection mode was changed to allow the user to select multiple rows at a time.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig7_HTML.jpg
Figure 9-7

Editing an item in the grocery list

Listing 9-6 is the implementation of the callback function that is run when the user clicks the Add button. It presents the user with a Gtk.Dialog that asks the user to choose a category, enter a product name and quantity of products to buy, and select whether or not to purchase the product.

If all of the fields are valid, the row is added under the chosen category. Also, if the user specified that the product should be purchased, the quantity is added to the total quantity of the category.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
BUY_IT = 0
QUANTITY = 1
PRODUCT = 2
PRODUCT_CATEGORY = 0
PRODUCT_CHILD = 1
GroceryItem = (( PRODUCT_CATEGORY, True, 0, "Cleaning Supplies"), ( PRODUCT_CHILD, True, 1, "Paper Towels" ),
               ( PRODUCT_CHILD, True, 3, "Toilet Paper" ), ( PRODUCT_CATEGORY, True, 0, "Food"), ( PRODUCT_CHILD, True, 2, "Bread" ),
               ( PRODUCT_CHILD, False, 1, "Butter" ),
               ( PRODUCT_CHILD, True, 1, "Milk" ),
               ( PRODUCT_CHILD, False, 3, "Chips" ),
               ( PRODUCT_CHILD, True, 4, "Soda" ))
class AddDialog(Gtk.Dialog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        parent = kwargs['parent']
        # set up buttons
        self.add_button("Add", Gtk.ResponseType.OK)
        self.add_button("Cancel", Gtk.ResponseType.CANCEL)
        # set up dialog widgets
        combobox = Gtk.ComboBoxText.new()
        entry = Gtk.Entry.new()
        spin = Gtk.SpinButton.new_with_range(0, 100, 1)
        check = Gtk.CheckButton.new_with_mnemonic("_Buy the Product")
        spin.set_digits(0)
        # Add all of the categories to the combo box. for row in GroceryItem:
            (ptype, buy, quant, prod) = row
            if ptype == PRODUCT_CATEGORY:
                combobox.append_text(prod)
        # create a grid
        grid = Gtk.Grid.new()
        grid.set_row_spacing (5)
        grid.set_column_spacing(5)
        # fill out the grid
        grid.attach(Gtk.Label.new("Category:"), 0, 0, 1, 1)
        grid.attach(Gtk.Label.new("Product:"), 0, 1, 1, 1)
        grid.attach(Gtk.Label.new("Quantity:"), 0, 2, 1, 1)
        grid.attach(combobox, 1, 0, 1, 1)
        grid.attach(entry, 1, 1, 1, 1)
        grid.attach(spin, 1, 2, 1, 1)
        grid.attach(check, 1, 3, 1, 1)
        self.get_content_area().pack_start(grid, True, True, 5) self.show_all()
        # run the dialog and check the results
        if self.run() != Gtk.ResponseType.OK:
            self.destroy()
            return
        quantity = spin.get_value()
        product = entry.get_text()
        category = combobox.get_active_text()
        buy = check.get_active()
        if product == "" or category == None:
            print("All of the fields were not correctly filled out!")
            return
        model = parent.get_treeview().get_model();
        iter = model.get_iter_from_string("0")
        # Retrieve an iterator pointing to the selected category. while iter:
            (name,) = model.get(iter, PRODUCT)
            if name == None or name.lower() == category.lower():
                break
            iter = model.iter_next(iter)
        #
        #
        # Convert the category iterator to a path so that it  # will not become invalid and add the new product as a child of the category.
        path = model.get_path(iter)
        child = model.append(iter)
        model.set(child, BUY_IT, buy, QUANTITY, quantity, PRODUCT, product)
        # Add the quantity to the running total if it is to be purchased. if buy:
            iter = model.get_iter(path)
            (i,) = model.get(iter, QUANTITY) i += quantity
            model.set(iter, QUANTITY, i)
        self.destroy()
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(275, 270)
        self.treeview = Gtk.TreeView.new()
        self.setup_tree_view(self.treeview)
        store = Gtk.TreeStore.new((GObject.TYPE_BOOLEAN,
                               GObject.TYPE_INT,
                               GObject.TYPE_STRING))
        iter = None
        i = 0
        for row in GroceryItem:
            (ptype, buy, quant, prod) = row
            if ptype == PRODUCT_CATEGORY:
                j = i + 1
                (ptype1, buy1, quant1, prod1) = GroceryItem[j]
                while j < len(GroceryItem) and ptype1 == PRODUCT_CHILD:
                    if buy1:
                        quant += quant1
                    j += 1;
                    if j < len(GroceryItem):
                        (ptype1, buy1, quant1, prod1) = GroceryItem[j] iter = store.append(None)
                store.set(iter, BUY_IT, buy, QUANTITY, quant, PRODUCT, prod)
            else:
                child = store.append(iter)
                store.set(child, BUY_IT, buy, QUANTITY, quant, PRODUCT, prod)
            i += 1
        self.treeview.set_model(store)
        self.treeview.expand_all()
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(self.treeview)
        button_add = Gtk.Button.new_with_label("Add")
        button_add.connect("clicked", self.on_add_button_clicked, self)
        button_remove = Gtk.Button.new_with_label("Remove")
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        hbox.pack_end(button_remove, False, True, 5)
        hbox.pack_end(button_add, False, True, 5)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        vbox.pack_end(hbox, False, True, 5)
        vbox.pack_end(scrolled_win, True, True, 5)
        self.add(vbox)
    def setup_tree_view(self, treeview):
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Buy", renderer, text=BUY_IT)
        self.treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Count", renderer, text=QUANTITY)
        treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn("Product", renderer, text=PRODUCT)
        treeview.append_column(column)
    def on_add_button_clicked(self, button, parent):
        dialog = AddDialog(title="Add a Product", parent=parent,
                             flags=Gtk.DialogFlags.MODAL)
    def get_treeview(self):
        return self.treeview
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="Grocery List")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 9-6

Adding a New Product

Retrieving Row Data

Retrieving the values stored in a tree model row is very similar to adding a row. In Listing 9-6 model.get_iter_from_string() is first used to retrieve a tree iterator that points to the first row in the tree view. This corresponds to the first category.

Next, model.iter_next() is used to loop through all of the root-level rows. For each root-level row, the following code is run. First, the product name is retrieved with model.get(). This function works like treestore.set(), which accepts a Gtk.TreeModel, an iterator pointing to a row, and a list of one or more column numbers. This method always returns a tuple even if you supply a single column as a parameter.
(name,) = model.get(iter, PRODUCT)
if name.lower() == category.lower():
    break

Then the current product is compared to the chosen category name. If the two strings match, the loop is exited, because the correct category was found. The iter variable now points to the selected category.

Adding a New Row

Adding new rows to the tree model is done in the same way as they were originally added during startup. In the following code, the Gtk.TreeIter that points to the chosen category is first converted into a tree path, since it becomes invalidated when the tree store is changed. Note that it does not have to be converted to a tree row reference, because its location will not possibly change.
path = model.get_path(iter)
child = model.append(iter)
model.set(child, BUY_IT, buy, QUANTITY, quantity, PRODUCT, product)

Next, a new row is appended with treestore.append(), where iter is the parent row. That row is populated with treestore.set(), using the data entered by the user in the dialog.

Combo Boxes

Listing 9-6 introduces a new widget called Gtk.ComboBox.

Gtk.ComboBox is a widget that allows the user to choose from a number of options in a drop-down list.

The combo box displays the selected choice in its normal state. Combo boxes can be used in two different ways, depending on what method you use to instantiate the widget, either with a custom Gtk.TreeModel or with a default model with only a single column of strings.

In Listing 9-6 a new Gtk.ComboBox was created with Gtk.ComboBoxText.new(), which creates a specialized combo box that contains only one column of strings. This is simply a convenience method, because the drop-down list of a combo box is internally handled with a Gtk.TreeModel. This allows you to easily append and prepend options and insert new options with the following methods.
combobox.append_text(text)
combobox.prepend_text(text)
combobox.insert_text(position, text)

The first function combobox.get_active_text() returns an integer that refers to the index of the current row or -1 if there is no selection. This can be converted into a string and then into a Gtk.TreePath. Also, combobox.get_active_iter() retrieves an iterator pointing to the selected row, returning True if the iterator was set.

Removing Multiple Rows

The next step is to add the ability to remove products from the list. Since we have added the ability for multiple rows to be selected, the code must also be able to remove more than one row.

Listing 9-7 implements two methods. The first method, remove_row(), is called for every selected row, removing the row if it is not a category. If the removed row was to be purchased, its quantity is removed from the category’s running total. The second function, remove_products(), is the method that is run when the Remove button is clicked.
    def remove_row(self, ref, model):
        # Convert the tree row reference to a path and retrieve the iterator. path = ref.get_path()
        iter = model.get_iter(path)
        # Only remove the row if it is not a root row.
        parent = model.iter_parent(iter)
        if parent:
            (buy, quantity) = model.get(iter, BUY_IT, QUANTITY)
            (pnum,) = model.get(parent, QUANTITY)
            if (buy):
                pnum -= quantity
                model.set(parent, QUANTITY, pnum)
            iter = model.get_iter(path)
            model.remove(iter)
    def remove_products(self, button, treeview):
        selection = treeview.get_selection()
        model = treeview.get_model()
        rows = selection.get_selected_rows(model)
        # Create tree row references to all of the selected rows. references = []
        for data in rows:
            ref = Gtk.TreeRowReference.new(model, data)
            references.append(ref)
        for ref in references:
            self.remove_row(ref, model)
Listing 9-7

Removing One or More Products

When the Remove button is pressed, the remove_products() method is called. This function begins by calling selection.get_selected_rows()to retrieve a Python list of tree paths that point to the selected rows. Since the application is altering the rows, the list of paths is converted into a list of row references. This makes sure that all of the tree paths remain valid.

After the paths are converted to tree row references, the list is iterated via a Python for statement and the remove_row() method is called for every item. Within remove_row(), a new function is used to check whether the row is a category.

If the selected row is a category, we know that it is a root element and have no parents. Therefore, the following model.iter_parent() call performs two tasks. First, if the parent iterator is not set, this method returns False, and the category row is not removed. If the row has a parent, which means that it is a product, the parent iterator is set and used later in the function.
parent = model.iter_parent(iter)

Second, the function retrieves information about the selected product and its parent category. If the product is set to be purchased, its quantity is subtracted from the total product count displayed by the category. Since changing this data invalidates the iterator, the path is converted into an iterator, and the row is removed from the tree model.

Handling Double-clicks

Double-clicks are handled with the row-activated signal of the Gtk.TreeView . The signal is emitted when the user double-clicks a row, when the user presses the spacebar, Shift+spacebar, Return, or Enter on a noneditable row, or when you call treeview.row_activated().
def row_activated(self, treeview, path, column, data):
    model = treeview.get_model()
    if model.get_iter(path))
        # Handle the selection ...
Listing 9-8

Editing a Clicked Row

In Listing 9-8, the callback method row_activated() is called when the user activates a row within the tree view. The activated row is retrieved from the tree path object with treemodel.get_iter(). From there, you are free to use whatever functions/methods you have learned thus far to retrieve or alter the content of the row.

Editable Text Renderers

It would be very useful to allow the user to edit the contents of a tree view. This could be accomplished by presenting a dialog that contains a Gtk.Entry , in which the user would be able to edit the content of a cell. However, GTK+ provides a much simpler way to edit textual components that is integrated into the tree cell by using Gtk.CellRendererText’s edited signal.

When a user clicks a cell in the selected row that is marked as editable, a Gtk.Entry is placed in the cell that contains the current contents of the cell. An example of a cell being edited is shown in Figure 9-8.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig8_HTML.jpg
Figure 9-8

An editable cell

After the user presses the Enter key or removes focus from the text entry, the edited widget is emitted. You need to connect to this signal and apply the changes once it is emitted. Listing 9-9 shows you how to create the Gtk.ListStore Grocery List application where the product column is editable.
    def set_up_treeview(self, treeview):
        renderer = Gtk.CellRenderer.Text.new()
        column = Gtk.TreeViewColumn.new_with_attributes("Buy", renderer, "text", BUY_IT)
        treeview.append_column(column)
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn.new_with_attributes("Count", renderer, "text", QUANTITY)
        treeview.append_column(column)
        # Set up the third column in the tree view to be editable. renderer = Gtk.CellRendererText.new() renderer.set_property("editable", True) renderer.connect("edited", self.cell_edited, treeview)
        column = Gtk.TreeViewColumn.new_with_attributes("Product", renderer, "text", PRODUCT)
        treeview.append_column(column)
    def cell_edited(self, renderer, path, new_text, treeview):Tree View Widget
        if len(new_text) > 0:
            model = treeview.get_model()
            iter = model.get_iter_from_string(path)
            if iter:
                model.set(iter, PRODUCT, new_text)
Listing 9-9

Editing a Cell’s Text

Creating editable Gtk.CellRendererText cells is a very simple process. The first thing you need to do is set the editable and editable-set properties of the text renderer to True.
renderer.set_property("editable", True)

Remember that setting the editable property applies it to the whole column of data that is drawn by the renderer. If you want to specify row by row whether the cell should be editable, you should add it as an attribute of the column.

The next thing you need to do is connect the cell renderer to the edited signal provided by Gtk.CellRendererText. The callback function for this signal receives the cell renderer, a Gtk.TreePath string pointing to the edited row, and the new text that was entered by the user. This signal is emitted when the user presses the Enter key or moves focus from the cell’s Gtk.Entry while the cell is being edited.

The edited signal is necessary, because changes are not automatically applied to the cell. This allows you to filter out invalid entries. For example, in Listing 9-9, the new text is not applied when the new string is empty.
iter = model.get_iter_from_string(path)
if iter:
    model.set(iter, PRODUCT, new_text)

Once you are ready to apply the text, you can convert the Gtk.TreePath string directly into a Gtk.TreeIter with model.get_iter_from_string(). This function returns True if the iterator was successfully set, which means that the path string points to a valid row.

Caution

You always want to check that the path is valid, even though it is supplied by GTK+, because there is a chance that the row has been removed or moved since the callback function was initialized.

After you retrieve the Gtk.TreeIter, you can use model.set() to apply the new text string to the column. In Listing 9-9, new_text was applied to the PRODUCT column of the Gtk.ListStore.

Cell Data Methods

If you need to further customize every cell before it is rendered to the screen, you can use cell data methods. They allow you to tinker with every property of each individual cell. For example, you can set the foreground color based on the content of the cell or restrict the number of decimal places a floating-point number that are shown. It can also be used to set properties that are calculated during runtime.

Figure 9-9, which creates a color list, shows an application that uses cell data functions to set the background color of each cell based on the text property of the Gtk.CellRendererText.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig9_HTML.jpg
Figure 9-9

Screenshot of Listing 9-10

Caution

Make sure not to use cell data functions if you have a large number of rows in your tree model. Cell data functions process every cell in the column before it is rendered, so they can significantly slow down tree models with many rows.

In Listing 9-10, a cell data function is used to set the background color to the value of the color string stored by the cell. The foreground color is also set to white for every cell, although this could also be applied to the whole renderer with the model.set(). This application shows a list of the 256 web-safe colors.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
clr = ( "00", "33", "66", "99", "CC", "FF" )
COLOR = 0
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, 175)
        treeview = Gtk.TreeView.new()
        self.setup_tree_view(treeview)
        store = Gtk.ListStore.new((GObject.TYPE_STRING,
                                   GObject.TYPE_STRING, GObject.TYPE_STRING))
        for var1 in clr:
            for var2 in clr:
                for var3 in clr:
                    color = "#" + var1 + var2 + var3
                    iter = store.append()
                    store.set(iter, (COLOR,), (color,))
        treeview.set_model(store)
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(treeview)
        self.add(scrolled_win)
    def setup_tree_view(self, treeview):
        renderer = Gtk.CellRendererText.new()
        column = Gtk.TreeViewColumn.new()
        column.pack_start(renderer, True)
        column.add_attribute(renderer, "text", COLOR)
        column.set_title("Standard Colors")
        treeview.append_column(column)
        column.set_cell_data_func(renderer, self.cell_data_func, None)
    def cell_data_func(self, column, renderer, model, iter, data):
        # Get the color string stored by the column and make it the
        # foreground color
        (text,) = model.get(iter, COLOR)
        renderer.props.foreground_rgba = Gdk.RGBA(red=1.0, green=1.0,
                                                  blue=1.0, alpha=1.0)
        red = int(text[1:3], 16) / 255
        green = int(text[3:5], 16) / 255 blue = int(text[5:7], 16) / 255
        renderer.props.background_rgba = Gdk.RGBA(red=red, green=green,
                                                  blue=blue, alpha=1.0)
        renderer.props.text = text
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 List")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 9-10

Using Cell Data Functions

Another example of a useful cell data function is when you are using floating-point numbers, and you need to control the number of decimal places that are displayed. In fact, that example is used when you learn about spin button cell renderers in the “Spin Button Renderers” section of this chapter.

Once you have set up your cell data function, you need to connect it to a specific column by calling column.set_cell_data_func(). The last two parameters of this function allow you to supply data that is passed to the cell data function and an additional function that is called to destroy the data. You can set both of these parameters to None if they are not necessary.
column.set_cell_data_func(renderer, self.cell_data_func, None)

If you have added a cell data function to a column that you now want to remove, you should call column.set_cell_data_func() function parameter set to None.

As previously stated, cell data functions should only be used when you have a definite need for fine-tuning the rendering of the data. In most cases, you want to use additional column attributes or column.property_set() to change properties, depending on the scope of the settings. As a rule of thumb, cell data functions should only be used to apply settings that cannot be handled with column attributes or may not be set for every cell.

Cell Renderers

Up to this point, you have only learned about one type of cell renderer, Gtk.CellRendererText. This renderer allows you to display strings, numbers, and Boolean values as text. You are able to customize how the text is displayed with cell renderer attributes and cell data functions and allow it to be edited by the user.

GTK+ provides a large number of cell renderers that can display other types of widgets besides text. These are toggle buttons, images, spin buttons, combo boxes, progress bars, and accelerators, which are all covered in this chapter.

Toggle Button Renderers

Displaying Boolean values as “TRUE” or “FALSE” with Gtk.CellRendererText is a bit tacky, and it takes up a large amount of valuable space in each row, especially when there are a lot of visible Boolean columns. You might be thinking that it would be nice if you could display a check button for Boolean values instead of text strings. It turns out that you can — with the help of a type of cell renderer named Gtk.CellRendererToggle.

By default, toggle button cell renderers are drawn as a check button, as shown in Figure 9-10. You can also set up toggle button renderers to be drawn as radio buttons, but you need to manage the radio button functionality yourself.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig10_HTML.jpg
Figure 9-10

Toggle button renderers

As with editable text renderers, you have to manually apply the changes performed by the user; otherwise, the button will not toggle visually on the screen. Because of this, Gtk.CellRendererToggle provides the toggled signal, which is emitted when the user presses the check button. Listing 9-11 presents a toggled callback function for the Grocery List application. In this version of the application, the BUY_IT column is rendered with Gtk.CellRendererToggle.
def buy_it_toggled(renderer, path, treeview):
        model = treeview.get_model()
        iter = model.get_iter_from_string(path)
        if iter:
            (value,) = model.get(iter, BUY_IT)
            model.set_row(iter, (!value, None))
Listing 9-11

Using Cell Data Functions

Toggle cell renderers are created with Gtk.CellRendererToggle.new(). After creating a toggle cell renderer, you want to set its activatable property to True so that it can be toggled; otherwise, the user will not be able to toggle the button (which can be useful if you only want to display a setting but not allow it to be edited). column.property_set() can be used to apply this setting to every cell.

Next, the active property should be added as a column attribute instead of text, which was used by Gtk.CellRendererText. This property is set to True or False, depending on the desired state of the toggle button.

Then, you should connect the Gtk.CellRendererToggle cell renderer to a callback function for the toggled signal. Listing 9-11 gives an example callback function for the toggled signal. This callback function receives the cell renderer and a Gtk.TreePath string pointing to the row that contains the toggle button.

Within the callback function, you need to manually toggle the current value displayed by the toggle button as shown in the following two lines of code. The emission of a toggled signal only tells you that the user wants the button to be toggled; it does not perform the action for you.
(value,) = model.get(iter, BUY_IT)
model.set_row(iter, (!value, None))

To toggle the value, you can use model.get() to retrieve the current value stored by the cell. Since the cell is storing a Boolean value, you can set the new value to the opposite of the current in model.set_row().

As previously mentioned, Gtk.CellRendererToggle also allows you to render the toggle as a radio button. This can be initially set to the renderer by changing the radio property with renderer.set_radio().
renderer.set_radio(radio)

You need to realize that the only thing that is changed by setting radio to True is the rendering of the toggle button! You have to manually implement the functionality of a radio button through your toggled callback function. This includes activating the new toggle button and deactivating the previously selected toggle button.

Pixbuf Renderers

Adding images in the form of GdkPixbuf objects as a column in a Gtk.TreeView is a very useful feature provided by Gtk.CellRendererPixbuf. An example of a pixbuf renderer is shown in Figure 9-11, in which there is a small icon to the left of each item.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig11_HTML.jpg
Figure 9-11

Pixbuf renderers

You have already learned almost everything necessary to add GdkPixbuf images to a tree view in previous sections, but Listing 9-12 presents a simple example to guide you. There is no need to create a separate column header for pixbufs in most cases, so Listing 9-12 shows you how to include multiple renderers in one column. Pixbuf cell renderers are extremely useful in types of tree view implementations, such as file system browsers.
def set_up_treeview(self, treeview):
    column = Gtk.TreeViewColumn.new()
    column.set_resizable(True)
    column.set_title("Some Items")
    renderer = Gtk.CellRendererPixbuf.new()
    # it is important to pack the renderer BEFORE adding attributes!! column.pack_start(renderer, False) column.add_attribute(renderer, "pixbuf", ICON)
    renderer = Gtk.CellRendererText.new()
    # it is important to pack the renderer BEFORE adding attributes!! column.pack_start(renderer, True) column.add_attribute(renderer, "text", ICON_NAME) treeview.append_column(column)
Listing 9-12

GdkPixbuf Cell Renderers

New Gtk.CellRendererPixbuf objects are created with Gtk.CellRendererPixbuf.new(). You then want to add the renderer to the column. Since there is multiple renderers Gtk.CellRendererPixbuf.new() in our column, you can use column.pack_start() to add the renderer to the column. It is important to pack the renderer into the column BEFORE adding an attributes. Failure to do this invalidates the renderer and you receive a runtime warning and no data appears in the column.

Next, you need to add attributes to the column for the Gtk.CellRendererPixbuf. In Listing 9-12, the pixbuf property was used so that we could load a custom icon from a file. However, pixbufs are not the only type of image supported by Gtk.CellRendererPixbuf.

If you are using a Gtk.TreeStore, it is useful to display a different pixbuf when the row is expanded and when it is retracted. To do this, you can specify two GdkPixbuf objects to pixbuf-expander-open and pixbuf-expander-closed. For example, you may want to do this to display an open folder when the row is expanded and a closed folder when the row is retracted.

When you create the tree model, you need to use a new type called GdkPixbuf.Pixbuf, which stores GdkPixbuf objects in each model column. Every time you add a GdkPixbuf to a tree model column, its reference count is incremented by one.

Spin Button Renderers

In Chapter 5, you learned how to use the Gtk.SpinButton widget. While Gtk.CellRendererText can display numbers, a better option is to use Gtk.CellRendererSpin. Instead of displaying a Gtk.Entry when the content is to be edited, a Gtk.SpinButton is used. An example of a cell rendered with Gtk.CellRendererSpin that is being edited is shown in Figure 9-12.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig12_HTML.jpg
Figure 9-12

Spin button renderers

You notice that the floating-point numbers in the first column in Figure 9-12 show multiple decimal places. You can set the number of decimal places shown in the spin button but not the displayed text. To decrease or eliminate the number of decimal places, you should use a cell data function. An example of a cell data function that hides decimal places is shown in Listing 9-13.
def cell_edited(self, renderer, path, new_text, treeview):
    # Retrieve the current value stored by the spin button renderer's adjustme adjustment = renderer.get_property("adjustment")
    value = "%.0f" % adjustment.get_value() model = treeview.get_model()
    iter = model.get_iter_from_string(path) if iter:
        model.set(iter, QUANTITY, value)
Listing 9-13

Cell Data Function for Floating-Point Numbers

Recall that if you want to dictate the number of decimal places shown by a floating-point number in a column using Gtk.CellRendererText or another derived renderer, you need to use a cell data function. In Listing 9-13, a sample cell data function was shown that reads in the current floating-point number and forces the renderer to display no decimal places. This is necessary because Gtk.CellRendererSpin stores numbers as floating-point numbers.

Gtk.CellRendererSpin is compatible with both integers and floating-point numbers, because its parameters are stored in a Gtk.Adjustment . Listing 9-13 is an implementation of the Grocery List application in which the Quantity column is rendered with Gtk.CellRendererSpin.
def setup_tree_view(self, renderer, column, adj):
    adj = Gtk.Adjustment.new(0.0, 0.0, 100.0, 1.0, 2.0, 2.0)
    renderer = Gtk.CellRendererSpin(editable=True, adjustment=adj, digits=0)
    column = Gtk.TreeViewColumn("Count", renderer, text=QUANTITY)
    treeview.append_column(column)
    renderer.connect("edited", self.cell_edited, treeview)
    # Add a cell renderer for the PRODUCT column
Listing 9-14

Spin Button Cell Renderers

New Gtk.CellRendererSpin objects are created with Gtk.CellRendererSpin(). When you create the renderer, you should set the editable, adjustment, and digits properties of the object, as follows.
Gtk.CellRendererSpin(editable=True, adjustment=adj, digits=0)

Gtk.CellRendererSpin provides three properties: adjustment, climb rate, and digits. These are stored in a Gtk.Adjustment defining the spin button’s properties, the acceleration rate when an arrow button is held down, and the number of decimal places to display in the spin button respectively. The climb rate and number of decimals to display are both set to zero by default.

After setting up the cell renderer, you should then connect to the edited signal to the cell renderer, which is used to apply the new value chosen by the user to the cell. There is usually no need to filter this value, because the adjustment already limits the values allowed by the cell. The callback function is run after the user presses the Enter key or moves focus from the spin button of a cell that is being edited.

Within the cell_edited()callback method in Listing 9-14 you need to first retrieve the adjustment of the spin button renderer, because it stores the new value that is to be displayed. This new value can then be applied to the given cell.

Note

Although the edited signal of a Gtk.CellRendererText still receives the new_text parameter, this should not be used. The parameter does not store a textual version of the spin button’s value. Furthermore, the value used in model.set() that replaces the current value must be supplied as a floating-point number, so a string is not acceptable regardless of its contents.

You can retrieve the adjustment’s value with renderer.get_property("adjustment"), applying it to the appropriate column. if the QUANTITY column is used to display a floating-point number (GObject.TYPE_FLOAT), you can use the returned type in its current state. We have instead chosen to convert the float value to a string value.

When creating the tree model, the column must be of the type GObject.TYPE_FLOAT, even if you want to store an integer. You should use cell data functions to limit the number of decimal places displayed by each cell.

Combo Box Renderers

Gtk.CellRendererCombo provides a cell renderer for a widget that you have just learned about, Gtk.ComboBox. Combo box cell renderers are useful, because they allow you to present multiple predefined options to the user. Gtk.CellRendererCombo renders text in a similar way to Gtk.CellRendererText, but instead of showing a Gtk.Entry widget when editing, a Gtk.ComboBox widget is presented to the user. An example of a Gtk.CellRendererCombo cell being edited is shown in Figure 9-13.

../images/142357_2_En_9_Chapter/142357_2_En_9_Fig13_HTML.jpg
Figure 9-13

A combo box cell renderer

To use Gtk.CellRendererCombo, you need to create a Gtk.TreeModel for every cell in the column. In Listing 9-15, the QUANTITY column of the Grocery List application from Listing 9-1 is rendered with Gtk.CellRendererCombo.
def setup_tree_view(self, treeview):
    # Create a GtkListStore that will be used for the combo box
    renderer. model = Gtk.ListStore.new((GObject.TYPE_STRING,
                              GObject.TYPE_STRING))
    iter = model.append()
    model.set(iter, 0, "None")
    iter = model.append()
    model.set(iter, 0, "One")
    iter = model.append()
    model.set(iter, 0, "Half a Dozen")
    iter = model.append()
    model.set(iter, 0, "Dozen")
    iter = model.append()
    model.set(iter, 0, "Two Dozen")
    # Create the GtkCellRendererCombo and add the tree model. Then, add the
    # renderer to a new column and add the column to the GtkTreeView.
    renderer = Gtk.CellRendererCombo(text_column=0, editable=True,
                                      has_entry=True, model=model)
    column = Gtk.TreeViewColumn("Count", renderer, text=QUANTITY)
    treeview.append_column(column)
    renderer.connect("edited", self.cell_edited, treeview)
    renderer = Gtk.CellRendererText.new()
    column = Gtk.TreeViewColumn("Product", renderer, text=PRODUCT)
    treeview.append_column(column)
def cell_edited(self, renderer, path, new_text, treeview):
    # Make sure the text is not empty. If not, apply it to the tree view
    cell. if new_text != "":
        model = treeview.get_model()
        iter = model.get_iter_from_string(path)
        if iter:
            model.set(iter, QUANTITY, new_text)
Listing 9-15

Combo Box Cell Renderers

New combo box cell renderers are created with Gtk.CellRendererCombo(). Gtk.CellRendererCombo has three properties in addition to those inherited from Gtk.CellRendererText: "has_entry", "model", and "text_column".
renderer = Gtk.CellRendererCombo(text_column=0, editable=True,
                                         has_entry=True, model=model)

The first property you need to set is "text_column", which refers to the column in the combo box’s tree model that is displayed in the cell renderer. This must be a type supported by Gtk.CellRendererText, such as GObject.TYPE_STRING, GObject.TYPE_INT, or GObject.TYPE_BOOLEAN. The model property is a Gtk.TreeModel that is used as the content of the combo box. You must also set the editable property to True, so the cell content may be edited.

Lastly, there is a widget called Gtk.ComboBoxEntry that gives the user choices like a normal combo box, but it also uses a Gtk.Entry widget to allow the user to enter a custom string instead of choosing an existing option. To allow this functionality with a combo box cell renderer, you must set the has-entry property to True. This is turned on by default, which means that you must turn it off to restrict the choices to those that appear in Gtk.CellRendererCombo’s tree model.

As with other cell renderers derived from Gtk.CellRendererText, you want to use the text field as the column attribute and set its initial text when creating the tree view’s model. You can then use the edited signal to apply the text to the tree model. In Listing 9-15, the changes are only applied when the "new_text" string is not empty, since the user is free to enter free-form text as well.

Progress Bar Renderers

Another type of cell renderer is Gtk.CellRendererProgress, which implements the Gtk.ProgressBar widget. While progress bars support pulsing, Gtk.CellRendererProgress only allows you to set the current value of the progress bar. Figure 9-14 shows a Gtk.TreeView widget that has a progress bar cell renderer in the second column, which displays textual feedback.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig14_HTML.jpg
Figure 9-14

Progress bar cell renderers

Progress bar cell renderers are another easy feature to implement in a program. You can use Gtk.CellRendererProgress() to create new Gtk.CellRendererProgress objects. Gtk.CellRendererProgress provides two properties: "text" and "value". The progress bar state is defined by the "value" property, which is an integer with a value between 0 and 100. A value of 0 refers to an empty progress bar, and 100 refers to a full progress bar. Since it is stored as an integer, the tree model column corresponding to the value of the progress bar should have the type GObject.TYPE_INT.

The second property provided by Gtk.CellRendererProgress is text. This property is a string that is drawn over the top of the progress bar. This property can be ignored in some cases, but it is usually a good idea to give the user more information about the progress of a process. Examples of possible progress bar strings are “67% Complete”, “3 of 80 Files Processed”, “Installing foo . . .”, and so on.

Gtk.CellRendererProgress is a useful cell renderer in some cases, but you should be careful when you deploy it. You should avoid using multiple progress bars in one row, because doing so could confuse the user and takes up a lot of horizontal space. Also, tree views with many rows appear messy. In many cases, it would be better for the user to use a textual cell renderer instead of a progress bar cell renderer.

However, there are some cases where Gtk.CellRendererProgress is a good choice. For example, if your application has to manage multiple downloads at the same time, progress bar cell renderers are an easy way to give coherent feedback about progress for each download.

Keyboard Accelerator Renderers

GTK+ 2.10 introduced a new type of cell renderer called Gtk.CellRendererAccel, which displays a textual representation of a keyboard accelerator. An example of an accelerator cell renderer is shown in Figure 9-15.
../images/142357_2_En_9_Chapter/142357_2_En_9_Fig15_HTML.jpg
Figure 9-15

Accelerator cell renderers

Listing 9-16 creates a list of actions along with their keyboard accelerators. This type of tree view could be used to allow the user to edit the accelerators for an application. The accelerator is displayed as text, since the renderer is derived from Gtk.CellRendererText.

To edit the accelerator, the user needs to click the cell once. The cell then shows a string asking for a key. The new key code is added, along with any mask keys, such as Ctrl and Shift, into the cell. Basically, the first keyboard shortcut pressed is displayed by the cell.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
ACTION = 0
MASK = 1
VALUE = 2
list = [( "Cut", Gdk.ModifierType.CONTROL_MASK, Gdk.KEY_X ), ( "Copy", Gdk.ModifierType.CONTROL_MASK, Gdk.KEY_C ), ( "Paste", Gdk.ModifierType.CONTROL_MASK, Gdk.KEY_V ), ( "New", Gdk.ModifierType.CONTROL_MASK, Gdk.KEY_N ), ( "Open", Gdk.ModifierType.CONTROL_MASK, Gdk.KEY_O ), ( "Print", Gdk.ModifierType.CONTROL_MASK, Gdk.KEY_P )]
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_size_request(250, 250)
        treeview = Gtk.TreeView.new()
        self.setup_tree_view(treeview)
        store = Gtk.ListStore(GObject.TYPE_STRING,
                              GObject.TYPE_INT, GObject.TYPE_UINT)
        for row in list:
            (action, mask, value) = row
            iter = store.append(None)
            store.set(iter, ACTION, action, MASK, mask, VALUE, value)
        treeview.set_model(store)
          scrolled_win = Gtk.ScrolledWindow.new(None, None)
          scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                  Gtk.PolicyType.AUTOMATIC)
        scrolled_win.add(treeview)
        self.add(scrolled_win)
    def setup_tree_view(self, treeview):
        renderer = Gtk.CellRendererAccel()
        column = Gtk.TreeViewColumn("Action", renderer, text=ACTION)
        treeview.append_column(column)
        renderer = Gtk.CellRendererAccel(accel_mode=Gtk.CellRendererAccelMode.GTK, editable=True)
        column = Gtk.TreeViewColumn("Key", renderer, accel_mods=MASK, accel_key=VALUE)
        treeview.append_column(column)
        renderer.connect("accel_edited", self.accel_edited, treeview)
    def accel_edited(self, renderer, path, accel_key, mask, hardware_keycode, treeview):
        model = treeview.get_model()
        iter = model.get_iter_from_string(path)
        if iter:
                model.set(iter, MASK, mask, VALUE, accel_key)
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="Accelerator Keys")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 9-16

Combo Box Cell Renderers

You can use Gtk.CellRendererAccel() to create new Gtk.CellRendererAccel objects. Gtk.CellRendererAccel provides the following four properties that can be accessed with renderer.get().
  • Gdk.ModifierType.SHIFT_MASK: The Shift key.

  • Gdk.ModifierType.CONTROL_MASK: The Ctrl key.

  • Gdk.ModifierType.MOD_MASK,Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK,Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK: The first modifier usually represents the Alt key, but these are interpreted based on your X server mapping of the keys. They can also correspond to the Meta, Super, or Hyper key.

  • Gdk.ModifierType.SUPER_MASK: Introduced in 2.10, this allows you to explicitly state the Super modifier. This modifier may not be available on all systems!

  • Gdk.ModifierType.HYPER_MASK: Introduced in 2.10, this allows you to explicitly state the Hyper modifier. This modifier may not be available on all systems!

  • Gdk.ModifierType.META_MODIFIER: Introduced in 2.10, this allows you to explicitly state the Meta modifier. This modifier may not be available on all systems!

In most cases, you want to set the modifier mask (acel-mods) and the accelerator key value (accel-key) as two attributes of the tree view column using Gtk.CellRendererAccel. In this case, the modifier mask is of type GObject.TYPE_INT, and the accelerator key value GObject.TYPE_UINT. Because of this, you want to make sure to case the Gdk.ModifierType value to an int when setting the content of the modifier mask column.
store = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_UINT)

Gtk.CellRendererAccel provides two signals. The first, accel-cleared, allows you to reset the accelerator when the user removes the current value. In most cases, you will not need to do this unless you have a default value that you want the accelerator to revert to.

Of greater importance, accel-edited allows you to apply changes that the user makes to the keyboard accelerator, as long as you set the editable property to True. The callback function receives a path string to the row in question along with the accelerator key code, mask and hardware key code. In the callback function, you can apply the changes with store.set(), as you would with any other editable type of cell.

Test Your Understanding

In Exercise 1, you have the opportunity to practice using the Gtk.TreeView widget, along with multiple types of cell renderers. This is an extremely important exercise for you to try, because you need to use the Gtk.TreeView widget in many applications. As always, when you are finished, you can find one possible solution in Appendix D.

Exercise 1: File Browser

By now, you have probably had enough of Grocery List applications, so let’s try something different. In this exercise, create a file browser using the Gtk.TreeView widget. You should use Gtk.ListStore for the file browser and allow the user to browse through the file system.

The file browser should show images to differentiate among directories and files. Images are found in the downloadable source code at www.gtkbook.com . You can also use the Python directory utility functions to retrieve directory content. Double-clicking a directory should move you to that location.

Summary

In this chapter, you learned how to use the Gtk.TreeView widget. This widget allows you to display lists and tree structures of data with Gtk.ListStore and Gtk.TreeStore respectively. You also learned the relationship among the tree view, tree model, columns, and cell renderers and how to use each of the objects.

Next, you learned about the types of objects that can be used to refer to a row within the tree view. These include tree iterators, paths, and row references. Each of these objects has its own advantages and disadvantages. Tree iterators can be used directly with models, but they become invalid when the tree model changes. Tree paths are easily understandable, because they have associated human-readable strings, but may not point to the same row if the tree model is changed. Lastly, tree row references are useful, because they remain valid for as long as the row exists, even when the model is changed.

You next learned how to handle selections of one row or multiple rows. With multiple row selections, you can use a for-each function, or you can get a Python list of the selected rows. A useful signal when dealing with selections is Gtk.TreeView’s row-activated signal, which allows you to handle double-clicks.

After that, you learned how to create editable cells with Gtk.CellRendererText’s edited signal, which displays a Gtk.Entry to allow the user to edit the content in the cell. Cell data functions can also be connected to columns. These cell data functions allow you to customize each cell before it is rendered to the screen.

Lastly, you learned about a number of cell renderers that allow you to display toggle buttons, pixbufs, spin buttons, combo boxes, progress bars, and keyboard accelerator strings. You were also introduced to the Gtk.ComboBox widget.

Congratulations! You are now familiar with one of the hardest and most versatile widgets provided by GTK+. In the next chapter, you learn how to create menus, toolbars, and pop-up menus. You also learn how to automate menu creation with user interface (UI) files.

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

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