17. Editor de Texto Multilinha
O widget Gtk.TextView
pode ser usado para exibir e editar grandes quantidades de texto formatado. Como o Gtk.TreeView
, ele possui um design de modelo/visualização. Neste caso, o Gtk.TextBuffer
é o modelo que representa o texto que está sendo editado. Isto permite que dois ou mais widgets Gtk.TextView
compartilhem o mesmo Gtk.TextBuffer
, e permite que os buffers de texto sejam exibidos de forma ligeiramente diferente. Ou você pode manter vários buffers de texto e optar por exibir cada um deles em momentos diferentes no mesmo widget Gtk.TextView
.
17.1. A visão
O Gtk.TextView
é o frontend com o qual o usuário pode adicionar, editar e excluir dados textuais. Eles são comumente usados para editar várias linhas de texto. Ao criar um Gtk.TextView
ele contém seu próprio padrão Gtk.TextBuffer
, que você pode acessar através do método Gtk.TextView.get_buffer()
.
Por padrão, o texto pode ser adicionado, editado e removido da Gtk.TextView
. Você pode desabilitar isso chamando Gtk.TextView.set_editable()
. Se o texto não for editável, você geralmente deseja ocultar o cursor de texto com Gtk.TextView.set_cursor_visible()
também. Em alguns casos, pode ser útil definir a justificação do texto com Gtk.TextView.set_justification()
. O texto pode ser exibido na borda da esquerda, (Gtk.Justification.LEFT
), na borda da direita (Gtk.Justification.RIGHT
), centralizado (Gtk.Justification.CENTER
) ou distribuído em toda a largura (Gtk.Justification.FILL
).
Outra configuração padrão do widget Gtk.TextView
é que linhas longas de texto continuarão horizontalmente até que uma quebra seja inserida. Para encapsular o texto e impedir que ele saia das bordas da tela, chame Gtk.TextView.set_wrap_mode()
.
17.2. O modelo
O Gtk.TextBuffer
é o núcleo do widget Gtk.TextView
e é usado para armazenar qualquer texto que esteja sendo exibido na Gtk.TextView
. Definir e recuperar o conteúdo é possível com Gtk.TextBuffer.set_text()
e Gtk.TextBuffer.get_text()
. No entanto, a maior parte da manipulação de texto é realizada com iteradores, representados por um Gtk.TextIter
. Um iterador representa uma posição entre dois caracteres no buffer de texto. Iteradores não são válidos indefinidamente; sempre que o buffer é modificado de uma maneira que afeta o conteúdo do buffer, todos os iteradores pendentes se tornam inválidos.
Por causa disso, os iteradores não podem ser usados para preservar posições nas modificações do buffer. Para preservar uma posição, use Gtk.TextMark
. Um buffer de texto contém duas marcas internas; uma marca “insert” (que é a posição do cursor) e a marca “selection_bound”. Ambos podem ser recuperados usando Gtk.TextBuffer.get_insert()
e Gtk.TextBuffer.get_selection_bound()
, respectivamente. Por padrão, a localização de um Gtk.TextMark
não é mostrada. Isso pode ser alterado chamando Gtk.TextMark.set_visible()
.
Existem muitos métodos para recuperar um Gtk.TextIter
. Por exemplo, Gtk.TextBuffer.get_start_iter()
retorna um iterador apontando para a primeira posição no buffer de texto, enquanto Gtk.TextBuffer.get_end_iter()
retorna um iterador apontando após o último caractere válido. A recuperação dos limites do texto selecionado pode ser obtida chamando Gtk.TextBuffer.get_selection_bounds()
.
Para inserir texto em uma posição específica use Gtk.TextBuffer.insert()
. Outro método útil é Gtk.TextBuffer.insert_at_cursor()
que insere texto onde quer que o cursor esteja posicionado no momento. Para remover partes do buffer de texto, use Gtk.TextBuffer.delete()
.
Além disso, Gtk.TextIter
pode ser usado para localizar correspondências textuais no buffer usando Gtk.TextIter.forward_search()
e Gtk.TextIter.backward_search()
. Os iters inicial e final são usados como ponto de partida da pesquisa e avançam/retrocedem dependendo dos requisitos.
17.4. Exemplo
1import gi
2
3gi.require_version("Gtk", "3.0")
4from gi.repository import Gtk, Pango
5
6
7class SearchDialog(Gtk.Dialog):
8 def __init__(self, parent):
9 super().__init__(title="Search", transient_for=parent, modal=True)
10 self.add_buttons(
11 Gtk.STOCK_FIND,
12 Gtk.ResponseType.OK,
13 Gtk.STOCK_CANCEL,
14 Gtk.ResponseType.CANCEL,
15 )
16
17 box = self.get_content_area()
18
19 label = Gtk.Label(label="Insert text you want to search for:")
20 box.add(label)
21
22 self.entry = Gtk.Entry()
23 box.add(self.entry)
24
25 self.show_all()
26
27
28class TextViewWindow(Gtk.Window):
29 def __init__(self):
30 Gtk.Window.__init__(self, title="TextView Example")
31
32 self.set_default_size(-1, 350)
33
34 self.grid = Gtk.Grid()
35 self.add(self.grid)
36
37 self.create_textview()
38 self.create_toolbar()
39 self.create_buttons()
40
41 def create_toolbar(self):
42 toolbar = Gtk.Toolbar()
43 self.grid.attach(toolbar, 0, 0, 3, 1)
44
45 button_bold = Gtk.ToolButton()
46 button_bold.set_icon_name("format-text-bold-symbolic")
47 toolbar.insert(button_bold, 0)
48
49 button_italic = Gtk.ToolButton()
50 button_italic.set_icon_name("format-text-italic-symbolic")
51 toolbar.insert(button_italic, 1)
52
53 button_underline = Gtk.ToolButton()
54 button_underline.set_icon_name("format-text-underline-symbolic")
55 toolbar.insert(button_underline, 2)
56
57 button_bold.connect("clicked", self.on_button_clicked, self.tag_bold)
58 button_italic.connect("clicked", self.on_button_clicked, self.tag_italic)
59 button_underline.connect("clicked", self.on_button_clicked, self.tag_underline)
60
61 toolbar.insert(Gtk.SeparatorToolItem(), 3)
62
63 radio_justifyleft = Gtk.RadioToolButton()
64 radio_justifyleft.set_icon_name("format-justify-left-symbolic")
65 toolbar.insert(radio_justifyleft, 4)
66
67 radio_justifycenter = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
68 radio_justifycenter.set_icon_name("format-justify-center-symbolic")
69 toolbar.insert(radio_justifycenter, 5)
70
71 radio_justifyright = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
72 radio_justifyright.set_icon_name("format-justify-right-symbolic")
73 toolbar.insert(radio_justifyright, 6)
74
75 radio_justifyfill = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
76 radio_justifyfill.set_icon_name("format-justify-fill-symbolic")
77 toolbar.insert(radio_justifyfill, 7)
78
79 radio_justifyleft.connect(
80 "toggled", self.on_justify_toggled, Gtk.Justification.LEFT
81 )
82 radio_justifycenter.connect(
83 "toggled", self.on_justify_toggled, Gtk.Justification.CENTER
84 )
85 radio_justifyright.connect(
86 "toggled", self.on_justify_toggled, Gtk.Justification.RIGHT
87 )
88 radio_justifyfill.connect(
89 "toggled", self.on_justify_toggled, Gtk.Justification.FILL
90 )
91
92 toolbar.insert(Gtk.SeparatorToolItem(), 8)
93
94 button_clear = Gtk.ToolButton()
95 button_clear.set_icon_name("edit-clear-symbolic")
96 button_clear.connect("clicked", self.on_clear_clicked)
97 toolbar.insert(button_clear, 9)
98
99 toolbar.insert(Gtk.SeparatorToolItem(), 10)
100
101 button_search = Gtk.ToolButton()
102 button_search.set_icon_name("system-search-symbolic")
103 button_search.connect("clicked", self.on_search_clicked)
104 toolbar.insert(button_search, 11)
105
106 def create_textview(self):
107 scrolledwindow = Gtk.ScrolledWindow()
108 scrolledwindow.set_hexpand(True)
109 scrolledwindow.set_vexpand(True)
110 self.grid.attach(scrolledwindow, 0, 1, 3, 1)
111
112 self.textview = Gtk.TextView()
113 self.textbuffer = self.textview.get_buffer()
114 self.textbuffer.set_text(
115 "This is some text inside of a Gtk.TextView. "
116 + "Select text and click one of the buttons 'bold', 'italic', "
117 + "or 'underline' to modify the text accordingly."
118 )
119 scrolledwindow.add(self.textview)
120
121 self.tag_bold = self.textbuffer.create_tag("bold", weight=Pango.Weight.BOLD)
122 self.tag_italic = self.textbuffer.create_tag("italic", style=Pango.Style.ITALIC)
123 self.tag_underline = self.textbuffer.create_tag(
124 "underline", underline=Pango.Underline.SINGLE
125 )
126 self.tag_found = self.textbuffer.create_tag("found", background="yellow")
127
128 def create_buttons(self):
129 check_editable = Gtk.CheckButton(label="Editable")
130 check_editable.set_active(True)
131 check_editable.connect("toggled", self.on_editable_toggled)
132 self.grid.attach(check_editable, 0, 2, 1, 1)
133
134 check_cursor = Gtk.CheckButton(label="Cursor Visible")
135 check_cursor.set_active(True)
136 check_editable.connect("toggled", self.on_cursor_toggled)
137 self.grid.attach_next_to(
138 check_cursor, check_editable, Gtk.PositionType.RIGHT, 1, 1
139 )
140
141 radio_wrapnone = Gtk.RadioButton.new_with_label_from_widget(None, "No Wrapping")
142 self.grid.attach(radio_wrapnone, 0, 3, 1, 1)
143
144 radio_wrapchar = Gtk.RadioButton.new_with_label_from_widget(
145 radio_wrapnone, "Character Wrapping"
146 )
147 self.grid.attach_next_to(
148 radio_wrapchar, radio_wrapnone, Gtk.PositionType.RIGHT, 1, 1
149 )
150
151 radio_wrapword = Gtk.RadioButton.new_with_label_from_widget(
152 radio_wrapnone, "Word Wrapping"
153 )
154 self.grid.attach_next_to(
155 radio_wrapword, radio_wrapchar, Gtk.PositionType.RIGHT, 1, 1
156 )
157
158 radio_wrapnone.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.NONE)
159 radio_wrapchar.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.CHAR)
160 radio_wrapword.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.WORD)
161
162 def on_button_clicked(self, widget, tag):
163 bounds = self.textbuffer.get_selection_bounds()
164 if len(bounds) != 0:
165 start, end = bounds
166 self.textbuffer.apply_tag(tag, start, end)
167
168 def on_clear_clicked(self, widget):
169 start = self.textbuffer.get_start_iter()
170 end = self.textbuffer.get_end_iter()
171 self.textbuffer.remove_all_tags(start, end)
172
173 def on_editable_toggled(self, widget):
174 self.textview.set_editable(widget.get_active())
175
176 def on_cursor_toggled(self, widget):
177 self.textview.set_cursor_visible(widget.get_active())
178
179 def on_wrap_toggled(self, widget, mode):
180 self.textview.set_wrap_mode(mode)
181
182 def on_justify_toggled(self, widget, justification):
183 self.textview.set_justification(justification)
184
185 def on_search_clicked(self, widget):
186 dialog = SearchDialog(self)
187 response = dialog.run()
188 if response == Gtk.ResponseType.OK:
189 cursor_mark = self.textbuffer.get_insert()
190 start = self.textbuffer.get_iter_at_mark(cursor_mark)
191 if start.get_offset() == self.textbuffer.get_char_count():
192 start = self.textbuffer.get_start_iter()
193
194 self.search_and_mark(dialog.entry.get_text(), start)
195
196 dialog.destroy()
197
198 def search_and_mark(self, text, start):
199 end = self.textbuffer.get_end_iter()
200 match = start.forward_search(text, 0, end)
201
202 if match is not None:
203 match_start, match_end = match
204 self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
205 self.search_and_mark(text, match_end)
206
207
208win = TextViewWindow()
209win.connect("destroy", Gtk.main_quit)
210win.show_all()
211Gtk.main()