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
12 from rox
.Menu
import Menu
, set_save_name
14 default_font
= Option('default_font', 'serif')
16 background_colour
= Option('background', '#fff')
17 foreground_colour
= Option('foreground', '#000')
22 ('/File', '', '<Branch>'),
23 ('/File/Save', 'save', ''),
24 ('/File/Open Parent', 'up', ''),
25 ('/File/Close', 'close', ''),
26 ('/File/', '', '<Separator>'),
27 ('/File/New', 'new', ''),
28 ('/Edit', '', '<Branch>'),
29 ('/Edit/Undo', 'undo', ''),
30 ('/Edit/Redo', 'redo', ''),
31 ('/Edit/', '', '<Separator>'),
32 ('/Edit/Search...', 'search', ''),
33 ('/Edit/Goto line...', 'goto', ''),
34 ('/Edit/', '', '<Separator>'),
35 ('/Edit/Process...', 'process', ''),
36 ('/Options', 'show_options', ''),
37 ('/Help', 'help', '', 'F1'),
41 "iso8859_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5",
42 "iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10",
43 "iso8859_13", "iso8859_14", "iso8859_15",
44 "ascii", "base64_codec", "charmap",
45 "cp037", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252",
46 "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp424",
47 "cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856",
48 "cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866",
49 "cp869", "cp874", "cp875", "hex_codec",
52 "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish",
53 "mbcs", "quopri_codec", "raw_unicode_escape",
55 "utf_16_be", "utf_16_le", "utf_16", "utf_7", "utf_8", "uu_codec",
59 class Abort(Exception):
64 """Called when the minibuffer is opened."""
66 def key_press(self
, kev
):
67 """A keypress event in the minibuffer text entry."""
70 """The minibuffer text has changed."""
73 """Return or Enter pressed."""
75 info
= 'Press Escape to close the minibuffer.'
77 class EditWindow(g
.Window
, XDSLoader
, Saveable
):
78 def __init__(self
, filename
= None):
79 g
.Window
.__init
__(self
)
80 XDSLoader
.__init
__(self
, ['text/plain', 'UTF8_STRING'])
81 self
.set_default_size(g
.gdk
.screen_width() * 2 / 3,
82 g
.gdk
.screen_height() / 2)
87 app_options
.add_notify(self
.update_styles
)
89 self
.buffer = Buffer()
91 scrollbar
= g
.VScrollbar()
92 self
.text
= g
.TextView()
93 self
.text
.set_property('left-margin', 4)
94 self
.text
.set_property('right-margin', 4)
95 self
.text
.set_buffer(self
.buffer)
96 adj
= scrollbar
.get_adjustment()
97 self
.text
.set_scroll_adjustments(None, adj
)
98 self
.text
.set_size_request(10, 10)
99 self
.xds_proxy_for(self
.text
)
100 self
.text
.set_wrap_mode(g
.WRAP_WORD
)
103 self
.insert_mark
= self
.buffer.get_mark('insert')
104 self
.selection_bound_mark
= self
.buffer.get_mark('selection_bound')
105 start
= self
.buffer.get_start_iter()
106 self
.mark_start
= self
.buffer.create_mark('mark_start', start
, TRUE
)
107 self
.mark_end
= self
.buffer.create_mark('mark_end', start
, FALSE
)
108 tag
= self
.buffer.create_tag('marked')
109 tag
.set_property('background', 'green')
112 # When searching, this is where the cursor was when the minibuffer
114 start
= self
.buffer.get_start_iter()
115 self
.search_base
= self
.buffer.create_mark('search_base', start
, TRUE
)
121 tools
.set_style(g
.TOOLBAR_ICONS
)
122 vbox
.pack_start(tools
, FALSE
, TRUE
, 0)
125 tools
.insert_stock(g
.STOCK_HELP
, 'Help', None, self
.help, None, 0)
126 tools
.insert_stock(g
.STOCK_REDO
, 'Redo', None, self
.redo
, None, 0)
127 tools
.insert_stock(g
.STOCK_UNDO
, 'Undo', None, self
.undo
, None, 0)
128 tools
.insert_stock(g
.STOCK_FIND
, 'Search', None, self
.search
, None, 0)
129 tools
.insert_stock(g
.STOCK_SAVE
, 'Save', None, self
.save
, None, 0)
130 tools
.insert_stock(g
.STOCK_GO_UP
, 'Up', None, self
.up
, None, 0)
131 tools
.insert_stock(g
.STOCK_CLOSE
, 'Close', None, self
.close
, None, 0)
133 hbox
= g
.HBox(FALSE
) # View + Minibuffer + Scrollbar
134 vbox
.pack_start(hbox
, TRUE
, TRUE
)
136 inner_vbox
= g
.VBox(FALSE
) # View + Minibuffer
137 hbox
.pack_start(inner_vbox
, TRUE
, TRUE
)
138 inner_vbox
.pack_start(self
.text
, TRUE
, TRUE
)
139 hbox
.pack_start(scrollbar
, FALSE
, TRUE
)
143 # Create the minibuffer
144 self
.mini_hbox
= g
.HBox(FALSE
)
146 info
.set_relief(g
.RELIEF_NONE
)
147 info
.unset_flags(g
.CAN_FOCUS
)
149 image
.set_from_stock(g
.STOCK_DIALOG_INFO
, size
= g
.ICON_SIZE_SMALL_TOOLBAR
)
152 info
.connect('clicked', self
.mini_show_info
)
154 self
.mini_hbox
.pack_start(info
, FALSE
, TRUE
, 0)
155 self
.mini_label
= g
.Label('')
156 self
.mini_hbox
.pack_start(self
.mini_label
, FALSE
, TRUE
, 0)
157 self
.mini_entry
= g
.Entry()
158 self
.mini_hbox
.pack_start(self
.mini_entry
, TRUE
, TRUE
, 0)
159 inner_vbox
.pack_start(self
.mini_hbox
, FALSE
, TRUE
)
160 self
.mini_entry
.connect('key-press-event', self
.mini_key_press
)
161 self
.mini_entry
.connect('changed', self
.mini_changed
)
164 self
.connect('destroy', self
.destroyed
)
166 self
.connect('delete-event', self
.delete_event
)
167 self
.text
.grab_focus()
171 self
.uri
= os
.path
.abspath(filename
)
176 # Loading might take a while, so get something on the screen
182 self
.load_file(filename
)
184 self
.save_mode
= os
.stat(filename
).st_mode
189 self
.buffer.connect('modified-changed', self
.update_title
)
190 self
.buffer.set_modified(FALSE
)
192 self
.text
.connect('button-press-event', self
.button_press
)
194 menu
.attach(self
, self
)
195 self
.buffer.place_cursor(self
.buffer.get_start_iter())
196 self
.buffer.start_undo_history()
198 def destroyed(self
, widget
):
199 app_options
.remove_notify(self
.update_styles
)
202 def update_styles(self
):
205 font
= pango
.FontDescription(default_font
.value
)
206 bg
= g
.gdk
.color_parse(background_colour
.value
)
207 fg
= g
.gdk
.color_parse(foreground_colour
.value
)
209 rox
.report_exception()
211 self
.text
.modify_font(font
)
212 self
.text
.modify_base(g
.STATE_NORMAL
, bg
)
213 self
.text
.modify_text(g
.STATE_NORMAL
, fg
)
215 def button_press(self
, text
, event
):
216 if event
.button
!= 3:
218 menu
.popup(self
, event
)
221 def delete_event(self
, window
, event
):
222 if self
.buffer.get_modified():
223 self
.save(discard
= 1)
227 def update_title(self
, *unused
):
228 title
= self
.uri
or '<Untitled>'
229 if self
.buffer.get_modified():
231 self
.set_title(title
)
233 def xds_load_from_stream(self
, name
, t
, stream
):
234 if t
== 'UTF8_STRING':
235 return # Gtk will handle it
237 self
.insert_data(stream
.read())
241 def get_encoding(self
, message
):
242 "Returns (encoding, errors), or raises Abort to cancel."
245 box
= g
.MessageDialog(self
, 0, g
.MESSAGE_QUESTION
, g
.BUTTONS_CANCEL
, message
)
246 box
.set_has_separator(FALSE
)
249 box
.vbox
.pack_start(frame
, TRUE
, TRUE
)
250 frame
.set_border_width(6)
252 hbox
= g
.HBox(FALSE
, 4)
253 hbox
.set_border_width(6)
255 hbox
.pack_start(g
.Label('Encoding:'), FALSE
, TRUE
, 0)
257 combo
.disable_activate()
258 combo
.entry
.connect('activate', lambda w
: box
.activate_default())
259 combo
.set_popdown_strings(known_codecs
)
260 hbox
.pack_start(combo
, TRUE
, TRUE
, 0)
261 ignore_errors
= g
.CheckButton('Ignore errors')
262 hbox
.pack_start(ignore_errors
, FALSE
, TRUE
)
267 box
.add_button(g
.STOCK_CONVERT
, g
.RESPONSE_YES
)
268 box
.set_default_response(g
.RESPONSE_YES
)
271 combo
.entry
.grab_focus()
274 if resp
!= g
.RESPONSE_YES
:
278 if ignore_errors
.get_active():
282 encoding
= combo
.entry
.get_text()
284 codecs
.getdecoder(encoding
)
287 rox
.alert("Unknown encoding '%s'" % encoding
)
291 return encoding
, errors
293 def insert_data(self
, data
):
298 decoder
= codecs
.getdecoder(encoding
)
300 data
= decoder(data
, errors
)[0]
301 assert '\0' not in data
306 encoding
, errors
= self
.get_encoding(
307 "Data is not valid %s. Please select the file's encoding."
308 "Turn on 'ignore errors' to try and load it anyway." % encoding
)
310 self
.buffer.insert_at_cursor(data
, -1)
313 def load_file(self
, path
):
318 file = open(path
, 'r')
319 contents
= file.read()
322 self
.insert_data(contents
)
326 rox
.report_exception()
329 def close(self
, button
= None):
330 if self
.buffer.get_modified():
331 self
.save(discard
= 1)
338 def up(self
, button
= None):
340 filer
.show_file(self
.uri
)
342 rox
.alert("File is not saved to disk yet")
344 def has_selection(self
):
345 s
, e
= self
.get_selection_range()
346 return not e
.equal(s
)
348 def get_marked_range(self
):
349 s
= self
.buffer.get_iter_at_mark(self
.mark_start
)
350 e
= self
.buffer.get_iter_at_mark(self
.mark_end
)
353 def get_selection_range(self
):
354 s
= self
.buffer.get_iter_at_mark(self
.insert_mark
)
355 e
= self
.buffer.get_iter_at_mark(self
.selection_bound_mark
)
358 def save(self
, widget
= None, discard
= 0):
359 from rox
.saving
import SaveBox
362 self
.savebox
.destroy()
364 if self
.has_selection() and not discard
:
365 saver
= SelectionSaver(self
)
366 self
.savebox
= SaveBox(saver
, 'Selection', 'text/plain')
367 self
.savebox
.connect('destroy', lambda w
: saver
.destroy())
369 uri
= self
.uri
or 'TextFile'
370 self
.savebox
= SaveBox(self
, uri
, 'text/plain', discard
)
373 def help(self
, button
= None):
374 filer
.open_dir(rox
.app_dir
+ '/Help')
376 def save_to_stream(self
, stream
):
377 s
= self
.buffer.get_start_iter()
378 e
= self
.buffer.get_end_iter()
379 stream
.write(self
.buffer.get_text(s
, e
, TRUE
))
381 def set_uri(self
, uri
):
383 self
.buffer.set_modified(FALSE
)
389 def change_font(self
):
390 style
= self
.text
.get_style().copy()
391 style
.font
= load_font(options
.get('edit_font'))
392 self
.text
.set_style(style
)
394 def show_options(self
):
397 def set_marked(self
, start
= None, end
= None):
398 "Set the marked region (from the selection if no region is given)."
400 assert not self
.marked
407 start
, end
= self
.get_selection_range()
408 buffer.move_mark(self
.mark_start
, start
)
409 buffer.move_mark(self
.mark_end
, end
)
410 buffer.apply_tag_by_name('marked',
411 buffer.get_iter_at_mark(self
.mark_start
),
412 buffer.get_iter_at_mark(self
.mark_end
))
415 def clear_marked(self
):
420 buffer.remove_tag_by_name('marked',
421 buffer.get_iter_at_mark(self
.mark_start
),
422 buffer.get_iter_at_mark(self
.mark_end
))
424 def undo(self
, widget
= None):
427 def redo(self
, widget
= None):
430 def goto(self
, widget
= None):
431 from goto
import Goto
432 self
.set_minibuffer(Goto())
434 def search(self
, widget
= None):
435 from search
import Search
436 self
.set_minibuffer(Search())
438 def process(self
, widget
= None):
439 from process
import Process
440 self
.set_minibuffer(Process())
442 def set_mini_label(self
, label
):
443 self
.mini_label
.set_text(label
)
445 def set_minibuffer(self
, minibuffer
):
446 assert minibuffer
is None or isinstance(minibuffer
, Minibuffer
)
448 self
.minibuffer
= None
451 self
.mini_entry
.set_text('')
452 self
.minibuffer
= minibuffer
453 minibuffer
.setup(self
)
454 self
.mini_entry
.grab_focus()
455 self
.mini_hbox
.show_all()
457 self
.mini_hbox
.hide()
458 self
.text
.grab_focus()
460 def mini_key_press(self
, entry
, kev
):
461 if kev
.keyval
== g
.keysyms
.Escape
:
462 self
.set_minibuffer(None)
464 if kev
.keyval
== g
.keysyms
.Return
or kev
.keyval
== g
.keysyms
.KP_Enter
:
465 self
.minibuffer
.activate()
468 return self
.minibuffer
.key_press(kev
)
470 def mini_changed(self
, entry
):
471 if not self
.minibuffer
:
473 self
.minibuffer
.changed()
475 def mini_show_info(self
, *unused
):
476 assert self
.minibuffer
478 self
.info_box
.destroy()
479 self
.info_box
= g
.MessageDialog(self
, 0, g
.MESSAGE_INFO
, g
.BUTTONS_OK
,
480 self
.minibuffer
.info
)
481 self
.info_box
.set_title('Minibuffer help')
484 self
.info_box
.connect('destroy', destroy
)
486 self
.info_box
.connect('response', lambda w
, r
: w
.destroy())
488 class SelectionSaver(Saveable
):
489 def __init__(self
, window
):
493 def save_to_stream(self
, stream
):
494 s
, e
= self
.window
.get_marked_range()
495 stream
.write(self
.window
.buffer.get_text(s
, e
, TRUE
))
498 # Called when savebox is remove. Get rid of the selection marker
499 self
.window
.clear_marked()