Removed svn:eol-style from binaries.
[rox-edit.git] / EditWindow.py
blob371413c640d1cb53acb2af99d305dfa6b96ad57c
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, gtksourceview
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 Again'), 'search_again', '<Shift>F4', g.STOCK_GO_FORWARD),
90 Action(_('Search and Replace....'), 'search_replace',
91 '<Ctrl>F4', g.STOCK_FIND_AND_REPLACE),
92 Action(_('Goto line...'), 'goto', 'F5', g.STOCK_JUMP_TO),
95 bookmark_menu = [
96 Separator(),
97 Action(_('Toggle Bookmark'), 'toggle_bookmark', '<Ctrl>F2'),
98 Action(_('Next Bookmark'), 'next_bookmark', 'F2'),
99 Action(_('Previous Bookmark'), 'prev_bookmark', '<Shift>F2'),
102 if have_sourceview:
103 edit_menu += bookmark_menu
105 menu = Menu('main', [
106 SubMenu(_('File'), [
107 Action(_('Save'), 'save', '<Ctrl>S', g.STOCK_SAVE),
108 Action(_('Save As...'), 'save_as', 'F3', g.STOCK_SAVE_AS),
109 Action(_('Open Parent'), 'up', '', g.STOCK_GO_UP),
110 Action(_('Show Changes'), 'diff', '', 'rox-diff'),
111 ToggleItem(_('Word Wrap'), 'word_wrap'),
112 Action(_('Close'), 'close', '', g.STOCK_CLOSE),
113 Separator(),
114 Action(_('New'), 'new', '', g.STOCK_NEW)]),
116 SubMenu(_('Edit'), edit_menu),
118 Action(_('Options'), 'show_options', '', g.STOCK_PROPERTIES),
119 Action(_('Help'), 'help', 'F1', g.STOCK_HELP),
122 known_codecs = (
123 "iso8859_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5",
124 "iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10",
125 "iso8859_13", "iso8859_14", "iso8859_15",
126 "ascii", "base64_codec", "charmap",
127 "cp037", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252",
128 "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp424",
129 "cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856",
130 "cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866",
131 "cp869", "cp874", "cp875", "hex_codec",
132 "koi8_r",
133 "latin_1",
134 "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish",
135 "mbcs", "quopri_codec", "raw_unicode_escape",
136 "rot_13",
137 "utf_16_be", "utf_16_le", "utf_16", "utf_7", "utf_8", "uu_codec",
138 "zlib_codec"
141 class Abort(Exception):
142 pass
144 class Minibuffer:
145 def setup(self):
146 """Called when the minibuffer is opened."""
148 def key_press(self, kev):
149 """A keypress event in the minibuffer text entry."""
151 def changed(self):
152 """The minibuffer text has changed."""
154 def activate(self):
155 """Return or Enter pressed."""
157 def close(self):
158 """Called when the minibuffer is closed.
159 Remove any widgets created in setup."""
161 info = 'Press Escape to close the minibuffer.'
163 class DiffLoader(XDSLoader):
164 def __init__(self, window):
165 XDSLoader.__init__(self, ['text/plain'])
166 self.window = window
168 def xds_load_from_file(self, path):
169 self.window.diff(path = path)
171 def xds_load_from_stream(self, name, type, stream):
172 tmp = diff.Tmp(suffix = '-' + (name or 'tmp'))
173 import shutil
174 shutil.copyfileobj(stream, tmp)
175 tmp.seek(0)
176 self.window.diff(path = tmp.name)
178 class DndToolbar(g.Toolbar, XDSLoader):
179 def __init__(self):
180 g.Toolbar.__init__(self)
181 XDSLoader.__init__(self, ['text/plain'])
183 def xds_load_from_path(self, path):
184 EditWindow(path)
186 def xds_load_from_stream(self, name, type, stream):
187 EditWindow(contents = stream.read())
189 class EditWindow(rox.Window, XDSLoader, Saveable):
190 _word_wrap = False
191 wrap_button = None
192 minibuffer = None
193 search_minibuffer = None # (persists for search_again)
195 def __init__(self, filename = None, show = True, line_number = None, contents = None):
196 rox.Window.__init__(self)
197 XDSLoader.__init__(self, ['text/plain', 'UTF8_STRING'])
198 self.set_default_size(g.gdk.screen_width() * 2 / 3,
199 g.gdk.screen_height() / 2)
201 self.savebox = None
202 self.info_box = None
203 self.language = None
205 app_options.add_notify(self.update_styles)
207 if filename:
208 import os.path
209 if not os.path.exists(filename):
210 try:
211 filename2, line_number = filename.split(':')
212 line_number = long(line_number)
213 except ValueError:
214 # Either there was no ':', or it wasn't followed by a number
215 pass
216 else:
217 filename = filename2
218 self.uri = os.path.abspath(filename)
219 self.mime_type = mime.get_type(self.uri, 1)
220 else:
221 self.uri = None
222 self.mime_type = mime.lookup('text', 'plain')
224 self.buffer = Buffer()
226 if have_sourceview:
227 self.text = gtksourceview.SourceView(self.buffer)
228 pixbuf = g.gdk.pixbuf_new_from_file(rox.app_dir+"/images/marker.png")
229 self.text.set_marker_pixbuf("bookmark", pixbuf)
230 if self.mime_type:
231 self.buffer.set_type(self.mime_type)
232 else:
233 self.text = g.TextView()
234 self.text.set_buffer(self.buffer)
236 self.text.set_size_request(10, 10)
237 self.xds_proxy_for(self.text)
239 self.insert_mark = self.buffer.get_mark('insert')
240 self.selection_bound_mark = self.buffer.get_mark('selection_bound')
241 start = self.buffer.get_start_iter()
242 self.mark_start = self.buffer.create_mark('mark_start', start, True)
243 self.mark_end = self.buffer.create_mark('mark_end', start, False)
244 self.mark_tmp = self.buffer.create_mark('mark_tmp', start, False)
245 tag = self.buffer.create_tag('marked')
246 tag.set_property('background', 'green')
247 self.marked = 0
249 # When searching, this is where the cursor was when the minibuffer
250 # was opened.
251 start = self.buffer.get_start_iter()
252 self.search_base = self.buffer.create_mark('search_base', start, True)
254 vbox = g.VBox(False)
255 self.add(vbox)
257 tools = DndToolbar()
258 tools.set_style(g.TOOLBAR_ICONS)
259 vbox.pack_start(tools, False, True, 0)
261 self.status_label = g.Label('')
262 tools.append_widget(self.status_label, None, None)
263 tools.insert_stock(g.STOCK_HELP, _('Help'), None, self.help, None, 0)
264 diff = tools.insert_stock('rox-diff', _('Show changes from saved copy.\n'
265 'Or, drop a backup file onto this button to see changes from that.'),
266 None, self.diff, None, 0)
267 DiffLoader(self).xds_proxy_for(diff)
269 if have_spell:
270 self.spell = None
271 image_spell = g.Image()
272 image_spell.set_from_stock(g.STOCK_SPELL_CHECK, tools.get_icon_size())
273 self.spell_button = tools.insert_element(g.TOOLBAR_CHILD_TOGGLEBUTTON,
274 None, _("Check Spelling"), _("Check Spelling"), None,
275 image_spell, self.toggle_spell, None, 0)
277 image_wrap = g.Image()
278 image_wrap.set_from_file(rox.app_dir + '/images/rox-word-wrap.png')
279 self.wrap_button = tools.insert_element(g.TOOLBAR_CHILD_TOGGLEBUTTON,
280 None, _("Word Wrap"), _("Word Wrap"), None, image_wrap,
281 lambda button: self.set_word_wrap(button.get_active()),
282 None, 0)
283 tools.insert_stock(g.STOCK_REDO, _('Redo'), None, self.redo, None, 0)
284 tools.insert_stock(g.STOCK_UNDO, _('Undo'), None, self.undo, None, 0)
285 tools.insert_stock(g.STOCK_FIND_AND_REPLACE, _('Replace'), None, self.search_replace, None, 0)
286 tools.insert_stock(g.STOCK_FIND, _('Search'), None, self.search, None, 0)
287 tools.insert_stock(g.STOCK_SAVE_AS, _('Save As'), None, self.save_as, None, 0)
288 self.save_button = tools.insert_stock(g.STOCK_SAVE, _('Save'), None, self.save, None, 0)
289 tools.insert_stock(g.STOCK_GO_UP, _('Up'), None, self.up, None, 0)
290 tools.insert_stock(g.STOCK_CLOSE, _('Close'), None, self.close, None, 0)
291 # Set minimum size to ignore the label
292 tools.set_size_request(tools.size_request()[0], -1)
294 self.tools = tools
296 swin = g.ScrolledWindow()
297 swin.set_policy(g.POLICY_AUTOMATIC, g.POLICY_AUTOMATIC)
298 vbox.pack_start(swin, True, True)
300 swin.add(self.text)
302 if show:
303 self.show_all()
304 self.update_styles()
306 self.update_title()
308 # Create the minibuffer
309 self.mini_hbox = g.HBox(False)
311 info = rox.ButtonMixed(g.STOCK_DIALOG_INFO, '')
312 info.set_relief(g.RELIEF_NONE)
313 info.unset_flags(g.CAN_FOCUS)
314 info.connect('clicked', self.mini_show_info)
316 close = rox.ButtonMixed(g.STOCK_STOP, '')
317 close.set_relief(g.RELIEF_NONE)
318 close.unset_flags(g.CAN_FOCUS)
319 close.connect('clicked', lambda e: self.set_minibuffer(None))
321 self.mini_hbox.pack_end(info, False, True, 0)
322 self.mini_hbox.pack_start(close, False, True, 0)
323 self.mini_label = g.Label('')
324 self.mini_hbox.pack_start(self.mini_label, False, True, 0)
325 self.mini_entry = g.Entry()
326 self.mini_hbox.pack_start(self.mini_entry, True, True, 0)
327 vbox.pack_start(self.mini_hbox, False, True)
328 self.mini_entry.connect('key-press-event', self.mini_key_press)
329 self.mini_entry.connect('changed', self.mini_changed)
331 self.connect('destroy', self.destroyed)
333 self.connect('delete-event', self.delete_event)
334 self.text.grab_focus()
335 self.text.connect('key-press-event', self.key_press)
337 # FIXME: why does this freeze Edit?
338 #if have_spell:
339 #if self.mime_type.media == 'text' and self.mime_type.subtype == 'plain':
340 #self.toggle_spell()
341 ##self.spell.set_language ("en_US")
343 def update_current_line(*unused):
344 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
345 bound = self.buffer.get_iter_at_mark(self.selection_bound_mark)
346 if cursor.compare(bound) == 0:
347 n_lines = self.buffer.get_line_count()
348 self.status_label.set_text(_('Line %s of %d') % (cursor.get_line() + 1, n_lines))
349 else:
350 n_lines = abs(cursor.get_line() - bound.get_line()) + 1
351 if n_lines == 1:
352 n_chars = abs(cursor.get_line_offset() - bound.get_line_offset())
353 if n_chars == 1:
354 bytes = to_utf8(self.buffer.get_text(cursor, bound, False))[0]
355 self.status_label.set_text(_('One character selected (%s)') %
356 ' '.join(map(lambda x: '0x%2x' % ord(x), bytes)))
357 else:
358 self.status_label.set_text(_('%d characters selected') % n_chars)
359 else:
360 self.status_label.set_text(_('%d lines selected') % n_lines)
361 self.buffer.connect('mark-set', update_current_line)
362 self.buffer.connect('changed', update_current_line)
365 # Loading might take a while, so get something on the screen
366 # now...
367 g.gdk.flush()
369 if filename:
370 try:
371 self.load_file(filename)
372 if filename != '-':
373 self.save_last_stat = os.stat(filename)
374 except Abort:
375 self.destroy()
376 raise
377 if contents:
378 self.insert_data(contents)
380 self.buffer.connect('modified-changed', self.update_title)
381 self.buffer.set_modified(False)
383 def button_press(text, event):
384 if event.button != 3:
385 return False
386 #self.text.emit('populate-popup', menu.menu)
387 menu.popup(self, event)
388 return True
389 self.text.connect('button-press-event', button_press)
390 self.text.connect('popup-menu', lambda text: menu.popup(self, None))
392 menu.attach(self, self)
393 self.buffer.place_cursor(self.buffer.get_start_iter())
394 self.buffer.start_undo_history()
396 if line_number:
397 iter = self.buffer.get_iter_at_line(int(line_number) - 1)
398 self.buffer.place_cursor(iter)
399 self.text.scroll_to_mark(self.insert_mark, 0.05, False)
401 def key_press(self, text, kev):
402 if kev.keyval == g.keysyms.Return or kev.keyval == g.keysyms.KP_Enter:
403 return self.auto_indent()
404 elif kev.keyval == g.keysyms.Tab or kev.keyval == g.keysyms.KP_Tab:
405 return self.indent_block()
406 elif kev.keyval == g.keysyms.ISO_Left_Tab:
407 return self.unindent_block()
408 elif kev.keyval == g.keysyms.Escape:
409 self.set_minibuffer(None)
410 return True
411 return False
413 def auto_indent(self):
414 if not auto_indent.int_value:
415 return False
417 start = self.buffer.get_iter_at_mark(self.insert_mark)
418 end = start.copy()
419 start.set_line_offset(0)
420 end.forward_to_line_end()
421 line = self.buffer.get_text(start, end, False)
422 indent = ''
424 if self.mime_type.subtype == 'x-python':
425 try:
426 l = line.split('\n')[0]
427 except:
428 l = line
429 if l.endswith(':') and not l.startswith('#'):
430 if use_spaces_for_tabs.int_value:
431 indent += ' ' * tab_width.int_value
432 else:
433 indent += '\t'
434 elif have_sourceview:
435 return False
437 for x in line:
438 if x in ' \t':
439 indent += x
440 else:
441 break
443 self.buffer.begin_user_action()
444 self.buffer.insert_at_cursor('\n' + indent)
445 self.buffer.end_user_action()
446 return True
448 def indent_block(self):
449 try:
450 (start, end) = self.buffer.get_selection_bounds()
451 start_line = start.get_line()
452 end_line = end.get_line()
453 self.buffer.begin_user_action()
454 for i in range(start_line, end_line+1):
455 iter = self.buffer.get_iter_at_line(i)
456 self.buffer.insert(iter, '\t')
457 self.buffer.end_user_action()
458 return True
459 except:
460 return False
462 def unindent_block(self):
463 try:
464 (start, end) = self.buffer.get_selection_bounds()
465 start_line = start.get_line()
466 end_line = end.get_line()
467 self.buffer.begin_user_action()
468 for i in range(start_line, end_line+1):
469 iter = self.buffer.get_iter_at_line(i)
470 if iter.get_char() == '\t':
471 next_char = iter.copy()
472 next_char.forward_char()
473 self.buffer.delete(iter, next_char)
474 self.buffer.end_user_action()
475 return True
476 except:
477 return False
479 def destroyed(self, widget):
480 app_options.remove_notify(self.update_styles)
482 def update_styles(self):
483 try:
484 import pango
485 font = pango.FontDescription(default_font.value)
486 bg = g.gdk.color_parse(background_colour.value)
487 fg = g.gdk.color_parse(foreground_colour.value)
489 self.text.set_left_margin(layout_left_margin.int_value)
490 self.text.set_right_margin(layout_right_margin.int_value)
492 self.text.set_pixels_above_lines(layout_before_para.int_value)
493 self.text.set_pixels_below_lines(layout_after_para.int_value)
494 self.text.set_pixels_inside_wrap(layout_inside_para.int_value)
495 self.text.set_indent(layout_indent_para.int_value)
497 self.word_wrap = bool(word_wrap.int_value)
499 if show_toolbar.int_value:
500 self.tools.show()
501 else:
502 self.tools.hide()
503 except:
504 rox.report_exception()
505 else:
506 self.text.modify_font(font)
507 self.text.modify_base(g.STATE_NORMAL, bg)
508 self.text.modify_text(g.STATE_NORMAL, fg)
510 if have_sourceview:
511 self.text.set_show_line_numbers(show_line_numbers.int_value)
512 self.text.set_show_line_markers(show_line_markers.int_value)
513 self.text.set_auto_indent(auto_indent.int_value)
514 self.text.set_tabs_width(tab_width.int_value)
515 self.text.set_insert_spaces_instead_of_tabs(use_spaces_for_tabs.int_value)
516 self.text.set_margin(right_margin.int_value)
517 self.text.set_show_margin(show_margin.int_value)
518 self.text.set_smart_home_end(smart_home_end.int_value)
519 if self.buffer.language == 'Python':
520 self.text.set_auto_indent(False)
522 def cut(self): self.text.emit('cut_clipboard')
523 def copy(self): self.text.emit('copy_clipboard')
524 def paste(self): self.text.emit('paste_clipboard')
526 def delete_event(self, window, event):
527 if self.buffer.get_modified():
528 self.save_as(discard = 1)
529 return 1
530 return 0
532 def update_title(self, *unused):
533 title = self.uri or '<'+_('Untitled')+'>'
534 if self.buffer.get_modified():
535 title = title + " *"
536 self.save_button.set_sensitive(True)
537 else:
538 self.save_button.set_sensitive(False)
539 self.set_title(title)
541 def xds_load_from_stream(self, name, t, stream):
542 if t == 'UTF8_STRING':
543 return # Gtk will handle it
544 try:
545 dnd_mark = self.buffer.get_mark('gtk_drag_target')
546 if dnd_mark:
547 dnd_pos = self.buffer.get_iter_at_mark(dnd_mark)
548 self.buffer.move_mark(self.insert_mark, dnd_pos)
549 self.insert_data(stream.read())
550 except Abort:
551 pass
553 def get_encoding(self, message):
554 "Returns (encoding, errors), or raises Abort to cancel."
555 box = g.MessageDialog(self, 0, g.MESSAGE_QUESTION, g.BUTTONS_CANCEL, message)
556 box.set_has_separator(False)
558 frame = g.Frame()
559 box.vbox.pack_start(frame, True, True)
560 frame.set_border_width(6)
562 hbox = g.HBox(False, 4)
563 hbox.set_border_width(6)
565 hbox.pack_start(g.Label(_('Encoding:')), False, True, 0)
566 combo = g.Combo()
567 combo.disable_activate()
568 combo.entry.connect('activate', lambda w: box.activate_default())
569 combo.set_popdown_strings(known_codecs)
570 hbox.pack_start(combo, True, True, 0)
571 ignore_errors = g.CheckButton(_('Ignore errors'))
572 hbox.pack_start(ignore_errors, False, True)
574 frame.add(hbox)
576 box.vbox.show_all()
577 box.add_button(g.STOCK_CONVERT, g.RESPONSE_YES)
578 box.set_default_response(g.RESPONSE_YES)
580 while 1:
581 combo.entry.grab_focus()
583 resp = box.run()
584 if resp != g.RESPONSE_YES:
585 box.destroy()
586 raise Abort
588 if ignore_errors.get_active():
589 errors = 'replace'
590 else:
591 errors = 'strict'
592 encoding = combo.entry.get_text()
593 try:
594 codecs.getdecoder(encoding)
595 break
596 except:
597 rox.alert(_("Unknown encoding '%s'") % encoding)
599 box.destroy()
601 return encoding, errors
603 def insert_data(self, data):
604 import codecs
605 errors = 'strict'
606 encoding = 'utf-8'
607 while 1:
608 decoder = codecs.getdecoder(encoding)
609 try:
610 data = decoder(data, errors)[0]
611 if errors == 'strict':
612 assert '\0' not in data
613 else:
614 if '\0' in data:
615 data = data.replace('\0', '\\0')
616 break
617 except:
618 pass
620 encoding, errors = self.get_encoding(
621 _("Data is not valid %s. Please select the file's encoding. "
622 "Turn on 'ignore errors' to try and load it anyway.")
623 % encoding)
625 self.buffer.begin_user_action()
626 self.buffer.insert_at_cursor(data)
627 self.buffer.end_user_action()
628 return 1
630 def load_file(self, path):
631 try:
632 if path == '-':
633 file = sys.stdin
634 else:
635 file = open(path, 'r')
636 contents = file.read()
637 if path != '-':
638 file.close()
640 self.buffer.begin_not_undoable_action()
641 self.insert_data(contents)
642 self.buffer.end_not_undoable_action()
643 except Abort:
644 raise
645 except:
646 rox.report_exception()
647 raise Abort
649 def close(self, button = None):
650 if self.buffer.get_modified():
651 self.save_as(discard = 1)
652 else:
653 self.destroy()
655 def discard(self):
656 self.destroy()
658 def up(self, button = None):
659 if self.uri:
660 filer.show_file(self.uri)
661 else:
662 rox.alert(_('File is not saved to disk yet'))
665 def toggle_spell(self, button = None):
666 if self.spell:
667 self.spell.detach()
668 self.spell = None
669 self.spell_button.set_active(False)
670 else:
671 try:
672 self.spell = gtkspell.Spell(self.text)
673 self.spell_button.set_active(True)
674 except:
675 self.spell = None
676 self.spell_button.set_active(False)
678 #self.spell_button.set_active(self.spell != None)
680 def diff(self, button = None, path = None):
681 path = path or self.uri
682 if not path:
683 rox.alert(_('This file has never been saved; nothing to compare it to!\n'
684 'Note: you can drop a file onto the toolbar button to see '
685 'the changes from that file.'))
686 return
687 diff.show_diff(path, self.save_to_stream)
689 def has_selection(self):
690 s, e = self.get_selection_range()
691 return not e.equal(s)
693 def get_marked_range(self):
694 s = self.buffer.get_iter_at_mark(self.mark_start)
695 e = self.buffer.get_iter_at_mark(self.mark_end)
696 if s.compare(e) > 0:
697 return e, s
698 return s, e
700 def get_selection_range(self):
701 s = self.buffer.get_iter_at_mark(self.insert_mark)
702 e = self.buffer.get_iter_at_mark(self.selection_bound_mark)
703 if s.compare(e) > 0:
704 return e, s
705 return s, e
707 def save(self, widget = None):
708 if self.uri:
709 self.save_to_file(self.uri)
710 self.buffer.set_modified(False)
711 else:
712 self.save_as(discard=0)
714 def save_as(self, widget = None, discard = 0):
715 from rox.saving import SaveBox
717 if self.savebox:
718 self.savebox.destroy()
720 try:
721 self.mime_type = mime.get_type(self.uri, 1)
722 except:
723 self.mime_type = mime.lookup('text', 'plain')
725 mime_text = self.mime_type.media + '/' + self.mime_type.subtype
727 if self.has_selection() and not discard:
728 saver = SelectionSaver(self)
729 self.savebox = SaveBox(saver, 'Selection', mime_text)
730 self.savebox.connect('destroy', lambda w: saver.destroy())
731 else:
732 uri = self.uri or _('TextFile')
733 self.savebox = SaveBox(self, uri, mime_text, discard)
734 self.savebox.show()
736 def help(self, button = None):
737 filer.open_dir(os.path.join(rox.app_dir, 'Help'))
739 def save_to_stream(self, stream):
740 s = self.buffer.get_start_iter()
741 e = self.buffer.get_end_iter()
742 stream.write(self.buffer.get_text(s, e, True))
744 def set_uri(self, uri):
745 self.uri = uri
746 self.buffer.set_modified(False)
747 self.update_title()
749 def new(self):
750 EditWindow()
752 def change_font(self):
753 style = self.text.get_style().copy()
754 style.font = load_font(options.get('edit_font'))
755 self.text.set_style(style)
757 def show_options(self):
758 rox.edit_options()
760 def set_marked(self, start = None, end = None):
761 "Set the marked region (from the selection if no region is given)."
762 self.clear_marked()
763 assert not self.marked
765 buffer = self.buffer
766 if start:
767 assert end
768 else:
769 assert not end
770 start, end = self.get_selection_range()
771 buffer.move_mark(self.mark_start, start)
772 buffer.move_mark(self.mark_end, end)
773 buffer.apply_tag_by_name('marked',
774 buffer.get_iter_at_mark(self.mark_start),
775 buffer.get_iter_at_mark(self.mark_end))
776 self.marked = 1
778 def clear_marked(self):
779 if not self.marked:
780 return
781 self.marked = 0
782 buffer = self.buffer
783 buffer.remove_tag_by_name('marked',
784 buffer.get_iter_at_mark(self.mark_start),
785 buffer.get_iter_at_mark(self.mark_end))
787 def undo(self, widget = None):
788 self.buffer.undo()
789 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
790 self.text.scroll_to_iter(cursor, 0.05, False)
792 def redo(self, widget = None):
793 self.buffer.redo()
794 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
795 self.text.scroll_to_iter(cursor, 0.05, False)
797 def goto(self, widget = None):
798 from goto import Goto
799 self.set_minibuffer(Goto())
801 def search(self, widget = None):
802 if self.search_minibuffer is None:
803 from search import Search
804 self.search_minibuffer = Search()
805 self.set_minibuffer(self.search_minibuffer)
807 def search_again(self, widget = None):
808 if self.search_minibuffer and self.search_minibuffer is self.minibuffer:
809 self.minibuffer.activate() # Search again with same text
811 if self.minibuffer is None:
812 # Search mini-buffer not yet open
813 self.search()
814 self.minibuffer.restore_previous_search()
815 self.minibuffer.search_again()
817 def search_replace(self, widget = None):
818 from search import Replace
819 Replace(self).show()
821 def toggle_bookmark(self):
822 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
823 name = str(cursor.get_line())
824 marker = self.buffer.get_marker(name)
825 if marker:
826 self.buffer.delete_marker(marker);
827 else:
828 marker = self.buffer.create_marker(name, "bookmark", cursor);
830 def next_bookmark(self):
831 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
832 cursor.forward_char()
833 marker = self.buffer.get_next_marker(cursor)
834 if marker:
835 self.buffer.get_iter_at_marker (cursor, marker)
836 self.buffer.place_cursor(cursor)
837 self.text.scroll_to_iter(cursor, 0.05, False)
839 def prev_bookmark(self):
840 cursor = self.buffer.get_iter_at_mark(self.insert_mark)
841 cursor.backward_char()
842 marker = self.buffer.get_prev_marker(cursor)
843 if marker:
844 self.buffer.get_iter_at_marker (cursor, marker)
845 self.buffer.place_cursor(cursor)
846 self.text.scroll_to_iter(cursor, 0.05, False)
848 def set_mini_label(self, label):
849 self.mini_label.set_text(label)
851 def set_minibuffer(self, minibuffer):
852 assert minibuffer is None or isinstance(minibuffer, Minibuffer)
854 if self.minibuffer:
855 self.minibuffer.close()
857 self.minibuffer = None
859 if minibuffer:
860 self.mini_entry.set_text('')
861 self.minibuffer = minibuffer
862 minibuffer.setup(self)
863 self.mini_entry.grab_focus()
864 self.mini_hbox.show_all()
865 else:
866 self.mini_hbox.hide()
867 self.text.grab_focus()
869 def mini_key_press(self, entry, kev):
870 if kev.keyval == g.keysyms.Escape:
871 self.set_minibuffer(None)
872 return 1
873 if kev.keyval == g.keysyms.Return or kev.keyval == g.keysyms.KP_Enter:
874 self.minibuffer.activate()
875 return 1
877 return self.minibuffer.key_press(kev)
879 def mini_changed(self, entry):
880 if not self.minibuffer:
881 return
882 self.minibuffer.changed()
884 def mini_show_info(self, *unused):
885 assert self.minibuffer
886 if self.info_box:
887 self.info_box.destroy()
888 self.info_box = g.MessageDialog(self, 0, g.MESSAGE_INFO, g.BUTTONS_OK,
889 self.minibuffer.info)
890 self.info_box.set_title(_('Minibuffer help'))
891 def destroy(box):
892 self.info_box = None
893 self.info_box.connect('destroy', destroy)
894 self.info_box.show()
895 self.info_box.connect('response', lambda w, r: w.destroy())
897 def process_selected(self, process):
898 """Calls process(line) on each line in the selection, or each line in the file
899 if there is no selection. If the result is not None, the text is replaced."""
900 self.buffer.begin_user_action()
901 try:
902 self._process_selected(process)
903 finally:
904 self.buffer.end_user_action()
906 def _process_selected(self, process):
907 if self.has_selection():
908 def get_end():
909 start, end = self.get_selection_range()
910 if start.compare(end) > 0:
911 return start
912 return end
913 start, end = self.get_selection_range()
914 if start.compare(end) > 0:
915 start = end
916 else:
917 def get_end():
918 return self.buffer.get_end_iter()
919 start = self.buffer.get_start_iter()
920 end = get_end()
922 while start.compare(end) <= 0:
923 line_end = start.copy()
924 line_end.forward_to_line_end()
925 if line_end.compare(end) >= 0:
926 line_end = end
927 line = self.buffer.get_text(start, line_end, False)
928 new = process(line)
929 if new is not None:
930 self.buffer.move_mark(self.mark_tmp, start)
931 self.buffer.insert(line_end, new)
932 start = self.buffer.get_iter_at_mark(self.mark_tmp)
933 line_end = start.copy()
934 line_end.forward_chars(len(line.decode('utf-8')))
935 self.buffer.delete(start, line_end)
937 start = self.buffer.get_iter_at_mark(self.mark_tmp)
938 end = get_end()
939 if not start.forward_line(): break
941 def set_word_wrap(self, value):
942 self._word_wrap = value
943 self.wrap_button.set_active(value)
944 if value:
945 self.text.set_wrap_mode(g.WRAP_WORD)
946 else:
947 self.text.set_wrap_mode(g.WRAP_NONE)
949 word_wrap = property(lambda self: self._word_wrap, set_word_wrap)
951 class SelectionSaver(Saveable):
952 def __init__(self, window):
953 self.window = window
954 window.set_marked()
956 def save_to_stream(self, stream):
957 s, e = self.window.get_marked_range()
958 stream.write(self.window.buffer.get_text(s, e, True))
960 def destroy(self):
961 # Called when savebox is remove. Get rid of the selection marker
962 self.window.clear_marked()