Multiline Text Editor

Gtk.TextView 控件可以用来显示和编辑大量的格式化的文本。与 Gtk.TreeView 类似,其也有一个模型/视图的设计, Gtk.TextBuffer 就是代表正在被编辑的文本 的模型。模型也允许两个或更多个的 Gtk.TextView 共享同一个 Gtk.TextBuffer , 并允许这些文本有不同的显示。或者你也可以在同一个 Gtk.TextView 的不同时间显示 不同 text buffer 的文本。

视图(The View)

Gtk.TextView 就是用户可以添加、编辑或者删除文本的前端,其通常用来编辑 多行的文本。当创建一个 Gtk.TextView 时其自动包含一个默认的 Gtk.TextBuffer , 你可以通过 Gtk.TextView.get_buffer() 来获取。

默认情况下,文本可以在 Gtk.TextView 中被添加、编辑与删除,你可以通过调用 Gtk.TextView.set_editable() 来禁止编辑。如果文本不可被编辑,你也通常想要使用 Gtk.TextView.set_cursor_visible() 来隐藏文本光标。在有些时候可以通过 Gtk.TextView.set_justification() 来设置文本的对齐方式很有用,文本可以显示为 左对齐(Gtk.Justification.LEFT),右对齐(attr:Gtk.Justification.RIGHT),或者 中间对齐(attr:Gtk.Justification.CENTER),或者占满整行(attr:Gtk.Justification.FILL)。

Gtk.TextView 的另一项默认设置是长行会一直水平延伸知道遇到一个断行。调用 Gtk.TextView.set_wrap_mode() 可以使文本自动换行一避免文本跑出屏幕边缘。

TextView 对象

class Gtk.TextView

创建一个新的 Gtk.TextView 并关联一个空的 Gtk.TextBuffer

get_buffer()

返回 text view 正在显示的 Gtk.TextBuffer

set_editable(editable)

设置 Gtk.TextView 的文本是否可编辑。

set_cursor_visible(visible)

设置现在光标的位置是否显示。不可编辑文本不应该有一个可见的光标,因此你 可能会关闭光标的显示。

set_justification(justification)

设置文本的额默认对齐方式。

justification 的值可以是下列之一:

  • Gtk.Justification.LEFT: 文本左对齐。
  • Gtk.Justification.RIGHT: 文本右对齐。
  • Gtk.Justification.CENTER: 文本居中对齐。
  • Gtk.Justification.FILL: 文本占满整行。
set_wrap_mode(wrap_mode)

设置view是否自动断行。

wrap_mode 的值可以是下列之一:

  • Gtk.WrapMode.NONE: 不自动断行,文本域会一直随着文本变宽。
  • Gtk.WrapMode.CHAR: 自动断行,可以光标可以出现的地方断行(通常字符之间)。
  • Gtk.WrapMode.WORD: 自动断行,可以在单词之间断行。
  • Gtk.WrapMode.WORD_CHAR: 自动断行,可以在单词之间断行,但如果 空间不够时Wrap text, 也可以在 graphemes 出断行。

模型(The Model)

Gtk.TextBufferGtk.TextView 控件的核心,用来保存 Gtk.TextView 显示的文本。设置和获取文本内容可以他用过 Gtk.TextBuffer.set_text()Gtk.TextBuffer.get_text 。 但是绝大多数的文本操作是通过 iterators 来完成的,即 Gtk.TextIter 。 iterator迭代器代表了文本buffer中两个字符之间的一个位置。迭代器并不是一直有效的, 一旦文本内容被修改并影响了buffer的内容,所有的迭代去就都无效了。

正因为此,迭代器不能用来在buffer修改前后保留位置。要保存一个位置,使用 Gtk.TextMark 。 一个text buffer包含两个内建的标记,”insert” 标记光标的位置,”selection_bound” 标记, 可以通过 Gtk.TextBuffer.get_insert()Gtk.TextBuffer.get_selection_bound() 来获得他们。默认 Gtk.TextMark 的位置是不显示的,可以通过 Gtk.TextMark.set_visible() 设置。

