2
# Copyright (C) 2010 Rick Spencer rick.spencer@canonical.com
3
#This program is free software: you can redistribute it and/or modify it
4
#under the terms of the GNU General Public License version 3, as published
5
#by the Free Software Foundation.
7
#This program is distributed in the hope that it will be useful, but
8
#WITHOUT ANY WARRANTY; without even the implied warranties of
9
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10
#PURPOSE. See the GNU General Public License for more details.
12
#You should have received a copy of the GNU General Public License along
13
#with this program. If not, see <http://www.gnu.org/licenses/>.
15
"""Module for the TextView widgth wich encapsulates management of TextBuffer
16
and TextIter for common functionality, such as cut, copy, paste, undo, redo,
17
and highlighting of text.
20
#create the TextEditor and set the text
22
editor.text = "Text to add to the editor"
24
#use cut, works the same for copy, paste, undo, and redo
25
def __handle_on_cut(self, widget, data=None):
28
#add string to highlight
29
self.editor.add_highlight("Ubuntu")
30
self.editor.add_highlight("Quickly")
33
self.editor.clear_highlight("Ubuntu")
34
self.editor.clear_all_highlight()
37
#Configure as a TextView
38
self.editor.set_wrap_mode(gtk.WRAP_CHAR)
40
#Access the gtk.TextBuffer if needed
41
buffer = self.editor.get_buffer()
44
A TextEditor is gtk.TextView
54
print "couldn't load depencies"
56
class TextEditor( gtk.TextView ):
57
"""TextEditor encapsulates management of TextBuffer and TextIter for
58
common functionality, such as cut, copy, paste, undo, redo, and
64
"""Create a TextEditor
68
gtk.TextView.__init__( self)
70
self._highlight_strings = []
71
found_tag = gtk.TextTag("highlight")
72
found_tag.set_property("background","yellow")
73
self.get_buffer().get_tag_table().add(found_tag)
75
self.insert_event = self.get_buffer().connect("insert-text",self._on_insert)
76
self.delete_event = self.get_buffer().connect("delete-range",self._on_delete)
77
self.change_event = self.get_buffer().connect("changed",self._on_text_changed)
78
self._auto_bullet = None
79
self.auto_bullets = False
80
self.clipboard = gtk.Clipboard()
87
"""text - a string specifying all the text currently
88
in the TextEditor's buffer.
90
This property is read/write.
93
start_iter = self.get_buffer().get_iter_at_offset(0)
94
end_iter = self.get_buffer().get_iter_at_offset(-1)
95
return self.get_buffer().get_text(start_iter,end_iter)
99
self.get_buffer().set_text(text)
101
def append(self, text):
102
"""append: appends text to the end of the textbuffer.
105
text - a string to add to the buffer. The text will be the
106
last text in the buffer. The insertion cursor will not be moved.
110
end_iter = self.get_buffer().get_iter_at_offset(-1)
111
self.get_buffer().insert(end_iter, text)
113
def prepend(self, text):
114
"""prepend: appends text to the start of the textbuffer.
117
text - a string to add to the buffer. The text will be the
118
first text in the buffer. The insertion cursor will not be moved.
122
start_iter = self.get_buffer().get_iter_at_offset(0)
123
self.get_buffer().insert(start_iter, text)
124
insert_iter = self.get_buffer().get_iter_at_offset(len(text)-1)
125
self.get_buffer().place_cursor(insert_iter)
127
def cursor_to_end(self):
128
"""cursor_to_end: moves the insertion curson to the last position
133
end_iter = self.get_buffer().get_iter_at_offset(-1)
134
self.get_buffer().place_cursor(end_iter)
136
def cursor_to_start(self):
137
"""cursor_to_start: moves the insertion curson to the first position
142
start_iter = self.get_buffer().get_iter_at_offset(0)
143
self.get_buffer().place_cursor(start_iter)
145
def add_highlight(self, text):
146
"""add_highlight: add string to be highlighted when entered in the
150
text - a string to be highlighted
154
if text not in self._highlight_strings:
155
self._highlight_strings.append(text)
158
def clear_highlight(self, text):
159
"""clear_highlight: stops a string from being highlighted in the
163
text - the string to stop highlighting. If the string is not currently
164
being highlighted, the function will be ignored.
168
if text in self._highlight_strings:
169
del(self._highlight_strings[text])
172
def clear_all_highlights(self):
173
"""clear_all_highlight: stops highlighting all strings in the buffer.
174
The TextEditor will forget about all strings specified for highlighting.
175
If no strings are currently set for highlighting, the function will be
181
self._highlight_strings = []
184
def _highlight(self):
185
"""_highlight: internal method to trigger highlighting.
186
Do not call directly.
190
start_iter = self.get_buffer().get_iter_at_offset(0)
191
end_iter = self.get_buffer().get_iter_at_offset(-1)
192
text = self.get_buffer().get_text(start_iter,end_iter)
193
self.get_buffer().remove_all_tags(start_iter, end_iter)
194
for s in self._highlight_strings:
195
hits = [match.start() for match in re.finditer(re.escape(s), text)]
197
start_iter = self.get_buffer().get_iter_at_offset(hit)
198
end_iter = self.get_buffer().get_iter_at_offset(hit + len(s))
199
self.get_buffer().apply_tag_by_name("highlight",start_iter,end_iter)
201
def cut(self, widget=None, data=None):
202
"""cut: cut currently selected text and put it on the clipboard.
203
This function can be called as a function, or assigned as a signal
208
self.get_buffer().cut_clipboard(self.clipboard, True)
210
def copy(self, widget=None, data=None):
211
"""copy: copy currently selected text to the clipboard.
212
This function can be called as a function, or assigned as a signal
217
self.get_buffer().copy_clipboard(self.clipboard)
219
def paste(self, widget=None, data=None):
220
"""paste: Insert any text currently on the clipboard into the
222
This function can be called as a function, or assigned as a signal
227
self.get_buffer().paste_clipboard(self.clipboard,None,True)
229
def undo(self, widget=None, data=None):
230
"""undo: revert (undo) the last action.
231
This function can be called as a function, or assigned as a signal
236
if len(self.undos) == 0:
239
self.get_buffer().disconnect(self.delete_event)
240
self.get_buffer().disconnect(self.insert_event)
242
undo = self.undos[-1]
243
redo = self._do_action(undo)
244
self.redos.append(redo)
247
self.insert_event = self.get_buffer().connect("insert-text",self._on_insert)
248
self.delete_event = self.get_buffer().connect("delete-range",self._on_delete)
251
def _do_action(self, action):
252
if action["action"] == "delete":
253
start_iter = self.get_buffer().get_iter_at_offset(action["offset"])
254
end_iter = self.get_buffer().get_iter_at_offset(action["offset"] + len(action["text"]))
255
self.get_buffer().delete(start_iter, end_iter)
256
action["action"] = "insert"
258
elif action["action"] == "insert":
259
start_iter = self.get_buffer().get_iter_at_offset(action["offset"])
260
self.get_buffer().insert(start_iter, action["text"])
261
action["action"] = "delete"
265
def redo(self, widget=None, data=None):
266
"""redo: revert (undo) the last revert (undo) action.
267
This function can be called as a function, or assigned as a signal
272
if len(self.redos) == 0:
275
self.get_buffer().disconnect(self.delete_event)
276
self.get_buffer().disconnect(self.insert_event)
278
redo = self.redos[-1]
279
undo = self._do_action(redo)
280
self.undos.append(undo)
283
self.insert_event = self.get_buffer().connect("insert-text",self._on_insert)
284
self.delete_event = self.get_buffer().connect("delete-range",self._on_delete)
288
def _on_text_changed(self, buff):
289
if self._auto_bullet is not None:
290
self.get_buffer().disconnect(self.change_event)
291
buff.insert_at_cursor(self._auto_bullet)
292
self._auto_bullet = None
293
self.change_event = self.get_buffer().connect("changed",self._on_text_changed)
295
def _on_insert(self, text_buffer, iter, text, length, data=None):
296
"""_on_insert: internal function to handle programatically inserted
297
text. Do not call directly.
300
#TODO: add auto-whatever functionality by connecting to an event such
301
#as text-changed, etc...
303
cmd = {"action":"delete","offset":iter.get_offset(),"text":text}
306
if text == "\n" and self.auto_bullets:
307
cur_line = iter.get_line()
308
prev_line_iter = self.get_buffer().get_iter_at_line(cur_line)
309
pl_offset = prev_line_iter.get_offset()
310
pl_text = self.get_buffer().get_text(prev_line_iter, iter)
311
if pl_text.strip().find("*") == 0:
313
if not pl_text.startswith("*"):
314
ws = (pl_text.split("*")[0])
315
self._auto_bullet = ws + "* "
317
def _on_delete(self, text_buffer, start_iter, end_iter, data=None):
318
"""_on_insert: internal function to handle delete
319
text. Do not call directly.
324
text = self.get_buffer().get_text(start_iter,end_iter)
325
cmd = {"action":"insert","offset":start_iter.get_offset(),"text":text}
328
def _add_undo(self, cmd):
329
"""internal function to capture current state to add to undo stack.
330
Do not call directly.
334
#delete the oldest undo if undo maximum is in effect
335
if self.undo_max is not None and len(self.undos) >= self.undo_max:
337
self.undos.append(cmd)
339
class TestWindow(gtk.Window):
340
"""For testing and demonstrating AsycnTaskProgressBox.
344
#create a window a VBox to hold the controls
345
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
346
self.set_title("TextEditor Test Window")
347
windowbox = gtk.VBox(False, 2)
350
self.editor = TextEditor()
352
windowbox.pack_end(self.editor)
353
self.set_size_request(200,200)
357
self.connect("destroy", gtk.main_quit)
358
self.editor.text = "this is some inserted text"
359
self.editor.append("\nLine 3")
360
self.editor.prepend("Line1\n")
361
self.editor.cursor_to_end()
362
self.editor.cursor_to_start()
363
self.editor.add_highlight("his")
364
self.editor.clear_all_highlights()
365
self.editor.add_highlight("some")
366
self.editor.undo_max = 100
367
self.editor.auto_bullets = True
368
cut_button = gtk.Button("Cut")
369
cut_button.connect("clicked",self.editor.cut)
371
windowbox.pack_start(cut_button, False)
373
copy_button = gtk.Button("Copy")
374
copy_button.connect("clicked",self.editor.copy)
376
windowbox.pack_start(copy_button, False)
378
paste_button = gtk.Button("Paste")
379
paste_button.connect("clicked",self.editor.paste)
381
windowbox.pack_start(paste_button, False)
383
undo_button = gtk.Button("Undo")
384
undo_button.connect("clicked",self.editor.undo)
386
windowbox.pack_start(undo_button, False)
388
redo_button = gtk.Button("Redo")
389
redo_button.connect("clicked",self.editor.redo)
391
windowbox.pack_start(redo_button, False)
393
print self.editor.text
396
if __name__== "__main__":