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

8. Text View Widget

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

This chapter teaches you how to use the Gtk.TextView widget. The text view widget is similar to a Gtk.Entry widget, except it is capable of holding text that spans multiple lines. Scrolled windows allow the document to exist beyond the bounds of the screen.

Before you learn about Gtk.TextView, this chapter begins by introducing a few new widgets. The first two widgets are scrolled windows and viewports. Scrolled windows are composed of two scrollbars that scroll the child widget. A few widgets support scrolling already, including Gtk.Layout, Gtk.TreeView, and Gtk.TextView. For all other widgets that you want to scroll, you need to add them first to a Gtk.Viewport widget, which gives its child widget scrolling abilities.

In this chapter, you learn the following:
  • How to use scrolled windows and viewports

  • How to use the Gtk.TextView widget and apply text buffers

  • The functions that text iterators and text marks perform when dealing with buffers

  • Methods for applying styles to the whole or part of a document

  • How to cut, copy, and paste to and from the clipboard

  • How to insert images and child widgets into a text view

Scrolled Windows

Before you can learn about the Gtk.TextView widget, you need to learn about two container widgets called Gtk.ScrolledWindow and Gtk.Viewport. Scrolled windows use two scrollbars to allow a widget to take up more space than is visible on the screen. This widget allows the Gtk.TextView widget to contain documents that expand beyond the bounds of the window.

Both scrollbars in the scrolled window have associated Gtk.Adjustment objects. These adjustments track the current position and range of a scrollbar; however, you will not need to directly access the adjustments in most cases.

A scrollbar’s Gtk.Adjustment holds information about scroll bounds, steps, and its current position. The value variable is the current position of the scrollbar between the bounds. This variable must always be between the lower and upper values, which are the bounds of the adjustment. The page_size is the area that can be visible on the screen at one time, depending on the size of the widget. The step_increment and page_increment variables are used for stepping when an arrow is pressed or when the Page Down key is pressed.

Figure 8-1 is a screenshot of the window created with the code in Listing 8-1. Both scrollbars are enabled because the table containing the buttons is larger than the visible area.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig1_HTML.jpg
Figure 8-1

A synchronized scrolled window and viewport

Listing 8-1 shows how to use scrolled windows and viewports. As a scrollbar moves, the viewport scrolls as well because the adjustments are synchronized. Try to resize the window to see how the scrollbars react to becoming larger and smaller than the child widget.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        grid1 = Gtk.Grid.new()
        grid2 = Gtk.Grid.new()
        grid1.set_column_homogeneous = True
        grid2.set_column_homogeneous = True
        grid1.set_row_homogeneous = True
        grid2.set_row_homogeneous = True
        grid1.set_column_spacing = 5
        grid2.set_column_spacing = 5
        grid1.set_row_spacing = 5
        grid2.set_row_spacing = 5
        i = 0
        while i < 10:
            j = 0
            while j < 10:
                button = Gtk.Button.new_with_label("Close")
                button.set_relief(Gtk.ReliefStyle.NONE)
                button.connect("clicked", self.on_button_clicked)
                grid1.attach(button, i, j, 1, 1)
                button = Gtk.Button.new_with_label("Close")
                button.set_relief(Gtk.ReliefStyle.NONE)
                button.connect("clicked", self.on_button_clicked)
                grid2.attach(button, i, j, 1, 1)
                j += 1
            i += 1
        swin = Gtk.ScrolledWindow.new(None, None)
        horizontal = swin.get_hadjustment()
        vertical = swin.get_vadjustment()
        viewport = Gtk.Viewport.new(horizontal, vertical)
        swin.set_border_width(5)
        swin.set_propagate_natural_width(True)
        swin.set_propagate_natural_height(True)
        viewport.set_border_width(5)
        swin.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        swin.add_with_viewport(grid1)
        viewport.add(grid2)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
        vbox.set_homogeneous = True
        vbox.pack_start(viewport, True, True, 5)
        vbox.pack_start(swin, True, True, 5)
        self.add (vbox)
        self.show_all()
    def on_button_clicked(self, button):
        self.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
        self.window = AppWindow(application=self,
               title="Scrolled Windows & Viewports")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-1

Using Scrolled Windows

Newly scrolled windows are created with Gtk.ScrolledWindow.new(). In Listing 8-1 each parameter is set to None, which causes the scrolled window to create two default adjustments for you. In most cases, you want to use the default adjustments, but it is also possible to specify your own horizontal and vertical adjustments for the scroll bars.

The adjustments in this example are used when the new viewport is created with Gtk.Viewport.new(). The viewport adjustments are initialized with those from the scrolled window, which makes sure that both containers are scrolled at the same time.

As you set up a scrollable window, the first decision you need to make is when the scrollbars will be visible. In this example, Gtk.PolicyType.AUTOMATIC was used for both scrollbars so that each is only shown when needed. Gtk.PolicyType.ALWAYS is the default policy for both scrollbars. The following are three enumeration values provided by Gtk.PolicyType.
  • Gtk.PolicyType.ALWAYS: The scrollbar is always visible. It is displayed as disabled or grayed out if scrolling is not possible.

  • Gtk.PolicyType.AUTOMATIC: The scrollbar is only visible if scrolling is possible. If it is not needed, the scrollbar temporarily disappears.

  • Gtk.PolicyType.NEVER: The scrollbar is never shown.

Another property, although not used by very many applications, is the placement of the scrollbars. In most applications, you want the scrollbars to appear along the bottom and the right side of the widget, which is the default functionality.

However, if you want to change this, you can call set_placement(). This function receives a Gtk.CornerType value, which defines where the content is placed with respect to the scrollbars. For example, the default value is Gtk.CornerType.TOP_LEFT, because the content normally appears above and to the left of the scrollbars.
swin.set_placement(window_placement)

Available Gtk.CornerType values include Gtk.CornerType.TOP_LEFT, Gtk.CornerType.BOTTOM_LEFT, Gtk.CornerType.TOP_RIGHT, and Gtk.CornerType.BOTTOM_RIGHT, which define where the content is placed with respect to the scrollbars.

Caution

It is a very rare occasion when set_placement() should be used! In almost every possible case, you should not use this function, because it can confuse the user. Unless you have a good reason for changing the placement, use the default value.

It is possible to set the shadow type of the widget with respect to the child widget by calling set_shadow_type().
swin.set_shadow_type(type)

In Chapter 4, you learned how to use the Gtk.ShadowType enumeration along with handle boxes to set the type of border to place around the child widget. The same values as before set the shadow type of a scrolled window.

