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
.v_adj
= scrollbar
.get_adjustment()
93 self
.text
= g
.TextView()
94 self
.text
.set_property('left-margin', 4)
95 self
.text
.set_property('right-margin', 4)
96 self
.text
.set_buffer(self
.buffer)
97 self
.text
.set_scroll_adjustments(None, self
.v_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
)
193 self
.text
.connect('scroll-event', self
.scroll_event
)
195 menu
.attach(self
, self
)
196 self
.buffer.place_cursor(self
.buffer.get_start_iter())
197 self
.buffer.start_undo_history()
199 def destroyed(self
, widget
):
200 app_options
.remove_notify(self
.update_styles
)
203 def update_styles(self
):
206 font
= pango
.FontDescription(default_font
.value
)
207 bg
= g
.gdk
.color_parse(background_colour
.value
)
208 fg
= g
.gdk
.color_parse(foreground_colour
.value
)
210 rox
.report_exception()
212 self
.text
.modify_font(font
)
213 self
.text
.modify_base(g
.STATE_NORMAL
, bg
)
214 self
.text
.modify_text(g
.STATE_NORMAL
, fg
)
216 def button_press(self
, text
, event
):
217 if event
.button
!= 3:
219 menu
.popup(self
, event
)
222 def scroll_event(self
, text
, event
):
223 dir = event
.direction
224 new
= self
.v_adj
.value
225 if dir == g
.gdk
.SCROLL_UP
:
227 elif dir == g
.gdk
.SCROLL_DOWN
:
229 self
.v_adj
.set_value(new
)
231 def delete_event(self
, window
, event
):
232 if self
.buffer.get_modified():
233 self
.save(discard
= 1)
237 def update_title(self
, *unused
):
238 title
= self
.uri
or '<Untitled>'
239 if self
.buffer.get_modified():
241 self
.set_title(title
)
243 def xds_load_from_stream(self
, name
, t
, stream
):
244 if t
== 'UTF8_STRING':
245 return # Gtk will handle it
247 self
.insert_data(stream
.read())
251 def get_encoding(self
, message
):
252 "Returns (encoding, errors), or raises Abort to cancel."
255 box
= g
.MessageDialog(self
, 0, g
.MESSAGE_QUESTION
, g
.BUTTONS_CANCEL
, message
)
256 box
.set_has_separator(FALSE
)
259 box
.vbox
.pack_start(frame
, TRUE
, TRUE
)
260 frame
.set_border_width(6)
262 hbox
= g
.HBox(FALSE
, 4)
263 hbox
.set_border_width(6)
265 hbox
.pack_start(g
.Label('Encoding:'), FALSE
, TRUE
, 0)
267 combo
.disable_activate()
268 combo
.entry
.connect('activate', lambda w
: box
.activate_default())
269 combo
.set_popdown_strings(known_codecs
)
270 hbox
.pack_start(combo
, TRUE
, TRUE
, 0)
271 ignore_errors
= g
.CheckButton('Ignore errors')
272 hbox
.pack_start(ignore_errors
, FALSE
, TRUE
)
277 box
.add_button(g
.STOCK_CONVERT
, g
.RESPONSE_YES
)
278 box
.set_default_response(g
.RESPONSE_YES
)
281 combo
.entry
.grab_focus()
284 if resp
!= g
.RESPONSE_YES
:
288 if ignore_errors
.get_active():
292 encoding
= combo
.entry
.get_text()
294 codecs
.getdecoder(encoding
)
297 rox
.alert("Unknown encoding '%s'" % encoding
)
301 return encoding
, errors
303 def insert_data(self
, data
):
308 decoder
= codecs
.getdecoder(encoding
)
310 data
= decoder(data
, errors
)[0]
311 assert '\0' not in data
316 encoding
, errors
= self
.get_encoding(
317 "Data is not valid %s. Please select the file's encoding."
318 "Turn on 'ignore errors' to try and load it anyway." % encoding
)
321 self
.buffer.insert_at_cursor(data
)
323 self
.buffer.insert_at_cursor(data
, -1)
326 def load_file(self
, path
):
331 file = open(path
, 'r')
332 contents
= file.read()
335 self
.insert_data(contents
)
339 rox
.report_exception()
342 def close(self
, button
= None):
343 if self
.buffer.get_modified():
344 self
.save(discard
= 1)
351 def up(self
, button
= None):
353 filer
.show_file(self
.uri
)
355 rox
.alert("File is not saved to disk yet")
357 def has_selection(self
):
358 s
, e
= self
.get_selection_range()
359 return not e
.equal(s
)
361 def get_marked_range(self
):
362 s
= self
.buffer.get_iter_at_mark(self
.mark_start
)
363 e
= self
.buffer.get_iter_at_mark(self
.mark_end
)
366 def get_selection_range(self
):
367 s
= self
.buffer.get_iter_at_mark(self
.insert_mark
)
368 e
= self
.buffer.get_iter_at_mark(self
.selection_bound_mark
)
371 def save(self
, widget
= None, discard
= 0):
372 from rox
.saving
import SaveBox
375 self
.savebox
.destroy()
377 if self
.has_selection() and not discard
:
378 saver
= SelectionSaver(self
)
379 self
.savebox
= SaveBox(saver
, 'Selection', 'text/plain')
380 self
.savebox
.connect('destroy', lambda w
: saver
.destroy())
382 uri
= self
.uri
or 'TextFile'
383 self
.savebox
= SaveBox(self
, uri
, 'text/plain', discard
)
386 def help(self
, button
= None):
387 filer
.open_dir(rox
.app_dir
+ '/Help')
389 def save_to_stream(self
, stream
):
390 s
= self
.buffer.get_start_iter()
391 e
= self
.buffer.get_end_iter()
392 stream
.write(self
.buffer.get_text(s
, e
, TRUE
))
394 def set_uri(self
, uri
):
396 self
.buffer.set_modified(FALSE
)
402 def change_font(self
):
403 style
= self
.text
.get_style().copy()
404 style
.font
= load_font(options
.get('edit_font'))
405 self
.text
.set_style(style
)
407 def show_options(self
):
410 def set_marked(self
, start
= None, end
= None):
411 "Set the marked region (from the selection if no region is given)."
413 assert not self
.marked
420 start
, end
= self
.get_selection_range()
421 buffer.move_mark(self
.mark_start
, start
)
422 buffer.move_mark(self
.mark_end
, end
)
423 buffer.apply_tag_by_name('marked',
424 buffer.get_iter_at_mark(self
.mark_start
),
425 buffer.get_iter_at_mark(self
.mark_end
))
428 def clear_marked(self
):
433 buffer.remove_tag_by_name('marked',
434 buffer.get_iter_at_mark(self
.mark_start
),
435 buffer.get_iter_at_mark(self
.mark_end
))
437 def undo(self
, widget
= None):
440 def redo(self
, widget
= None):
443 def goto(self
, widget
= None):
444 from goto
import Goto
445 self
.set_minibuffer(Goto())
447 def search(self
, widget
= None):
448 from search
import Search
449 self
.set_minibuffer(Search())
451 def process(self
, widget
= None):
452 from process
import Process
453 self
.set_minibuffer(Process())
455 def set_mini_label(self
, label
):
456 self
.mini_label
.set_text(label
)
458 def set_minibuffer(self
, minibuffer
):
459 assert minibuffer
is None or isinstance(minibuffer
, Minibuffer
)
461 self
.minibuffer
= None
464 self
.mini_entry
.set_text('')
465 self
.minibuffer
= minibuffer
466 minibuffer
.setup(self
)
467 self
.mini_entry
.grab_focus()
468 self
.mini_hbox
.show_all()
470 self
.mini_hbox
.hide()
471 self
.text
.grab_focus()
473 def mini_key_press(self
, entry
, kev
):
474 if kev
.keyval
== g
.keysyms
.Escape
:
475 self
.set_minibuffer(None)
477 if kev
.keyval
== g
.keysyms
.Return
or kev
.keyval
== g
.keysyms
.KP_Enter
:
478 self
.minibuffer
.activate()
481 return self
.minibuffer
.key_press(kev
)
483 def mini_changed(self
, entry
):
484 if not self
.minibuffer
:
486 self
.minibuffer
.changed()
488 def mini_show_info(self
, *unused
):
489 assert self
.minibuffer
491 self
.info_box
.destroy()
492 self
.info_box
= g
.MessageDialog(self
, 0, g
.MESSAGE_INFO
, g
.BUTTONS_OK
,
493 self
.minibuffer
.info
)
494 self
.info_box
.set_title('Minibuffer help')
497 self
.info_box
.connect('destroy', destroy
)
499 self
.info_box
.connect('response', lambda w
, r
: w
.destroy())
501 class SelectionSaver(Saveable
):
502 def __init__(self
, window
):
506 def save_to_stream(self
, stream
):
507 s
, e
= self
.window
.get_marked_range()
508 stream
.write(self
.window
.buffer.get_text(s
, e
, TRUE
))
511 # Called when savebox is remove. Get rid of the selection marker
512 self
.window
.clear_marked()