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