15. Multiline Text Editor

The Gtk.TextView widget can be used to display and edit large amounts of formatted text. Like the Gtk.TreeView, it has a model/view design. In this case the Gtk.TextBuffer is the model which represents the text being edited. This allows two or more Gtk.TextView widgets to share the same Gtk.TextBuffer, and allows those text buffers to be displayed slightly differently. Or you could maintain several text buffers and choose to display each one at different times in the same Gtk.TextView widget.

15.1. The View

The Gtk.TextView is the frontend with which the user can add, edit and delete textual data. They are commonly used to edit multiple lines of text. When creating a Gtk.TextView it contains its own default Gtk.TextBuffer, which you can access via the Gtk.TextView.get_buffer() method.

By default, text can be added, edited and removed from the Gtk.TextView. You can disable this by calling Gtk.TextView.set_editable(). If the text is not editable, you usually want to hide the text cursor with Gtk.TextView.set_cursor_visible() as well. In some cases it may be useful to set the justification of the text with Gtk.TextView.set_justification(). The text can be displayed at the left edge, (Gtk.Justification.LEFT), at the right edge (Gtk.Justification.RIGHT), centered (Gtk.Justification.CENTER), or distributed across the complete width (Gtk.Justification.FILL).

Another default setting of the Gtk.TextView widget is long lines of text will continue horizontally until a break is entered. To wrap the text and prevent it going off the edges of the screen call Gtk.TextView.set_wrap_mode().

15.1.1. TextView Objects

class Gtk.TextView

Creates a new Gtk.TextView associated with an empty default Gtk.TextBuffer.

get_buffer()

Returns the Gtk.TextBuffer being displayed by this text view.

set_editable(editable)

Sets the default editability of this Gtk.TextView.

set_cursor_visible(visible)

Toggles whether the insertion point is displayed. A buffer with no editable text probably shouldn’t have a visible cursor, so you may want to turn the cursor off.

set_justification(justification)

Sets the default justification of text.

justification can be one of the following values:

  • Gtk.Justification.LEFT: Text is placed at the left edge.
  • Gtk.Justification.RIGHT: Text is placed at the right edge.
  • Gtk.Justification.CENTER: Text is placed in the center.
  • Gtk.Justification.FILL: Text is distributed across the complete width.
set_wrap_mode(wrap_mode)

Sets the line wrapping for the view.

wrap_mode can be one of the following values:

  • Gtk.WrapMode.NONE: Do not wrap lines; just make the text area wider.
  • Gtk.WrapMode.CHAR: Wrap text, breaking lines anywhere the cursor can appear (between characters, usually).
  • Gtk.WrapMode.WORD: Wrap text, breaking lines in between words.
  • Gtk.WrapMode.WORD_CHAR: Wrap text, breaking lines in between words, or if that is not enough, also between graphemes.

15.2. The Model

The Gtk.TextBuffer is the core of the Gtk.TextView widget, and is used to hold whatever text is being displayed in the Gtk.TextView. Setting and retrieving the contents is possible with Gtk.TextBuffer.set_text() and Gtk.TextBuffer.get_text(). However, most text manipulation is accomplished with iterators, represented by a Gtk.TextIter. An iterator represents a position between two characters in the text buffer. Iterators are not valid indefinitely; whenever the buffer is modified in a way that affects the contents of the buffer, all outstanding iterators become invalid.

Because of this, iterators can’t be used to preserve positions across buffer modifications. To preserve a position, use Gtk.TextMark. A text buffer contains two built-in marks; an “insert” mark (which is the position of the cursor) and the “selection_bound” mark. Both of them can be retrieved using Gtk.TextBuffer.get_insert() and Gtk.TextBuffer.get_selection_bound(), respectively. By default, the location of a Gtk.TextMark is not shown. This can be changed by calling Gtk.TextMark.set_visible().

Many methods exist to retrieve a Gtk.TextIter. For instance, Gtk.TextBuffer.get_start_iter() returns an iterator pointing to the first position in the text buffer, whereas Gtk.TextBuffer.get_end_iter() returns an iterator pointing past the last valid character. Retrieving the bounds of the selected text can be achieved by calling Gtk.TextBuffer.get_selection_bounds().

To insert text at a specific position use Gtk.TextBuffer.insert(). Another useful method is Gtk.TextBuffer.insert_at_cursor() which inserts text wherever the cursor may be currently positioned. To remove portions of the text buffer use Gtk.TextBuffer.delete().