After you have set up a scrolled window, you should add a child widget for it to be of any use. There are two possible ways to do this, and the method is chosen based on the type of child widget. If you are using a Gtk.TextView, Gtk.TreeView, Gtk.IconView, Gtk.Viewport, or Gtk.Layout widget, you should use the default add() method, since all five of these widgets include native scrolling support.

All other GTK+ widgets do not have native scrolling support. For those widgets, add_with_viewport() should be used. This function gives the child scrolling support by first packing it into a container widget called a Gtk.Viewport. This widget implements scrolling ability for the child widget that lacks its own support. The viewport is then automatically added to the scrolled window.

Caution

You should never pack Gtk.TextView, Gtk.TreeView, Gtk.IconView, Gtk.Viewport, or Gtk.Layout widgets into a scrolled window with add_with_viewport(), because scrolling may not be performed correctly on the widget!

It is possible to manually add a widget to a new Gtk.Viewport and then add that viewport to a scrolled window with add(), but the convenience function allows you to ignore the viewport completely.

The scrolled window is simply a container with scrollbars. Neither the container nor the scrollbars perform any action by themselves. Scrolling is handled by the child widget, which is why the child must already have native scrolling support to work correctly with the Gtk.ScrolledWindow widget.

When you add a child widget that has scrolling support, a function is called to add adjustments for each axis. Nothing is done unless the child widget has scrolling support, which is why a viewport is required by most widgets. When the scrollbar is clicked and dragged by the user, the value in the adjustment changes, which causes the value-changed signal to be emitted. This action also causes the child widget to render itself accordingly.

Because the Gtk.Viewport widget did not have any scrollbars of its own, it relied completely on the adjustments to define its current position on the screen. The scrollbars are used in the Gtk.ScrolledWindow widget as an easy mechanism for adjusting the current value of the adjustment.

Text Views

The Gtk.TextView widget displays multiple lines of text of a document. It provides many ways to customize the whole of a document or individual portions of it. It is even possible to insert GdkPixbuf objects and child widgets into a document. Gtk.TextView is the first reasonably involved widget you have encountered up to this point, so the rest of this chapter is dedicated to many aspects of the widget. It is a very versatile widget that you need to use in many GTK+ applications.

The first few examples of this chapter may lead you to believe that Gtk.TextView can only display simple documents, but that is not the case. It can also display many types of rich text, word processing, and interactive documents that are used by a wide variety of applications. You learn how to do this in the sections that follow.

Figure 8-2 introduces you to a simple text view window that allows you to enter text and do some basic layout design. But it also does not have many features and is lacking features found in many word processors.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig2_HTML.jpg
Figure 8-2

A Gtk.TextView widget

Text views are used in every type of text and document editing application that uses GTK+. If you have ever used AbiWord, gedit, or most other text editors created for GNOME, you have used the Gtk.TextView widget. It is also used in the Gaim application in instant message windows. (In fact, all the examples in this book were created in the OpenLDev application, which uses Gtk.TextView for source code editing!)

Text Buffers

Each text view displays the contents of a class called Gtk.TextBuffer. Text buffers store the current state of the content within a text view. They hold text, images, child widgets, text tags, and all other information necessary for rendering the document.

A single text buffer is capable of being displayed by multiple text views, but each text view has only one associated buffer. Most programmers do not take advantage of this feature, but it becomes important when you learn how to embed child widgets into a text buffer in a later section.

As with all text widgets in GTK+, text is stored as UTF-8 strings. UTF-8 is a type of character encoding that uses from 1 byte to 4 bytes for every character. To differentiate the number of bytes that a character takes up, “0” always precedes a character that is 1 byte, “110” precedes 2-byte characters, “1110” comes before 3-byte sequences, and so on. UTF-8 characters that span multiple bytes have “10” in the two most significant bits of the rest of the bytes.

By doing this, the basic 128 ASCII characters are still supported, because an additional 7 bits are available in a single-byte character after the initial “0”. UTF-8 also provides support for characters in many other languages. This method also avoids small byte sequences occurring within larger byte sequences.

When handling text buffers, you need to know two terms: offset and index. The word “offset” refers to one character. UTF-8 characters may span one or more bytes within the buffer, so a character offset in a Gtk.TextBuffer may not be a single byte long.

Caution

The word “index” refers to an individual byte. You need to be careful when stepping through a text buffer in later examples, because you cannot refer to an index that is between two character offsets.

Listing 8-2 illustrates one of the simplest text view examples you could create. A new Gtk.TextView widget is created. Its buffer is retrieved, and text is inserted into the buffer. A scrolled window then contains the text view.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, 150)
        textview = Gtk.TextView.new()
        buffer = textview.get_buffer()
        text = "Your 1st GtkTextView widget!"
        buffer.set_text(text, len(text))
        scrolled_win = Gtk.ScrolledWindow.new (None, None)
        scrolled_win.add(textview)
        self.add(scrolled_win)
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="Text Views")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-2

A Simple Gtk.TextView Example

Most new Gtk.TextView widgets are created with Gtk.TextView.new(). By using this function, an empty buffer is created for you. This default buffer can be replaced later with set_buffer() or retrieved with get_buffer().

If you want to set the initial buffer to one that you have already created, you can create the text view with Gtk.TextView.new_with_buffer(). In most cases, it is easier to simply use the default text buffer.

Once you have access to a Gtk.TextBuffer object, there are many ways to add content, but the easiest method is to call set_text(). This function receives a text buffer, a UTF-8 text string to set as the buffer’s new text, and the length of the text.
set_text(text, length)

If the text string is NULL-terminated, you can use –1 as the length of the string. This function silently fails if a null character is found before the specified length of text.

The current contents of the buffer are completely replaced by the new text string. In the “Text Iterators and Marks” section, you are introduced to functions that allow you to insert text into a buffer without overwriting the current content that are more suitable for inserting large amounts of text.

Recall from the previous section that there are five widgets that have native scrolling abilities, including the Gtk.TextView widget. Because text views already have the facilities to manage adjustments, container.add() should always add them to scrolled windows.

Text View Properties

Gtk.TextView was created to be a very versatile widget. Because of this, many properties are provided for the widget. In this section, you learn about a number of these widget properties.

One feature that makes the text view widget extremely useful is that you are able to apply changes to the whole or only an individual part of the widget. Text tags change the properties of a segment of text. Customizing only a part of the document is covered in a later section of this chapter.

