2 from rox
import g
, filer
, app_options
4 from rox
.loading
import XDSLoader
5 from rox
.options
import Option
6 from buffer import Buffer
7 from rox
.saving
import Saveable
13 from rox
.Menu
import Menu
, set_save_name
15 default_font
= Option('default_font', 'serif')
17 background_colour
= Option('background', '#fff')
18 foreground_colour
= Option('foreground', '#000')
20 auto_indent
= Option('autoindent', '1')
22 layout_left_margin
= Option('layout_left_margin', 2)
23 layout_right_margin
= Option('layout_right_margin', 4)
25 layout_before_para
= Option('layout_before_para', 0)
26 layout_after_para
= Option('layout_after_para', 0)
27 layout_inside_para
= Option('layout_inside_para', 0)
28 layout_indent_para
= Option('layout_indent_para', 2)
34 (_('/File'), '', '<Branch>'),
35 (_('/File/') + _('Save'), 'save', '<StockItem>', '<Ctrl>S', g
.STOCK_SAVE
),
36 (_('/File/') + _('Open Parent'), 'up', '<StockItem>', '', g
.STOCK_GO_UP
),
37 (_('/File/') + _('Close'), 'close', '<StockItem>', '', g
.STOCK_CLOSE
),
38 (_('/File/'), '', '<Separator>'),
39 (_('/File/') + _('New'), 'new', '<StockItem>', '', g
.STOCK_NEW
),
41 (_('/Edit'), '', '<Branch>'),
42 (_('/Edit/') + _('Cut'), 'cut', '<StockItem>', '<Ctrl>X', g
.STOCK_CUT
),
43 (_('/Edit/') + _('Copy'), 'copy', '<StockItem>', '<Ctrl>C', g
.STOCK_COPY
),
44 (_('/Edit/') + _('Paste'), 'paste', '<StockItem>', '<Ctrl>V', g
.STOCK_PASTE
),
45 (_('/Edit/'), '', '<Separator>'),
46 (_('/Edit/') + _('Undo'), 'undo', '<StockItem>', '<Ctrl>Z', g
.STOCK_UNDO
),
47 (_('/Edit/') + _('Redo'), 'redo', '<StockItem>', '<Ctrl>Y', g
.STOCK_REDO
),
48 (_('/Edit/'), '', '<Separator>'),
49 (_('/Edit/') + _('Search...'), 'search', '<StockItem>', 'F4', g
.STOCK_FIND
),
50 (_('/Edit/') + _('Search and Replace....'), 'search_replace',
51 '<StockItem>', '<Ctrl>F4', g
.STOCK_FIND_AND_REPLACE
),
52 (_('/Edit/') + _('Goto line...'), 'goto', '<StockItem>', 'F5', g
.STOCK_JUMP_TO
),
54 (_('/Options'), 'show_options', '<StockItem>', '', g
.STOCK_PROPERTIES
),
55 (_('/Help'), 'help', '<StockItem>', 'F1', g
.STOCK_HELP
),
58 rox
.croak(_('Edit requires ROX-Lib2 1.9.8 or later'))
61 "iso8859_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5",
62 "iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10",
63 "iso8859_13", "iso8859_14", "iso8859_15",
64 "ascii", "base64_codec", "charmap",
65 "cp037", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252",
66 "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp424",
67 "cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856",
68 "cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866",
69 "cp869", "cp874", "cp875", "hex_codec",
72 "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish",
73 "mbcs", "quopri_codec", "raw_unicode_escape",
75 "utf_16_be", "utf_16_le", "utf_16", "utf_7", "utf_8", "uu_codec",
79 class Abort(Exception):
84 """Called when the minibuffer is opened."""
86 def key_press(self
, kev
):
87 """A keypress event in the minibuffer text entry."""
90 """The minibuffer text has changed."""
93 """Return or Enter pressed."""
95 info
= 'Press Escape to close the minibuffer.'
97 class EditWindow(rox
.Window
, XDSLoader
, Saveable
):
98 def __init__(self
, filename
= None):
99 rox
.Window
.__init
__(self
)
100 XDSLoader
.__init
__(self
, ['text/plain', 'UTF8_STRING'])
101 self
.set_default_size(g
.gdk
.screen_width() * 2 / 3,
102 g
.gdk
.screen_height() / 2)
107 app_options
.add_notify(self
.update_styles
)
109 self
.buffer = Buffer()
111 self
.text
= g
.TextView()
112 self
.text
.set_buffer(self
.buffer)
113 self
.text
.set_size_request(10, 10)
114 self
.xds_proxy_for(self
.text
)
115 self
.text
.set_wrap_mode(g
.WRAP_WORD
)
118 self
.insert_mark
= self
.buffer.get_mark('insert')
119 self
.selection_bound_mark
= self
.buffer.get_mark('selection_bound')
120 start
= self
.buffer.get_start_iter()
121 self
.mark_start
= self
.buffer.create_mark('mark_start', start
, TRUE
)
122 self
.mark_end
= self
.buffer.create_mark('mark_end', start
, FALSE
)
123 self
.mark_tmp
= self
.buffer.create_mark('mark_tmp', start
, FALSE
)
124 tag
= self
.buffer.create_tag('marked')
125 tag
.set_property('background', 'green')
128 # When searching, this is where the cursor was when the minibuffer
130 start
= self
.buffer.get_start_iter()
131 self
.search_base
= self
.buffer.create_mark('search_base', start
, TRUE
)
137 tools
.set_style(g
.TOOLBAR_ICONS
)
138 vbox
.pack_start(tools
, FALSE
, TRUE
, 0)
141 tools
.insert_stock(g
.STOCK_HELP
, _('Help'), None, self
.help, None, 0)
142 tools
.insert_stock(g
.STOCK_REDO
, _('Redo'), None, self
.redo
, None, 0)
143 tools
.insert_stock(g
.STOCK_UNDO
, _('Undo'), None, self
.undo
, None, 0)
144 tools
.insert_stock(g
.STOCK_FIND_AND_REPLACE
, _('Replace'), None, self
.search_replace
, None, 0)
145 tools
.insert_stock(g
.STOCK_FIND
, _('Search'), None, self
.search
, None, 0)
146 tools
.insert_stock(g
.STOCK_SAVE
, _('Save'), None, self
.save
, None, 0)
147 tools
.insert_stock(g
.STOCK_GO_UP
, _('Up'), None, self
.up
, None, 0)
148 tools
.insert_stock(g
.STOCK_CLOSE
, _('Close'), None, self
.close
, None, 0)
150 swin
= g
.ScrolledWindow()
151 swin
.set_policy(g
.POLICY_NEVER
, g
.POLICY_AUTOMATIC
)
152 vbox
.pack_start(swin
, True, True)
158 # Create the minibuffer
159 self
.mini_hbox
= g
.HBox(FALSE
)
161 info
.set_relief(g
.RELIEF_NONE
)
162 info
.unset_flags(g
.CAN_FOCUS
)
164 image
.set_from_stock(g
.STOCK_DIALOG_INFO
, size
= g
.ICON_SIZE_SMALL_TOOLBAR
)
167 info
.connect('clicked', self
.mini_show_info
)
169 self
.mini_hbox
.pack_start(info
, FALSE
, TRUE
, 0)
170 self
.mini_label
= g
.Label('')
171 self
.mini_hbox
.pack_start(self
.mini_label
, FALSE
, TRUE
, 0)
172 self
.mini_entry
= g
.Entry()
173 self
.mini_hbox
.pack_start(self
.mini_entry
, TRUE
, TRUE
, 0)
174 vbox
.pack_start(self
.mini_hbox
, FALSE
, TRUE
)
175 self
.mini_entry
.connect('key-press-event', self
.mini_key_press
)
176 self
.mini_entry
.connect('changed', self
.mini_changed
)
178 self
.connect('destroy', self
.destroyed
)
180 self
.connect('delete-event', self
.delete_event
)
181 self
.text
.grab_focus()
182 self
.text
.connect('key-press-event', self
.key_press
)
186 self
.uri
= os
.path
.abspath(filename
)
191 # Loading might take a while, so get something on the screen
197 self
.load_file(filename
)
199 self
.save_mode
= os
.stat(filename
).st_mode
204 self
.buffer.connect('modified-changed', self
.update_title
)
205 self
.buffer.set_modified(FALSE
)
207 self
.text
.connect('button-press-event', self
.button_press
)
209 menu
.attach(self
, self
)
210 self
.buffer.place_cursor(self
.buffer.get_start_iter())
211 self
.buffer.start_undo_history()
213 def key_press(self
, text
, kev
):
214 if kev
.keyval
!= g
.keysyms
.Return
and kev
.keyval
!= g
.keysyms
.KP_Enter
:
216 if not auto_indent
.int_value
:
218 start
= self
.buffer.get_iter_at_mark(self
.insert_mark
)
220 start
.set_line_offset(0)
221 end
.forward_to_line_end()
222 line
= self
.buffer.get_text(start
, end
, False)
229 self
.buffer.begin_user_action()
230 self
.buffer.insert_at_cursor('\n' + indent
)
231 self
.buffer.end_user_action()
234 def destroyed(self
, widget
):
235 app_options
.remove_notify(self
.update_styles
)
237 def update_styles(self
):
240 font
= pango
.FontDescription(default_font
.value
)
241 bg
= g
.gdk
.color_parse(background_colour
.value
)
242 fg
= g
.gdk
.color_parse(foreground_colour
.value
)
244 self
.text
.set_left_margin(layout_left_margin
.int_value
)
245 self
.text
.set_right_margin(layout_right_margin
.int_value
)
247 self
.text
.set_pixels_above_lines(layout_before_para
.int_value
)
248 self
.text
.set_pixels_below_lines(layout_after_para
.int_value
)
249 self
.text
.set_pixels_inside_wrap(layout_inside_para
.int_value
)
250 self
.text
.set_indent(layout_indent_para
.int_value
)
252 rox
.report_exception()
254 self
.text
.modify_font(font
)
255 self
.text
.modify_base(g
.STATE_NORMAL
, bg
)
256 self
.text
.modify_text(g
.STATE_NORMAL
, fg
)
258 def cut(self
): self
.text
.emit('cut_clipboard')
259 def copy(self
): self
.text
.emit('copy_clipboard')
260 def paste(self
): self
.text
.emit('paste_clipboard')
262 def button_press(self
, text
, event
):
263 if event
.button
!= 3:
265 menu
.popup(self
, event
)
268 def delete_event(self
, window
, event
):
269 if self
.buffer.get_modified():
270 self
.save(discard
= 1)
274 def update_title(self
, *unused
):
275 title
= self
.uri
or '<Untitled>'
276 if self
.buffer.get_modified():
278 self
.set_title(title
)
280 def xds_load_from_stream(self
, name
, t
, stream
):
281 if t
== 'UTF8_STRING':
282 return # Gtk will handle it
284 self
.insert_data(stream
.read())
288 def get_encoding(self
, message
):
289 "Returns (encoding, errors), or raises Abort to cancel."
292 box
= g
.MessageDialog(self
, 0, g
.MESSAGE_QUESTION
, g
.BUTTONS_CANCEL
, message
)
293 box
.set_has_separator(FALSE
)
296 box
.vbox
.pack_start(frame
, TRUE
, TRUE
)
297 frame
.set_border_width(6)
299 hbox
= g
.HBox(FALSE
, 4)
300 hbox
.set_border_width(6)
302 hbox
.pack_start(g
.Label('Encoding:'), FALSE
, TRUE
, 0)
304 combo
.disable_activate()
305 combo
.entry
.connect('activate', lambda w
: box
.activate_default())
306 combo
.set_popdown_strings(known_codecs
)
307 hbox
.pack_start(combo
, TRUE
, TRUE
, 0)
308 ignore_errors
= g
.CheckButton('Ignore errors')
309 hbox
.pack_start(ignore_errors
, FALSE
, TRUE
)
314 box
.add_button(g
.STOCK_CONVERT
, g
.RESPONSE_YES
)
315 box
.set_default_response(g
.RESPONSE_YES
)
318 combo
.entry
.grab_focus()
321 if resp
!= g
.RESPONSE_YES
:
325 if ignore_errors
.get_active():
329 encoding
= combo
.entry
.get_text()
331 codecs
.getdecoder(encoding
)
334 rox
.alert(_("Unknown encoding '%s'") % encoding
)
338 return encoding
, errors
340 def insert_data(self
, data
):
345 decoder
= codecs
.getdecoder(encoding
)
347 data
= decoder(data
, errors
)[0]
348 if errors
== 'strict':
349 assert '\0' not in data
352 data
= data
.replace('\0', '\\0')
357 encoding
, errors
= self
.get_encoding(
358 _("Data is not valid %s. Please select the file's encoding. "
359 "Turn on 'ignore errors' to try and load it anyway.")
362 self
.buffer.begin_user_action()
363 self
.buffer.insert_at_cursor(data
)
364 self
.buffer.end_user_action()
367 def load_file(self
, path
):
372 file = open(path
, 'r')
373 contents
= file.read()
376 self
.insert_data(contents
)
380 rox
.report_exception()
383 def close(self
, button
= None):
384 if self
.buffer.get_modified():
385 self
.save(discard
= 1)
392 def up(self
, button
= None):
394 filer
.show_file(self
.uri
)
396 rox
.alert(_('File is not saved to disk yet'))
398 def has_selection(self
):
399 s
, e
= self
.get_selection_range()
400 return not e
.equal(s
)
402 def get_marked_range(self
):
403 s
= self
.buffer.get_iter_at_mark(self
.mark_start
)
404 e
= self
.buffer.get_iter_at_mark(self
.mark_end
)
409 def get_selection_range(self
):
410 s
= self
.buffer.get_iter_at_mark(self
.insert_mark
)
411 e
= self
.buffer.get_iter_at_mark(self
.selection_bound_mark
)
416 def save(self
, widget
= None, discard
= 0):
417 from rox
.saving
import SaveBox
420 self
.savebox
.destroy()
422 if self
.has_selection() and not discard
:
423 saver
= SelectionSaver(self
)
424 self
.savebox
= SaveBox(saver
, 'Selection', 'text/plain')
425 self
.savebox
.connect('destroy', lambda w
: saver
.destroy())
427 uri
= self
.uri
or 'TextFile'
428 self
.savebox
= SaveBox(self
, uri
, 'text/plain', discard
)
431 def help(self
, button
= None):
432 filer
.open_dir(os
.path
.join(rox
.app_dir
, 'Help'))
434 def save_to_stream(self
, stream
):
435 s
= self
.buffer.get_start_iter()
436 e
= self
.buffer.get_end_iter()
437 stream
.write(self
.buffer.get_text(s
, e
, TRUE
))
439 def set_uri(self
, uri
):
441 self
.buffer.set_modified(FALSE
)
447 def change_font(self
):
448 style
= self
.text
.get_style().copy()
449 style
.font
= load_font(options
.get('edit_font'))
450 self
.text
.set_style(style
)
452 def show_options(self
):
455 def set_marked(self
, start
= None, end
= None):
456 "Set the marked region (from the selection if no region is given)."
458 assert not self
.marked
465 start
, end
= self
.get_selection_range()
466 buffer.move_mark(self
.mark_start
, start
)
467 buffer.move_mark(self
.mark_end
, end
)
468 buffer.apply_tag_by_name('marked',
469 buffer.get_iter_at_mark(self
.mark_start
),
470 buffer.get_iter_at_mark(self
.mark_end
))
473 def clear_marked(self
):
478 buffer.remove_tag_by_name('marked',
479 buffer.get_iter_at_mark(self
.mark_start
),
480 buffer.get_iter_at_mark(self
.mark_end
))
482 def undo(self
, widget
= None):
485 def redo(self
, widget
= None):
488 def goto(self
, widget
= None):
489 from goto
import Goto
490 self
.set_minibuffer(Goto())
492 def search(self
, widget
= None):
493 from search
import Search
494 self
.set_minibuffer(Search())
496 def search_replace(self
, widget
= None):
497 from search
import Replace
500 def set_mini_label(self
, label
):
501 self
.mini_label
.set_text(label
)
503 def set_minibuffer(self
, minibuffer
):
504 assert minibuffer
is None or isinstance(minibuffer
, Minibuffer
)
506 self
.minibuffer
= None
509 self
.mini_entry
.set_text('')
510 self
.minibuffer
= minibuffer
511 minibuffer
.setup(self
)
512 self
.mini_entry
.grab_focus()
513 self
.mini_hbox
.show_all()
515 self
.mini_hbox
.hide()
516 self
.text
.grab_focus()
518 def mini_key_press(self
, entry
, kev
):
519 if kev
.keyval
== g
.keysyms
.Escape
:
520 self
.set_minibuffer(None)
522 if kev
.keyval
== g
.keysyms
.Return
or kev
.keyval
== g
.keysyms
.KP_Enter
:
523 self
.minibuffer
.activate()
526 return self
.minibuffer
.key_press(kev
)
528 def mini_changed(self
, entry
):
529 if not self
.minibuffer
:
531 self
.minibuffer
.changed()
533 def mini_show_info(self
, *unused
):
534 assert self
.minibuffer
536 self
.info_box
.destroy()
537 self
.info_box
= g
.MessageDialog(self
, 0, g
.MESSAGE_INFO
, g
.BUTTONS_OK
,
538 self
.minibuffer
.info
)
539 self
.info_box
.set_title(_('Minibuffer help'))
542 self
.info_box
.connect('destroy', destroy
)
544 self
.info_box
.connect('response', lambda w
, r
: w
.destroy())
546 def process_selected(self
, process
):
547 """Calls process(line) on each line in the selection, or each line in the file
548 if there is no selection. If the result is not None, the text is replaced."""
549 self
.buffer.begin_user_action()
550 self
._process
_selected
(process
)
551 self
.buffer.end_user_action()
553 def _process_selected(self
, process
):
554 if self
.has_selection():
556 start
, end
= self
.get_selection_range()
557 if start
.compare(end
) > 0:
560 start
, end
= self
.get_selection_range()
561 if start
.compare(end
) > 0:
565 return self
.buffer.get_end_iter()
566 start
= self
.buffer.get_start_iter()
569 while start
.compare(end
) <= 0:
570 line_end
= start
.copy()
571 line_end
.forward_to_line_end()
572 if line_end
.compare(end
) >= 0:
574 line
= self
.buffer.get_text(start
, line_end
, False)
577 self
.buffer.move_mark(self
.mark_tmp
, start
)
578 self
.buffer.insert(line_end
, new
)
579 start
= self
.buffer.get_iter_at_mark(self
.mark_tmp
)
580 line_end
= start
.copy()
581 line_end
.forward_chars(len(line
))
582 self
.buffer.delete(start
, line_end
)
584 start
= self
.buffer.get_iter_at_mark(self
.mark_tmp
)
586 if not start
.forward_line(): break
588 class SelectionSaver(Saveable
):
589 def __init__(self
, window
):
593 def save_to_stream(self
, stream
):
594 s
, e
= self
.window
.get_marked_range()
595 stream
.write(self
.window
.buffer.get_text(s
, e
, TRUE
))
598 # Called when savebox is remove. Get rid of the selection marker
599 self
.window
.clear_marked()