1
2
3
4 """
5 Inspired by
6 PyAlaCarte and PyAlaMode editors from wxPython
7
8 Combines the lab_bench and offers the possibility to transform the
9 current work into script files.
10 """
11
12 import sys
13 import os
14
15 import wx
16 import wx.lib.dialogs
17
18 import wx.py
19 import wx.py.frame as PyFrame
20 import wx.py.dispatcher as dispatcher
21
22 from wx.py.editor import *
23
24 from mathbench.basement.history_manager import HistoryManager
25
26 from mathbench.lab.labbench import LabBench
27 import mathbench.lab.advisor as advisor
28
29 from mathbench.basement.librarian import LibrarianSingleton
30
31 ID_EXECUTE = wx.NewId()
32 ID_EXECUTE_MAIN = wx.NewId()
33 ID_PREFERENCE = wx.NewId()
34 ID_LIBRARY = wx.NewId()
35 ID_HISTORY_TO_SCRIPT = wx.NewId()
36 ID_LOAD_OLD_SESSION = wx.NewId()
37
38
39 from wx.html import HtmlEasyPrinting
40
42 """
43 Small printing class taken from: http://wiki.wxpython.org/Printing
44 """
47
48 - def GetHtmlText(self,text):
49 "Simple conversion of text. Use a more powerful version"
50 html_text = text.replace('\n', '<BR>')
51 return html_text
52
53 - def Print(self, text, doc_name):
54 self.SetHeader(doc_name)
55 self.PrintText(self.GetHtmlText(text),doc_name)
56
57 - def PreviewText(self, text, doc_name):
58 self.SetHeader(doc_name)
59 HtmlEasyPrinting.PreviewText(self, self.GetHtmlText(text))
60
62 """
63 Very light customisation of the base class
64 """
66 """
67 Create the instance
68 """
69 EditorNotebook.__init__(self,parent)
70 self.config = config
71 self.parent = parent
72
73 - def AddPage(self,page,text="Page",select=False):
74 """
75 Also enforce the configuration options for each new element.
76 """
77 EditorNotebook.AddPage(self,page,text=text,select=select)
78 dispatcher.send(signal='NewPageOnNotebook', sender=self)
79
80
82 """
83 LabBook instance that manage all the sutff displayed on screen
84 (editors and shells).
85
86 To add the singleton behaviour to a LabBench instance.
87
88 implemented in a similar way as in :
89 http://www.haypocalc.com/blog/index.php/2006/05/12/3-motifs-de-conception-et-python
90 """
91
92
93 __instance = None
94
95
96 _init_shell_session = []
97
98
99 - def __init__(self, parent=None, id=-1, title='MathBench',
100 config=None,
101 pos=wx.DefaultPosition, size=(800, 600),
102 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE,
103 filename=None, history_dir=None):
104 """Create LabBookFrame instance."""
105 self.config = config
106 self.history_dir = history_dir
107 EditorNotebookFrame.__init__(self, parent, id, title, pos,
108 size, style, filename)
109 self._modify_menus()
110
111 self.options_frame = None
112
113 self.execution_shells = {}
114
115 - def __new__(cls,parent=None, id=-1, title='Math Bench', config=None,
116 pos=wx.DefaultPosition, size=(800, 600),
117 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE,
118 filename=None, history_dir=None):
125
126
127
129 """
130 Create the top level widgets contained by the frame
131 """
132
133 self.notebook = LabEditorNotebook(parent=self,config=self.config)
134 dispatcher.connect(receiver=self._newPageOnNotebook,
135 signal='NewPageOnNotebook', sender=self.notebook)
136
138 """
139 Create a toolbar showing some helping options
140 """
141 self.helpbar = wx.Panel( self,-1, size=wx.Size(-1,20))
142 help_sizer = wx.BoxSizer(wx.HORIZONTAL)
143 tb_img_size = (16,16)
144 bt_size = (20,20)
145 art_prov = wx.ArtProvider()
146
147 img = art_prov.GetBitmap(wx.ART_HELP_BOOK,
148 wx.ART_TOOLBAR,
149 tb_img_size)
150 btn = wx.BitmapButton(self.helpbar, 50, img)
151 btn.SetToolTipString("Show the library desk.")
152 self.Bind(wx.EVT_BUTTON, self.OnHelpToolClick, id=50)
153 help_sizer.Add(btn,proportion=0,border=3,flag=wx.ALL)
154
155
156 dummylongtext = wx.StaticText(self.helpbar,-1,"mathbench.basement.configuration.py")
157 dummylongtext.Fit()
158 nice_width = dummylongtext.GetBestSize().width
159 dummylongtext.Hide()
160 self.query_widget = wx.TextCtrl(name='query',
161 parent=self.helpbar,
162 size=wx.Size(nice_width,-1))
163 self.query_widget.SetToolTipString("Press 'Enter' to launch the search")
164 self.query_widget.Bind(wx.EVT_KEY_DOWN,self.OnQueryKeyDown)
165 help_sizer.Add(self.query_widget,proportion=1,flag=wx.EXPAND | wx.ALL, border=3)
166
167
168 img = art_prov.GetBitmap(wx.ART_DELETE,
169 wx.ART_TOOLBAR,
170 tb_img_size)
171 btn = wx.BitmapButton(self.helpbar, 30, img)
172 btn.SetToolTipString("Clear the search form.")
173 self.Bind(wx.EVT_BUTTON, self.OnHelpToolClick, id=30)
174 help_sizer.Add(btn,proportion=0,border=3,flag=wx.ALL)
175 self.helpbar.SetSizer(help_sizer)
176
177
178 img = art_prov.GetBitmap(wx.ART_FIND,
179 wx.ART_TOOLBAR,
180 tb_img_size)
181 btn = wx.BitmapButton(self.helpbar,35, img)
182 btn.SetToolTipString("Search the documentation.")
183 self.Bind(wx.EVT_BUTTON, self.OnHelpToolClick, id=35)
184 help_sizer.Add(btn,proportion=0,border=3,flag=wx.ALL)
185
186 self.helpbar.SetSizer(help_sizer)
187
207
209 """
210 Setup prior to first buffer creation.
211
212 Called automatically by base class during init.
213 """
214 sizer = wx.BoxSizer(wx.VERTICAL)
215
216 self._init_notebook()
217 sizer.Add(self.notebook,proportion=1,flag=wx.EXPAND)
218
219 self._init_helpbar()
220 sizer.Add(self.helpbar,proportion=0,flag=wx.EXPAND)
221 self.SetSizer(sizer)
222
223 self._init_shell_widget()
224
225
226
227
228
229 self.shell.setStatusText = self.SetStatusText
230
231 self.shell.redirectStdout(True)
232 self.shell.redirectStderr(True)
233
234 self.notebook.AddPage(page=self.labBench, text='*Shell*', select=True)
235 self.setEditor(self.labBench.editor)
236
237 wx.CallAfter(self.shell.SetFocus)
238
239 self.init_worksession()
240
242 """
243 When a new page is added perform some necessary checking and
244 settings.
245 """
246 page_nb = self.notebook.GetPageCount()
247 if page_nb>1:
248 self.enforce_options(self.notebook.GetPage(page_nb-1).editor.window)
249 else:
250
251 self.enforce_options(self.shell)
252
254 """
255 Put here the functions to call at the begining of the working
256 session of the shell.
257
258 This is separated from the init function, so that anybody can
259 overwrite the content of this function.
260 """
261 for cmd_str in LabBook._init_shell_session:
262 self.shell.run(cmd_str)
263
265 """
266 Enforce the choices the user made with the options.
267 """
268 if self.config is None:
269 return
270
271 section_name = "Auto Completion"
272 if self.config.has_section(section_name):
273 window.autoComplete = (self.config.get(section_name,"Show_Auto_Completion")=="1")
274 window.autoCompleteIncludeMagic = (self.config.get(section_name,"Include_Magic_Attributes")=="1")
275 window.autoCompleteIncludeSingle = (self.config.get(section_name,"Include_Single_Underscores")=="1")
276 window.autoCompleteIncludeDouble = (self.config.get(section_name,"Include_Double_Underscores")=="1")
277
278 section_name = "Call Tips"
279 if self.config.has_section(section_name):
280 window.autoCallTip = (self.config.get(section_name,"Show_Call_Tips")=="1")
281 window.callTipInsert = (self.config.get(section_name,"Insert_Call_Tips")=="1")
282
283 section_name = "View"
284 if self.config.has_section(section_name):
285 window.SetWrapMode((self.config.get(section_name,"Wrap_Lines")=="1"))
286 wx.FutureCall(1, self.shell.EnsureCaretVisible)
287 window.lineNumbers = (self.config.get(section_name,"Show_Line_Numbers")=="1")
288 window.setDisplayLineNumbers(window.lineNumbers)
289
291 """
292 Recreate the menus, overwritting what has been done in the
293 wx/py/frame.py
294 """
295 art_prov = wx.ArtProvider()
296
297 for i in reversed(range(self.menuBar.GetMenuCount())):
298 self.menuBar.Remove(i)
299
300 m = self.fileMenu = wx.Menu()
301 m.Append(PyFrame.ID_NEW, '&New \tCtrl+N',
302 'New file')
303 m.Append(PyFrame.ID_OPEN, '&Open... \tCtrl+O',
304 'Open file')
305 m.AppendSeparator()
306 m.Append(PyFrame.ID_REVERT, '&Revert \tCtrl+R',
307 'Revert to last saved version')
308 m.Append(PyFrame.ID_CLOSE, '&Close \tCtrl+W',
309 'Close file')
310 m.AppendSeparator()
311 m.Append(PyFrame.ID_SAVE, '&Save... \tCtrl+S',
312 'Save file')
313 m.Append(PyFrame.ID_SAVEAS, 'Save &As \tCtrl+Shift+S',
314 'Save file with new name')
315 m.AppendSeparator()
316 m.Append(PyFrame.ID_PRINT, '&Print... \tCtrl+P',
317 'Print file')
318 m.AppendSeparator()
319 m.Append(ID_HISTORY_TO_SCRIPT, 'Session &To Script',
320 "Create a script from the session's history")
321 m.Append(ID_LOAD_OLD_SESSION, "Recover &Older Session",
322 'Load (and exectute) the history of a previous session')
323 m.AppendSeparator()
324 m.Append(ID_EXECUTE, '&Execute \tF9',
325 'Execute the script in a new shell')
326 m.Append(ID_EXECUTE_MAIN, 'Execute in &main shell \tShift+F9',
327 'Execute the script in the main shell')
328 m.Append(PyFrame.ID_NAMESPACE, '&Update Namespace \tCtrl+Shift+N',
329 'Update namespace for autocompletion and calltips')
330 m.AppendSeparator()
331 m.Append(PyFrame.ID_EXIT, 'E&xit\tCtrl+Q', 'Exit Program')
332
333
334 m = self.editMenu = wx.Menu()
335 m.Append(PyFrame.ID_UNDO, '&Undo \tCtrl+Z',
336 'Undo the last action')
337 m.Append(PyFrame.ID_REDO, '&Redo \tCtrl+Y',
338 'Redo the last undone action')
339 m.AppendSeparator()
340 m.Append(PyFrame.ID_CUT, 'Cu&t \tCtrl+X',
341 'Cut the selection')
342 m.Append(PyFrame.ID_COPY, '&Copy \tCtrl+C',
343 'Copy the selection')
344 m.Append(PyFrame.ID_COPY_PLUS, 'Cop&y Plus \tCtrl+Shift+C',
345 'Copy the selection - retaining prompts')
346 m.Append(PyFrame.ID_PASTE, '&Paste \tCtrl+V', 'Paste from clipboard')
347 m.Append(PyFrame.ID_PASTE_PLUS, "Past&e'n'run \tCtrl+Shift+V",
348 'Paste and run commands')
349 m.AppendSeparator()
350 m.Append(PyFrame.ID_EMPTYBUFFER, 'E&mpty Buffer...',
351 'Delete all the contents of the edit buffer')
352 m.Append(PyFrame.ID_SELECTALL, 'Select A&ll \tCtrl+A',
353 'Select all text')
354 if wx.Platform == '__WXGTK__':
355 m.AppendSeparator()
356 m.Append(ID_PREFERENCE,"&Preferences",
357 "Open the preference panel")
358
359
360 m = self.autocompMenu = wx.Menu()
361 m.Append(PyFrame.ID_AUTOCOMP_SHOW, 'Show &Auto Completion\tCtrl+Shift+A',
362 'Show auto completion list', wx.ITEM_CHECK)
363 m.Append(PyFrame.ID_AUTOCOMP_MAGIC, 'Include &Magic Attributes\tCtrl+Shift+M',
364 'Include attributes visible to __getattr__ and __setattr__',
365 wx.ITEM_CHECK)
366 m.Append(PyFrame.ID_AUTOCOMP_SINGLE, 'Include Single &Underscores\tCtrl+Shift+U',
367 'Include attibutes prefixed by a single underscore', wx.ITEM_CHECK)
368 m.Append(PyFrame.ID_AUTOCOMP_DOUBLE, 'Include &Double Underscores\tCtrl+Shift+D',
369 'Include attibutes prefixed by a double underscore', wx.ITEM_CHECK)
370 m = self.calltipsMenu = wx.Menu()
371 m.Append(PyFrame.ID_CALLTIPS_SHOW, 'Show Call &Tips\tCtrl+Shift+T',
372 'Show call tips with argument signature and docstring', wx.ITEM_CHECK)
373 m.Append(PyFrame.ID_CALLTIPS_INSERT, '&Insert Call Tips\tCtrl+Shift+I',
374 '&Insert Call Tips', wx.ITEM_CHECK)
375
376 m = self.viewMenu = wx.Menu()
377 m.AppendMenu(PyFrame.ID_AUTOCOMP, '&Auto Completion', self.autocompMenu,
378 'Auto Completion Options')
379 m.AppendMenu(PyFrame.ID_CALLTIPS, '&Call Tips', self.calltipsMenu,
380 'Call Tip Options')
381 m.Append(PyFrame.ID_WRAP, '&Wrap Lines\tCtrl+Shift+W',
382 'Wrap lines at right edge', wx.ITEM_CHECK)
383 m.Append(PyFrame.ID_SHOW_LINENUMBERS, '&Show Line Numbers\tCtrl+Shift+L', 'Show Line Numbers', wx.ITEM_CHECK)
384 m.AppendSeparator()
385 m.Append(PyFrame.ID_TOGGLE_MAXIMIZE, '&Toggle Maximize\tF11', 'Maximize/Restore Application')
386
387 if wx.Platform == '__WXMAC__':
388 m.AppendSeparator()
389 m.Append(ID_LIBRARY,"&Library desk",
390 "Show the library desk and its useful pointers")
391 m.Append(PyFrame.ID_HELP, '&Help\tF1', 'Help!')
392 m.Append(PyFrame.ID_ABOUT, '&About...', 'About this program')
393 if wx.Platform != '__WXGTK__':
394 if wx.Platform == '__WXMAC__':
395 wx.App.SetMacPreferencesMenuItemId(ID_PREFERENCE)
396 m.Append(ID_PREFERENCE,"&Preferences",
397 "Open the preference panel")
398
399
400 m = self.searchMenu = wx.Menu()
401 m.Append(PyFrame.ID_FIND, '&Find Text... \tCtrl+F',
402 'Search for text in the edit buffer')
403 m.Append(PyFrame.ID_FINDNEXT, 'Find &Next \tF3',
404 'Find next/previous instance of the search text')
405
406
407 m = self.helpMenu = wx.Menu()
408 m.Append(PyFrame.ID_HELP, '&Help\tF1', 'Help!')
409 if wx.Platform != "__WXMAC__":
410 m.AppendSeparator()
411 m.Append(ID_LIBRARY,"&Library desk",
412 "Show the library desk and its useful pointers")
413 m.AppendSeparator()
414 m.Append(PyFrame.ID_ABOUT, '&About...', 'About this program')
415
416 b = self.menuBar
417 b.Append(self.fileMenu, '&Script')
418 b.Append(self.editMenu, '&Edit')
419 b.Append(self.viewMenu, '&View')
420 b.Append(self.searchMenu, '&Search')
421 if wx.Platform != "__WXMAC__":
422 b.Append(self.helpMenu, '&Help')
423
424
425 self.Bind(wx.EVT_MENU, self.OnExecute, id=ID_EXECUTE)
426 self.Bind(wx.EVT_MENU, self.OnExecuteInMainShell, id=ID_EXECUTE_MAIN)
427 self.Bind(wx.EVT_MENU, self.OnPreference, id=ID_PREFERENCE)
428 self.Bind(wx.EVT_MENU, self.OnLibrary, id=ID_LIBRARY)
429 self.Bind(wx.EVT_MENU, self.OnHistoryToScript, id=ID_HISTORY_TO_SCRIPT)
430 self.Bind(wx.EVT_MENU, self.OnLoadOldSession, id=ID_LOAD_OLD_SESSION)
431 self.shell.Bind(wx.EVT_CLOSE, self.OnClose)
432
434 """
435 Check is the content of the current buffer has been saved (at
436 its latest version).
437 """
438 if not self.bufferHasChanged():
439 return True
440 else:
441 d = wx.MessageDialog(self,u'You need to save the script first.\nDo you want to do is now ?',u'%s'%title, wx.YES_NO | wx.ICON_WARNING)
442 yes_no = d.ShowModal()
443 if yes_no != wx.ID_YES:
444 return False
445 cancel = self.bufferSave()
446 return not cancel
447
449 """
450 When the editor change, update the menu options.
451 """
452 EditorNotebookFrame._editorChange(self,editor)
453
454 if isinstance(editor,wx.py.shell.Shell):
455 self.fileMenu.Enable(ID_EXECUTE,False)
456 self.fileMenu.Enable(ID_EXECUTE_MAIN,False)
457 self.fileMenu.Enable(ID_HISTORY_TO_SCRIPT,True)
458 self.fileMenu.Enable(ID_LOAD_OLD_SESSION,True)
459 else:
460 self.fileMenu.Enable(ID_EXECUTE,True)
461 self.fileMenu.Enable(ID_EXECUTE_MAIN,True)
462 self.fileMenu.Enable(ID_HISTORY_TO_SCRIPT,False)
463 self.fileMenu.Enable(ID_LOAD_OLD_SESSION,False)
464
466 """
467 Create a new file from the given text
468 """
469 self.bufferNew()
470 self.editor.setText(text)
471
473 """
474 Create new buffer.
475
476 This method is redefined only to include a workaround for
477 bug#1752674
478 """
479 cancel = EditorNotebookFrame.bufferNew(self)
480 if not cancel:
481 self.editor.buffer.overwriteConfirm = lambda s,filepath=None: True
482 self.editor.buffer.confirmed = False
483 return cancel
484
486 """
487 printing ability
488 """
489 printer = Printer()
490 if isinstance(self.editor,wx.py.shell.Shell):
491 printer.Print(self.editor.GetText(),
492 "*Shell*")
493 else:
494 printer.Print(self.editor.getText(),
495 self.editor.buffer.name)
496
497
499 """
500 Close everything and don't let a creepy plugin hold the end of the app.
501 """
502 dispatcher.send(signal="LabBook.close")
503 sys.exit(0)
504
506 """
507 Display a small helpign text
508 """
509 msg = """\
510 Basic usage
511 -----------
512
513 1) type and execute a few commands in the shell
514
515 2) ask for this commands to be transformed into a plain script file
516 (via the menu "Script"->"Session To script")
517
518 3) edit the script
519
520 4) execute it with Script->Execute
521
522 5) save it when you're done !
523
524 and later on...
525
526 - open an existing script and edit it
527
528 - restart an old shell session (see "Script"->"Recover Old Session")
529
530 Search for documentation
531 ------------------------
532
533 Code samples and documentation are available at the library desk:
534
535 - type a query at the bottom of the application's window
536
537 or
538
539 - launch the desk via the menu "Help"->"Library Desk"
540
541
542 Shortcut keys
543 -------------
544
545 (also accessible by typing shell.help())
546
547 %s
548 """ % wx.py.shell.HELP_TEXT
549 dlg =wx.lib.dialogs.ScrolledMessageDialog(self, msg, "Help about Mathbench")
550 dlg.Show()
551
553 """Display an About window."""
554 title = 'MathBench'
555 text = """
556 Not a whole laboratory, just a small bench'
557
558 That is: another fine, flaky program for lazy scientists :)
559
560 Written by Thibauld Nion
561 And heavily based on wxPython's py library.
562 """
563 dialog = wx.MessageDialog(self, text, title,
564 wx.OK | wx.ICON_INFORMATION)
565 dialog.ShowModal()
566 dialog.Destroy()
567
573
574
576 """
577 Execute the script in a new shell frame.
578 """
579 if not self._checkCurrentBufferSaved():
580 return
581 script_filepath = self.editor.buffer.doc.filepath
582 if self.execution_shells.has_key(script_filepath):
583 execframe = self.execution_shells[script_filepath]
584 else:
585
586 execframe = wx.py.shell.ShellFrame()
587 self.execution_shells[script_filepath] = execframe
588 def OnCloseExecShell(event):
589 """
590 remove any reference to this shell
591 """
592 self.execution_shells.pop(script_filepath)
593 event.Skip()
594 execframe.Bind(wx.EVT_CLOSE,OnCloseExecShell)
595
596 execframe.shell.addHistory = lambda x: None
597
598 execframe.Hide()
599 execframe.Show()
600
601
602 execframe.shell.runfile(script_filepath)
603
604 - def OnExecuteInMainShell(self,event):
605 """
606 Execute the script in the main shell.
607 """
608 if not self._checkCurrentBufferSaved():
609 return
610 filepath = self.editor.buffer.doc.filepath
611 self.notebook.SetSelection(0)
612
613 self.shell.run("execfile('%s')" % filepath)
614
616 """
617 Show the preference panel
618 """
619 if self.options_frame is None:
620 self.options_frame = advisor.OptionsFrame(self,self.config)
621 dispatcher.connect(signal='OptionsChanged',
622 receiver=self._onOptionsChanged,sender=self.options_frame)
623 dispatcher.connect(signal="OptionsClosed",
624 receiver=self._onOptionsClosed,sender=self.options_frame)
625 else:
626 self.options_frame.Hide()
627 self.options_frame.Show()
628
629
630 - def OnHistoryToScript(self,event):
631 """
632 Pass the shell's history to a new script.
633 """
634 txtL = self.historyman.get_history_from_landmark()
635 txtL.reverse()
636 script = os.linesep.join(txtL)
637 return self.log_sink_to_file(script)
638
639
641 """
642 Load an older session from its history file.
643 """
644 d = wx.FileDialog(self,"Please chose a session to load",self.history_dir,
645 wildcard="*.session", style=wx.FD_OPEN)
646 ok_cancel = d.ShowModal()
647 path = d.GetPath()
648 d.Destroy()
649 if ok_cancel==wx.ID_OK and os.path.isfile(path):
650 f = open(path)
651 txt = f.read()
652 txtL = txt.split(self.historyman.COMMANDS_SEPARATOR)
653 txtL.reverse()
654
655 sesname = os.path.splitext(os.path.basename(path))[0]
656 self.shell.run("# Recovering session: %s" % sesname)
657
658
659 self.shell.Execute("\n\n".join(txtL))
660
661
663 """
664 Take into accout a change in the options
665 """
666
667 self.enforce_options(self.shell)
668 for page_id in range(1,self.notebook.GetPageCount()):
669 self.enforce_options(self.notebook.GetPage(page_id).editor.window)
670
672 """
673 Remove the reference to the options frame
674 """
675 self.options_frame = None
676
691
692
694 """
695 When user clicks on enter while typing in the query widget,
696 then launch the search !
697 """
698 if event.GetKeyCode() == wx.WXK_RETURN:
699 LibrarianSingleton.search(self.query_widget.GetValue())
700 event.Skip()
701
702
703
704
705
707 """
708 Add a command to be executed at session init.
709 """
710 self._init_shell_session.append(cmd_str)
711 initShellSessionAppend = classmethod(initShellSessionAppend)
712