Listing 8-3 shows many of the properties that can customize the contents of Gtk.TextBuffer. You should note that many of these properties could be overridden in individual sections of a document with text tags.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(260, 150)
        font = Pango.font_description_from_string("Monospace Bold 10")
        textview = Gtk.TextView.new()
        textview.modify_font(font)
        textview.set_wrap_mode(Gtk.WrapMode.WORD)
        textview.set_justification(Gtk.Justification.RIGHT)
        textview.set_editable(True)
        textview.set_cursor_visible(True)
        textview.set_pixels_above_lines(5)
        textview.set_pixels_below_lines(5)
        textview.set_pixels_inside_wrap(5)
        textview.set_left_margin(10)
        textview.set_right_margin(10)
        buffer = textview.get_buffer()
        text = "This is some text! Change me! Please!"
        buffer.set_text(text, len(text))
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.ALWAYS)
        scrolled_win.add(textview)
        self.add(scrolled_win)
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="Text Views Properties")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-3

Using Gtk.TextView Properties

The best way to explain what each of Gtk.TextView’s properties does is to show you a screenshot of the result, which can be viewed in Figure 8-3. You should compile the application on your own machine and try changing the values used in Listing 8-3 to get a feel for what they do as well.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig3_HTML.jpg
Figure 8-3

Gtk.TextView with nondefault properties

It is possible to change the font and colors of individual parts of the text view content, but as shown in Listing 8-3 it is still possible to use the functions from past chapters to change the content of the whole widget. This is useful when editing documents that have a consistent style, such as text files.

When dealing with a widget that displays text on multiple lines, you need to decide if and how text is wrapped. In Listing 8-3 the wrap mode was set to Gtk.WrapMode.WORD with set_wrap_mode(). This setting wraps the text but does not split a word over two lines. There are four types of wrap modes available in the Gtk.WrapMode enumeration.
  • Gtk.WrapMode.NONE: No wrapping occurs. If a scrolled window contains the view, the scrollbar expands; otherwise, the text view expands on the screen. If a scrolled window does not contain the Gtk.TextView widget, it expands the widget horizontally.

  • Gtk.WrapMode.CHAR: Wrap to the character, even if the wrap point occurs in the middle of a word. This is usually not a good choice for a text editor, since it splits words over two lines.

  • Gtk.WrapMode.WORD: Fill up the line with the largest number of words possible but do not break a word to wrap. Instead, bring the whole word onto the next line.

  • Gtk.WrapMode.WORD_CHAR: Wrap in the same way as GTK_WRAP_WORD, but if a whole word takes up more than one visible width of the text view, wrap it by the character.

At times, you may want to prevent the user from editing the document. The editable property can be changed for the entire text view with set_editable(). It is worth noting that with text tags, you can override set_editable() for certain sections of the document, so it is not always an end-all solution.

Contrast this with set_sensitive(), which prevents the user from interacting with the widget at all. If a text view is set as not editable, the user is still able to perform operations on the text that do not require the text buffer to be edited, such as selecting text. Setting a text view as insensitive prevents the user from performing any of these actions.

When you disable editing within a document, it is also useful to stop the cursor from being visible with set_cursor_visible(). By default, both of these properties are set to True, so both need to be changed to keep them in sync.

By default, there is no extra spacing placed between lines, but Listing 8-3 shows you how to add spacing above a line, below a line, and between wrapped lines. These functions add extra space between lines, so you can assume that there is already enough spacing between lines. In most cases, you should not use this feature, because spacing may not look correct to the user.

Justification is another important property of text views, especially when dealing with rich text documents. There are four default justification values: Gtk.Justification.LEFT, Gtk.Justification.RIGHT, Gtk.Justification.CENTER, and Gtk.Justification.FILL.

Justification can be set for the whole text view with set_justification(), but it can be overridden for specific sections of text with text tags. In most cases, you want to use the default Gtk.Justification.LEFT justification unless the user wants it to be changed. Text is aligned to the left of the view by default.
textview.set_justification(justification)

The last properties set by Listing 8-3 were the left and right margins. By default, there is no extra margin space added to either the left or right side, but you can add a certain number of pixels to the left with set_left_margin() or to the right with set_right_margin().

Pango Tab Arrays

Tabs added to a text view are set to a default width, but there are times when you want to change that. For example, in a source code editor, one user may want to indent two spaces while another may want to indent five spaces. GTK+ provides the Pango.TabArray object, which defines a new tab size.

When changing the default tab size, you first calculate the number of horizontal pixels the tab takes up based on the current font. The following make_tab_array() function can calculate a new tab size. The function begins by creating a string out of the desired number of spaces. That string is then translated into a Pango.Layout object, which retrieves the pixel width of the displayed string. Lastly, the Pango.Layout is translated into a Pango.TabArray, which can be applied to a text view.
def make_tab_array(fontdesc, tab_size, textview):
    if tab_size < 100:
        return
    tab_string = ' ' * tab_size
    layout = Gtk.Widget.create_pango_layout(textview, tab_string)
    layout.set_font_description(fontdesc)
    (width, height) = layout.get_pixel_size()
    tab_array = Pango.TabArray.new(1, True)
    tab_array.set_tab(0, Pango.TabAlign.LEFT, width)
    textview.set_tabs(tab_array)

The Pango.Layout object represents a whole paragraph of text. Normally, Pango uses it internally for laying out text within a widget. However, it can be employed by this example to calculate the width of the tab string.

We begin by creating a new Pango.Layout object from the Gtk.TextView and creating the tab string with Gtk.Widget.create_pango_layout(). This uses the default font description of the text view. This is fine if the whole document has the same font applied to it. Pango.Layout describes how to render a paragraph of text.
layout = Gtk.Widget.create_pango_layout(textview, tab_string)
If the font varies within the document, or it has not already been applied to the text view, you want to specify the font to use for the calculations. You can set the font of a Pango.Layout with set_font_description(). This uses a Pango.FontDescription object to describe the layout’s font.
layout.set_font_description(fd)
Once you have correctly configured your Pango.Layout, the width of the string can be retrieved with get_pixel_size(). This is the calculated space that the string takes up within the buffer, which should be added when the user presses the Tab key within the widget.
(width, height) = layout.get_pixel_size()
Now that you have retrieved the width of the tab, you need to create a new Pango.TabArray with Pango.TabArray.new(). This function receives the number of elements that should be added to the array and notification of whether the size of each element is going to be specified in pixels.
tab_array = Pango.TabArray.new(1, True)

