Text View - PyGObject

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
Toggle table of contents sidebar

# Text View

The `Gtk.TextView` widget can be used to display and edit large amounts of formatted text. Like the `Gtk.ListView`, 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.

See also

Text Widget Overview in the GTK documentation.

## 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 is kept in the `Gtk.TextView.props.buffer` property.

By default, text can be added, edited and removed from the `Gtk.TextView`. You can disable this by changing `Gtk.TextView.props.editable`. If the text is not editable, you usually want to hide the text cursor with `Gtk.TextView.props.cursor_visible` as well. In some cases it may be useful to set the justification of the text with `Gtk.TextView.props.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 set `Gtk.TextView.props.wrap_mode` similar to Label.

## 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.props.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.

## 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 behavior 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 (“background” property)

- Foreground colour (“foreground” 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()`.

## Example

      1import gi
      2
      3gi.require_version('Gtk', '4.0')
      4from gi.repository import Gtk, Pango
      5
      6
      7class SearchDialog(Gtk.Window):
      8    def __init__(self, parent):
      9        super().__init__(title='Search', transient_for=parent)
     10
     11        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
     12        self.set_child(box)
     13
     14        label = Gtk.Label(label='Insert text you want to search for:')
     15        box.append(label)
     16
     17        self.entry = Gtk.Entry()
     18        box.append(self.entry)
     19
     20        self.button = Gtk.Button(label='Find')
     21        box.append(self.button)
     22
     23
     24class TextViewWindow(Gtk.ApplicationWindow):
     25    def __init__(self, **kargs):
     26        super().__init__(**kargs, title='TextView Demo')
     27
     28        self.set_default_size(500, 400)
     29
     30        self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
     31        self.set_child(self.box)
     32
     33        self.create_textview()
     34        self.create_toolbar()
     35        self.create_buttons()
     36
     37    def create_toolbar(self):
     38        toolbar = Gtk.Box(spacing=6)
     39        toolbar.props.margin_top = 6
     40        toolbar.props.margin_start = 6
     41        toolbar.props.margin_end = 6
     42        self.box.prepend(toolbar)
     43
     44        button_bold = Gtk.Button(icon_name='format-text-bold-symbolic')
     45        toolbar.append(button_bold)
     46
     47        button_italic = Gtk.Button(icon_name='format-text-italic-symbolic')
     48        toolbar.append(button_italic)
     49
     50        button_underline = Gtk.Button(
     51            icon_name='format-text-underline-symbolic'
     52        )
     53        toolbar.append(button_underline)
     54
     55        button_bold.connect('clicked', self.on_button_clicked, self.tag_bold)
     56        button_italic.connect(
     57            'clicked', self.on_button_clicked, self.tag_italic
     58        )
     59        button_underline.connect(
     60            'clicked', self.on_button_clicked, self.tag_underline
     61        )
     62
     63        toolbar.append(Gtk.Separator())
     64
     65        justifyleft = Gtk.ToggleButton(
     66            icon_name='format-justify-left-symbolic'
     67        )
     68        toolbar.append(justifyleft)
     69
     70        justifycenter = Gtk.ToggleButton(
     71            icon_name='format-justify-center-symbolic'
     72        )
     73        justifycenter.set_group(justifyleft)
     74        toolbar.append(justifycenter)
     75
     76        justifyright = Gtk.ToggleButton(
     77            icon_name='format-justify-right-symbolic'
     78        )
     79        justifyright.set_group(justifyleft)
     80        toolbar.append(justifyright)
     81
     82        justifyfill = Gtk.ToggleButton(
     83            icon_name='format-justify-fill-symbolic'
     84        )
     85        justifyfill.set_group(justifyleft)
     86        toolbar.append(justifyfill)
     87
     88        justifyleft.connect(
     89            'toggled', self.on_justify_toggled, Gtk.Justification.LEFT
     90        )
     91        justifycenter.connect(
     92            'toggled', self.on_justify_toggled, Gtk.Justification.CENTER
     93        )
     94        justifyright.connect(
     95            'toggled', self.on_justify_toggled, Gtk.Justification.RIGHT
     96        )
     97        justifyfill.connect(
     98            'toggled', self.on_justify_toggled, Gtk.Justification.FILL
     99        )
    100
    101        toolbar.append(Gtk.Separator())
    102
    103        button_clear = Gtk.Button(icon_name='edit-clear-symbolic')
    104        button_clear.connect('clicked', self.on_clear_clicked)
    105        toolbar.append(button_clear)
    106
    107        toolbar.append(Gtk.Separator())
    108
    109        button_search = Gtk.Button(icon_name='system-search-symbolic')
    110        button_search.connect('clicked', self.on_search_clicked)
    111        toolbar.append(button_search)
    112
    113    def create_textview(self):
    114        scrolledwindow = Gtk.ScrolledWindow()
    115        scrolledwindow.props.hexpand = True
    116        scrolledwindow.props.vexpand = True
    117        self.box.append(scrolledwindow)
    118
    119        self.textview = Gtk.TextView()
    120        self.textbuffer = self.textview.get_buffer()
    121        self.textbuffer.set_text(
    122            'This is some text inside of a Gtk.TextView. ' +
    123            'Select text and click one of the buttons "bold", "italic", ' +
    124            'or "underline" to modify the text accordingly.'
    125        )
    126        scrolledwindow.set_child(self.textview)
    127
    128        self.tag_bold = self.textbuffer.create_tag(
    129            'bold', weight=Pango.Weight.BOLD
    130        )
    131        self.tag_italic = self.textbuffer.create_tag(
    132            'italic', style=Pango.Style.ITALIC
    133        )
    134        self.tag_underline = self.textbuffer.create_tag(
    135            'underline', underline=Pango.Underline.SINGLE
    136        )
    137        self.tag_found = self.textbuffer.create_tag(
    138            'found', background='yellow'
    139        )
    140
    141    def create_buttons(self):
    142        grid = Gtk.Grid()
    143        self.box.append(grid)
    144
    145        check_editable = Gtk.CheckButton(label='Editable')
    146        check_editable.props.active = True
    147        check_editable.connect('toggled', self.on_editable_toggled)
    148        grid.attach(check_editable, 0, 0, 1, 1)
    149
    150        check_cursor = Gtk.CheckButton(label='Cursor Visible')
    151        check_cursor.props.active = True
    152        check_editable.connect('toggled', self.on_cursor_toggled)
    153        grid.attach_next_to(
    154            check_cursor, check_editable, Gtk.PositionType.RIGHT, 1, 1
    155        )
    156
    157        radio_wrapnone = Gtk.CheckButton(label='No Wrapping')
    158        radio_wrapnone.props.active = True
    159        grid.attach(radio_wrapnone, 0, 1, 1, 1)
    160
    161        radio_wrapchar = Gtk.CheckButton(label='Character Wrapping')
    162        radio_wrapchar.set_group(radio_wrapnone)
    163        grid.attach_next_to(
    164            radio_wrapchar, radio_wrapnone, Gtk.PositionType.RIGHT, 1, 1
    165        )
    166
    167        radio_wrapword = Gtk.CheckButton(label='Word Wrapping')
    168        radio_wrapword.set_group(radio_wrapnone)
    169        grid.attach_next_to(
    170            radio_wrapword, radio_wrapchar, Gtk.PositionType.RIGHT, 1, 1
    171        )
    172
    173        radio_wrapnone.connect(
    174            'toggled', self.on_wrap_toggled, Gtk.WrapMode.NONE
    175        )
    176        radio_wrapchar.connect(
    177            'toggled', self.on_wrap_toggled, Gtk.WrapMode.CHAR
    178        )
    179        radio_wrapword.connect(
    180            'toggled', self.on_wrap_toggled, Gtk.WrapMode.WORD
    181        )
    182
    183    def on_button_clicked(self, _widget, tag):
    184        bounds = self.textbuffer.get_selection_bounds()
    185        if len(bounds) != 0:
    186            start, end = bounds
    187            self.textbuffer.apply_tag(tag, start, end)
    188
    189    def on_clear_clicked(self, _widget):
    190        start = self.textbuffer.get_start_iter()
    191        end = self.textbuffer.get_end_iter()
    192        self.textbuffer.remove_all_tags(start, end)
    193
    194    def on_editable_toggled(self, widget):
    195        self.textview.props.editable = widget.props.active
    196
    197    def on_cursor_toggled(self, widget):
    198        self.textview.props.cursor_visible = widget.props.active
    199
    200    def on_wrap_toggled(self, _widget, mode):
    201        self.textview.props.wrap_mode = mode
    202
    203    def on_justify_toggled(self, _widget, justification):
    204        self.textview.props.justification = justification
    205
    206    def on_search_clicked(self, _widget):
    207        self.search_dialog = SearchDialog(self)
    208        self.search_dialog.button.connect('clicked', self.on_find_clicked)
    209        self.search_dialog.present()
    210
    211    def on_find_clicked(self, _button):
    212        cursor_mark = self.textbuffer.get_insert()
    213        start = self.textbuffer.get_iter_at_mark(cursor_mark)
    214        if start.get_offset() == self.textbuffer.get_char_count():
    215            start = self.textbuffer.get_start_iter()
    216
    217        self.search_and_mark(self.search_dialog.entry.get_text(), start)
    218
    219    def search_and_mark(self, text, start):
    220        end = self.textbuffer.get_end_iter()
    221        match = start.forward_search(text, 0, end)
    222
    223        if match is not None:
    224            match_start, match_end = match
    225            self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
    226            self.search_and_mark(text, match_end)
    227
    228
    229def on_activate(app):
    230    win = TextViewWindow(application=app)
    231    win.present()
    232
    233
    234app = Gtk.Application(application_id='com.example.App')
    235app.connect('activate', on_activate)
    236
    237app.run(None)