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.
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.
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.
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.
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.
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.
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.
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.
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.
Using Gtk.TextView 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.
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.
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.
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.
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.
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.
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
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.
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.
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().
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.
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!
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.
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.
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
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.
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.
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().
Using The Gtk.TextIter Find Function
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
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.
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.
Using Text Iterators
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.
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.
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
Inserting Images into Text Buffers
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.
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.
Inserting Child Widgets into a Text Buffer
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.
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.
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
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.