Simple implementation of "Copy as Doctests"
[reinteract/rox.git] / lib / reinteract / main.py
blob44ba3b4e6d6b44b565d4b23ed397b2197da53bf7
1 import gtk
2 import pango
4 import os
5 import sys
7 from notebook import Notebook
8 from shell_buffer import ShellBuffer
9 from shell_view import ShellView
11 from format_escaped import format_escaped
12 from optparse import OptionParser
14 usage = "usage: %prog [options]"
15 op = OptionParser(usage=usage)
16 op.add_option("-u", "--ui", type="choice", choices=("standard", "hildon"),
17 default="standard", help=("which user interface to use (standard or "
18 "hildon), default=%default"))
20 options, args = op.parse_args()
21 use_hildon = False
23 if options.ui == "hildon":
24 try:
25 import hildon
26 use_hildon = True
27 except ImportError, e:
28 print "Error importing hildon. Falling back to standard ui."
30 notebook = Notebook()
32 if use_hildon:
33 w = hildon.Window()
34 else:
35 w = gtk.Window()
37 v = gtk.VBox()
38 w.add(v)
40 buf = ShellBuffer(notebook)
41 view = ShellView(buf)
42 view.modify_font(pango.FontDescription("monospace"))
43 buf = view.get_buffer()
45 ui_manager = gtk.UIManager()
46 w.add_accel_group(ui_manager.get_accel_group())
48 def quit():
49 if not confirm_discard('Save the unchanged changes to worksheet "%s" before quitting?', '_Quit without saving'):
50 return
51 gtk.main_quit()
53 def on_quit(action):
54 quit()
56 def on_cut(action):
57 view.emit('cut-clipboard')
59 def on_copy(action):
60 view.emit('copy-clipboard')
62 def on_copy_as_doctests(action):
63 view.get_buffer().copy_as_doctests(view.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD))
65 def on_paste(action):
66 view.emit('paste-clipboard')
68 def on_delete(action):
69 buf.delete_selection(True, view.get_editable())
71 def confirm_discard(message_format, continue_button_text):
72 if not buf.code_modified:
73 return True
75 if buf.filename == None:
76 save_button_text = gtk.STOCK_SAVE_AS
77 else:
78 save_button_text = gtk.STOCK_SAVE
80 if buf.filename == None:
81 name = "Unsaved Worksheet"
82 else:
83 name = buf.filename
85 message = format_escaped("<big><b>" + message_format + "</b></big>", name)
87 dialog = gtk.MessageDialog(parent=w, buttons=gtk.BUTTONS_NONE,
88 type=gtk.MESSAGE_WARNING)
89 dialog.set_markup(message)
91 dialog.add_buttons(continue_button_text, gtk.RESPONSE_OK,
92 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
93 save_button_text, 1)
94 dialog.set_default_response(1)
95 response = dialog.run()
96 dialog.destroy()
98 if response == gtk.RESPONSE_OK:
99 return True
100 elif response == 1:
101 if buf.filename == None:
102 save_as()
103 else:
104 buf.save()
106 if buf.code_modified:
107 return False
108 else:
109 return True
110 else:
111 return False
113 def on_new(action):
114 if not confirm_discard('Discard unsaved changes to worksheet "%s"?', '_Discard'):
115 return
117 buf.clear()
119 def load(filename):
120 notebook.set_path([os.path.dirname(os.path.abspath(filename))])
122 buf.load(filename)
123 calculate()
125 def on_open(action):
126 if not confirm_discard('Discard unsaved changes to worksheet "%s"?', '_Discard'):
127 return
129 if use_hildon:
130 chooser = hildon.FileChooserDialog(w, gtk.FILE_CHOOSER_ACTION_OPEN)
131 else:
132 chooser = gtk.FileChooserDialog("Open Worksheet...", w, gtk.FILE_CHOOSER_ACTION_OPEN,
133 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
134 gtk.STOCK_OPEN, gtk.RESPONSE_OK))
135 chooser.set_default_response(gtk.RESPONSE_OK)
136 response = chooser.run()
137 filename = None
138 if response == gtk.RESPONSE_OK:
139 filename = chooser.get_filename()
141 if filename != None:
142 load(filename)
144 chooser.destroy()
146 def on_save(action):
147 if buf.filename == None:
148 on_save_as(action)
149 else:
150 buf.save()
152 def save_as():
153 if use_hildon:
154 chooser = hildon.FileChooserDialog(w, gtk.FILE_CHOOSER_ACTION_SAVE)
155 else:
156 chooser = gtk.FileChooserDialog("Save As...", w, gtk.FILE_CHOOSER_ACTION_SAVE,
157 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
158 gtk.STOCK_SAVE, gtk.RESPONSE_OK))
159 chooser.set_default_response(gtk.RESPONSE_OK)
160 response = chooser.run()
161 filename = None
162 if response == gtk.RESPONSE_OK:
163 filename = chooser.get_filename()
165 if filename != None:
166 buf.save(filename)
167 notebook.set_path([os.path.dirname(os.path.abspath(filename))])
169 chooser.destroy()
171 def on_save_as(action):
172 save_as()
174 def calculate():
175 buf.calculate()
177 # This is a hack to work around the fact that scroll_mark_onscreen()
178 # doesn't wait for a size-allocate cycle, so doesn't properly handle
179 # embedded request widgets
180 w, h = view.size_request()
181 view.size_allocate((view.allocation.x, view.allocation.y, w, h))
183 view.scroll_mark_onscreen(buf.get_insert())
185 def on_calculate(action):
186 calculate()
188 action_group = gtk.ActionGroup("main")
189 action_group.add_actions([
190 ('file', None, "_File"),
191 ('edit', None, "_Edit"),
192 ('new', gtk.STOCK_NEW, None, None, None, on_new),
193 ('open', gtk.STOCK_OPEN, None, None, None, on_open),
194 ('save', gtk.STOCK_SAVE, None, None, None, on_save),
195 ('save-as', gtk.STOCK_SAVE_AS, None, None, None, on_save_as),
196 ('quit', gtk.STOCK_QUIT, None, None, None, on_quit),
197 ('cut', gtk.STOCK_CUT, None, None, None, on_cut),
198 ('copy', gtk.STOCK_COPY, None, None, None, on_copy),
199 ('copy-as-doctests',
200 gtk.STOCK_COPY,
201 "Copy as _Doctests",
202 "<control><shift>c",
203 None,
204 on_copy_as_doctests),
205 ('paste', gtk.STOCK_PASTE, None, None, None, on_paste),
206 ('delete', gtk.STOCK_DELETE, None, None, None, on_delete),
207 ('calculate', gtk.STOCK_REFRESH, "_Calculate", '<control>Return', None, on_calculate),
210 ui_manager.insert_action_group(action_group, 0)
212 if use_hildon:
213 menu_element = 'popup'
214 else:
215 menu_element = 'menubar'
217 ui_string="""
218 <ui>
219 <%(menu_element)s name="TopMenu">
220 <menu action="file">
221 <menuitem action="new"/>
222 <menuitem action="open"/>
223 <separator/>
224 <menuitem action="save"/>
225 <menuitem action="save-as"/>
226 <separator/>
227 <menuitem action="quit"/>
228 </menu>
229 <menu action="edit">
230 <menuitem action="cut"/>
231 <menuitem action="copy"/>
232 <menuitem action="copy-as-doctests"/>
233 <menuitem action="paste"/>
234 <menuitem action="delete"/>
235 <separator/>
236 <menuitem action="calculate"/>
237 </menu>
238 </%(menu_element)s>
239 <toolbar name="ToolBar">
240 <toolitem action="calculate"/>
241 </toolbar>
242 </ui>
243 """ % { 'menu_element': menu_element }
245 ui_manager.add_ui_from_string(ui_string)
246 ui_manager.ensure_update()
248 menu = ui_manager.get_widget("/TopMenu")
249 toolbar = ui_manager.get_widget("/ToolBar")
251 if use_hildon:
252 w.set_menu(menu)
253 w.add_toolbar(toolbar)
254 else:
255 v.pack_start(menu, expand=False, fill=False)
256 v.pack_start(toolbar, expand=False, fill=False)
258 sw = gtk.ScrolledWindow()
259 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
260 v.pack_start(sw, expand=True, fill=True)
262 sw.add(view)
264 w.set_default_size(700, 800)
266 v.show_all()
267 view.grab_focus()
269 def update_title(*args):
270 if buf.code_modified:
271 title = "*"
272 else:
273 title = ""
275 if buf.filename == None:
276 title += "Unsaved Worksheet"
277 else:
278 title += os.path.basename(buf.filename)
280 title += " - Reinteract"
282 w.set_title(title)
284 buf.connect('filename-changed', update_title)
285 buf.connect('code-modified-changed', update_title)
287 update_title()
289 # We have a <Control>Return accelerator, but this hooks up <Control>KP_Enter as well;
290 # maybe someone wants that
291 def on_key_press_event(window, event):
292 if (event.keyval == 0xff0d or event.keyval == 0xff8d) and (event.state & gtk.gdk.CONTROL_MASK != 0):
293 calculate()
294 return True
295 return False
297 w.connect('key-press-event', on_key_press_event)
299 if len(args) > 0:
300 load(args[0])
301 else:
302 # If you run reinteract from the command line, you'd expect to be able to
303 # create a worksheet, test it, then save it in the current directory, and
304 # have that act the same as loading the worksheet to start with. This is
305 # less obviously right when run from a menu item.
306 notebook.set_path([os.getcwd()])
308 if use_hildon:
309 settings = w.get_settings()
310 settings.set_property("gtk-button-images", False)
311 settings.set_property("gtk-menu-images", False)
313 w.show()
315 def on_delete_event(window, event):
316 quit()
317 return True
319 w.connect('delete-event', on_delete_event)
321 gtk.main()