有很多方法可以获取 Gtk.TextIter 。例如, Gtk.TextBuffer.get_start_iter() 返回只想text buffer地一个位置的迭代器,而 Gtk.TextBuffer.get_end_iter() 则返回 最后一个有效字符处的迭代器。获取选中文本的边界可以通过 Gtk.TextBuffer.get_selection_bounds()

要在一个指定位置插入文本请使用 Gtk.TextBuffer.insert() 。另一个非常有用的方法 Gtk.TextBuffer.insert_at_cursor() 可以在光标指向的位置插入文本。要删除一部分 文本请使用 Gtk.TextBuffer.delete()

另外,Gtk.TextIter 可以通过 Gtk.TextIter.forward_search()Gtk.TextIter.backward_search() 用来搜索文本。根据需求可以使用buffer开始和结束 的iters来进行向前/后的搜索。

TextBuffer 对象

class Gtk.TextBuffer
set_text(text[, length])

删除buffer当前的内容,并插入 lengthtext 中的字符。 如果 length 为-1或忽略, text 全部被插入。

get_text(start_iter, end_iter, include_hidden_chars)

返回从 start_iter (包含)和 end_iter (不含)之间的文本,如果 include_hidden_charsFalse ,则不包含未显示的文本。

get_insert()

返回代表当前光标位置(插入点)的 Gtk.TextMark

get_selection_bound()

返回代表选中区域边界的 Gtk.TextMark

create_mark(mark_name, where[, left_gravity])

Gtk.TextIter where 处创建一个 Gtk.TextMark 。 如果 mark_nameNone ,则mark是匿名的。如果一个标记有left_gravity, 文本被插入到当前位置后,标记会移动到新插入文本的左边。如果标记为right_gravity (left_gravityFalse),标记会移动到新插入文本的右边。因此标准的 从左到右的光标为right gravity的标记(当你输入的时候,光标会出现在你输入文本的右边)。

如果 left_gravity 被忽略,默认为 False

get_mark(mark_name)

返回buffer中名字为mark_name的 :class`Gtk.TextMark` ,如果不存在则返回 None

get_start_iter()

返回指向buffer地一个位置的 Gtk.TextIter

get_end_iter()

返回只想buffer最后一个有效字符的 Gtk.TextIter

get_selection_bounds()

返回包含两个 Gtk.TextIter tuple对象,分别指向选中的第一个字符和 后不第一个未选中的字符。如果没有文本被选中则返回空的tuple。

insert(text_iter, text[, length])

text_iter 处插入 textlength 个字符。如果 length 为-1或忽略, 全部的 text 会被插入。

insert_at_cursor(text[, length])

insert() 的简单调用,使用光标位置作为插入点。

delete(start_iter, end_iter)

删除 start_iterend_iter 之间的文本。

create_tag(tag_name, **kwargs)

创建一个tag并添加到buffer的tag表中。

如果 tag_nameNone ,则tag是匿名的,否则tag_name不能与tag表中 已经存在的tag重名。

kwargs 为任意数量的键值对,代表了tag的属性列表,可以通过 tag.set_property 来设置。

apply_tag(tag, start_iter, end_iter)

应用 tag 到给定范围的文本。

remove_tag(tag, start_iter, end_iter)

删除给定范围内所有的 tag

remove_all_tags(start_iter, end_iter)

删除给定范围内所有的tag。

class Gtk.TextIter

向前搜索 needle 。搜索在达到limit后不会再继续。

flags 可以为下列之一,或者他们的组合(通过或操作符 | )。

  • 0: 精确匹配
  • Gtk.TextSearchFlags.VISIBLE_ONLY: 匹配可能在 needle 中间穿插 有不可见字符,即 needle 为匹配到的字符串的可能不连续的子序列。
  • Gtk.TextSearchFlags.TEXT_ONLY: 匹配可能包含图像pixbuf或者子空间 混合在匹配到的范围内。
  • Gtk.TextSearchFlags.CASE_INSENSITIVE: 匹配忽略大小写。

返回包含指向开始与匹配到的文本后边的 Gtk.TextIter 的元组。如果 没有找到,则返回 None

forward_search() 相同,但是向后搜索。