You should always create the tab array with only one element, because there is only one tab type supported at this time. If True is not specified for the second parameter, tabs are stored as Pango units; 1 pixel is equal to 1,024 Pango units.

Before applying the tab array, you need to add the width. This is done with set_tab(). The integer “0” refers to the first element in the Pango.TabArray, the only one that should ever exist. Pango.TabAlign.LEFT must always be specified for the second parameter, because it is currently the only supported value. The last parameter is the width of the tab in pixels.
tab_array.set_tab(0, Pango.TabAlign.LEFT, width)
When you receive the tab array back from the function, you need to apply it to the whole of the text view with set_tab(). This makes sure that all tabs within the text view are set to the same width. However, as with all other text view properties, this value can be overridden for individual paragraphs or sections of text.
textview.set_tabs(tab_array)

Text Iterators and Marks

When manipulating text within a Gtk.TextBuffer, there are two objects that can keep track of a position within the buffer: Gtk.TextIter and Gtk.TextMark. Functions are provided by GTK + to translate between these two types of objects.

Text iterators represent a position between two characters in a buffer. They are utilized when manipulating text within a buffer. The problem presented by text iterators is that they automatically become invalidated when a text buffer is edited. Even if the same text is inserted and then removed from the buffer, the text iterator becomes invalidated, because iterators are meant to be allocated on the stack and used immediately.

For keeping track of a position throughout changes within a text buffer, the Gtk.TextMark object is provided. Text marks remain intact while buffers are manipulated and move their position based on how the buffer is manipulated. You can retrieve an iterator pointing to a text mark with get_iter_at_mark(), which makes marks ideal for tracking a position in the document.
get_iter_at_mark(iter, mark)

Text marks act as though they are invisible cursors within the text, changing position depending on how the text is edited. If text is added before the mark, it moves to the right so that it remains in the same textual position.

By default, text marks have a gravity set to the right. This means that it moves to the right as text is added. Let us assume that the text surrounding a mark is deleted. The mark moves to the position between the two pieces of text on either side of the deleted text. Then, if text is inserted at the text mark, because of its right gravity setting, it remains on the right side of the inserted text. This is similar to the cursor, because as text is inserted, the cursor remains to the right of the inserted text.

Tip

By default, text marks are invisible within the text. However, you can set a Gtk.TextMark as visible by calling set_visible(), which places a vertical bar to indicate where it is located.

Text marks can be accessed in two ways. You can retrieve a text mark at a specific Gtk.TextIter location. It is also possible to set up a text mark with a string as its name, which makes marks easy to keep track of.

Two default text marks are always provided by GTK+ for every Gtk.TextBuffer: insert and selection_bound. The insert text mark refers to the current cursor position within the buffer. The selection_bound text mark refers to the boundary of selected text if there is any selected text. If no text is selected, these two marks point to the same position.

The "insert" and "selection_bound" text marks are extremely useful when manipulating buffers. They can be manipulated to automatically select or deselect text within a buffer and help you figure out where text should logically be inserted within a buffer.

Editing the Text Buffer

GTK+ provides a wide array of functions for retrieving text iterators as well as manipulating text buffers. In this section, you see a few of the most important of these methods in use in Listing 8-4, and then you are introduced to many more. Figure 8-4 displays an application that inserts and retrieves the text with a Gtk.TextBuffer.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig4_HTML.jpg
Figure 8-4

An application using a Gtk.TextView widget

Listing 8-4 is a simple example that performs two functions. When the Insert Text button shown in Figure 8-4 is clicked, the string shown in the Gtk.Entry widget is inserted at the current cursor position. When the Get Text button is clicked, any selected text is output with print().
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(-1, -1)
        textview = Gtk.TextView.new()
        entry = Gtk.Entry.new()
        insert_button = Gtk.Button.new_with_label("Insert Text")
        retrieve = Gtk.Button.new_with_label("Get Text")
        insert_button.connect("clicked", self.on_insert_text, (entry, textview))
        retrieve.connect("clicked", self.on_retrieve_text, (entry, textview))
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.add(textview)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
        hbox.pack_start(entry, True, True, 0)
        hbox.pack_start(insert_button, True, True, 0)
        hbox.pack_start(retrieve, True, True, 0)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
        vbox.pack_start(scrolled_win, True, True, 0)
        vbox.pack_start(hbox, True, True, 0)
        self.add(vbox)
        self.show_all()
    def on_insert_text(self, button, w):
        buffer = w[1].get_buffer()
        text = w[0].get_text()
        mark = buffer.get_insert()
        iter = buffer.get_iter_at_mark(mark)
        buffer.insert(iter, text, len(text))
    def on_retrieve_text(self, button, w):
        buffer = w[1].get_buffer()
        (start, end) = buffer.get_selection_bounds()
        text = buffer.get_text(start, end, False)
        print(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="Text Iterators")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-4

Using Text Iterators

An important property of iterators is that the same iterator can be used repeatedly, because iterators become invalidated every time you edit a text buffer. In this way, you can continue to reuse the same Gtk.TextIter object instead of creating a huge number of variables.

Retrieving Text Iterators and Marks

As stated before, there are quite a number of functions available for retrieving text iterators and text marks, many of which is used throughout this chapter.

Listing 8-4 begins by retrieving the insert mark with buffer.get_insert(). It is also possible to use buffer.get_selection_bound() to retrieve the “selection_bound” text mark.
mark = buffer.get_insert()
iter = buffer.get_iter_at_mark(mark)

Once you have retrieved a mark, you can translate it into a text iterator with textbuffer.get_iter_at_mark(), so that it can manipulate the buffer.

The other function presented by Listing 8-4 for retrieving text iterators is buffer.get_selection_bounds(), which returns the iterators located at the insert and selection_bound marks. You can set one or both of the text iterator parameters to None, which prevent the value from returning, although it would make more sense to use the functions for the specific mark if you only need one or the other.

When retrieving the contents of a buffer, you need to specify a start and end iterator for the slice of text. If you want to get the whole contents of the document, you need iterators pointing to the beginning and end of the document, which can be retrieved with buffer.get_bounds().
buffer.get_bounds(start, end)

It is also possible to retrieve only the beginning or end iterator for the text buffer independently of the other with buffer.get_start_iter() or buffer.get_end_iter().

Text within a buffer can be retrieved with buffer.get_text(). It returns all the text between the start and end iterators. If the last parameter is set to True, then invisible text is also returned.
buffer.get_text(start, end, boolean)

Caution

