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¶
![../../_images/textview.png](../../_images/textview.png)
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)