Added Edit.xml file, which can be used as a feed for 0launch (Thomas Leonard).
[rox-edit.git] / EditWindow.py
blob1f4d9b3ef438324f62ebc0863d901d517e888a98
2 import rox
3 from rox import g, filer, app_options, mime
4 import sys
5 from rox.loading import XDSLoader
6 from rox.options import Option
7 from rox import OptionsBox
8 from rox.saving import Saveable
9 import os
10 import diff
11 import codecs
13 # WARNING: This is a temporary hack, until we write a way choose between
14 # the two ways of doing toolbars or we abandon the old method entirely
15 import warnings
16 warnings.filterwarnings('ignore', category=DeprecationWarning,
17 module='EditWindow')
18 # End temporary hack
20 def optional_section(available):
21 """If requires is None, the section is enabled. Otherwise,
22 the section is shaded and the requires message is shown at
23 the top."""
24 if available:
25 def build_enabled_section(box, node, label):
26 return box.do_box(node, None, g.VBox(False, 0))
27 return build_enabled_section
28 else:
29 def build_disabled_section(box, node, label):
30 assert label is not None
31 box, = box.do_box(node, None, g.VBox(False, 0))
32 box.set_sensitive(False)
33 frame = g.Frame(label)
34 box.set_border_width(4)
35 frame.add(box)
36 return [frame]
37 return build_disabled_section
39 try:
40 import gtkspell
41 have_spell = True
42 except:
43 have_spell = False
45 to_utf8 = codecs.getencoder('utf-8')
47 from buffer import Buffer, have_sourceview
48 from rox.Menu import Menu, set_save_name, SubMenu, Separator, Action, ToggleItem
50 OptionsBox.widget_registry['source-view-only'] = optional_section(have_sourceview)
52 default_font = Option('default_font', 'serif')
54 background_colour = Option('background', '#fff')
55 foreground_colour = Option('foreground', '#000')
57 auto_indent = Option('autoindent', '1')
58 word_wrap = Option('wordwrap', '1')
60 layout_left_margin = Option('layout_left_margin', 2)
61 layout_right_margin = Option('layout_right_margin', 4)
63 layout_before_para = Option('layout_before_para', 0)
64 layout_after_para = Option('layout_after_para', 0)
65 layout_inside_para = Option('layout_inside_para', 0)
66 layout_indent_para = Option('layout_indent_para', 2)
68 right_margin = Option('right_margin', 80)
69 show_margin = Option('show_margin', True)
70 smart_home_end = Option('smart_home_end', True)
71 show_line_numbers = Option('show_line_numbers', True)
72 show_line_markers = Option('show_line_markers', True)
73 tab_width = Option('tab_width', 4)
74 use_spaces_for_tabs = Option('use_spaces_for_tabs', False)
76 show_toolbar = Option('show_toolbar', 1)
78 set_save_name('Edit', site='rox.sourceforge.net')
80 edit_menu = [
81 Action(_('Cut'), 'cut', '<Ctrl>X', g.STOCK_CUT),
82 Action(_('Copy'), 'copy', '<Ctrl>C', g.STOCK_COPY),
83 Action(_('Paste'), 'paste', '<Ctrl>V', g.STOCK_PASTE),
84 Separator(),
85 Action(_('Undo'), 'undo', '<Ctrl>Z', g.STOCK_UNDO),
86 Action(_('Redo'), 'redo', '<Ctrl>Y', g.STOCK_REDO),
87 Separator(),
88 Action(_('Search...'), 'search', 'F4', g.STOCK_FIND),
89 Action(_('Search and Replace....'), 'search_replace',
90 '<Ctrl>F4', g.STOCK_FIND_AND_REPLACE),
91 Action(_('Goto line...'), 'goto', 'F5', g.STOCK_JUMP_TO),
94 bookmark_menu = [
95 Separator(),
96 Action(_('Toggle Bookmark'), 'toggle_bookmark', '<Ctrl>F2'),
97 Action(_('Next Bookmark'), 'next_bookmark', 'F2'),
98 Action(_('Previous Bookmark'), 'prev_bookmark', '<Shift>F2'),
101 if have_sourceview:
102 edit_menu += bookmark_menu
104 menu = Menu('main', [
105 SubMenu(_('File'), [
106 Action(_('Save'), 'save', '<Ctrl>S', g.STOCK_SAVE),
107 Action(_('Save As...'), 'save_as', '', g.STOCK_SAVE_AS),
108 Action(_('Open Parent'), 'up', '', g.STOCK_GO_UP),
109 Action(_('Show Changes'), 'diff', '', 'rox-diff'),
110 ToggleItem(_('Word Wrap'), 'word_wrap'),
111 Action(_('Close'), 'close', '', g.STOCK_CLOSE),
112 Separator(),
113 Action(_('New'), 'new', '', g.STOCK_NEW)]),
115 SubMenu(_('Edit'), edit_menu),
117 Action(_('Options'), 'show_options', '', g.STOCK_PROPERTIES),
118 Action(_('Help'), 'help', 'F1', g.STOCK_HELP),
121 known_codecs = (
122 "iso8859_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5",
123 "iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10",
124 "iso8859_13", "iso8859_14", "iso8859_15",
125 "ascii", "base64_codec", "charmap",
126 "cp037", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252",
127 "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp424",
128 "cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856",
129 "cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866",
130 "cp869", "cp874", "cp875", "hex_codec",
131 "koi8_r",
132 "latin_1",
133 "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish",
134 "mbcs", "quopri_codec", "raw_unicode_escape",
135 "rot_13",
136 "utf_16_be", "utf_16_le", "utf_16", "utf_7", "utf_8", "uu_codec",
137 "zlib_codec"
140 class Abort(Exception):
141 pass
143 class Minibuffer:
144 def setup(self):
145 """Called when the minibuffer is opened."""
147 def key_press(self, kev):
148 """A keypress event in the minibuffer text entry."""
150 def changed(self):
151 """The minibuffer text has changed."""
153 def activate(self):
154 """Return or Enter pressed."""
156 info = 'Press Escape to close the minibuffer.'
158 class DiffLoader(XDSLoader):
159 def __init__(self, window):
160 XDSLoader.__init__(self, ['text/plain'])
161 self.window = window
163 def xds_load_from_file(self, path):
164 self.window.diff(path = path)
166 def xds_load_from_stream(self, name, type, stream):
167 tmp = diff.Tmp(suffix = '-' + (name or 'tmp'))
168 import shutil
169 shutil.copyfileobj(stream, tmp)
170 tmp.seek(0)
171 self.window.diff(path = tmp.name)
173 class EditWindow(rox.Window, XDSLoader, Saveable):
174 _word_wrap = False
175 wrap_button = None
177 def __init__(self, filename = None, show = True, line_number = None):
178 rox.Window.__init__(self)
179 XDSLoader.__init__(self, ['text/plain', 'UTF8_STRING'])
180 self.set_default_size(g.gdk.screen_width() * 2 / 3,
181 g.gdk.screen_height() / 2)
183 self.savebox = None
184 self.info_box = None
185 self.language = None
187 app_options.add_notify(self.update_styles)
190 if filename:
191 import os.path
192 self.uri = os.path.abspath(filename)
193 self.mime_type = mime.get_type(self.uri, 1)
194 else:
195 self.uri = None
196 self.mime_type = mime.lookup('text', 'plain')
198 self.buffer = Buffer()
200 try:
201 import gtksourceview
202 self.text = gtksourceview.SourceView(self.buffer)
203 pixbuf = g.gdk.pixbuf_new_from_file(rox.app_dir+"/images/marker.png")
204 self.text.set_marker_pixbuf("bookmark", pixbuf)
205 if self.mime_type:
206 self.buffer.set_type(self.mime_type)
207 except:
208 self.text = g.TextView()
209 self.text.set_buffer(self.buffer)
211 self.text.set_size_request(10, 10)
212 self.xds_proxy_for(self.text)
214 self.insert_mark = self.buffer.get_mark('insert')
215 self.selection_bound_mark = self.buffer.get_mark('selection_bound')
216 start = self.buffer.get_start_iter()
217 self.mark_start = self.buffer.create_mark('mark_start', start, True)
218 self.mark_end = self.buffer.create_mark('mark_end', start, False)
219 self.mark_tmp = self.buffer.create_mark('mark_tmp', start, False)
220 tag = self.buffer.create_tag('marked')
221 tag.set_property('background', 'green')
222 self.marked = 0
224 # When searching, this is where the cursor was when the minibuffer
225 # was opened.
226 start = self.buffer.get_start_iter()
227 self.search_base = self.buffer.create_mark('search_base', start, True)
229 vbox = g.VBox(False)
230 self.add(vbox)
232 tools = g.Toolbar()
233 tools.set_style(g.TOOLBAR_ICONS)
234 vbox.pack_start(tools, False, True, 0)
236 self.status_label = g.Label('')
237 tools.append_widget(self.status_label, None, None)
238 tools.insert_stock(g.STOCK_HELP, _('Help'), None, self.help, None, 0)
239 diff = tools.insert_stock('rox-diff', _('Show changes from saved copy.\n'
240 'Or, drop a backup file onto this button to see changes from that.'),
241 None, self.diff, None, 0)
242 DiffLoader(self).xds_proxy_for(diff)
244 if have_spell:
245 self.spell = None
246 image_spell = g.Image()
247 image_spell.set_from_stock(g.STOCK_SPELL_CHECK, tools.get_icon_size())
248 self.spell_button = tools.insert_element(g.TOOLBAR_CHILD_TOGGLEBUTTON,
249 None, _("Check Spelling"), _("Check Spelling"), None,
250 image_spell, self.toggle_spell, None, 0)
252 image_wrap = g.Image()
253 image_wrap.set_from_file(rox.app_dir + '/images/rox-word-wrap.png')
254 self.wrap_button = tools.insert_element(g.TOOLBAR_CHILD_TOGGLEBUTTON,
255 None, _("Word Wrap"), _("Word Wrap"), None, image_wrap,
256 lambda button: self.set_word_wrap(button.get_active()),
257 None, 0)
258 tools.insert_stock(g.STOCK_REDO, _('Redo'), None, self.redo, None, 0)
259 tools.insert_stock(g.STOCK_UNDO, _('Undo'), None, self.undo, None, 0)
260 tools.insert_stock(g.STOCK_FIND_AND_REPLACE, _('Replace'), None, self.search_replace, None, 0)
261 tools.insert_stock(g.STOCK_FIND, _('Search'), None, self.search, None, 0)
262 tools.insert_stock(g.STOCK_SAVE_AS, _('Save As'), None, self.save_as, None, 0)
263 self.save_button = tools.insert_stock(g.STOCK_SAVE, _('Save'), None, self.save, None, 0)
264 tools.insert_stock(g.STOCK_GO_UP, _('Up'), None, self.up, None, 0)
265 tools.insert_stock(g.STOCK_CLOSE, _('Close'), None, self.close, None, 0)
266 # Set minimum size to ignore the label
267 tools.set_size_request(tools.size_request()[0], -1)
269 self.tools = tools
271 swin = g.ScrolledWindow()
272 swin.set_policy(g.POLICY_AUTOMATIC, g.POLICY_AUTOMATIC)
273 vbox.pack_start(swin, True, True)
275 swin.add(self.text)
277 if show:
278 self.show_all()
279 self.update_styles()
281 self.update_title()
283 # Create the minibuffer
284 self.mini_hbox = g.HBox(False)
286 info = rox.ButtonMixed(g.STOCK_DIALOG_INFO, '')
287 info.set_relief(g.RELIEF_NONE)
288 info.unset_flags(g.CAN_FOCUS)
289 info.connect('clicked', self.mini_show_info)
291 close = rox.ButtonMixed(g.STOCK_STOP, '')
292 close.set_relief(g.RELIEF_NONE)
293 close.unset_flags(g.CAN_FOCUS)
294 close.connect('clicked', lambda e: self.set_minibuffer(None))
296 self.mini_hbox.pack_end(info, False, True, 0)
297 self.mini_hbox.pack_start(close, False, True, 0)
298 self.mini_label = g.Label('')
299 self.mini_hbox.pack_start(self.mini_label, False, True, 0)
300 self.mini_entry = g.Entry()
301 self.mini_hbox.pack_start(self.mini_entry, True, True, 0)
302 vbox.pack_start(self.mini_hbox, False, True)
303 self.mini_entry.connect('key-press-event', self.mini_key_press)
304 self.mini_entry.connect('changed', self.mini_changed)
306 self.connect('destroy', self.destroyed)
308 self.connect('delete-event', self.delete_event)
309 self.text.grab_focus()
310 self.text.connect('key-press-event', self.key_press)
312 # FIXME: why does this freeze Edit?
313 #if have_spell:
314 #if self.mime_type.media == 'text' and self.mime_type.subtype == 'plain':
315 #self.toggle_spell()
316 ##self.spell.set_language ("en_US")
318 def update_current_line(*unused):
319 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
320 bound = self.buffer.get_iter_at_mark(self.selection_bound_mark)
321 if cursor.compare(bound) == 0:
322 n_lines = self.buffer.get_line_count()
323 self.status_label.set_text(_('Line %s of %d') % (cursor.get_line() + 1, n_lines))
324 else:
325 n_lines = abs(cursor.get_line() - bound.get_line()) + 1
326 if n_lines == 1:
327 n_chars = abs(cursor.get_line_offset() - bound.get_line_offset())
328 if n_chars == 1:
329 bytes = to_utf8(self.buffer.get_text(cursor, bound, False))[0]
330 self.status_label.set_text(_('One character selected (%s)') %
331 ' '.join(map(lambda x: '0x%2x' % ord(x), bytes)))
332 else:
333 self.status_label.set_text(_('%d characters selected') % n_chars)
334 else:
335 self.status_label.set_text(_('%d lines selected') % n_lines)
336 self.buffer.connect('mark-set', update_current_line)
337 self.buffer.connect('changed', update_current_line)
340 # Loading might take a while, so get something on the screen
341 # now...
342 g.gdk.flush()
344 if filename:
345 try:
346 self.load_file(filename)
347 if filename != '-':
348 self.save_last_stat = os.stat(filename)
349 except Abort:
350 self.destroy()
351 raise
353 self.buffer.connect('modified-changed', self.update_title)
354 self.buffer.set_modified(False)
356 def button_press(text, event):
357 if event.button != 3:
358 return False
359 menu.popup(self, event)
360 return True
361 self.text.connect('button-press-event', button_press)
362 self.text.connect('popup-menu', lambda text: menu.popup(self, None))
364 menu.attach(self, self)
365 self.buffer.place_cursor(self.buffer.get_start_iter())
366 self.buffer.start_undo_history()
368 if line_number:
369 iter = self.buffer.get_iter_at_line(int(line_number) - 1)
370 self.buffer.place_cursor(iter)
371 self.text.scroll_to_mark(self.insert_mark, 0.05, False)
373 def key_press(self, text, kev):
374 if kev.keyval == g.keysyms.Return or kev.keyval == g.keysyms.KP_Enter:
375 return self.auto_indent()
376 elif kev.keyval == g.keysyms.Tab or kev.keyval == g.keysyms.KP_Tab:
377 return self.indent_block()
378 elif kev.keyval == g.keysyms.ISO_Left_Tab:
379 return self.unindent_block()
381 def auto_indent(self):
382 if not auto_indent.int_value:
383 return False
385 start = self.buffer.get_iter_at_mark(self.insert_mark)
386 end = start.copy()
387 start.set_line_offset(0)
388 end.forward_to_line_end()
389 line = self.buffer.get_text(start, end, False)
390 indent = ''
392 if self.mime_type.subtype == 'x-python':
393 try:
394 l = line.split('\n')[0]
395 except:
396 l = line
397 if l.endswith(':') and not l.startswith('#'):
398 if use_spaces_for_tabs.int_value:
399 for i in tab_width.int_value:
400 indent += ' '
401 else:
402 indent += '\t'
403 elif have_sourceview:
404 return False
406 for x in line:
407 if x in ' \t':
408 indent += x
409 else:
410 break
412 self.buffer.begin_user_action()
413 self.buffer.insert_at_cursor('\n' + indent)
414 self.buffer.end_user_action()
415 return True
417 def indent_block(self):
418 try:
419 (start, end) = self.buffer.get_selection_bounds()
420 start_line = start.get_line()
421 end_line = end.get_line()
422 self.buffer.begin_user_action()
423 for i in range(start_line, end_line+1):
424 iter = self.buffer.get_iter_at_line(i)
425 self.buffer.insert(iter, '\t')
426 self.buffer.end_user_action()
427 return True
428 except:
429 return False
431 def unindent_block(self):
432 try:
433 (start, end) = self.buffer.get_selection_bounds()
434 start_line = start.get_line()
435 end_line = end.get_line()
436 self.buffer.begin_user_action()
437 for i in range(start_line, end_line+1):
438 iter = self.buffer.get_iter_at_line(i)
439 if iter.get_char() == '\t':
440 next_char = iter.copy()
441 next_char.forward_char()
442 self.buffer.delete(iter, next_char)
443 self.buffer.end_user_action()
444 return True
445 except:
446 return False
448 def destroyed(self, widget):
449 app_options.remove_notify(self.update_styles)
451 def update_styles(self):
452 try:
453 import pango
454 font = pango.FontDescription(default_font.value)
455 bg = g.gdk.color_parse(background_colour.value)
456 fg = g.gdk.color_parse(foreground_colour.value)
458 self.text.set_left_margin(layout_left_margin.int_value)
459 self.text.set_right_margin(layout_right_margin.int_value)
461 self.text.set_pixels_above_lines(layout_before_para.int_value)
462 self.text.set_pixels_below_lines(layout_after_para.int_value)
463 self.text.set_pixels_inside_wrap(layout_inside_para.int_value)
464 self.text.set_indent(layout_indent_para.int_value)
466 self.word_wrap = bool(word_wrap.int_value)
468 if show_toolbar.int_value:
469 self.tools.show()
470 else:
471 self.tools.hide()
472 except:
473 rox.report_exception()
474 else:
475 self.text.modify_font(font)
476 self.text.modify_base(g.STATE_NORMAL, bg)
477 self.text.modify_text(g.STATE_NORMAL, fg)
479 if have_sourceview:
480 self.text.set_show_line_numbers(show_line_numbers.int_value)
481 self.text.set_show_line_markers(show_line_markers.int_value)
482 self.text.set_auto_indent(auto_indent.int_value)
483 self.text.set_tabs_width(tab_width.int_value)
484 self.text.set_insert_spaces_instead_of_tabs(use_spaces_for_tabs.int_value)
485 self.text.set_margin(right_margin.int_value)
486 self.text.set_show_margin(show_margin.int_value)
487 self.text.set_smart_home_end(smart_home_end.int_value)
488 if self.buffer.language == 'Python':
489 self.text.set_auto_indent(False)
491 def cut(self): self.text.emit('cut_clipboard')
492 def copy(self): self.text.emit('copy_clipboard')
493 def paste(self): self.text.emit('paste_clipboard')
495 def delete_event(self, window, event):
496 if self.buffer.get_modified():
497 self.save_as(discard = 1)
498 return 1
499 return 0
501 def update_title(self, *unused):
502 title = self.uri or '<'+_('Untitled')+'>'
503 if self.buffer.get_modified():
504 title = title + " *"
505 self.save_button.set_sensitive(True)
506 else:
507 self.save_button.set_sensitive(False)
508 self.set_title(title)
510 def xds_load_from_stream(self, name, t, stream):
511 if t == 'UTF8_STRING':
512 return # Gtk will handle it
513 try:
514 self.insert_data(stream.read())
515 except Abort:
516 pass
518 def get_encoding(self, message):
519 "Returns (encoding, errors), or raises Abort to cancel."
520 box = g.MessageDialog(self, 0, g.MESSAGE_QUESTION, g.BUTTONS_CANCEL, message)
521 box.set_has_separator(False)
523 frame = g.Frame()
524 box.vbox.pack_start(frame, True, True)
525 frame.set_border_width(6)
527 hbox = g.HBox(False, 4)
528 hbox.set_border_width(6)
530 hbox.pack_start(g.Label(_('Encoding:')), False, True, 0)
531 combo = g.Combo()
532 combo.disable_activate()
533 combo.entry.connect('activate', lambda w: box.activate_default())
534 combo.set_popdown_strings(known_codecs)
535 hbox.pack_start(combo, True, True, 0)
536 ignore_errors = g.CheckButton(_('Ignore errors'))
537 hbox.pack_start(ignore_errors, False, True)
539 frame.add(hbox)
541 box.vbox.show_all()
542 box.add_button(g.STOCK_CONVERT, g.RESPONSE_YES)
543 box.set_default_response(g.RESPONSE_YES)
545 while 1:
546 combo.entry.grab_focus()
548 resp = box.run()
549 if resp != g.RESPONSE_YES:
550 box.destroy()
551 raise Abort
553 if ignore_errors.get_active():
554 errors = 'replace'
555 else:
556 errors = 'strict'
557 encoding = combo.entry.get_text()
558 try:
559 codecs.getdecoder(encoding)
560 break
561 except:
562 rox.alert(_("Unknown encoding '%s'") % encoding)
564 box.destroy()
566 return encoding, errors
568 def insert_data(self, data):
569 import codecs
570 errors = 'strict'
571 encoding = 'utf-8'
572 while 1:
573 decoder = codecs.getdecoder(encoding)
574 try:
575 data = decoder(data, errors)[0]
576 if errors == 'strict':
577 assert '\0' not in data
578 else:
579 if '\0' in data:
580 data = data.replace('\0', '\\0')
581 break
582 except:
583 pass
585 encoding, errors = self.get_encoding(
586 _("Data is not valid %s. Please select the file's encoding. "
587 "Turn on 'ignore errors' to try and load it anyway.")
588 % encoding)
590 self.buffer.begin_user_action()
591 self.buffer.insert_at_cursor(data)
592 self.buffer.end_user_action()
593 return 1
595 def load_file(self, path):
596 try:
597 if path == '-':
598 file = sys.stdin
599 else:
600 file = open(path, 'r')
601 contents = file.read()
602 if path != '-':
603 file.close()
605 self.buffer.begin_not_undoable_action()
606 self.insert_data(contents)
607 self.buffer.end_not_undoable_action()
608 except Abort:
609 raise
610 except:
611 rox.report_exception()
612 raise Abort
614 def close(self, button = None):
615 if self.buffer.get_modified():
616 self.save_as(discard = 1)
617 else:
618 self.destroy()
620 def discard(self):
621 self.destroy()
623 def up(self, button = None):
624 if self.uri:
625 filer.show_file(self.uri)
626 else:
627 rox.alert(_('File is not saved to disk yet'))
630 def toggle_spell(self, button = None):
631 if self.spell:
632 self.spell.detach()
633 self.spell = None
634 self.spell_button.set_active(False)
635 else:
636 try:
637 self.spell = gtkspell.Spell(self.text)
638 self.spell_button.set_active(True)
639 except:
640 self.spell = None
641 self.spell_button.set_active(False)
643 #self.spell_button.set_active(self.spell != None)
645 def diff(self, button = None, path = None):
646 path = path or self.uri
647 if not path:
648 rox.alert(_('This file has never been saved; nothing to compare it to!\n'
649 'Note: you can drop a file onto the toolbar button to see '
650 'the changes from that file.'))
651 return
652 diff.show_diff(path, self.save_to_stream)
654 def has_selection(self):
655 s, e = self.get_selection_range()
656 return not e.equal(s)
658 def get_marked_range(self):
659 s = self.buffer.get_iter_at_mark(self.mark_start)
660 e = self.buffer.get_iter_at_mark(self.mark_end)
661 if s.compare(e) > 0:
662 return e, s
663 return s, e
665 def get_selection_range(self):
666 s = self.buffer.get_iter_at_mark(self.insert_mark)
667 e = self.buffer.get_iter_at_mark(self.selection_bound_mark)
668 if s.compare(e) > 0:
669 return e, s
670 return s, e
672 def save(self, widget = None):
673 if self.uri:
674 self.save_to_file(self.uri)
675 self.buffer.set_modified(False)
676 else:
677 self.save_as(discard=0)
679 def save_as(self, widget = None, discard = 0):
680 from rox.saving import SaveBox
682 if self.savebox:
683 self.savebox.destroy()
685 try:
686 self.mime_type = mime.get_type(self.uri, 1)
687 except:
688 self.mime_type = mime.lookup('text', 'plain')
690 mime_text = self.mime_type.media + '/' + self.mime_type.subtype
692 if self.has_selection() and not discard:
693 saver = SelectionSaver(self)
694 self.savebox = SaveBox(saver, 'Selection', mime_text)
695 self.savebox.connect('destroy', lambda w: saver.destroy())
696 else:
697 uri = self.uri or _('TextFile')
698 self.savebox = SaveBox(self, uri, mime_text, discard)
699 self.savebox.show()
701 def help(self, button = None):
702 filer.open_dir(os.path.join(rox.app_dir, 'Help'))
704 def save_to_stream(self, stream):
705 s = self.buffer.get_start_iter()
706 e = self.buffer.get_end_iter()
707 stream.write(self.buffer.get_text(s, e, True))
709 def set_uri(self, uri):
710 self.uri = uri
711 self.buffer.set_modified(False)
712 self.update_title()
714 def new(self):
715 EditWindow()
717 def change_font(self):
718 style = self.text.get_style().copy()
719 style.font = load_font(options.get('edit_font'))
720 self.text.set_style(style)
722 def show_options(self):
723 rox.edit_options()
725 def set_marked(self, start = None, end = None):
726 "Set the marked region (from the selection if no region is given)."
727 self.clear_marked()
728 assert not self.marked
730 buffer = self.buffer
731 if start:
732 assert end
733 else:
734 assert not end
735 start, end = self.get_selection_range()
736 buffer.move_mark(self.mark_start, start)
737 buffer.move_mark(self.mark_end, end)
738 buffer.apply_tag_by_name('marked',
739 buffer.get_iter_at_mark(self.mark_start),
740 buffer.get_iter_at_mark(self.mark_end))
741 self.marked = 1
743 def clear_marked(self):
744 if not self.marked:
745 return
746 self.marked = 0
747 buffer = self.buffer
748 buffer.remove_tag_by_name('marked',
749 buffer.get_iter_at_mark(self.mark_start),
750 buffer.get_iter_at_mark(self.mark_end))
752 def undo(self, widget = None):
753 self.buffer.undo()
754 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
755 self.text.scroll_to_iter(cursor, 0.05, False)
757 def redo(self, widget = None):
758 self.buffer.redo()
759 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
760 self.text.scroll_to_iter(cursor, 0.05, False)
762 def goto(self, widget = None):
763 from goto import Goto
764 self.set_minibuffer(Goto())
766 def search(self, widget = None):
767 from search import Search
768 self.set_minibuffer(Search())
770 def search_replace(self, widget = None):
771 from search import Replace
772 Replace(self).show()
774 def toggle_bookmark(self):
775 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
776 name = str(cursor.get_line())
777 marker = self.buffer.get_marker(name)
778 if marker:
779 self.buffer.delete_marker(marker);
780 else:
781 marker = self.buffer.create_marker(name, "bookmark", cursor);
783 def next_bookmark(self):
784 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
785 cursor.forward_char()
786 marker = self.buffer.get_next_marker(cursor)
787 if marker:
788 self.buffer.get_iter_at_marker (cursor, marker)
789 self.buffer.place_cursor(cursor)
790 self.text.scroll_to_iter(cursor, 0.05, False)
792 def prev_bookmark(self):
793 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
794 cursor.backward_char()
795 marker = self.buffer.get_prev_marker(cursor)
796 if marker:
797 self.buffer.get_iter_at_marker (cursor, marker)
798 self.buffer.place_cursor(cursor)
799 self.text.scroll_to_iter(cursor, 0.05, False)
801 def set_mini_label(self, label):
802 self.mini_label.set_text(label)
804 def set_minibuffer(self, minibuffer):
805 assert minibuffer is None or isinstance(minibuffer, Minibuffer)
807 try:
808 self.minibuffer.close()
809 except:
810 pass
812 self.minibuffer = None
814 if minibuffer:
815 self.mini_entry.set_text('')
816 self.minibuffer = minibuffer
817 minibuffer.setup(self)
818 self.mini_entry.grab_focus()
819 self.mini_hbox.show_all()
820 else:
821 self.mini_hbox.hide()
822 self.text.grab_focus()
824 def mini_key_press(self, entry, kev):
825 if kev.keyval == g.keysyms.Escape:
826 self.set_minibuffer(None)
827 return 1
828 if kev.keyval == g.keysyms.Return or kev.keyval == g.keysyms.KP_Enter:
829 self.minibuffer.activate()
830 return 1
832 return self.minibuffer.key_press(kev)
834 def mini_changed(self, entry):
835 if not self.minibuffer:
836 return
837 self.minibuffer.changed()
839 def mini_show_info(self, *unused):
840 assert self.minibuffer
841 if self.info_box:
842 self.info_box.destroy()
843 self.info_box = g.MessageDialog(self, 0, g.MESSAGE_INFO, g.BUTTONS_OK,
844 self.minibuffer.info)
845 self.info_box.set_title(_('Minibuffer help'))
846 def destroy(box):
847 self.info_box = None
848 self.info_box.connect('destroy', destroy)
849 self.info_box.show()
850 self.info_box.connect('response', lambda w, r: w.destroy())
852 def process_selected(self, process):
853 """Calls process(line) on each line in the selection, or each line in the file
854 if there is no selection. If the result is not None, the text is replaced."""
855 self.buffer.begin_user_action()
856 try:
857 self._process_selected(process)
858 finally:
859 self.buffer.end_user_action()
861 def _process_selected(self, process):
862 if self.has_selection():
863 def get_end():
864 start, end = self.get_selection_range()
865 if start.compare(end) > 0:
866 return start
867 return end
868 start, end = self.get_selection_range()
869 if start.compare(end) > 0:
870 start = end
871 else:
872 def get_end():
873 return self.buffer.get_end_iter()
874 start = self.buffer.get_start_iter()
875 end = get_end()
877 while start.compare(end) <= 0:
878 line_end = start.copy()
879 line_end.forward_to_line_end()
880 if line_end.compare(end) >= 0:
881 line_end = end
882 line = self.buffer.get_text(start, line_end, False)
883 new = process(line)
884 if new is not None:
885 self.buffer.move_mark(self.mark_tmp, start)
886 self.buffer.insert(line_end, new)
887 start = self.buffer.get_iter_at_mark(self.mark_tmp)
888 line_end = start.copy()
889 line_end.forward_chars(len(line.decode('utf-8')))
890 self.buffer.delete(start, line_end)
892 start = self.buffer.get_iter_at_mark(self.mark_tmp)
893 end = get_end()
894 if not start.forward_line(): break
896 def set_word_wrap(self, value):
897 self._word_wrap = value
898 self.wrap_button.set_active(value)
899 if value:
900 self.text.set_wrap_mode(g.WRAP_WORD)
901 else:
902 self.text.set_wrap_mode(g.WRAP_NONE)
904 word_wrap = property(lambda self: self._word_wrap, set_word_wrap)
906 class SelectionSaver(Saveable):
907 def __init__(self, window):
908 self.window = window
909 window.set_marked()
911 def save_to_stream(self, stream):
912 s, e = self.window.get_marked_range()
913 stream.write(self.window.buffer.get_text(s, e, True))
915 def destroy(self):
916 # Called when savebox is remove. Get rid of the selection marker
917 self.window.clear_marked()