class Gtk.TextMark
set_visible(visible)

设置标记的可见性。插入点通常是可见的,即你可以看到一个竖直的光标条;而且, text控件也会使用一个可见的标记来指示拖拽操作的释放点。绝大多数其他的比较 是不可见的。标记默认是不可见的。

Tags

buffer内的文本可以通过tag来标记。tag就是一个可以应用到一个文本范围的属性。例如, “bold” tag使应用到文本加粗。然而tag的概念比其更多,tag不一定会影响外观,也可能会 影响鼠标和按键的行为,”lock” 可以锁定一段文本使用户不能编辑,或者数不尽的其他的事情。 tag由 Gtk.TextTag 对象代表。一个 Gtk.TextTag 可以应用到任何数量 的文本范围,任何数量的buffer。

所有的tag都存储在 Gtk.TextTagTable 中。一个tab表定义了一系列的tag并可以一起使用。 每一个buffer都有一个与之关联的tag表,只有表中的tag才可以应用到buffer。但一个tag表可以 在多个buffer间共享。

要指定buffer内的一部分为本应该有特定的格式,你必须先定义该格式的tag,然后使用 Gtk.TextBuffer.create_tag()Gtk.TextBuffer.apply_tag() 来将tag应用到文本区域。

tag = textbuffer.create_tag("orange_bg", background="orange")
textbuffer.apply_tag(tag, start_iter, end_iter)

以下列出一些应用到文本的通常使用的风格:

  • 背景颜色(“foreground” property)
  • 前景颜色(“background” property)
  • 下划线(“underline” property)
  • 粗体(“weight” property)
  • 斜提(“style” property)
  • 删除线(“strikethrough” property)
  • 对齐(“justification” property)
  • 大小(“size” and “size-points” properties)
  • 自动换行(“wrap-mode” property)

你也可以使用 Gtk.TextBuffer.remove_tag() 删除某个特定的tag,或者使用 Gtk.TextBuffer.remove_all_tags() 删除给的区域所有的tag。

Example

../../_images/textview_example.png
  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
from gi.repository import Gtk, Pango

class SearchDialog(Gtk.Dialog):

    def __init__(self, parent):
        Gtk.Dialog.__init__(self, 'Search', parent,
                Gtk.DialogFlags.MODAL, buttons=(
                    Gtk.STOCK_FIND, Gtk.ResponseType.OK,
                    Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))

        box = self.get_content_area()

        label = Gtk.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 Demo')

        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.new_from_stock(Gtk.STOCK_BOLD)
        toolbar.insert(button_bold, 0)

        button_italic = Gtk.ToolButton.new_from_stock(Gtk.STOCK_ITALIC)
        toolbar.insert(button_italic, 1)

        button_underline = Gtk.ToolButton.new_from_stock(Gtk.STOCK_UNDERLINE)
        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_stock_id(Gtk.STOCK_JUSTIFY_LEFT)
        toolbar.insert(radio_justifyleft, 4)

        radio_justifycenter = Gtk.RadioToolButton.new_with_stock_from_widget(
                radio_justifyleft, Gtk.STOCK_JUSTIFY_CENTER)
        toolbar.insert(radio_justifycenter, 5)

        radio_justifyright = Gtk.RadioToolButton.new_with_stock_from_widget(
                radio_justifyleft, Gtk.STOCK_JUSTIFY_RIGHT)
        toolbar.insert(radio_justifyright, 6)

        radio_justifyfill = Gtk.RadioToolButton.new_with_stock_from_widget(
                radio_justifyleft, Gtk.STOCK_JUSTIFY_FILL)
        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.new_from_stock(Gtk.STOCK_CLEAR)
        button_clear.connect('clicked', self.on_clear_clicked)
        toolbar.insert(button_clear, 9)

        toolbar.insert(Gtk.SeparatorToolItem(), 10)

        button_search = Gtk.ToolButton.new_from_stock(Gtk.STOCK_FIND)
        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 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('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('Cursor Visible')
        check_cursor.set_active(True)
        check_cursor.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 != 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('delete-event', Gtk.main_quit)
win.show_all()
Gtk.main()