Text View — PyGObject

Skip to main content
`Ctrl`+`K`

# 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(icon_name="format-text-underline-symbolic")
     51        toolbar.append(button_underline)
     52
     53        button_bold.connect("clicked", self.on_button_clicked, self.tag_bold)
     54        button_italic.connect("clicked", self.on_button_clicked, self.tag_italic)
     55        button_underline.connect("clicked", self.on_button_clicked, self.tag_underline)
     56
     57        toolbar.append(Gtk.Separator())
     58
     59        justifyleft = Gtk.ToggleButton(icon_name="format-justify-left-symbolic")
     60        toolbar.append(justifyleft)
     61
     62        justifycenter = Gtk.ToggleButton(icon_name="format-justify-center-symbolic")
     63        justifycenter.set_group(justifyleft)
     64        toolbar.append(justifycenter)
     65
     66        justifyright = Gtk.ToggleButton(icon_name="format-justify-right-symbolic")
     67        justifyright.set_group(justifyleft)
     68        toolbar.append(justifyright)
     69
     70        justifyfill = Gtk.ToggleButton(icon_name="format-justify-fill-symbolic")
     71        justifyfill.set_group(justifyleft)
     72        toolbar.append(justifyfill)
     73
     74        justifyleft.connect("toggled", self.on_justify_toggled, Gtk.Justification.LEFT)
     75        justifycenter.connect(
     76            "toggled", self.on_justify_toggled, Gtk.Justification.CENTER
     77        )
     78        justifyright.connect(
     79            "toggled", self.on_justify_toggled, Gtk.Justification.RIGHT
     80        )
     81        justifyfill.connect("toggled", self.on_justify_toggled, Gtk.Justification.FILL)
     82
     83        toolbar.append(Gtk.Separator())
     84
     85        button_clear = Gtk.Button(icon_name="edit-clear-symbolic")
     86        button_clear.connect("clicked", self.on_clear_clicked)
     87        toolbar.append(button_clear)
     88
     89        toolbar.append(Gtk.Separator())
     90
     91        button_search = Gtk.Button(icon_name="system-search-symbolic")
     92        button_search.connect("clicked", self.on_search_clicked)
     93        toolbar.append(button_search)
     94
     95    def create_textview(self):
     96        scrolledwindow = Gtk.ScrolledWindow()
     97        scrolledwindow.props.hexpand = True
     98        scrolledwindow.props.vexpand = True
     99        self.box.append(scrolledwindow)
    100
    101        self.textview = Gtk.TextView()
    102        self.textbuffer = self.textview.get_buffer()
    103        self.textbuffer.set_text(
    104            "This is some text inside of a Gtk.TextView. "
    105            + 'Select text and click one of the buttons "bold", "italic", '
    106            + 'or "underline" to modify the text accordingly.'
    107        )
    108        scrolledwindow.set_child(self.textview)
    109
    110        self.tag_bold = self.textbuffer.create_tag("bold", weight=Pango.Weight.BOLD)
    111        self.tag_italic = self.textbuffer.create_tag("italic", style=Pango.Style.ITALIC)
    112        self.tag_underline = self.textbuffer.create_tag(
    113            "underline", underline=Pango.Underline.SINGLE
    114        )
    115        self.tag_found = self.textbuffer.create_tag("found", background="yellow")
    116
    117    def create_buttons(self):
    118        grid = Gtk.Grid()
    119        self.box.append(grid)
    120
    121        check_editable = Gtk.CheckButton(label="Editable")
    122        check_editable.props.active = True
    123        check_editable.connect("toggled", self.on_editable_toggled)
    124        grid.attach(check_editable, 0, 0, 1, 1)
    125
    126        check_cursor = Gtk.CheckButton(label="Cursor Visible")
    127        check_cursor.props.active = True
    128        check_editable.connect("toggled", self.on_cursor_toggled)
    129        grid.attach_next_to(check_cursor, check_editable, Gtk.PositionType.RIGHT, 1, 1)
    130
    131        radio_wrapnone = Gtk.CheckButton(label="No Wrapping")
    132        radio_wrapnone.props.active = True
    133        grid.attach(radio_wrapnone, 0, 1, 1, 1)
    134
    135        radio_wrapchar = Gtk.CheckButton(label="Character Wrapping")
    136        radio_wrapchar.set_group(radio_wrapnone)
    137        grid.attach_next_to(
    138            radio_wrapchar, radio_wrapnone, Gtk.PositionType.RIGHT, 1, 1
    139        )
    140
    141        radio_wrapword = Gtk.CheckButton(label="Word Wrapping")
    142        radio_wrapword.set_group(radio_wrapnone)
    143        grid.attach_next_to(
    144            radio_wrapword, radio_wrapchar, Gtk.PositionType.RIGHT, 1, 1
    145        )
    146
    147        radio_wrapnone.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.NONE)
    148        radio_wrapchar.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.CHAR)
    149        radio_wrapword.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.WORD)
    150
    151    def on_button_clicked(self, _widget, tag):
    152        bounds = self.textbuffer.get_selection_bounds()
    153        if len(bounds) != 0:
    154            start, end = bounds
    155            self.textbuffer.apply_tag(tag, start, end)
    156
    157    def on_clear_clicked(self, _widget):
    158        start = self.textbuffer.get_start_iter()
    159        end = self.textbuffer.get_end_iter()
    160        self.textbuffer.remove_all_tags(start, end)
    161
    162    def on_editable_toggled(self, widget):
    163        self.textview.props.editable = widget.props.active
    164
    165    def on_cursor_toggled(self, widget):
    166        self.textview.props.cursor_visible = widget.props.active
    167
    168    def on_wrap_toggled(self, _widget, mode):
    169        self.textview.props.wrap_mode = mode
    170
    171    def on_justify_toggled(self, _widget, justification):
    172        self.textview.props.justification = justification
    173
    174    def on_search_clicked(self, _widget):
    175        self.search_dialog = SearchDialog(self)
    176        self.search_dialog.button.connect("clicked", self.on_find_clicked)
    177        self.search_dialog.present()
    178
    179    def on_find_clicked(self, _button):
    180        cursor_mark = self.textbuffer.get_insert()
    181        start = self.textbuffer.get_iter_at_mark(cursor_mark)
    182        if start.get_offset() == self.textbuffer.get_char_count():
    183            start = self.textbuffer.get_start_iter()
    184
    185        self.search_and_mark(self.search_dialog.entry.get_text(), start)
    186
    187    def search_and_mark(self, text, start):
    188        end = self.textbuffer.get_end_iter()
    189        match = start.forward_search(text, 0, end)
    190
    191        if match is not None:
    192            match_start, match_end = match
    193            self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
    194            self.search_and_mark(text, match_end)
    195
    196
    197def on_activate(app):
    198    win = TextViewWindow(application=app)
    199    win.present()
    200
    201
    202app = Gtk.Application(application_id="com.example.App")
    203app.connect("activate", on_activate)
    204
    205app.run(None)