You should only use buffer.get_text() for retrieving the whole contents of a buffer. It ignores any image or widget objects embedded in the text buffer, so character indexes may not correspond to the correct location. For retrieving individual parts of a text buffer, use buffer.get_slice() instead.

Recall that the offset refers to the number of individual characters within the buffer. These characters can be one or more bytes long. The buffer.get_iter_at_offset() function allows you to retrieve the iterator at the location of a specific offset from the beginning of the buffer.
buffer.get_iter_at_offset(iter, character_offset)

GTK+ also provides buffer.get_iter_at_line_index(), which chooses a position of an individual byte on the specified line. You should be extremely careful when using this function, because the index must always point to the beginning of a UTF-8 character. Remember that characters in UTF-8 may not be only a single byte!

Rather than choosing a character offset, you can retrieve the first iterator on a specified line with buffer.get_iter_at_line().
buffer.get_iter_at_line(iter, character_offset)

If you want to retrieve the iterator at an offset from the first character of a specific line, buffer.get_iter_at_line_offset()does the trick.

Changing Text Buffer Contents

You have already learned how to reset the contents of a whole text buffer, but it is also useful to edit only a portion of a document. There are a number of functions provided for this purpose. Listing 8-4 shows you how to insert text into a buffer.

If you need to insert text in an arbitrary position of the buffer, you should use buffer.insert(). To do this, you need a Gtk.TextIter pointing to the insertion point, the text string to insert into the buffer that must be UTF-8, and the length of the text.
buffer.get_insert()

When this function is called, the text buffer emits the insert-text signal, and the text iterator is invalidated. However, the text iterator is then reinitialized to the end of the inserted text.

A convenience method named insert_at_cursor() can call buffer.insert() at the cursor’s current position. This can easily be implemented by using the insert text mark, but it helps you avoid repetitive calls.
buffer.insert_at_cursor(text, length)
You can delete the text between two text iterators with gtk_text_buffer_delete(). The order in which you specify the iterators is irrelevant, because the method automatically places them in the correct order.
buffer.delete(start, end)

This function emits the "delete-range" signal, and both iterators are invalidated. However, the start and end iterators are both reinitialized to the start location of the deleted text.

Cutting, Copying, and Pasting Text

Figure 8-5 shows a text view with an entry field and buttons that can access the clipboard functions via the text view object.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig5_HTML.jpg
Figure 8-5

Gtk.TextView clipboard buttons

Three clipboard options are cut, copy, and paste, which are standard to almost all text editors. They are built into every Gtk.TextView widget. However, there are times that you want to implement your own versions of these functions to include in an application menu or toolbar.

Listing 8-5 gives an example of each of these methods. When one of the three Gtk.Button widgets is clicked, some action is initialized. Try using the buttons and the right-click menu to show that both use the same Gtk.Clipboard object. These functions can also be called by using the built-in keyboard accelerators, which are Ctrl+C, Ctrl+X, and Ctrl+V.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        textview = Gtk.TextView.new()
        cut = Gtk.Button.new_with_label("Cut")
        copy = Gtk.Button.new_with_label("Copy")
        paste = Gtk.Button.new_with_label("Paste")
        cut.connect("clicked", self.on_cut_clicked, textview)
        copy.connect("clicked", self.on_copy_clicked, textview)
        paste.connect("clicked", self.on_paste_clicked, textview)
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.set_size_request(300, 200)
        scrolled_win.add(textview)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
        hbox.pack_start(cut, True, True, 0)
        hbox.pack_start(copy, True, True, 0)
        hbox.pack_start(paste, True, True, 0)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
        vbox.pack_start(scrolled_win, True, True, 0)
        vbox.pack_start(hbox, True, True, 0)
        self.add(vbox)
    def on_cut_clicked(self, button, textview):
        clipboard = Gtk.Clipboard.get(Gdk.Atom.intern("CLIPBOARD", False))
        buffer = textview.get_buffer()
        buffer.cut_clipboard(clipboard, True)
    def on_copy_clicked(self, button, textview):
        clipboard = Gtk.Clipboard.get(Gdk.Atom.intern("CLIPBOARD", False))
        buffer = textview.get_buffer()
        buffer.copy_clipboard(clipboard)
    def on_paste_clicked(self, button, textview):
    clipboard = Gtk.Clipboard.get(Gdk.Atom.intern("CLIPBOARD", False))
        buffer = textview.get_buffer()
        buffer.paste_clipboard (clipboard, None, True)
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="Cut, Copy & Paste")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-5

Using Text Iterators

Gtk.Clipboard is a central class where data can be transferred easily between applications. To retrieve a clipboard that has already been created, you should use clipboard.get(). GTK+ 3.x only supplies a single default clipboard. GTK+ 2.x provided named clipboards but that functionality is no longer supported.

Note

While it is possible to create your own Gtk.Clipboard objects, when performing basic tasks, you should use the default clipboard. You can retrieve it by executing the method Gdk.Atom.intern("CLIPBOARD", False) to Gtk.Clipboard.get().

It is feasible to directly interact with the Gtk.Clipboard object that you have created, adding and removing data from it. However, when performing simple tasks including copying and retrieving text strings for a Gtk.TextView widget, it makes more sense to use Gtk.TextBuffer’s built-in methods.

The simplest of Gtk.TextBuffer’s three clipboard actions is copying text, which can be done with the following:
buffer.copy_clipboard(clipboard)
The second clipboard function, buffer.cut_clipboard(clipboard, True) copies the selection to the clipboard as well as removing it from the buffer. If any of the selected text does not have the editable flag set, it is set to the third parameter of this function. This function copies not only text but also embedded objects such as images and text tags.
buffer.cut_clipboard(clipboard, True)
The last clipboard function, buffer.paste_clipboard() first retrieves the content of the clipboard. Next, the function does one of two things. If the second parameter, which accepts a Gtk.TextIter, has been specified, the content is inserted at the point of that iterator. If you specify None for the third parameter, the content is inserted at the cursor.
buffer.paste_clipboard (clipboard, None, True)

If any of the content that is going to be pasted does not have the editable flag set, then it is set automatically to default_editable. In most cases, you want to set this parameter to True, because it allows the pasted content to be edited. You should also note that the paste operation is asynchronous.

Searching the Text Buffer

In most applications that use the Gtk.TextView widget, you need to search through a text buffer in one or more instances. GTK+ provides two functions for finding text in a buffer: forward_search() and backward_search().

