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.
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)