In addition, Gtk.TextIter can be used to locate textual matches in the buffer using Gtk.TextIter.forward_search() and Gtk.TextIter.backward_search(). The start and end iters are used as the starting point of the search and move forwards/backwards depending on requirements.

15.2.1. TextBuffer Objects

class Gtk.TextBuffer
set_text(text[, length])

Deletes current contents of this buffer, and inserts length characters of text instead. If length is -1 or omitted, text is inserted completely.

get_text(start_iter, end_iter, include_hidden_chars)

Returns the text in the range start_iter (included) and end_iter (excluded). Excludes undisplayed text if include_hidden_chars is False.

get_insert()

Returns the Gtk.TextMark that represents the cursor (insertion point).

get_selection_bound()

Returns the Gtk.TextMark that represents the selection bound.

create_mark(mark_name, where[, left_gravity])

Creates a Gtk.TextMark at the position of the Gtk.TextIter where. If mark_name is None, the mark is anonymous; otherwise, the mark can be retrieved by name using get_mark(). If a mark has left gravity, and text is inserted at the mark’s current location, the mark will be moved to the left of the newly-inserted text. If the mark has right gravity (left_gravity is False), the mark will end up on the right of newly-inserted text. The standard left-to-right cursor is a mark with right gravity (when you type, the cursor stays on the right side of the text you’re typing).

If left_gravity is omitted, it defaults to False.

get_mark(mark_name)

Returns the Gtk.TextMark named name in this buffer, or None if no such mark exists in the buffer.

get_start_iter()

Returns a Gtk.TextIter pointing to first position in this buffer.

get_end_iter()

Returns a Gtk.TextIter pointing past the last valid character in this buffer.

get_selection_bounds()

Returns a tuple of two Gtk.TextIter objects pointing to the first character of the selection and to the first character after the selection, respectively. If no text is selected an empty tuple is returned.

insert(text_iter, text[, length])

Inserts length characters of text at position text_iter. If length is -1 or omitted, text is inserted completely.

insert_at_cursor(text[, length])

Simply calls insert(), using the current cursor position as the insertion point.

delete(start_iter, end_iter)

Deletes text between start_iter and end_iter.

create_tag(tag_name, **kwargs)

Creates a tag and adds it to the tag table of this buffer.

If tag_name is None, the tag is anonymous, otherwise a tag with the same name must not already exist in the tag table of the buffer.

kwargs is an arbitrary number of key-value pairs that represent a list properties to set on the tag, as with tag.set_property(prop_name, value).

apply_tag(tag, start_iter, end_iter)

Applies tag to the given range.

remove_tag(tag, start_iter, end_iter)

Removes all occurrences of tag from the given range.

remove_all_tags(start_iter, end_iter)

Removes all tags in the given range.

class Gtk.TextIter

Searches forward for needle. The search will not continue past the Gtk.TextIter limit.

flags can be set to one of the following, or any combination of it by concatenating them with the bitwise-OR operator |.

  • 0: The match must be exact.
  • Gtk.TextSearchFlags.VISIBLE_ONLY: The match may have invisible text interspersed in needle. i.e. needle will be a possibly-noncontiguous subsequence of the matched range.
  • Gtk.TextSearchFlags.TEXT_ONLY: The match may have pixbufs or child widgets mixed inside the matched range.
  • Gtk.TextSearchFlags.CASE_INSENSITIVE: The text will be matched regardless of what case it is in.

Returns a tupel containing a Gtk.TextIter pointing to the start and to the first character after the match. If no match was found, None is returned.

Same as forward_search(), but moves backward.

class Gtk.TextMark
set_visible(visible)

Sets the visibility of this mark; the insertion point is normally visible, i.e. you can see it as a vertical bar. Also, the text widget uses a visible mark to indicate where a drop will occur when dragging-and-dropping text. Most other marks are not visible. Marks are not visible by default.

15.3. Tags

Text in a buffer can be marked with tags. A tag is an attribute that can be applied to some range of text. For example, a tag might be called “bold” and make the text inside the tag bold. However, the tag concept is more general than that; tags don’t have to affect appearance. They can instead affect the behaviour of mouse and key presses, “lock” a range of text so the user can’t edit it, or countless other things. A tag is represented by a Gtk.TextTag object. One Gtk.TextTag can be applied to any number of text ranges in any number of buffers.