The following example shows you how to use the first of these functions to search for a text string in a Gtk.TextBuffer; a screenshot of the example is shown in Figure 8-6. The example begins when the user clicks the Find button.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig6_HTML.jpg
Figure 8-6

An application that searches a text buffer

The application in Listing 8-6 searches for all instances of the specified string within the text buffer. A dialog is presented to the user, displaying how many times the string was found in the document.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        textview = Gtk.TextView.new()
        entry = Gtk.Entry.new()
        entry.set_text("Search for ...")
        find = Gtk.Button.new_with_label("Find")
        find.connect("clicked", self.on_find_clicked, (textview, entry))
        scrolled_win = Gtk.ScrolledWindow.new (None, None)
        scrolled_win.set_size_request(250, 200)
        scrolled_win.add(textview)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
        hbox.pack_start(entry, True, True, 0)
        hbox.pack_start(find, True, True, 0)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
        vbox.pack_start(scrolled_win, True, True, 0)
        vbox.pack_start(hbox, True, True, 0)
        self.add(vbox)
    def on_find_clicked(self, button, w):
        find = w[1].get_text()
        find_len = len(find)
        buffer = w[0].get_buffer()
        start = buffer.get_start_iter()
        end_itr = buffer.get_end_iter()
        i = 0
        while True:
            end = start.copy()
            end.forward_chars(find_len)
            text = buffer.get_text(start, end, False)
            if text == find:
                i += 1
                start.forward_chars(find_len)
            else:
                start.forward_char()
            if end.compare(end_itr) == 0:
                break
                output = "The string '"+find+"' was found " + str(i) + " times!"
                dialog = Gtk.MessageDialog(parent=self,
                                        flags=Gtk.DialogFlags.MODAL,
                                        message_type=Gtk.MessageType.INFO,
                                        text=output, title="Information",
                                        buttons=("OK", Gtk.ResponseType.OK))
        dialog.run()
        dialog.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self,
                                    title="Searching Buffers")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-6

Using The Gtk.TextIter Find Function

The first thing the search function needs to do is retrieve the lower and upper search bound of the document with buffer.get_start_iter() and buffer.get_end_iter(). We use the end upper bound limit for testing purposes later in the code.
end = start.copy()
end.forward_chars(find_len)
The search loop begins by setting up an end Gtk.TextIter and then incremented by the length of the search string. This creates a slice of the buffer equal to the length of the search string.
text = buffer.get_text(start, end, False)
The buffer.get_text() retrieves the text between the two Gtk.TextIter’s. The third parameter is a boolean specifying whether only text is retrieved or to include other markers in the text.
if text == find:
    i += 1
    start.forward_chars(find_len)
else:
    start.forward_char()
if end.compare(end_itr) == 0:
    break

Next, we test if the search string matches the string from the buffer. If a match was found then we increment our match counter and move the start Gtk.TextIter past the string we found in the buffer. If a match was not found then increment the start Gtk.TextIter by one character. Lastly, we test upper search bound Gtk.TextIter is equal to the end of the buffer and break out of our endless loop if the two are equal.

After we break out of the loop, we report the search results to the user.

Scrolling Text Buffers

GTK+ does not automatically scroll to search matches that you select. To do this, you need to first call buffer.create_mark() to create a temporary Gtk.TextMark at the location of the found text.
buffer.create_mark(name, location, left_gravity)

The second parameter of buffer.create_mark() allows you to specify a text string as a name for the mark. This name can reference the mark later without the actual mark object. The mark is created at the location of the specified text iterator. The last parameter creates a mark with left gravity if set to True.

Then, use view.scroll_mark_onscreen() to scroll the buffer, so the mark is on the screen. After you are finished with the mark, you can remove it from the buffer with buffer.delete_mark().
textview.scroll_mark_onscreen(mark)
The problem with view.scroll_mark_onscreen() is that it only scrolls the minimum distance to show the mark on the screen. For example, you may want the mark to be centered within the buffer. To specify alignment parameters for where the mark appears within the visible buffer, call textview.scroll_to_mark().
textview.scroll_to_mark(mark, margin, use_align, xalign, yalign)

You begin by placing a margin, which reduces the scrollable area. The margin must be specified as a floating-point number, which reduces the area by that factor. In most cases, you want to use 0.0 as the margin so the area is not reduced at all.

If you specify False for the use_align parameter, the function scrolls the minimal distance to get the mark onscreen; otherwise, the function uses the two alignment parameters as guides, which allows you to specify horizontal and vertical alignment of the mark within the visible area.

An alignment of 0.0 refers to the left or top of the visible area, 1.0 refers to the right or bottom and 0.5 refers to the center. The function scrolls as far as possible, but it may not be able to scroll the mark to the specified position. For example, it is impossible to scroll the last line in a buffer to the top if the buffer is larger than one character tall.

There is another function, textview.scroll_to_iter(), which behaves in the same manner as textview.scroll_to_mark(). The only difference is that it receives a Gtk.TextIter instead of a Gtk.TextMark for the location, although in most cases, you should use text marks.

Text Tags

There are many functions provided for changing properties of all the text within a Gtk.TextBuffer, which have been covered in previous sections. But, as previously mentioned, it is also possible to change the display properties of only an individual section of text with the Gtk.TextTag object.

Text tags allow you to create documents where the text style varies among different parts of the text, which is commonly called rich text editing. A screenshot of a Gtk.TextView that uses multiple text styles is shown in Figure 8-7.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig7_HTML.jpg
Figure 8-7

Formatted text within a text buffer

