17. Multiline Text Editor¶
The Gtk.TextView
widget can be used to display and edit large amounts
of formatted text. Like the Gtk.TreeView
, 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.
17.1. 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 you can access via the Gtk.TextView.get_buffer()
method.
By default, text can be added, edited and removed from the Gtk.TextView
.
You can disable this by calling Gtk.TextView.set_editable()
.
If the text is not editable, you usually want to hide the text cursor with
Gtk.TextView.set_cursor_visible()
as well. In some cases it may be useful
to set the justification of the text with Gtk.TextView.set_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 call Gtk.TextView.set_wrap_mode()
.
17.2. 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.set_text()
and Gtk.TextBuffer.get_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.
17.3. 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 behaviour 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()
.
17.4. Example¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Pango
class SearchDialog(Gtk.Dialog):
def __init__(self, parent):
super().__init__(title="Search", transient_for=parent, modal=True)
self.add_buttons(
Gtk.STOCK_FIND,
Gtk.ResponseType.OK,
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
)
box = self.get_content_area()
label = Gtk.Label(label="Insert text you want to search for:")
box.add(label)
self.entry = Gtk.Entry()
box.add(self.entry)
self.show_all()
class TextViewWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="TextView Example")
self.set_default_size(-1, 350)
self.grid = Gtk.Grid()
self.add(self.grid)
self.create_textview()
self.create_toolbar()
self.create_buttons()
def create_toolbar(self):
toolbar = Gtk.Toolbar()
self.grid.attach(toolbar, 0, 0, 3, 1)
button_bold = Gtk.ToolButton()
button_bold.set_icon_name("format-text-bold-symbolic")
toolbar.insert(button_bold, 0)
button_italic = Gtk.ToolButton()
button_italic.set_icon_name("format-text-italic-symbolic")
toolbar.insert(button_italic, 1)
button_underline = Gtk.ToolButton()
button_underline.set_icon_name("format-text-underline-symbolic")
toolbar.insert(button_underline, 2)
button_bold.connect("clicked", self.on_button_clicked, self.tag_bold)
button_italic.connect("clicked", self.on_button_clicked, self.tag_italic)
button_underline.connect("clicked", self.on_button_clicked, self.tag_underline)
toolbar.insert(Gtk.SeparatorToolItem(), 3)
radio_justifyleft = Gtk.RadioToolButton()
radio_justifyleft.set_icon_name("format-justify-left-symbolic")
toolbar.insert(radio_justifyleft, 4)
radio_justifycenter = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
radio_justifycenter.set_icon_name("format-justify-center-symbolic")
toolbar.insert(radio_justifycenter, 5)
radio_justifyright = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
radio_justifyright.set_icon_name("format-justify-right-symbolic")
toolbar.insert(radio_justifyright, 6)
radio_justifyfill = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
radio_justifyfill.set_icon_name("format-justify-fill-symbolic")
toolbar.insert(radio_justifyfill, 7)
radio_justifyleft.connect(
"toggled", self.on_justify_toggled, Gtk.Justification.LEFT
)
radio_justifycenter.connect(
"toggled", self.on_justify_toggled, Gtk.Justification.CENTER
)
radio_justifyright.connect(
"toggled", self.on_justify_toggled, Gtk.Justification.RIGHT
)
radio_justifyfill.connect(
"toggled", self.on_justify_toggled, Gtk.Justification.FILL
)
toolbar.insert(Gtk.SeparatorToolItem(), 8)
button_clear = Gtk.ToolButton()
button_clear.set_icon_name("edit-clear-symbolic")
button_clear.connect("clicked", self.on_clear_clicked)
toolbar.insert(button_clear, 9)
toolbar.insert(Gtk.SeparatorToolItem(), 10)
button_search = Gtk.ToolButton()
button_search.set_icon_name("system-search-symbolic")
button_search.connect("clicked", self.on_search_clicked)
toolbar.insert(button_search, 11)
def create_textview(self):
scrolledwindow = Gtk.ScrolledWindow()
scrolledwindow.set_hexpand(True)
scrolledwindow.set_vexpand(True)
self.grid.attach(scrolledwindow, 0, 1, 3, 1)
self.textview = Gtk.TextView()
self.textbuffer = self.textview.get_buffer()
self.textbuffer.set_text(
"This is some text inside of a Gtk.TextView. "
+ "Select text and click one of the buttons 'bold', 'italic', "
+ "or 'underline' to modify the text accordingly."
)
scrolledwindow.add(self.textview)
self.tag_bold = self.textbuffer.create_tag("bold", weight=Pango.Weight.BOLD)
self.tag_italic = self.textbuffer.create_tag("italic", style=Pango.Style.ITALIC)
self.tag_underline = self.textbuffer.create_tag(
"underline", underline=Pango.Underline.SINGLE
)
self.tag_found = self.textbuffer.create_tag("found", background="yellow")
def create_buttons(self):
check_editable = Gtk.CheckButton(label="Editable")
check_editable.set_active(True)
check_editable.connect("toggled", self.on_editable_toggled)
self.grid.attach(check_editable, 0, 2, 1, 1)
check_cursor = Gtk.CheckButton(label="Cursor Visible")
check_cursor.set_active(True)
check_editable.connect("toggled", self.on_cursor_toggled)
self.grid.attach_next_to(
check_cursor, check_editable, Gtk.PositionType.RIGHT, 1, 1
)
radio_wrapnone = Gtk.RadioButton.new_with_label_from_widget(None, "No Wrapping")
self.grid.attach(radio_wrapnone, 0, 3, 1, 1)
radio_wrapchar = Gtk.RadioButton.new_with_label_from_widget(
radio_wrapnone, "Character Wrapping"
)
self.grid.attach_next_to(
radio_wrapchar, radio_wrapnone, Gtk.PositionType.RIGHT, 1, 1
)
radio_wrapword = Gtk.RadioButton.new_with_label_from_widget(
radio_wrapnone, "Word Wrapping"
)
self.grid.attach_next_to(
radio_wrapword, radio_wrapchar, Gtk.PositionType.RIGHT, 1, 1
)
radio_wrapnone.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.NONE)
radio_wrapchar.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.CHAR)
radio_wrapword.connect("toggled", self.on_wrap_toggled, Gtk.WrapMode.WORD)
def on_button_clicked(self, widget, tag):
bounds = self.textbuffer.get_selection_bounds()
if len(bounds) != 0:
start, end = bounds
self.textbuffer.apply_tag(tag, start, end)
def on_clear_clicked(self, widget):
start = self.textbuffer.get_start_iter()
end = self.textbuffer.get_end_iter()
self.textbuffer.remove_all_tags(start, end)
def on_editable_toggled(self, widget):
self.textview.set_editable(widget.get_active())
def on_cursor_toggled(self, widget):
self.textview.set_cursor_visible(widget.get_active())
def on_wrap_toggled(self, widget, mode):
self.textview.set_wrap_mode(mode)
def on_justify_toggled(self, widget, justification):
self.textview.set_justification(justification)
def on_search_clicked(self, widget):
dialog = SearchDialog(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
cursor_mark = self.textbuffer.get_insert()
start = self.textbuffer.get_iter_at_mark(cursor_mark)
if start.get_offset() == self.textbuffer.get_char_count():
start = self.textbuffer.get_start_iter()
self.search_and_mark(dialog.entry.get_text(), start)
dialog.destroy()
def search_and_mark(self, text, start):
end = self.textbuffer.get_end_iter()
match = start.forward_search(text, 0, end)
if match is not None:
match_start, match_end = match
self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
self.search_and_mark(text, match_end)
win = TextViewWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
|