Each tag is stored in a Gtk.TextTagTable. A tag table defines a set of tags that can be used together. Each buffer has one tag table associated with it; only tags from that tag table can be used with the buffer. A single tag table can be shared between multiple buffers, however.

To specify that some text in the buffer should have specific formatting, you must define a tag to hold that formatting information, and then apply that tag to the region of text using Gtk.TextBuffer.create_tag() and Gtk.TextBuffer.apply_tag():

tag = textbuffer.create_tag("orange_bg", background="orange")
textbuffer.apply_tag(tag, start_iter, end_iter)

The following are some of the common styles applied to text:

  • Background colour (“foreground” property)
  • Foreground colour (“background” property)
  • Underline (“underline” property)
  • Bold (“weight” property)
  • Italics (“style” property)
  • Strikethrough (“strikethrough” property)
  • Justification (“justification” property)
  • Size (“size” and “size-points” properties)
  • Text wrapping (“wrap-mode” property)

You can also delete particular tags later using Gtk.TextBuffer.remove_tag() or delete all tags in a given region by calling Gtk.TextBuffer.remove_all_tags().

15.4. Example

_images/textview_example.png
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
from gi.repository import Gtk, Pango

class SearchDialog(Gtk.Dialog):

    def __init__(self, parent):
        Gtk.Dialog.__init__(self, "Search", parent,
            Gtk.DialogFlags.MODAL, buttons=(
            Gtk.STOCK_FIND, Gtk.ResponseType.OK,
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))

        box = self.get_content_area()

        label = Gtk.Label("Insert text you want to search for:")
        box.add(label)

        self.entry = Gtk.Entry()
        box.add(self.entry)

        self.show_all()

class TextViewWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="TextView Example")

        self.set_default_size(-1, 350)

        self.grid = Gtk.Grid()
        self.add(self.grid)

        self.create_textview()
        self.create_toolbar()
        self.create_buttons()

    def create_toolbar(self):
        toolbar = Gtk.Toolbar()
        self.grid.attach(toolbar, 0, 0, 3, 1)

        button_bold = Gtk.ToolButton.new_from_stock(Gtk.STOCK_BOLD)
        toolbar.insert(button_bold, 0)

        button_italic = Gtk.ToolButton.new_from_stock(Gtk.STOCK_ITALIC)
        toolbar.insert(button_italic, 1)

        button_underline = Gtk.ToolButton.new_from_stock(Gtk.STOCK_UNDERLINE)
        toolbar.insert(button_underline, 2)

        button_bold.connect("clicked", self.on_button_clicked, self.tag_bold)
        button_italic.connect("clicked", self.on_button_clicked,
            self.tag_italic)
        button_underline.connect("clicked", self.on_button_clicked,
            self.tag_underline)

        toolbar.insert(Gtk.SeparatorToolItem(), 3)

        radio_justifyleft = Gtk.RadioToolButton()
        radio_justifyleft.set_stock_id(Gtk.STOCK_JUSTIFY_LEFT)
        toolbar.insert(radio_justifyleft, 4)

        radio_justifycenter = Gtk.RadioToolButton.new_with_stock_from_widget(
            radio_justifyleft, Gtk.STOCK_JUSTIFY_CENTER)
        toolbar.insert(radio_justifycenter, 5)

        radio_justifyright = Gtk.RadioToolButton.new_with_stock_from_widget(
            radio_justifyleft, Gtk.STOCK_JUSTIFY_RIGHT)
        toolbar.insert(radio_justifyright, 6)

        radio_justifyfill = Gtk.RadioToolButton.new_with_stock_from_widget(
            radio_justifyleft, Gtk.STOCK_JUSTIFY_FILL)
        toolbar.insert(radio_justifyfill, 7)

        radio_justifyleft.connect("toggled", self.on_justify_toggled,
            Gtk.Justification.LEFT)
        radio_justifycenter.connect("toggled", self.on_justify_toggled,
            Gtk.Justification.CENTER)
        radio_justifyright.connect("toggled", self.on_justify_toggled,
            Gtk.Justification.RIGHT)
        radio_justifyfill.connect("toggled", self.on_justify_toggled,
            Gtk.Justification.FILL)

        toolbar.insert(Gtk.SeparatorToolItem(), 8)

        button_clear = Gtk.ToolButton.new_from_stock(Gtk.STOCK_CLEAR)
        button_clear.connect("clicked", self.on_clear_clicked)
        toolbar.insert(button_clear, 9)

        toolbar.insert(Gtk.SeparatorToolItem(), 10)

        button_search = Gtk.ToolButton.new_from_stock(Gtk.STOCK_FIND)
        button_search.connect("clicked", self.on_search_clicked)
        toolbar.insert(button_search, 11)

    def create_textview(self):
        scrolledwindow = Gtk.ScrolledWindow()
        scrolledwindow.set_hexpand(True)
        scrolledwindow.set_vexpand(True)
        self.grid.attach(scrolledwindow, 0, 1, 3, 1)

        self.textview = Gtk.TextView()
        self.textbuffer = self.textview.get_buffer()
        self.textbuffer.set_text("This is some text inside of a Gtk.TextView. "
            + "Select text and click one of the buttons 'bold', 'italic', "
            + "or 'underline' to modify the text accordingly.")
        scrolledwindow.add(self.textview)

        self.tag_bold = self.textbuffer.create_tag("bold",
            weight=Pango.Weight.BOLD)
        self.tag_italic = self.textbuffer.create_tag("italic",
            style=Pango.Style.ITALIC)
        self.tag_underline = self.textbuffer.create_tag("underline",
            underline=Pango.Underline.SINGLE)
        self.tag_found = self.textbuffer.create_tag("found",
            background="yellow")

    def create_buttons(self):
        check_editable = Gtk.CheckButton("Editable")
        check_editable.set_active(True)
        check_editable.connect("toggled", self.on_editable_toggled)
        self.grid.attach(check_editable, 0, 2, 1, 1)

        check_cursor = Gtk.CheckButton("Cursor Visible")
        check_cursor.set_active(True)
        check_editable.connect("toggled", self.on_cursor_toggled)
        self.grid.attach_next_to(check_cursor, check_editable,
            Gtk.PositionType.RIGHT, 1, 1)

        radio_wrapnone = Gtk.RadioButton.new_with_label_from_widget(None,
            "No Wrapping")
        self.grid.attach(radio_wrapnone, 0, 3, 1, 1)

        radio_wrapchar = Gtk.RadioButton.new_with_label_from_widget(
            radio_wrapnone, "Character Wrapping")
        self.grid.attach_next_to(radio_wrapchar, radio_wrapnone,
            Gtk.PositionType.RIGHT, 1, 1)

        radio_wrapword = Gtk.RadioButton.new_with_label_from_widget(
            radio_wrapnone, "Word Wrapping")
        self.grid.attach_next_to(radio_wrapword, radio_wrapchar,
            Gtk.PositionType.RIGHT, 1, 1)

        radio_wrapnone.connect("toggled", self.on_wrap_toggled,
            Gtk.WrapMode.NONE)
        radio_wrapchar.connect("toggled", self.on_wrap_toggled,
            Gtk.WrapMode.CHAR)
        radio_wrapword.connect("toggled", self.on_wrap_toggled,
            Gtk.WrapMode.WORD)

    def on_button_clicked(self, widget, tag):
        bounds = self.textbuffer.get_selection_bounds()
        if len(bounds) != 0:
            start, end = bounds
            self.textbuffer.apply_tag(tag, start, end)

    def on_clear_clicked(self, widget):
        start = self.textbuffer.get_start_iter()
        end = self.textbuffer.get_end_iter()
        self.textbuffer.remove_all_tags(start, end)

    def on_editable_toggled(self, widget):
        self.textview.set_editable(widget.get_active())

    def on_cursor_toggled(self, widget):
        self.textview.set_cursor_visible(widget.get_active())

    def on_wrap_toggled(self, widget, mode):
        self.textview.set_wrap_mode(mode)

    def on_justify_toggled(self, widget, justification):
        self.textview.set_justification(justification)

    def on_search_clicked(self, widget):
        dialog = SearchDialog(self)
        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            cursor_mark = self.textbuffer.get_insert()
            start = self.textbuffer.get_iter_at_mark(cursor_mark)
            if start.get_offset() == self.textbuffer.get_char_count():
                start = self.textbuffer.get_start_iter()

            self.search_and_mark(dialog.entry.get_text(), start)

        dialog.destroy()

    def search_and_mark(self, text, start):
        end = self.textbuffer.get_end_iter()
        match = start.forward_search(text, 0, end)

        if match != None:
            match_start, match_end = match
            self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
            self.search_and_mark(text, match_end)

win = TextViewWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

Project Versions

Table Of Contents

Previous topic

14. IconView

Next topic

16. Menus

This Page