Text tags are actually a very simple concept to apply. In Listing 8-7 an application is created that allows the user to apply multiple styles or remove all the tags from the selection. After reading the rest of this section, you might want to try out other text properties by altering Listing 8-7 to include different style options.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango
text_to_scales = [("Quarter Sized", 0.25),
                  ("Double Extra Small", 0.5787037037037), ("Extra Small", 0.6444444444444), ("Small", 0.8333333333333), ("Medium", 1.0), ("Large", 1.2), ("Extra Large", 1.4399999999999), ("Double Extra Large", 1.728), ("Double Sized", 2.0)]
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(500, -1)
        textview = Gtk.TextView.new()
        buffer = textview.get_buffer()
        buffer.create_tag("bold", weight=Pango.Weight.BOLD)
        buffer.create_tag("italic", style=Pango.Style.ITALIC)
        buffer.create_tag("strike", strikethrough=True)
        buffer.create_tag("underline", underline=Pango.Underline.SINGLE)
        bold = Gtk.Button.new_with_label("Bold")
        italic = Gtk.Button.new_with_label("Italic")
        strike = Gtk.Button.new_with_label("Strike")
        underline = Gtk.Button.new_with_label("Underline")
        clear = Gtk.Button.new_with_label("Clear")
        scale_button = Gtk.ComboBoxText.new()
        i = 0
        while i < len(text_to_scales):
            (name, scale) = text_to_scales[i]
            scale_button.append_text(name)
            buffer.create_tag(tag_name=name, scale=scale)
            i += 1
        bold.__setattr__("tag", "bold")
        italic.__setattr__("tag", "italic")
        strike.__setattr__("tag", "strike")
        underline.__setattr__("tag", "underline")
        bold.connect("clicked", self.on_format, textview)
        italic.connect("clicked", self.on_format, textview)
        strike.connect("clicked", self.on_format, textview)
        underline.connect("clicked", self.on_format, textview)
        clear.connect("clicked", self.on_clear_clicked, textview)
        scale_button.connect("changed", self.on_scale_changed, textview)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 5)
        vbox.pack_start(bold, False, False, 0)
        vbox.pack_start(italic, False, False, 0)
        vbox.pack_start(strike, False, False, 0)
        vbox.pack_start(underline, False, False, 0)
        vbox.pack_start(scale_button, False, False, 0)
        vbox.pack_start(clear, False, False, 0)
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.add(textview)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.ALWAYS)
        hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5)
        hbox.pack_start(scrolled_win, True, True, 0)
        hbox.pack_start(vbox, False, True, 0)
        self.add(hbox)
    def on_format(self, button, textview):
        tagname = button.tag
        buffer = textview.get_buffer()
        (start, end) = buffer.get_selection_bounds()
        buffer.apply_tag_by_name(tagname, start, end)
    def on_scale_changed(self, button, textview):
        if button.get_active() == -1:
            return
        text = button.get_active_text()
        button.__setattr__("tag", text)
        self.on_format(button, textview)
        button.set_active(-1)
    def on_clear_clicked(self, button, textview):
        buffer = textview.get_buffer()
        (start, end) = buffer.get_selection_bounds()
        buffer.remove_all_tags(start, end)
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="Text Tags")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-7

Using Text Iterators

When you create a text tag, you normally have to add it to a Gtk.TextBuffer’s tag table, an object that holds all the tags available to a text buffer. You can create a new Gtk.TextTag object with Gtk.TextTag.new() and then add it to the tag table. However, you can do this all in one step with buffer.create_tag().
buffer.create_tag(tag_name, property_name=value)

The first parameter specifies the name of the tag to be added to the table Gtk.TextTag. This name can reference a tag for which you do not have the Gtk.TextTag object anymore. The next parameters are a set of keyword/value list of Gtk.TextTag style properties and their values.

For example, if you wanted to create a text tag that sets the background and foreground colors as black and white respectively, you could use the following method. This function returns the text tag that was created, although it has already been added to the text buffer’s tag table.
buffer.create_tag("colors", background="#000000", foreground="#FFFFFF")

There are a large number of style properties available in GTK+.

Once you have created a text tag and added it to a Gtk.TextBuffer’s tag table, you can apply it to ranges of text. In Listing 8-7 the tag is applied to selected text when a button is clicked. If there is no selected text, the cursor position is set to the style. All text typed at that position would have the tag applied as well.

Tags are generally applied to text with buffer.apply_tag_by_name(). The tag is applied to the text between the start and end iterators. If you still have access to the Gtk.TextTag object, you can also apply a tag with buffer.apply_tag().
buffer.apply_tag_by_name(tag_name, start, end)
Although not used in Listing 8-7 it is possible to remove a tag from an area of text with buffer.remove_tag_by_name(). This function removes all instances of the tag between the two iterators if they exist.
buffer.remove_tag_by_name(tag_name, start, end)

Note

These functions only remove tags from a certain range of text. If the tag was added to a larger range of text than the range specified, the tag is removed for the smaller range, and new bounds are created on either side of the selection. You can test this with the application in Listing 8-7.

If you have access to the Gtk.TextTag object, you can remove the tag with buffer.remove_tag().

It is also possible to remove every tag within a range with buffer.remove_all_tags().

Inserting Images

In some applications, you may want to insert images into a text buffer. This can easily be done with Gdk.Pixbuf objects. In Figure 8-8, two images were inserted into a text buffer as Gdk.Pixbuf objects.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig8_HTML.jpg
Figure 8-8

Formatted text within a text buffer

Adding a pixbuf to a Gtk.TextBuffer is performed in three steps. First, you must create the pixbuf object and retrieve the Gtk.TextIter where it is inserted. Then, you can use buffer.insert_pixbuf() to add it to the buffer. Listing 8-8 shows the process of creating a Gdk.Pixbuf object from a file and adding it to a text buffer.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(200, 150)
        textview = Gtk.TextView.new()
        buffer = textview.get_buffer()
        text = " Undo Redo"
        buffer.set_text(text, len(text))
        icon_theme = Gtk.IconTheme.get_default()
        undo = icon_theme.load_icon("edit-undo", -1,
                                     Gtk.IconLookupFlags.FORCE_SIZE)
        line = buffer.get_iter_at_line (0)
        buffer.insert_pixbuf(line, undo)
        redo = icon_theme.load_icon("edit-redo", -1,
                                     Gtk.IconLookupFlags.FORCE_SIZE)
        line = buffer.get_iter_at_line (1)
        buffer.insert_pixbuf(line, redo)
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.add(textview)
        self.add (scrolled_win)
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="Pixbufs")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-8

Inserting Images into Text Buffers

Inserting a Gdk.Pixbuf object into a text buffer is done with buffer.insert_pixbuf(). The Gdk.Pixbuf object is inserted at the specified location, which can be any valid text iterator in the buffer.
buffer.insert_pixbuf(iter, pixbuf)

Pixbufs are handled differently by various functions. For example, buffer.get_slice() places the 0xFFFC character where a pixbuf is located. However, the 0xFFFC character can occur as an actual character in the buffer, so that is not a reliable indicator of the location of a pixbuf.

Another example is buffer.get_text(), which completely ignores nontextual elements, so there is no way to check for pixbufs within the text using this function.

Therefore, if you are using pixbufs in a Gtk.TextBuffer, it is best to retrieve text from the buffer with buffer.get_slice(). You can then use iter.get_pixbuf() to check whether the 0xFFFC character represents a Gdk.Pixbuf object; it returns None if a pixbuf is not found at that location.
iter.get_pixbuf()

