2 from rox
import g
, filer
, app_options
4 from rox
.XDSLoader
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_name
= Option('default_name', 'TextFile')
15 default_font
= Option('default_font', 'serif')
17 background_colour
= Option('background', '#fff')
18 foreground_colour
= Option('foreground', '#000')
23 ('/File', '', '<Branch>'),
24 ('/File/Save', 'save', ''),
25 ('/File/Open Parent', 'up', ''),
26 ('/File/Close', 'close', ''),
27 ('/File/', '', '<Separator>'),
28 ('/File/New', 'new', ''),
29 ('/Edit', '', '<Branch>'),
30 ('/Edit/Undo', 'undo', ''),
31 ('/Edit/Redo', 'redo', ''),
32 ('/Edit/', '', '<Separator>'),
33 ('/Edit/Search...', 'search', ''),
34 ('/Edit/Goto line...', 'goto', ''),
35 ('/Edit/', '', '<Separator>'),
36 ('/Edit/Process...', 'process', ''),
37 ('/Options', 'show_options', ''),
38 ('/Help', 'help', '', 'F1'),
42 "iso8859_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5",
43 "iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10",
44 "iso8859_13", "iso8859_14", "iso8859_15",
45 "ascii", "base64_codec", "charmap",
46 "cp037", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252",
47 "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp424",
48 "cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856",
49 "cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866",
50 "cp869", "cp874", "cp875", "hex_codec",
53 "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish",
54 "mbcs", "quopri_codec", "raw_unicode_escape",
56 "utf_16_be", "utf_16_le", "utf_16", "utf_7", "utf_8", "uu_codec",
60 class Abort(Exception):
63 class EditWindow(g
.Window
, XDSLoader
, Saveable
):
64 def __init__(self
, filename
= None):
65 g
.Window
.__init
__(self
)
66 XDSLoader
.__init
__(self
, ['text/plain', 'UTF8_STRING'])
67 self
.set_default_size(g
.gdk
.screen_width() * 2 / 3,
68 g
.gdk
.screen_height() / 2)
72 app_options
.add_notify(self
.update_styles
)
74 self
.buffer = Buffer()
76 scrollbar
= g
.VScrollbar()
77 self
.text
= g
.TextView()
78 self
.text
.set_property('left-margin', 4)
79 self
.text
.set_property('right-margin', 4)
80 self
.text
.set_buffer(self
.buffer)
81 adj
= scrollbar
.get_adjustment()
82 self
.text
.set_scroll_adjustments(None, adj
)
83 self
.text
.set_size_request(10, 10)
84 self
.xds_proxy_for(self
.text
)
85 self
.text
.set_wrap_mode(g
.WRAP_WORD
)
88 self
.insert_mark
= self
.buffer.get_mark('insert')
89 self
.selection_bound_mark
= self
.buffer.get_mark('selection_bound')
90 start
= self
.buffer.get_start_iter()
91 self
.mark_start
= self
.buffer.create_mark('mark_start', start
, TRUE
)
92 self
.mark_end
= self
.buffer.create_mark('mark_end', start
, FALSE
)
93 tag
= self
.buffer.create_tag('marked')
94 tag
.set_property('background', 'green')
97 # When searching, this is where the cursor was when the minibuffer
99 start
= self
.buffer.get_start_iter()
100 self
.search_base
= self
.buffer.create_mark('search_base', start
, TRUE
)
106 tools
.set_style(g
.TOOLBAR_ICONS
)
107 vbox
.pack_start(tools
, FALSE
, TRUE
, 0)
110 tools
.insert_stock(g
.STOCK_HELP
, 'Help', None, self
.help, None, 0)
111 tools
.insert_stock(g
.STOCK_REDO
, 'Redo', None, self
.redo
, None, 0)
112 tools
.insert_stock(g
.STOCK_UNDO
, 'Undo', None, self
.undo
, None, 0)
113 tools
.insert_stock(g
.STOCK_FIND
, 'Search', None, self
.search
, None, 0)
114 tools
.insert_stock(g
.STOCK_SAVE
, 'Save', None, self
.save
, None, 0)
115 tools
.insert_stock(g
.STOCK_GO_UP
, 'Up', None, self
.up
, None, 0)
116 tools
.insert_stock(g
.STOCK_CLOSE
, 'Close', None, self
.close
, None, 0)
118 hbox
= g
.HBox(FALSE
) # View + Minibuffer + Scrollbar
119 vbox
.pack_start(hbox
, TRUE
, TRUE
)
121 inner_vbox
= g
.VBox(FALSE
) # View + Minibuffer
122 hbox
.pack_start(inner_vbox
, TRUE
, TRUE
)
123 inner_vbox
.pack_start(self
.text
, TRUE
, TRUE
)
124 hbox
.pack_start(scrollbar
, FALSE
, TRUE
)
128 # Create the minibuffer
129 self
.mini_hbox
= g
.HBox(FALSE
)
130 self
.mini_label
= g
.Label('')
131 self
.mini_hbox
.pack_start(self
.mini_label
, FALSE
, TRUE
, 0)
132 self
.mini_entry
= g
.Entry()
133 self
.mini_hbox
.pack_start(self
.mini_entry
, TRUE
, TRUE
, 0)
134 inner_vbox
.pack_start(self
.mini_hbox
, FALSE
, TRUE
)
135 self
.mini_entry
.connect('key-press-event', self
.mini_key_press
)
136 self
.mini_entry
.connect('changed', self
.mini_changed
)
139 self
.connect('destroy', self
.destroyed
)
141 self
.connect('delete-event', self
.delete_event
)
142 self
.text
.grab_focus()
146 self
.uri
= os
.path
.abspath(filename
)
151 # Loading might take a while, so get something on the screen
157 self
.load_file(filename
)
159 self
.save_mode
= os
.stat(filename
).st_mode
164 self
.buffer.connect('modified-changed', self
.update_title
)
165 self
.buffer.set_modified(FALSE
)
167 self
.text
.connect('button-press-event', self
.button_press
)
169 menu
.attach(self
, self
)
170 self
.buffer.place_cursor(self
.buffer.get_start_iter())
171 self
.buffer.start_undo_history()
173 def destroyed(self
, widget
):
174 app_options
.remove_notify(self
.update_styles
)
177 def update_styles(self
):
180 font
= pango
.FontDescription(default_font
.value
)
181 bg
= g
.gdk
.color_parse(background_colour
.value
)
182 fg
= g
.gdk
.color_parse(foreground_colour
.value
)
184 rox
.report_exception()
186 self
.text
.modify_font(font
)
187 self
.text
.modify_base(g
.STATE_NORMAL
, bg
)
188 self
.text
.modify_text(g
.STATE_NORMAL
, fg
)
190 def button_press(self
, text
, event
):
191 if event
.button
!= 3:
193 menu
.popup(self
, event
)
196 def delete_event(self
, window
, event
):
197 if self
.buffer.get_modified():
198 self
.save(discard
= 1)
202 def update_title(self
, *unused
):
203 title
= self
.uri
or '<Untitled>'
204 if self
.buffer.get_modified():
206 self
.set_title(title
)
208 def xds_load_from_stream(self
, name
, t
, stream
):
209 if t
== 'UTF8_STRING':
210 return # Gtk will handle it
212 self
.insert_data(stream
.read())
216 def get_encoding(self
, message
):
217 "Returns (encoding, errors), or raises Abort to cancel."
220 box
= g
.MessageDialog(self
, 0, g
.MESSAGE_QUESTION
, g
.BUTTONS_CANCEL
, message
)
221 box
.set_has_separator(FALSE
)
224 box
.vbox
.pack_start(frame
, TRUE
, TRUE
)
225 frame
.set_border_width(6)
227 hbox
= g
.HBox(FALSE
, 4)
228 hbox
.set_border_width(6)
230 hbox
.pack_start(g
.Label('Encoding:'), FALSE
, TRUE
, 0)
232 combo
.disable_activate()
233 combo
.entry
.connect('activate', lambda w
: box
.activate_default())
234 combo
.set_popdown_strings(known_codecs
)
235 hbox
.pack_start(combo
, TRUE
, TRUE
, 0)
236 ignore_errors
= g
.CheckButton('Ignore errors')
237 hbox
.pack_start(ignore_errors
, FALSE
, TRUE
)
242 box
.add_button(g
.STOCK_CONVERT
, g
.RESPONSE_YES
)
243 box
.set_default_response(g
.RESPONSE_YES
)
246 combo
.entry
.grab_focus()
249 if resp
!= g
.RESPONSE_YES
:
253 if ignore_errors
.get_active():
257 encoding
= combo
.entry
.get_text()
259 codecs
.getdecoder(encoding
)
262 rox
.alert("Unknown encoding '%s'" % encoding
)
266 return encoding
, errors
268 def insert_data(self
, data
):
273 decoder
= codecs
.getdecoder(encoding
)
275 data
= decoder(data
, errors
)[0]
276 assert '\0' not in data
281 encoding
, errors
= self
.get_encoding(
282 "Data is not valid %s. Please select the file's encoding."
283 "Turn on 'ignore errors' to try and load it anyway." % encoding
)
285 self
.buffer.insert_at_cursor(data
, -1)
288 def load_file(self
, path
):
293 file = open(path
, 'r')
294 contents
= file.read()
297 self
.insert_data(contents
)
301 rox
.report_exception()
304 def close(self
, button
= None):
305 if self
.buffer.get_modified():
306 self
.save(discard
= 1)
313 def up(self
, button
= None):
315 filer
.show_file(self
.uri
)
317 rox
.alert("File is not saved to disk yet")
319 def has_selection(self
):
320 s
, e
= self
.get_selection_range()
321 return not e
.equal(s
)
323 def get_marked_range(self
):
324 s
= self
.buffer.get_iter_at_mark(self
.mark_start
)
325 e
= self
.buffer.get_iter_at_mark(self
.mark_end
)
328 def get_selection_range(self
):
329 s
= self
.buffer.get_iter_at_mark(self
.insert_mark
)
330 e
= self
.buffer.get_iter_at_mark(self
.selection_bound_mark
)
333 def save(self
, widget
= None, discard
= 0):
334 from rox
.saving
import SaveBox
337 self
.savebox
.destroy()
339 if self
.has_selection() and not discard
:
340 saver
= SelectionSaver(self
)
341 self
.savebox
= SaveBox(saver
, 'Selection', 'text/plain')
342 self
.savebox
.connect('destroy', lambda w
: saver
.destroy())
344 uri
= self
.uri
or default_name
.value
345 self
.savebox
= SaveBox(self
, uri
, 'text/plain', discard
)
348 def help(self
, button
= None):
349 filer
.open_dir(rox
.app_dir
+ '/Help')
351 def save_to_stream(self
, stream
):
352 s
= self
.buffer.get_start_iter()
353 e
= self
.buffer.get_end_iter()
354 stream
.write(self
.buffer.get_text(s
, e
, TRUE
))
356 def set_uri(self
, uri
):
358 self
.buffer.set_modified(FALSE
)
364 def change_font(self
):
365 style
= self
.text
.get_style().copy()
366 style
.font
= load_font(options
.get('edit_font'))
367 self
.text
.set_style(style
)
369 def show_options(self
):
372 def set_marked(self
, start
= None, end
= None):
373 "Set the marked region (from the selection if no region is given)."
375 assert not self
.marked
382 start
, end
= self
.get_selection_range()
383 buffer.move_mark(self
.mark_start
, start
)
384 buffer.move_mark(self
.mark_end
, end
)
385 buffer.apply_tag_by_name('marked',
386 buffer.get_iter_at_mark(self
.mark_start
),
387 buffer.get_iter_at_mark(self
.mark_end
))
390 def clear_marked(self
):
395 buffer.remove_tag_by_name('marked',
396 buffer.get_iter_at_mark(self
.mark_start
),
397 buffer.get_iter_at_mark(self
.mark_end
))
399 def undo(self
, widget
= None):
402 def redo(self
, widget
= None):
405 def goto(self
, widget
= None):
406 from goto
import Goto
407 self
.set_minibuffer(Goto())
409 def search(self
, widget
= None):
410 from search
import Search
411 self
.set_minibuffer(Search())
413 def set_mini_label(self
, label
):
414 self
.mini_label
.set_text(label
)
416 def set_minibuffer(self
, minibuffer
):
417 self
.minibuffer
= minibuffer
420 minibuffer
.setup(self
)
421 self
.mini_entry
.set_text('')
422 self
.mini_entry
.grab_focus()
423 self
.mini_hbox
.show_all()
425 self
.mini_hbox
.hide()
426 self
.text
.grab_focus()
428 def mini_key_press(self
, entry
, kev
):
429 if kev
.keyval
== g
.keysyms
.Escape
:
430 self
.set_minibuffer(None)
433 return self
.minibuffer
.key_press(kev
)
435 def mini_changed(self
, entry
):
436 self
.minibuffer
.changed()
438 class SelectionSaver(Saveable
):
439 def __init__(self
, window
):
443 def save_to_stream(self
, stream
):
444 s
, e
= self
.window
.get_marked_range()
445 stream
.write(self
.window
.buffer.get_text(s
, e
, TRUE
))
448 # Called when savebox is remove. Get rid of the selection marker
449 self
.window
.clear_marked()