Inserting Child Widgets

Inserting widgets into a text buffer is a little more complicated than pixbufs, because you must notify both the text buffer and the text view to embed the widget. You begin by creating a Gtk.TextChildAnchor object, which marks the placement of the widget within the Gtk.TextBuffer. Then, you add the widget to the Gtk.TextView widget.

Figure 8-9 shows a Gtk.TextView widget that contains a child Gtk.Button widget. Listing 8-9 creates this window. When the button is pressed, self.destroy is called, which terminates the application.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig9_HTML.jpg
Figure 8-9

A child widget inserted into a text buffer

#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(25)
        self.set_border_width(10)
        self.set_size_request(250, 100)
        textview = Gtk.TextView.new()
        buffer = textview.get_buffer()
        text = " Click to exit!"
        buffer.set_text(text, len(text))
        iter = buffer.get_iter_at_offset(8)
        anchor = buffer.create_child_anchor(iter)
        button = Gtk.Button.new_with_label("the button")
        button.connect("clicked", self.on_button_clicked)
        button.set_relief(Gtk.ReliefStyle.NORMAL)
        textview.add_child_at_anchor(button, anchor)
        scrolled_win = Gtk.ScrolledWindow.new(None, None)
        scrolled_win.add(textview)
        scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
                                Gtk.PolicyType.ALWAYS)
        self.add(scrolled_win)
    def on_button_clicked(self, button):
        self.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Child Widgets")
        self.window.show_all()
        self.window.present()
    if __name__ == "__main__":
        app = Application()
        app.run(sys.argv)
Listing 8-9

Inserting Child Widgets into a Text Buffer

When creating a Gtk.TextChildAnchor, you need to initialize it and insert it into a Gtk.TextBuffer. You can do this by calling buffer.create_child_anchor().
buffer.create_child_anchor(iter)

A child anchor is created at the location of the specified text iterator. This child anchor is simply a mark that tells GTK+ that a child widget can be added to that point within the text buffer.

Next, you need to use textview.add_child_at_anchor() to add a child widget to the anchor point. As with Gdk.Pixbuf objects, child widgets appear as the 0xFFFC character. This means that, if you see that character, you need to check whether it is a child widget or a pixbuf, because they are indistinguishable otherwise.
textview.add_child_at_anchor(child, anchor)
To check whether a child widget is at the location of an 0xFFFC character, you should call iter.get_child_anchor(), which returns None if a child anchor is not located at that position.
iter.get_child_anchor()
You can then retrieve a list of the widgets added at the anchor point with anchor.get_widgets(). You need to note that only one child widget can be added at a single anchor, so the returned list usually contains only one element.
anchor.get_widgets()

The exception is when you are using the same buffer for multiple text views. In this case, multiple widgets can be added to the same anchor in the text views, as long as no text view contains more than one widget. This is because of the fact that the child widget is attached to an anchor handled by the text view instead of the text buffer.

Gtk.SourceView

Gtk.SourceView is a widget that is not actually a part of the GTK+ libraries. It is an external library to extend the Gtk.TextView widget. If you have ever used gedit, you have experienced the Gtk.SourceView widget.

There is a large list of features that the Gtk.SourceView widget adds to text views. A few of the most notable ones follow:
  • Line numbering

  • Syntax highlighting for many programming and scripting languages

  • Printing support for documents containing syntax highlighting

  • Automatic indentation

  • Bracket matching

  • Undo/Redo support

  • Source markers for denoting locations in source code

  • Highlighting the current line

Figure shows a screenshot of gedit using the Gtk.SourceView widget. It has line numbering, syntax highlighting, bracket matching, and line highlighting turned on.
../images/142357_2_En_8_Chapter/142357_2_En_8_Fig10_HTML.jpg
Figure 8-10

A child widget inserted into a text buffer

The Gtk.SourceView library has an entire separate API documentation, which can be viewed at http://gtksourceview.sourceforge.net .

Test Your Understanding

The following exercise instructs you to create a text editing application with basic functionality. It gives you practice on interacting with a Gtk.TextView widget.

Exercise 1: Text Editor

Use the Gtk.TextView widget to create a simple text editor. You should have the ability to perform multiple text editing functions, including creating a new document, opening a file, saving a file, searching the document, cutting text, copying text, and pasting text.

When creating a new document, you should make sure that the user actually wants to continue, because all changes are lost. When the Save button is pressed, it should always ask where to save the file. Once you have finished this exercise, a solution is shown in Appendix D.

Hint

This is a much larger GTK+ application than any previously created in this book, so you may want to take a few minutes to plan your solution on paper before diving right into the code. Then, implement one function at a time, making sure it works before continuing on to the next feature. We expand on this exercise in later chapters as well, so keep your solution handy!

This is the first instance of the Text Editor application that you are working on throughout this book. In the last few chapters of this book, you learn new elements that help you create a fully featured text editor.

The application is expanded in Chapter 10, where you add a menu and a toolbar. In Chapter 13, you add printing support and the ability to remember past open files and searches.

A solution to this exercise is in Appendix D. Much of the functionality of the text editor solution has been implemented by other examples in this chapter. Therefore, most of the solution should look familiar to you. It is a bare minimum solution, and I encourage you to expand on the basic requirements of the exercise for more practice.

Summary

In this chapter, you learned all about the Gtk.TextView, which allows you to display multiple lines of text. Text views are usually contained by a special type of Gtk.Bin container called Gtk.ScrolledWindow that gives scrollbars to the child widget to implement scrolling abilities.

A Gtk.TextBuffer handles text within a view. Text buffers allow you to change many different properties of the whole or portions of the text using text tags. They also provide cut, copy, and paste functions.

You can move throughout a text buffer by using Gtk.TextIter objects, but text iterators become invalid once the text buffer is changed. Text iterators can search forward or backward throughout a document. To keep a location over changes of a buffer, you need to use text marks. Text views are capable of displaying not only text but also images and child widgets. Child widgets are added at anchor points throughout a text buffer.

The last section of the chapter briefly introduced the Gtk.SourceView widget, which extends the functionality of the Gtk.TextView widget. It can be used when you need features such as syntax highlighting and line numbering.

In Chapter 9, you are introduced to two new widgets: combo boxes and tree views. Combo boxes allow you to select one option from a drop-down list. Tree views allow you to select one or more options from a list usually contained by a scrolled window. Gtk.TreeView is the most difficult widget that is covered in this book, so take your time with the next chapter.

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

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